クエリーに合わせた構造体を書くだけでGithub GraphQL API を扱えるGoのライブラリ[githubql]

golangとGraphQLの勉強中。

今回はGoでGithub APIのGraphQLのクライアント githubql を使ってみました。

githubqlの使い方

基本的にはGraphQLのqueryやmutationに合わせたGoの構造体を定義するだけです。

まずgithubqlのページに載っているサンプルを見てみましょう。 以下のようなシンプルなqueryの場合、

query {
    viewer {
        login
        createdAt
    }
}

このように構造体を定義し、

var query struct {
    Viewer struct {
        Login     githubql.String
        CreatedAt githubql.DateTime
    }
}

client.Queryを実行すると戻り値にGithubのGraphQL APIの結果が入っています。

err := client.Query(context.Background(), &query, nil)
if err != nil {
    // Handle error.
}
fmt.Println("    Login:", query.Viewer.Login)
fmt.Println("CreatedAt:", query.Viewer.CreatedAt)

今回試すクエリー(GraphQL Explorerの場合)

GithubのGraphQL APIはこちらで簡単に実行結果を確認することができます。 https://developer.github.com/v4/explorer/

今回はGithubにあるGraphQL関連のRepositoryとその言語を5件抽出するクエリーを書きました。

query {
    search(query: "GraphQL", first: 5, type: REPOSITORY) {
      edges {
        node {
          ... on Repository {
            nameWithOwner,
            url,
            languages(first:5) {
              edges {
                node {
                  ... on Language {
                    name,
                    color
                  }
                }
              }
            }
          }
        }
      }
    }
  }

結果

