クエリーに合わせた構造体を書くだけで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で書くと以下のようになります。
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)を再インストールしようとしたが、今度はインストール時にエラーが出て少し嵌ったので対応をメモ。
環境
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.
参考
- Bundlerでビルドオプションを指定する
- Unable to install version 0.7.3 on Mac OS Sierra
- 私の環境だと0.7.5はビルドオプション無しでもインストール出来ましたが、環境によってはエラーになるようです
リーンキャンバスのツールやテンプレート
先日、ミッドタウンで開催された書籍「RUNNING LEAN -実践リーンスタートアップ」の刊行記念セミナーに参加してきました。
『Running Lean -実践リーンスタートアップ』刊行記念 著者アッシュ・マウリャ氏 来日特別セミナー at Yahoo! JAPAN
著者のアッシュ・マウリャ氏の講演やYahoo!ラボの河合さんからの事例紹介がとても興味深い内容でした。
また、書籍の先行発売があったので早速購入していたのですが、セミナー終了後に著者のサイン会があったので私もサインをしてもらいました。

ありがとうございました!
ツール/テンプレート
書籍の中で、ビジネスモデルを簡潔に書き出すフォーマットとして「リーンキャンバス」というものが紹介されており、書籍もそのフォーマットの中身を埋めていく形で進むのですが、実際に自分もこれを使ってみようと思っていくつかツールやテンプレートを探してみました。
Leancanvas.com
著者の会社で作ってる本家ツールです。ブラウザ上でリーンキャンバスが作れます。
また、有料プランになりますが他の人とコラボレーションしながら作成することも出来ます。
1人までは無料で他の人とコラボレーション出来ますが、2人以上の人とコラボレーションする場合は有料になるようです。
(追記:修正しました。ご指摘ありがとうございます)
StartupWeekend
StartupWeekendのブログで紹介されていました。
リーンキャンバス(画像ファイル)へのリンクはこちら
The Business Model Canvas
リーンキャンバスの元となったビジネスモデルキャンバスです。
こちらは「ビジネスモデル・ジェネレーション」という書籍で日本でも今年の初めの方に紹介されていたようですね。
日本語版
いくつか見てみたのですが、日本語で書かれたリーンキャンバスのテンプレートが見つからなかったので、簡単ですが自分で作ってみました。
自分用ですが、使いたい人がいれば印刷するかKeynoteやパワポなどに貼り付けるなどして使ってください。
※クリエイティブ・コモンズ 表示-継承 3.0 非移植 ライセンスに基づいて改変しているつもりですが、不備などございましたらお手数ですがご連絡ください
ボルダリングとプロテイン
技術系の話ではないのですが、最近ボルダリングを始めて定期的にジムに通っています。
ボルダリングは手や足を置ける石がルートやレベルによって決まっていて、筋力だけでなくどのルートをどのように体を使ったら登れるか適度に頭を使って考える必要があり、パズルのようなゲーム感覚でトレーニングが出来て楽しいです。
また、なかなか登れなかったルートをゴールまで辿りつけた時の達成感がたまりません。
ジムに行くと登るのが楽しくて疲れを忘れて登ってしまうのですが何回か登ると腕がパンパンになってしまい、そうなると翌日からは確実に筋肉痛になってしばらく辛いです。
その筋肉痛を早く回復させたいのと、何よりボルダリングがもっと早く上達するように筋力をつけたくてプロテインを飲んでいるのですが、いろいろ試してみて最近飲んでいるのがこれ

飲みやすくて美味しい!これが次世代プロテイン! SAVAS(ザバス) アクアホエイプロテイン100 360g
- 出版社/メーカー: 明治製菓(株)
- メディア: ヘルスケア&ケア用品
- 購入: 1人 クリック: 3回
- この商品を含むブログ (1件) を見る
水だけで飲めて、味もスポーツドリンクに近くてそこそこ飲みやすいです。
まだボルダリング始めてから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
参考書籍

- 作者: Paolo Perrotta,角征典
- 出版社/メーカー: KADOKAWA/アスキー・メディアワークス
- 発売日: 2010/08/28
- メディア: 大型本
- 購入: 18人 クリック: 533回
- この商品を含むブログ (125件) を見る
procとlambdaの違いについてメモ
Rubyのメモ。
procとlambdaはどちらもProcクラスのインスタンスなのだけど、違いがある。
そもそものProcの使い方と、lambdaとprocとで何が違うのかを調べてみました。
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]) # *による明示的な展開があるためエラーにならない

- 作者: まつもとゆきひろ,David Flanagan,卜部昌平(監訳),長尾高弘
- 出版社/メーカー: オライリージャパン
- 発売日: 2009/01/26
- メディア: 大型本
- 購入: 21人 クリック: 356回
- この商品を含むブログ (129件) を見る
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にはアプリケーションのコンテキストが渡されるので、ヘルパーなども使えるそうです。
画面の方はどうなったか見てみましょう。
ユーザー 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も試してみたので、
整理して次回以降に書きたいと思います。

