Yaya作った
こんなんつくりました。
導入
1.用意するもの
2.Herokuにデプロイ
https://github.com/anoChick/yaya
↑の[Deploy to Heroku]ボタンを押します
HerokuにログインしていればApp作成画面が出てくると思います。 環境変数をいれていきましょう
[SLACK_API_TOKEN] SLACKのAPIトークンをいれてください。 [ROOT_URL] アプリケーションのROOTURLを入れる所なんですが、まだ生成されていないので適当な文字列を入れておいて下さい。
Deployボタンを押せばデプロイされます。
デプロイが完了したらwebページが生成されるので、そのURLをROOT_URLに設定しておいて下さい。 (Settingsの[Config Variables])でできます。
slackのbotがオンライン状態になったらデプロイ成功です。
動かす
GraphQLのfieldに付くresolveをTypeによって指定する。
GraphQLでDateTimeを扱う時、フォーマットを指定する。 - あのにのに
前回の記事で 「DateTime型とかはクエリ側で出力フォーマットを引数として指定できると便利!」 って話をしました。
ついでに課題として
DateTimeTypeって作ったけど意味なくない..?
いちいちresolveにフォーマット付与を記述するの面倒じゃない?
という物があったのでこれを解決したいと思います。
やること
Typeが**ならresolveでxxをする。と言うような仕組みを作ります。
今回の場合だと 「fieldのTypeがDateTimeTypeの場合、そのfieldに引数[:format]を用意し、resolveではformatに応じで出力フォーマット通りに書こうするようにする。」 になります。
これができるようになると、DateTimeTypeが付いたfieldは全て[:format]が使えるようになります。
ちなみに使用するgemは前回に引き続き
https://github.com/rmosolgo/graphql-ruby
です。
やってく
今回はGraphQL::Define::AssignObjectFieldをモンキーパッチをあてる形で動かしてみます。 GraphQL::Define::AssignObjectField#callの程よくfieldが得られたタイミングで
if field.type.to_s == 'DateTime' field.arguments['format'] = GraphQL::Argument.define(name: 'format', type: -> { GraphQL::STRING_TYPE }) end
ついた!
次にリゾルバを作ります。
class DateTimeTypeFormatResolver def initialize(attr_name) @attr_name = attr_name end def call(obj, args, ctx) return obj[@attr_name].strftime(args[:format]) if args[:format].present? obj[@attr_name] end end
こんな感じ。あとは
if field.type.to_s == 'DateTime' field.arguments['format'] = GraphQL::Argument.define(name: 'format', type: -> { GraphQL::STRING_TYPE }) field.resolve=(DateTimeTypeFormatResolver.new(name)) end
とか書けばok
一応これで完成。
割りと便利な気がする。
今回新たにうまれた課題としては
- 他でリゾルバを記述したくなったらどうするんだ
- この書き方だとModelの属性名とTypeのfield名が一致している場合しかつかえない
次↑について考えます。
あとはその他便利拡張あればやる。
GraphQLでDateTimeを扱う時、フォーマットを指定する。
GraphQLにはScalarTypeというクラスが存在する。
ScalarTypeは配列やオブジェクト(いわゆるkey-valueペア)、enum等ではないタイプの基底クラスになる。
GraphQLではこのScalarTypeをベースとした5つの基本タイプが仕様として定義されている。
- Int - 符号付き32ビット整数
- Float - 倍精度少数
- String - UTF-8文字シーケンス
- Boolean - true or false
- ID - GUID
これだけだとDateTime等で困るので、DateTimeのGraphQLTypeを作ってみる。
date_time_type.rb
graphql-rubyを使う
https://github.com/rmosolgo/graphql-ruby
DateTimeType = GraphQL::ScalarType.define do name 'DateTimeType' description 'ActiveRecord::Type::DateTimeに対応したType' end
このままだとクエリに対する出力結果が
{ "data": { "series": { "created_at": "2017-01-09T18:55:30.000Z" } } }
になる。せっかくGraphQLなので、フォーマットもクエリとして与えられるようにしたい。
created_atフィールドをいじる
普通にcreated_atを一属性として扱うだけなら
field :title, DateTimeType
の記述だけで済むが、今回は引数formatを与えると、その通りに整形してくれるようにする。
field :created_at do type DateTimeType argument :format, types.String resolve ->(obj, args, ctx) { return obj.created_at if args[:format].nil? obj.created_at.strftime(args[:format]) } end
これでqueryを叩く
{ series(id: 3) { created_at(format:"%Y年%m月%d日 %H:%M:%S") } }
↓
{ "data": { "series": { "created_at": "2017年01月09日 18:55:30" } } }
良い。 本当はフォーマットの仕組みをTypeそのものに持たせたいのだけれど、TypeのresultがStringを取ることになるので適切では無さそう。
RailsのカスタムGeneratorを自分で作る
事前に用意したテンプレートを基にファイルを生成するようなコマンドを作る。
今回はGemにしたいのでプラグイン作成の想定でやる。
Railsプラグイン作成環境を用意
bin/rails plugin new sampleplugin
gemspecファイルのTODOになってるところを書き換えて、bundle install
をする。
プラグイン自体はlib
ディレクトリ以下に作っていき、
test/dummy
にrailsプロジェクトがあるのでそこで動作確認をしていく。
Generatorクラスを作る
lib/generators/sample_generator.rb
にgeneratorの処理を記述する
class SampleGenerator < Rails::Generators::Base def initialize(args, *options) super @_args, @_options = args, options end def main # ここに処理を書く end end
これでbin/rails generate sample
などと叩くとmainが実行される。
テンプレートの用意
例えば
<%= @type_name %> = GraphQL::ObjectType.define do name '<%= @model_name %>' end
lib/generators/templates/types.rb
にこんな感じでファイルを作っておく。
テンプレートを使う
class SampleGenerator < Rails::Generators::Base source_root File.expand_path('../templates', __FILE__) def initialize(args, *options) super @type_name ='sample_type' @model_name = 'Sample' end def main template "types.rb", "app/graphql/types/#{@type_name}.rb" end end
できた。
Model(ActiveRecord)からGraphQL::ObjectTypesを自動生成する仕組みを考えるメモ
最近個人的にWebアプリ作ってます。 フロントはReactJS+Redux サーバサイドはRails それぞれ独立していて、GraphQLを用いて通信しています。
GraphQLのRuby実装として一番スターの多いgraphql-rubyを使っています。
GraphQL周りはまだ発展途上なのでいろいろと不便。 Typesを手動で定義するのが面倒なのでこれについて考えてみる。
Modelに対応するObjectの構成要素について
ActiveRecord内の要素で、今回着目するつもりなのは以下の通り - ActiveRecord::Attributes - ActiveRecord::Relation - ActiveRecord::Enum
ActiveRecordは GraphQL::ObjectTypeと対応させるとして, ActiveRecord::Relationの場合は has_oneの場合:GraphQL::ObjectType has_manyの場合:GraphQL::ListType(GraphQL::ObjectType) でよさそう。 ActiveRecord::EnumはそのままGraphQL::EnumTypeで問題ないはず。
ActiveRecord::AttributesはGraphQL::ScalarTypeになるのだけれど、 GraphQLの標準として定められているScalarTypeは
- Int
- Float
- String
- Boolean
- ID
5つだけ。
- ActiveModel::Type::BigInteger - ActiveModel::Type::Binary - ActiveModel::Type::Boolean - ActiveModel::Type::Decimal - ActiveModel::Type::DecimalWithoutScale - ActiveModel::Type::Float - ActiveModel::Type::Integer - ActiveModel::Type::String - ActiveModel::Type::Text - ActiveModel::Type::UnsignedInteger - ActiveModel::Type::Value
Rails側はこんな感じ
対応できないGraphQL::ScalarTypeを新たに定義するのでもいいけど ユーザ独自で定義したActiveModel::Typeにも対応させることを考えると、 ActiveModel::Typeレベルから対応関係を結んだほうがよさそう。
StorybookでReactJSコンポーネントのレビュー環境を作る
StorybookとはWeb開発におけるUIコンポーネントを作る環境のこと。
インストール
まずは導入するReactJSプロジェクトを用意する。
mkdir react-sample cd react-sample npm init npm i --save react react-dom
次にStorybookを導入するためのコマンド getstorybook
をいれる。
npm i -g getstorybook getstorybook
getstorybook - the simplest way to add a storybook to your project. • Detecting project type. ✓ • Adding storybook support to your "React" app. ✓ • Preparing to install dependencies. ✓ yarn install v0.17.6 info No lockfile found. [1/4] 🔍 Resolving packages... [2/4] 🚚 Fetching packages... [3/4] 🔗 Linking dependencies... [4/4] 📃 Building fresh packages... success Saved lockfile. ✨ Done in 21.14s. • Installing dependencies. ✓ To run your storybook, type: yarn run storybook For more information visit: http://getstorybook.io
このようになったら導入成功。 yarnの利用が推奨されてるらしい。
storiesというディレクトリが作れていて、中に
- Welcome.js
- Button.js
- index.js
3つのファイルが生成されている。
WelcomeとButtonはサンプルコンポーネントで,indexはコンポーネントをStorybookにつなぐためのもの。
使う
早速 yarn run storybook
で実行してみる。
http://localhost:6006/
にStorybookが表示される。
import React from 'react'; import { storiesOf, action, linkTo } from '@kadira/storybook'; import Button from './Button'; import Welcome from './Welcome'; storiesOf('Welcome', module) .add('to Storybook', () => ( <Welcome showApp={linkTo('Button')}/> )); storiesOf('Button', module) .add('with text', () => ( <Button onClick={action('clicked')}>Hello Button</Button> )) .add('with some emoji', () => ( <Button onClick={action('clicked')}>😀 😎 👍 💯</Button> ));
storiesOfでストーリーを定義出来る。
コンポーネントの視覚的なテストのようなイメージ。
add
でprops等を入れていく。
試しに small というpropertyがつくとボタンが小さくなるようにする。
index.js
に
.add('with small', () => ( <Button onClick={action('clicked')} small>Small Button</Button> ))
を追加する。
import React from 'react'; export default class Button extends React.Component { constructor(props){ super(props); this.styles = { border: '1px solid #eee', borderRadius: 3, backgroundColor: '#FFFFFF', cursor: 'pointer', fontSize: 15, padding: '3px 10px', margin: 10, }; } render() { if(this.props.small){ this.styles.fontSize = 10; } return ( <button style={this.styles} onClick={this.props.onClick} > {this.props.children} </button> ); } } Button.propTypes = { children: React.PropTypes.string.isRequired, onClick: React.PropTypes.func, };
Buttonコンポーネントをこんな感じに書き換える。
リアルタイムで見た目が変わった。
共有する
作成したStorybookプロジェクトはStorybookHUBで共有できる。
Storybook Hub - The Perfect Place To Develop & Review User Interfaces
まずStorybookにコメント機能を付ける。
// To get built in addons. npm i -D @kadira/storybook-database-cloud npm i -D @kadira/storybook-addon-comments
↑を実行した後`.storybook/addons.jsを記述する
import '@kadira/storybook/addons'; import '@kadira/storybook-database-cloud/register'; import '@kadira/storybook-addon-comments/register';
コミットしてGitHubにプッシュする。
Storybook Hub - The Perfect Place To Develop & Review User Interfaces StorybookHUBのサイトにAppを登録する。 いまプッシュしたGitHubリポジトリを対象にする。
自動でビルドされて、StorybookのURLが生成される。 ↓こんな感じ
publicで公開することもできれば、privateでAppを作成して招待するという事もできる。
Storyに対してコメントが出来る。
ちなみにPublicリポジトリの場合は無料で利用できるけれど、 Privateリポジトリはコラボレータを増やすのにお金がかかる。
クローリングアプリを作る Part.1
今年得た知識を集めたらいい感じのクローリングアプリが作れそうな気がしたのでやってみる。
作りたい物
Architect
こんな感じ。 非技術者でも簡単に利用できるような、 アプリ単体でも稼働するネイティブアプリケーション。
ポイントとしてはサーバとDBはちゃんと分離して、外に出せるようにしたい。 サーバは一般的なWebサーバとして実装しておけば外に出したとき接続先の変更だけで対応出来る。 DBは一工夫要りそう。
とりあえずは単体で完結するようなものを作る。