{
  "data": {
    "search": {
      "edges": [
        {
          "node": {
            "nameWithOwner": "facebook/graphql",
            "url": "https://github.com/facebook/graphql",
            "languages": {
              "edges": [
                {
                  "node": {
                    "name": "Shell",
                    "color": "#89e051"
                  }
                }
              ]
            }
          }
        },
        {
          "node": {
            "nameWithOwner": "graphql-go/graphql",
            "url": "https://github.com/graphql-go/graphql",
            "languages": {
              "edges": [
                {
                  "node": {
                    "name": "Go",
                    "color": "#375eab"
                  }
                }
              ]
            }
          }
        },

・・・以下省略

githubqlで書く場合

上記のクエリーをgithubqlを使ってGoで書くと以下のようになります。

Githubトークンを使ってclientを初期化します。

src := oauth2.StaticTokenSource(
    &oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")},
)
httpClient := oauth2.NewClient(context.Background(), src)

client := githubql.NewClient(httpClient)

続いてRepositoryとLanguageの構造体を定義します。

type Language struct {
    Name  githubql.String
    Color githubql.String
}

type Repository struct {
    NameWithOwner   githubql.String
    Url     githubql.String
    Languages struct {
        Nodes []struct {
            Language `graphql:"... on Language"`
        }
    } `graphql:"languages(first: 5)"`
}

GraphQLの引数を指定したいときは構造体にgraphql:"... on Language"のようにタグをつけます。

Searchも構造体で定義します。最初はここが分からなくて少しハマりました。先程のRepositoryの定義もSearchの中に入れ子で書けますが、今回は分けて書いてます。

var query struct {
    Search struct {
        Nodes []struct {
            Repository `graphql:"... on Repository"`
        }
    } `graphql:"search(first: 5, query: $q, type: $searchType)"`
}

Searchに渡すクエリパラメータを利用する時は、mapを作って渡します。

variables := map[string]interface{}{
    "q": githubql.String("GraphQL"),
    "searchType":  githubql.SearchTypeRepository,
}

err := client.Query(context.Background(), &query, variables)

以下が全体のソースです。

package main

import (
    "golang.org/x/oauth2"
    "os"
    "fmt"
    "context"
    "github.com/shurcooL/githubql"
)

func main() {
    src := oauth2.StaticTokenSource(
        &oauth2.Token{AccessToken: os.Getenv("GITHUB_TOKEN")},
    )
    httpClient := oauth2.NewClient(context.Background(), src)

    client := githubql.NewClient(httpClient)

    type Language struct {
        Name  githubql.String
        Color githubql.String
    }

    type Repository struct {
        NameWithOwner   githubql.String
        Url     githubql.String
        Languages struct {
            Nodes []struct {
                Language `graphql:"... on Language"`
            }
        } `graphql:"languages(first: 5)"`
    }

    var query struct {
        Search struct {
            Nodes []struct {
                Repository `graphql:"... on Repository"`
            }
        } `graphql:"search(first: 5, query: $q, type: $searchType)"`
    }

    variables := map[string]interface{}{
        "q": githubql.String("GraphQL"),
        "searchType":  githubql.SearchTypeRepository,
    }

    err := client.Query(context.Background(), &query, variables)
    if err != nil {
        fmt.Println(err)
    }

    for _, repo := range query.Search.Nodes {
        fmt.Println("---------")
        fmt.Println(repo.NameWithOwner)
        fmt.Println(repo.Url)
        for _, lang := range repo.Languages.Nodes {
            fmt.Println(lang.Name)
            fmt.Println(lang.Color)
        }
    }
}

実行結果

クエリーで定義した構造体に、GraphQL APIからのレスポンスのJSONマッピングされています。

---------
facebook/graphql
https://github.com/facebook/graphql
Shell
#89e051
---------
graphql-go/graphql
https://github.com/graphql-go/graphql
Go
#375eab
---------
Youshido/GraphQL
https://github.com/Youshido/GraphQL
PHP
#4F5D95
---------
graphql-elixir/graphql
https://github.com/graphql-elixir/graphql
Elixir
#6e4a7e
Erlang
#B83998
---------
GraphQLSwift/GraphQL
https://github.com/GraphQLSwift/GraphQL
Swift
#ffac45

以上です。

参考

charlock_holmes(0.7.3) が Siera(10.12.6)でインストールできない場合の対応

icu4cのバージョンを上げたらcharlock_holmesがicu4cを見つけられなくなったため、bundlerでcharlock_holmes(0.7.3)を再インストールしようとしたが、今度はインストール時にエラーが出て少し嵌ったので対応をメモ。

環境

  • OSX Siera 10.12.6
  • ruby 2.4.1p111
  • charlock_holmes 0.7.3
  • icu4c 59.1

bundle時のエラー内容

-- 前半省略 -- 

In file included from transliterator.cpp:5:
In file included from /usr/local/Cellar/icu4c/59.1/include/unicode/translit.h:25:
/usr/local/Cellar/icu4c/59.1/include/unicode/unistr.h:3025:7: error: delegating constructors are permitted only in C++11
      UnicodeString(ConstChar16Ptr(text)) {}
      ^~~~~~~~~~~~~
/usr/local/Cellar/icu4c/59.1/include/unicode/unistr.h:3087:7: error: delegating constructors are permitted only in C++11
      UnicodeString(ConstChar16Ptr(text), length) {}
      ^~~~~~~~~~~~~
/usr/local/Cellar/icu4c/59.1/include/unicode/unistr.h:3180:7: error: delegating constructors are permitted only in C++11
      UnicodeString(Char16Ptr(buffer), buffLength, buffCapacity) {}
      ^~~~~~~~~~~~~
transliterator.cpp:101:60: warning: implicit conversion loses integer precision: 'size_t' (aka 'unsigned long') to 'int32_t' (aka 'int') [-Wshorten-64-to-32]
  trans = Transliterator::createInstance(UnicodeString(id, id_len), UTRANS_FORWARD, p_error, status);
                                         ~~~~~~~~~~~~~     ^~~~~~
transliterator.cpp:106:34: warning: implicit conversion loses integer precision: 'size_t' (aka 'unsigned long') to 'int32_t' (aka 'int') [-Wshorten-64-to-32]
  u_txt = new UnicodeString(txt, txt_len);
              ~~~~~~~~~~~~~      ^~~~~~~
4 warnings and 3 errors generated.
make: *** [transliterator.o] Error 1

make failed, exit code 2

対応

bundle configにオプションを指定してbundlerを実行するとインストール出来た

$ bundle config --local build.charlock_holmes -- --with-cxxflags=-std=c++11

$ bundle
The latest bundler is 1.16.0.pre.2, but you are currently running 1.15.4.
To update, run `gem install bundler --pre`
Fetching gem metadata from https://rubygems.org/.
Fetching version metadata from https://rubygems.org/
Using bundler 1.15.4
Fetching charlock_holmes 0.7.3
Installing charlock_holmes 0.7.3 with native extensions
Bundle complete! 1 Gemfile dependency, 2 gems now installed.

参考

リーンキャンバスのツールやテンプレート

先日、ミッドタウンで開催された書籍「RUNNING LEAN -実践リーンスタートアップ」の刊行記念セミナーに参加してきました。
『Running Lean -実践リーンスタートアップ』刊行記念 著者アッシュ・マウリャ氏 来日特別セミナー at Yahoo! JAPAN
著者のアッシュ・マウリャ氏の講演やYahoo!ラボの河合さんからの事例紹介がとても興味深い内容でした。

Running Lean 実践リーンスタートアップ / 原タイトル:Running Lean 原著第2版の翻訳 (THE LEAN SERIES) (単行本・ムック) / アッシュ・マウリャ/著 角征典/訳


また、書籍の先行発売があったので早速購入していたのですが、セミナー終了後に著者のサイン会があったので私もサインをしてもらいました。

ありがとうございました!

ツール/テンプレート

書籍の中で、ビジネスモデルを簡潔に書き出すフォーマットとして「リーンキャンバス」というものが紹介されており、書籍もそのフォーマットの中身を埋めていく形で進むのですが、実際に自分もこれを使ってみようと思っていくつかツールやテンプレートを探してみました。

Leancanvas.com

著者の会社で作ってる本家ツールです。ブラウザ上でリーンキャンバスが作れます。
また、有料プランになりますが他の人とコラボレーションしながら作成することも出来ます。
1人までは無料で他の人とコラボレーション出来ますが、2人以上の人とコラボレーションする場合は有料になるようです。
(追記:修正しました。ご指摘ありがとうございます)

StartupWeekend

StartupWeekendのブログで紹介されていました。
リーンキャンバス(画像ファイル)へのリンクはこちら

The Business Model Canvas

リーンキャンバスの元となったビジネスモデルキャンバスです。
こちらは「ビジネスモデル・ジェネレーション」という書籍で日本でも今年の初めの方に紹介されていたようですね。

日本語版

いくつか見てみたのですが、日本語で書かれたリーンキャンバスのテンプレートが見つからなかったので、簡単ですが自分で作ってみました。
自分用ですが、使いたい人がいれば印刷するかKeynoteパワポなどに貼り付けるなどして使ってください。

クリエイティブ・コモンズ 表示-継承 3.0 非移植 ライセンスに基づいて改変しているつもりですが、不備などございましたらお手数ですがご連絡ください

ボルダリングとプロテイン

技術系の話ではないのですが、最近ボルダリングを始めて定期的にジムに通っています。

ボルダリングは手や足を置ける石がルートやレベルによって決まっていて、筋力だけでなくどのルートをどのように体を使ったら登れるか適度に頭を使って考える必要があり、パズルのようなゲーム感覚でトレーニングが出来て楽しいです。


また、なかなか登れなかったルートをゴールまで辿りつけた時の達成感がたまりません。


ジムに行くと登るのが楽しくて疲れを忘れて登ってしまうのですが何回か登ると腕がパンパンになってしまい、そうなると翌日からは確実に筋肉痛になってしばらく辛いです。


その筋肉痛を早く回復させたいのと、何よりボルダリングがもっと早く上達するように筋力をつけたくてプロテインを飲んでいるのですが、いろいろ試してみて最近飲んでいるのがこれ


水だけで飲めて、味もスポーツドリンクに近くてそこそこ飲みやすいです。


まだボルダリング始めてから2ヶ月ちょっとくらいですが、自分で見て分かるくらい腕の筋肉がついてきました。
今までつかめなかったホールドもだんだんつかめるようになってきて、効果が実感出来ます。

(´・ω・`)   n 
⌒`γ´⌒`ヽ( E)  
( .人 .人 γ ノ 
ミ(こノこノ `ー´ 
)にノこ( 

※イメージです


ザバスは他にもココア味やバニラ味などありますが、牛乳がないと飲みにくい(水でも飲めるけどまずい・・・)ので若干面倒だったのですが、これが出てからその面倒さから解放されました(^O^)


スポーツやってる人にはおすすめです!


Object#public_send()で安全にメソッドを呼び出す

rubyで動的にメソッド呼び出しをしたい時、Object#send()を使うと文字列やシンボルでメソッドを呼び出す事が出来ます。
ただこの機能を使うと意図せずにカプセル化を壊す事が出来てしまうため、publicメソッドのみ呼び出すようにしたい場合はObject#public_send()を使うと良いです。

myclass.rb

class MyClass
  def hoge
    "hogehoge"
  end
  
  private
    def fuga
      "fugafuga"
    end
end

public_sendを使ってprivateメソッド(fuga)を実行してみます。

[2] pry(main)> require './myclass.rb'
=> true
[3] pry(main)> obj = MyClass.new
=> #<MyClass:0x007fe89b2043e8>
[4] pry(main)> obj.send(:hoge)
=> "hogehoge"
[5] pry(main)> obj.send(:fuga)
=> "fugafuga"
[6] pry(main)> obj.public_send(:hoge)
=> "hogehoge"
[7] pry(main)> obj.public_send(:fuga)
NoMethodError: private method `fuga' called for #<MyClass:0x007fe89b2043e8>
from (pry):7:in `public_send'

NoMethodErrorが発生しました。

少人数や小規模で開発する時はあまり気にしなくてもよさそうですが、一定以上の規模の開発やメンバーにプログラミング初心者が含まれている場合は、共通で使うライブラリなどでObject#send()の代わりに使っておけば事故が減りそうです。

http://ruby-doc.org/core-1.9.3/Object.html#method-i-public_send


参考書籍

メタプログラミングRuby

メタプログラミングRuby

procとlambdaの違いについてメモ

Rubyのメモ。
procとlambdaはどちらもProcクラスのインスタンスなのだけど、違いがある。
そもそものProcの使い方と、lambdaとprocとで何が違うのかを調べてみました。


プログラミング言語Ruby」によると、

procはブロックのオブジェクト形態であり、ブロックのように振る舞う。lambdaは少し動作が異なり、ブロックというよりメソッドに近い。

らしい。

Procの実行方法

まず実行方法。
Procの実行方法はcall以外にも配列アクセス演算子やcallを呼び出すシンタックスシュガーがある。

#!/usr/bin/env ruby
f = Proc.new {|x,y| x*y }

# 以下の書き方は全て同じ意味(Ruby1.9以降)
p f.call(2,3)  # 6

# [] 配列アクセス演算子
p f[2,3]        # 6

# .() シンタックスシュガー
p f.(2,3)       # 6

lambdaリテラル

通常のlambdaの書き方

incre = lambda {|x| x+1 }
p incre.call(2)

lambdaリテラル

incre = ->(x){ x+1 }
p incre.call(2)

どちらも同じ意味になる

チェック方法

lambdaとprocのチェックをするには、 lambda? を使う。

先ほどのコードに以下を追加

l = ->(x,y){ x*y }
p l.call(2,3)   # 6

# lambdaかどうかのチェック
p f.lambda?   # false
p l.lambda?   # true

returnを使う場合

proc内でreturn文を使うとprocに変換されたブロックを囲うメソッドから戻ろうとする。
lambdaの中のreturn文は、lambda自身から戻る。

def make_proc(msg)
  Proc.new { p msg; return }
end

def test_proc
  p "start test_proc."
  proc_obj = make_proc("I'm proc.")
  proc_obj.call      #この行は実行されない
  p "end test_proc."
end

begin
  test_proc
rescue => e
  p e  # <LocalJumpError: unexpected return>
end

def make_lambda(msg)
  lambda { p msg; return }
end

def test_lambda
 p "start test_lambda."
 lambda_obj = make_lambda("I'm lambda.")
 lambda_obj.call   #実行される
 p "end test_lambda."
end

test_lambda

Procへの引数渡し

procはyieldと同様のセマンティクスを使う

p = Proc.new {|x,y| print x,y }
p.call(1)
p.call(1,2)
p.call(1,2,3)
p.call([1,2])

余分な引数は捨てられ、足りない値はnilになる。


lambdaはメソッドと同じく宣言した数と同じ引数を渡して呼び出す必要がある。

l = lambda {|x,y| print x,y }
l.call(1,2)
l.call(1)         # ArgumentError
l.call(1,2,3)   # ArgumentError
l.call([1,2])    # ArgumentError
l.call(*[1,2])  # *による明示的な展開があるためエラーにならない

参考:プログラミング言語Ruby

プログラミング言語 Ruby

プログラミング言語 Ruby

CookPadのchanko勉強会に行ってきたのでサンプルを作ってみた(View編)

6/8(金)にCookPadで開催された「chanko勉強会」に行ってきました。


chankoとはCookPadが開発したrails用のライブラリで、chankoを使うと
プロトタイプで作成した機能を素早くかつ安全にプロダクション環境で
実際のユーザーに使ってもらう事が出来ます。
https://github.com/cookpad/chanko


プロトタイプを実験的に特定のユーザーにのみ機能を公開することが出来るので、
自社のスタッフのみに公開したり、ある属性を持ったユーザーに対してのみ公開して
反応を見たりすることが出来るため、開発した機能を全体に公開する前に実際のユーザーからの
フィードバックを得られるというメリットがあります。

リーンスタートアップでいう「仮説と検証」のループを素早く回せるという事ですね。


以前、別のアジャイル系のイベントでchankoを使ったCookPadの取り組みを聞いていて
興味があったので申し込んでみたものの、落選…orz


と思ったら後日欠席者が出たとのことで、繰り上げ当選になりました(∩´∀`)∩ワーイ


勉強会の内容についてはid:antipopさんが詳しく書いてましたのでこちらで
http://kentaro.hatenablog.com/entry/2012/06/08/231419


せっかくなので忘れないうちに簡単なサンプルを作ってみようと思います。


chankoのサンプルプロジェクトの作成

rails new chanko_test


必要なgemの設定。chanko以外に、今回はtwitter-bootstrapとdeviseも入れておきます。

vi Gemfile

gem 'chanko', :git => 'git://github.com/cookpad/chanko.git'
gem 'twitter-bootstrap-rails'
gem 'devise'

bundle install


chankoのインストール

rails generate chanko:install


moduleのinclude

vi app/helpers/application_helper.rb

module ApplicationHelper
    include Chanko::Invoker
    include Chanko::Helper
end


chankoは「Unit」という単位で機能を作成します。
Unitはrails generateで生成出来るようになっています。

rails generate chanko deco     
      create  app/units/deco/deco.rb
      create  app/units/deco/views/_show.html.haml
      create  app/units/deco/specs/controllers/deco_controller_spec.rb
      create  app/units/deco/specs/models/deco_model_spec.rb
      create  app/units/deco/specs/helpers/deco_helper_spec.rb
      create  app/units/deco/stylesheets/deco.scss
      create  app/assets/stylesheets/units/deco
      create  app/units/deco/javascripts/deco.js
      create  app/assets/javascripts/units/deco
      create  app/units/deco/images/logo.png
      create  app/assets/images/units/deco


生成されたUnitを見てみましょう。
コメントアウトされたサンプルソースが記述されています。

vi app/units/deco/deco.rb

module Deco
  include Chanko::Unit

=begin
  active_if :always_true do |context, options|
    # "context' is invoking context such as controller
    true
  end

  scope(:controller) do
    function(:show) do
    end
  end

  scope(:view) do
    function(:show) do
      render :partial => "/show"
    end
  end

  models do
    expand("Recipe") do
      def your_method
      end

      class_methods do
        def your_class_method
        end
      end
    end
  end

  shared("your_shared_method") do
    'hello'
  end

  helpers do
    def your_helper_method
    end
  end
=end

end


chankoで作った機能を載せるためのアプリケーションを作ります。

rails g scaffold blog title:string content:text
rake db:migrate


デフォルトだと見た目が悪すぎるので、twitter bootstrapを入れます

rails g bootstrap:install
rails g bootstrap:layout application fixed

tableの見た目を少しいじりましたが省略します。


この状態でwebrickを起動するとこんな感じ。

ボタンはtwitter bootstrapのclassを使っていないので、chankoを使って少し見た目を変えてみます。

vi app/units/deco/deco.rb

module Deco
  include Chanko::Unit

  active_if :always_true do |context, options|
    # "context' is invoking context such as controller
    true
  end 
 
  scope(:view) do
    function(:show_button) do
      link_to :show, blog_path(blog), :method => :get, :class => 'btn'
    end 
  end 

先程生成されたUnitを編集しました。
active_ifの戻り値がtrueの場合にこのUnitで定義した機能が公開されます。


scope(:view)はViewに機能を追加する際に使用します。
渡すブロックの中でtwitter bootstrapのclassを設定したリンクを表示するようにしています。



次にアプリケーションから先ほど作成した機能を呼び出します。

vi app/views/blogs/index.html.erb

でViewのファイルを開いて

<td><%= link_to 'Show', blog %></td>

の行を以下のように変更します。

<td><%= invoke(:deco, :show_button, :locals => {:blog => blog}) %></td>

機能の呼び出しは
invoke(ユニット名, function名)
のように書きます。 :localsにはローカルスコープの変数を渡す事が出来ます。


この状態でアプリケーションを見てみると

おぉ、リンクがボタンに変わっていますね!
chankoのUnitで追加した機能が実行されてtwitter bootstrapのclassが反映されています。


では次に特定のユーザーだけにこの機能を見せるようにしましょう。

今のアプリケーションにdeviseをinstallしてユーザー管理の機能の追加します。
※deviseの手順は省略


表示の切り替えは app/units/deco/deco.rb の active_if で行います。
ユーザー名が"siso9to"の場合だけボタンが表示され、それ以外はテキストのリンクが
表示されるようにします。

active_if :always_true do |context, options|
    if context.current_user.username == "siso9to"
      true
    end 
end 

contextにはアプリケーションのコンテキストが渡されるので、ヘルパーなども使えるそうです。


画面の方はどうなったか見てみましょう。

ユーザー siso9toの場合

ボタンが表示されています。


ユーザー siso8toの場合

おぉ、ちゃんと指定したユーザー以外ではデフォルトの挙動をしました(テキストリンクを表示)。
※デフォルトの挙動については後述

今回は単純な例でしたが、例えば男性ユーザーのみに表示したい場合や、20代以上のみに
表示したい場合など、ユーザーのセグメントごとに表示を切り替えて様々な実験をすることが
出来そうです。


ちなみにUnitの方でエラーが発生した場合はどうなるのでしょうか?
chankoのUnitの中で発生したエラーはchankoの中で処理されます。

また、以下のように記述すると、Unitが無効の場合やエラーが発生した場合はデフォルトの
処理が実行されます。

invoke(ユニット名, function名) do
  #デフォルトの処理
end

この機能のおかげで、開発者は障害を恐れずにプロトタイプをプロダクション環境に
どんどんリリース出来ます。


もしエラーが発生した場合でも、プロトタイプの機能が表示されないだけでユーザーは
既存のアプリケーションを使用することが出来ます。
追加機能のリリースに失敗してアプリケーションが全て真っ白な画面に・・・という悲劇は
かなり回避できそうです。


デフォルトでは開発環境ではこの機能は無効になっている(例外がアプリケーション本体までthrowされる)
ので、もし開発環境でも本番環境と同じ振る舞いを確認したい場合は、

config/initializers/chanko_initializer.rb を開き、

Chanko.config.raise = true if Rails.env.development?

のtrueの部分をfalseにします。

Chanko.config.raise = false if Rails.env.development?


こうしておくと、先程追加した :show_buttonの中でraiseしても

  scope(:view) do
    function(:show_button) do
      raise 
      link_to :show, blog_path(blog), :method => :get, :class => 'btn'
    end 
  end 


画面にはエラーが表示されません(ログにはraiseの結果が出力されます)

これは便利ですね!


ここまでのソースはこちらにUPしておきました。
(大した事してないので公式のサンプル見たほうがいいですが・・・)
https://github.com/siso9to/chanko_test.git

今回はviewだけ記事に書きましたが、controllerやmodelも試してみたので、
整理して次回以降に書きたいと思います。