JQ Blog

OJ Gem

はじめ

ウェブサービスをやっていく中にはAPIを取得することが多い。今みているmastodonではOJというJson parserを使っていたので調べてみた。

インストール

1
gem install oj

or

1
2
# in Gemfile
gem 'oj'

使い方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require 'oj'

h = { 'one' => 1, 'array' => [ true, false ] }
json = Oj.dump(h)

# json =
# {
#   "one":1,
#   "array":[
#     true,
#     false
#   ]
# }

h2 = Oj.load(json)
puts "Same? #{h == h2}"
# true

他のParserとの比較

  • benchmark-ipsでベンチマークを取る
  • 比較対象
    • JSON
    • yajl
    • oj

まずロカールにapiのコントローラーをよいする。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Api::JsonCompareController < ActionController::API
  def index
    json = {
      test1: 'test1',
      test2: 'test2',
      test3: 'test3',
      test4: 'test4',
      test5: 'test5',
      test6: 'test6',
      test7: 'test7',
      test8: 'test8',
      test9: 'test9',
      test10: 'test10'
    }
    render json: json, status: :ok
  end
end

それから比較するコードを書く。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class JsonCompare
  require 'uri'
  require 'net/http'
  require 'json'
  require 'yajl'
  require 'oj'
  require 'benchmark/ips'

  def compare
    uri = URI "http://localhost:3000/api/json_compare"
    response = Net::HTTP.get uri

    Benchmark.ips do |x|
      x.report("JSON.parse") { JSON.parse(response) }
      x.report("Yajl") { Yajl::Parser.new.parse(response) }
      x.report("Oj") { Oj.load(response) }
      x.compare!
    end
  end
end

結果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Warming up --------------------------------------
          JSON.parse     9.786k i/100ms
                Yajl    10.364k i/100ms
                  Oj    17.009k i/100ms
Calculating -------------------------------------
          JSON.parse     99.053k (± 3.2%) i/s -    499.086k in   5.043910s
                Yajl    107.150k (± 3.4%) i/s -    538.928k in   5.036016s
                  Oj    183.400k (± 3.0%) i/s -    918.486k in   5.012844s

Comparison:
                  Oj:   183399.8 i/s
                Yajl:   107149.6 i/s - 1.71x  slower
          JSON.parse:    99053.1 i/s - 1.85x  slower

=> #<Benchmark::IPS::Report:0x007fdef3b04440
 @data=nil,
 @entries=
  [#<Benchmark::IPS::Report::Entry:0x007fdef8c3cbf0 @iterations=499086, @label="JSON.parse", @measurement_cycle=9786, @microseconds=5043910.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdef8c3cc68 @error=3164, @mean=99053.0983205518>>,
   #<Benchmark::IPS::Report::Entry:0x007fdef7a234a0 @iterations=538928, @label="Yajl", @measurement_cycle=10364, @microseconds=5036016.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdef7a23568 @error=3672, @mean=107149.64088869188>>,
   #<Benchmark::IPS::Report::Entry:0x007fdef89e6c98 @iterations=918486, @label="Oj", @measurement_cycle=17009, @microseconds=5012844.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdef89e6d10 @error=5584, @mean=183399.80361005384>>]>

OJYajlよりは1.7倍、JSONよりは1.8倍速かった。
Stringじゃなくてモデルにしたらどうなるだろう。
apiコントローラーのコードを変えてみる。

1
2
3
4
5
6
class Api::JsonCompareController < ActionController::API
  def index
    @users = User.order(:id)
    render json: @users, status: :ok
  end
end

ちなみにUser.count100
さっきの比較コードを実行してみると、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Warming up --------------------------------------
          JSON.parse   253.000  i/100ms
                Yajl   259.000  i/100ms
                  Oj   387.000  i/100ms
Calculating -------------------------------------
          JSON.parse      2.529k (± 3.4%) i/s -     12.650k in   5.007160s
                Yajl      2.612k (± 2.6%) i/s -     13.209k in   5.061015s
                  Oj      3.952k (± 3.6%) i/s -     20.124k in   5.098328s

Comparison:
                  Oj:     3952.4 i/s
                Yajl:     2611.7 i/s - 1.51x  slower
          JSON.parse:     2529.5 i/s - 1.56x  slower

=> #<Benchmark::IPS::Report:0x007fdf594c4678
 @data=nil,
 @entries=
  [#<Benchmark::IPS::Report::Entry:0x007fdf5da291a0 @iterations=12650, @label="JSON.parse", @measurement_cycle=253, @microseconds=5007160.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdf5da29268 @error=87, @mean=2529.463823054468>>,
   #<Benchmark::IPS::Report::Entry:0x007fdf5a59b8c0 @iterations=13209, @label="Yajl", @measurement_cycle=259, @microseconds=5061015.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdf5a59ba28 @error=67, @mean=2611.693591724044>>,
   #<Benchmark::IPS::Report::Entry:0x007fdf5d821510 @iterations=20124, @label="Oj", @measurement_cycle=387, @microseconds=5098328.0, @show_total_time=true, @stats=#<Benchmark::IPS::Stats::SD:0x007fdf5d821588 @error=141, @mean=3952.373849831794>>]>

OJYajlJSONより1.5倍速いことがわかる。

まとめ

単純に速度のパフォーマンス側をみるとOJを使った方が良さそう!

追記

mastodonでのOJの使い方

  • emoji.jsonの変換
1
2
# app/lib/emoji.rb
data = Oj.load(File.open(Rails.root.join('lib', 'assets', 'emoji.json')))
  • serviceクラスでのpayload作成
1
2
3
4
5
6
7
8
9
10
11
12
# app/services/batched_remove_status_service.rb
@json_payloads = statuses.map { |s| [s.id, Oj.dump(event: :delete, payload: s.id)] }.to_h

# app/services/fan_out_on_write_service.rb
@payload = InlineRenderer.render(status, nil, :status)
@payload = Oj.dump(event: :update, payload: @payload)

# app/services/notify_service.rb
Redis.current.publish("timeline:#{@recipient.id}", Oj.dump(event: :notification, payload: InlineRenderer.render(@notification, @recipient, :notification)))

# app/services/remove_status_service.rb
@payload = Oj.dump(event: :delete, payload: status.id)
  • reactコンポーネントへのprops
1
2
3
4
5
// app/views/home/index.html.haml
.app-holder#mastodon{ data: { props: Oj.dump(default_props) } }

// app/views/about/show.html.haml
#mastodon-timeline{ data: { props: Oj.dump(default_props) } }

参考

RubyのJSONパーサーのパース速度比較
OJ Github

Doorkeeper Gemについて

Doorkeeperとは

OAuth2による認証機能をアプリに追加するGemです。

インストール

  • Gem
1
gem 'doorkeeper'
  • db migration
1
2
3
rails generate doorkeeper:install
rails generate doorkeeper:migration
bundle exec rake db:migrate

すると、自動にroutes.rbuse_doorkeeperが追加される。それから以下のルートが生成される。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GET  /oauth/authorize/:code(.:format)
GET   /oauth/authorize(.:format)
DELETE    /oauth/authorize(.:format)
POST  /oauth/authorize(.:format)
POST  /oauth/token(.:format)
POST  /oauth/revoke(.:format)
GET   /oauth/applications(.:format)
POST  /oauth/applications(.:format)
GET   /oauth/applications/new(.:format)
GET   /oauth/applications/:id/edit(.:format)
GET   /oauth/applications/:id(.:format)
PATCH /oauth/applications/:id(.:format)
PUT   /oauth/applications/:id(.:format)
DELETE    /oauth/applications/:id(.:format)
GET   /oauth/authorized_applications(.:format)
DELETE    /oauth/authorized_applications/:id(.:format)
GET   /oauth/token/info(.:format)

このルートを利用してアプリケーション毎認証を作ってアクセストークンなどを出したりするのもできる。

使い方

POST /oauth/authorizeで取得できるcodeを利用してPOST /oauth/tokenでアクセストークンを取得。

1
2
3
4
5
6
7
8
curl -F response_type=code \
-F grant_type=authorization_code \
-F client_id=9b36d8c0db59eff5038aea7a417d73e69aea75b41aac771816d2ef1b3109cc2f \
-F client_secret=d6ea27703957b69939b8104ed4524595e210cd2e79af587744a7eb6e58f5b3d2 \
-F code=456b683f8c1e5ccde47bdfac47ee3ec96e0f1bec2546734b92980dcb806df207 \
-F redirect_uri=http://localhost:3000 \
-F resource_owner_id=1 \
-X POST http://localhost:3000/oauth/token
  • parameter
    • grant_type
      • initializers/doorkeeper.rbに設定したgrant_flows
    • client_id
      • doorkeeperで作ったアプリのApplication Id
    • client_secret
      • doorkeeperで作ったアプリのSecret
    • code
      • 取得したコード
    • redirect_uri
      • doorkeeperで作ったアプリのCallback urls
    • resource_owner_id
      • initializers/doorkeeper.rbに設定したresource_owner_authenticator
  • 結果
1
{"access_token":"51ebde838061a87f4b2ea318386387f6d265a7328b3edfacb4b7deee7c3bfa73","token_type":"bearer","created_at":1481614521}

こういうふうにアクセストークンが取得できる。

その他

場合によってcurlじゃなくて他のライブラリを使うこともできる。

  • Oauth2

Oauth2 gemをインストールする。それから

1
2
client = OAuth2::Client.new(CLIENT_ID, CLIENT_SECRET, site: SITE_URL)
token = client.auth_code.get_token(CODE, :redirect_uri => REDIRECT_URL, :grant_type => 'client_credentials')

とすると、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
=> #<OAuth2::AccessToken:0x007fe8cfa51f58
 @client=
  #<OAuth2::Client:0x007fe8cea45330
   @auth_code=#<OAuth2::Strategy::AuthCode:0x007fe8cc25b3d8 @client=#<OAuth2::Client:0x007fe8cea45330 ...>>,
   @connection=
    #<Faraday::Connection:0x007fe8cc25a5c8
     @builder=
      #<Faraday::RackBuilder:0x007fe8cc2593a8
       @app=
        #<Faraday::Request::UrlEncoded:0x007fe8cecb7140
         @app=#<Faraday::Adapter::NetHttp:0x007fe8cecb7208 @app=#<Proc:0x007fe8cecb73e8@/Users/jo/.rbenv/versions/2.4.1/lib/ruby/gems/2.4.0/gems/faraday-0.12.2/lib/faraday/rack_builder.rb:154 (lambda)>, @config_block=nil, @connection_options={}>>,
       @handlers=[Faraday::Request::UrlEncoded, Faraday::Adapter::NetHttp]>,
     @default_parallel_manager=nil,
     @headers={"User-Agent"=>"Faraday v0.12.2"},
     @options=#<struct Faraday::RequestOptions params_encoder=nil, proxy=nil, bind=nil, timeout=nil, open_timeout=nil, boundary=nil, oauth=nil, context=nil>,
     @parallel_manager=nil,
     @params={},
     @proxy=nil,
     @ssl=#<struct Faraday::SSLOptions verify=nil, ca_file=nil, ca_path=nil, verify_mode=nil, cert_store=nil, client_cert=nil, client_key=nil, certificate=nil, private_key=nil, verify_depth=nil, version=nil>,
     @url_prefix=#<URI::HTTP http://localhost:3000/home/access_token>>,
   @id="c4af4d3ab6de658d55e9f0383cfec3703586f5802be991c82f3df99bc80ea853",
   @options={:authorize_url=>"/oauth/authorize", :token_url=>"/oauth/token", :token_method=>:post, :auth_scheme=>:request_body, :connection_opts=>{}, :connection_build=>nil, :max_redirects=>5, :raise_errors=>true},
   @secret="928d7220f1f112cf954871da5559a54a64eabcec9b56ffa87ad43a11a678b548",
   @site="http://localhost:3000/home/access_token">,
 @expires_at=1506062175,
 @expires_in=7200,
 @options={:mode=>:header, :header_format=>"Bearer %s", :param_name=>"access_token"},
 @params={"token_type"=>"bearer", "created_at"=>1506054975},
 @refresh_token=nil,
 @token="c7f79becd53a4f4e571b95dc9e075d39034f9e613505fdb844bd8e778eb8933c">

こういうresponseを取得できる。

  • RestClient

RestClient gemをインストールし、

1
2
client = RestClient.post 'http://localhost:3000/oauth/token', {grant_type: 'client_credentials', client_id: CLIENT_ID, client_secret: CLIENT_SECRET}
client.body

すると、

1
"{\"access_token\":\"7328d931953ce2a4db130d02575a95a80b47683c2b2f9619492285465bce4b9d\",\"token_type\":\"bearer\",\"expires_in\":7200,\"created_at\":1506055209}"

というresponseを取得できる。

Api連携

before_action :doorkeeper_authorize!を使って有効TokenのみアクセスできるようにApiと連携ができる。

1
2
3
class Api::BaseController < ActionController::API
  before_action :doorkeeper_authorize!
end

こういうふうに設定をしておく。

1
2
3
4
5
6
7
8
9
10
class Api::UsersController < Api::BaseController
  def show
    user = User.find(params[:id])
    if user.present?
      render json: {user: user}
    else
      render json: {user: "cannot found this user"}
    end
  end
end

ユーザー情報を取得するapiを試してみると、

1
2
3
4
5
6
client = OAuth2::Client.new("c4af4d3ab6de658d55e9f0383cfec3703586f5802be991c82f3df99bc80ea853", "928d7220f1f112cf954871da5559a54a64eabcec9b56ffa87ad43a11a678b548", site: "http://localhost:3000")
token = client.auth_code.get_token("536b065b6ae6ca9acdc71d33387b4e5e6dbdd5bcc6950f7fcb2e96fd307bc295", :redirect_uri => "http://localhost:3000/home/access_token", :grant_type => 'client_credentials')
user_info = token.get('/api/users/1')
body = ActiveSupport::JSON.decode user_info.body
user_email = body["user"]["email"]
=> "user@example.com"

簡単にデータが取れる。
トークン無しでアクセスしようとしたら、

1
2
RestClient.get 'http://localhost:3000/api/users/1'
=> RestClient::Unauthorized: 401 Unauthorized

こういうふうに接続できない。

Doorkeeper gemはすごく簡単にOAuth2による認証機能を実装できる。Doorkeeperと連携してさらにApiを作ったり、ログイン機能に認証機能をつけるなどの実装も可能になる。

参考

doorkeeper
Doorkeeper gem でOauth認証できるアプリケーションを作った後の事
【Rails5】Doorkeeper gemでOAuth2.0のためのAPIを作って、rubyクライアントで呼び出す

Shrine Gemについて

  • shrineとは

公式サイトには

Shrine is a toolkit for file attachments in Ruby applications.

と書いてある。つまり、ShrineはRubyアプリケーションにおいてファイルをアタッチするために使えるツールキットである。

  • 使い方
    • gemのインストール
      gem 'shrine'
    
    • 初期設定
      # config/initializers/shrine.rb
      require "shrine"
      require "shrine/storage/file_system"
    
      Shrine.storages = {
        cache: Shrine::Storage::FileSystem.new("public", prefix: "uploads/cache"), # temporary
        store: Shrine::Storage::FileSystem.new("public", prefix: "uploads/store"), # permanent
      }
    
      Shrine.plugin :activerecord
      Shrine.plugin :cached_attachment_data # for forms
    
    • Model
      class Photo < ApplicationRecord
        include ImageUploader[:image]
      end
    
    • Uploader
      class ImageUploader < Shrine
      end
    
    • Controller
      Controllerのparamはimage_dataではなく、imageにする。
      def photo_params
        params.require(:photo).permit(:name, :image)
      end
    

ビューを作ってブラウザーで確認してみると、


大きすぎる。。!!

  • リサイズ
    リサイズのためには他のgemをインストールする必要がある。

    • gemのインストール
      gem 'image_processing'
      gem 'mini_magick', '>= 4.3.5'
    
    • Uploader編集
      require 'image_processing/mini_magick'
      class ImageUploader < Shrine
        include ImageProcessing::MiniMagick
        plugin :processing
        plugin :versions   # enable Shrine to handle a hash of files
        plugin :delete_raw # delete processed files after uploading
    
        process(:store) do |io, context|
          original = io.download
    
          size_800 = resize_to_limit!(original, 800, 800) { |cmd| cmd.auto_orient } # orient rotated images
          size_500 = resize_to_limit(size_800,  500, 500)
          size_300 = resize_to_limit(size_500,  300, 300)
    
          {original: io, large: size_800, medium: size_500, small: size_300}
        end
      end
    
    • ビューで表示する ビューでサイズ別の画像を取得できる。
      = image_tag, @photo.image[:original].url
      = image_tag, @photo.image[:large].url}
      = image_tag, @photo.image[:medium].url}"
      = image_tag, @photo.image[:small].url}"
    

    ビューで確認すると、

  • バリデーションの追加
    Uploadにバリデーションをかけるのももちろんできる。
    validation_helpersのプラグインを追加し、バリデーションを追加する。
    バリデーションの処理はAttacher.validateの中に記述する。

    • Uploaderの編集
      require 'image_processing/mini_magick'
      class ImageUploader < Shrine
        include ImageProcessing::MiniMagick
        plugin :processing
        plugin :versions   # enable Shrine to handle a hash of files
        plugin :delete_raw # delete processed files after uploading
        plugin :validation_helpers
    
        process(:store) do |io, context|
          original = io.download
    
          size_800 = resize_to_limit!(original, 800, 800) { |cmd| cmd.auto_orient } # orient rotated images
          size_500 = resize_to_limit(size_800,  500, 500)
          size_300 = resize_to_limit(size_500,  300, 300)
    
          {original: io, large: size_800, medium: size_500, small: size_300}
        end
    
        Attacher.validate do
          validate_max_size 5 * 1024 * 1024, message: 'Image is too large (max is 5 MB)'
          validate_mime_type_inclusion %w(image/jpeg image/jpg image/png)
        end
      end
    

    バリデーションにかかったら

      photo = Photo.new
      photo.image = File.open("img.png")
      photo.valid? #=> false
      photo.errors.to_hash #=> {image: ["Image is too large (max is 5 MB)"]}
    
  • Direct uploads
    • gemのインストール
      gem 'aws-sdk', '~> 2.1'
      gem 'roda'
    
    • config/initializers/shrine.rbファイルの編集
      require "shrine/storage/s3"
    
      s3_options = {
        access_key_id:     "abc",
        secret_access_key: "123",
        region:            "my-region",
        bucket:            "my-bucket",
      }
    
      Shrine.storages = {
        cache: Shrine::Storage::S3.new(prefix: "cache", **s3_options),
        store: Shrine::Storage::S3.new(prefix: "store", **s3_options),
      }
    
    • Uploaderにpluginの追加
      plugin :direct_upload
    
    • routes.rbにmount追加
      Rails.application.routes.draw do
        mount ImageUploader::UploadEndpoint => "/images"
      end
    
    • jsonの取得

    GET /images/cache/presignのrouteでjsonを取得できる。

      # GET /images/cache/presign
      {
        "url" => "https://my-bucket.s3-eu-west-1.amazonaws.com",
        "fields" => {
          "key" => "cache/b7d575850ba61b44c8a9ff889dfdb14d88cdc25f8dd121004c8",
          "policy" => "eyJleHBpcmF0aW9uIjoiMjAxNS0QwMToxMToyOVoiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJzaHJpbmUtdGVzdGluZyJ9LHsia2V5IjoiYjdkNTc1ODUwYmE2MWI0NGU3Y2M4YTliZmY4OGU5ZGZkYjE2NTQ0ZDk4OGNkYzI1ZjhkZDEyMTAwNGM4In0seyJ4LWFtei1jcmVkZW50aWFsIjoiQUtJQUlKRjU1VE1aWlk0NVVUNlEvMjAxNTEwMjQvZXUtd2VzdC0xL3MzL2F3czRfcmVxdWVzdCJ9LHsieC1hbXotYWxnb3JpdGhtIjoiQVdTNC1ITUFDLVNIQTI1NiJ9LHsieC1hbXotZGF0ZSI6IjIwMTUxMDI0VDAwMTEyOVoifV19",
          "x-amz-credential" => "AKIAIJF55TMZYT6Q/20151024/eu-west-1/s3/aws4_request",
          "x-amz-algorithm" => "AWS4-HMAC-SHA256",
          "x-amz-date" => "20151024T001129Z",
          "x-amz-signature" => "c1eb634f83f96b69bd675f535b3ff15ae184b102fcba51e4db5f4959b4ae26f4"
        }
      }
    

こう設定すると、ひとまずs3にdirect_uploadはできる。
公式documentにはjQuery-File-Upload, Dropzone, FineUploaderなどのJavaScriptファイルアップロードライブラリとも連携もできると書いてあるので、次は今のプロジェクトで使用しているDropzoneとの連携を調べてみる。

参照

Shrine githubページ
RailsでShrineを使ってファイルをS3にダイレクトアップロードするときの注意点
Shrineを使って画像をアップロードする

ActiveModelSerializersについて

Mastodonのソースコードを最近のRailsの記述を学ぼうとしたのでMastodonのソースコードを読み始めた。
Mastodonのrootページになる/aboutからみたのだが、最初からわからないことばかり!(泣)
MastodonにはActiveModelSerializersを使ってjsonの処理を行っていたのでActiveModelSerializersを調べてみた。

ActiveModelSerializersとは

ActiveModelSerializersによると

ActiveModelSerializers brings convention over configuration to your JSON generation.

と書いてある。 つまりActiveModelSerializersとはRailsで簡単で素早くjsonを作れるgemである。 JSON形式でレスポンスを返す場合、表示するパラメータを制御したいときに細かなJSONの制御を行うことができるので便利。

使い方

rails generate serializer Hogeを実行すると
app/serializers/hoge_serializer.rbファイルが作成される。

1
2
3
4
5
6
7
class HogeSerializer < ActiveModel::Serializer
  attributes :id, :name, :email, :name_size

  def name_size
    object.name.size
  end
end

上記のコードをみてみると
- attributesにレスポンスで表示したいパラメータを記述する。 - 関連オブジェクトで同時に表示したいオブジェクトがある場合はhas_manyhas_oneなどが使える。 - 関数を定義することで、独自のパラメータを追加することもできる。

Controllerで次のように書くと、

1
2
@hoge = Hoge.first
render json: @hoge

結果のjsonが以下のようになっている。

1
2
3
4
5
6
{
  id: 1,
  name: 'hoge',
  email: 'hoge@example.com',
  name_size: 4
}

このように、Serializerクラスで整形処理を行うことで、Controllerがより綺麗になると思う。

参照

active_model_serializersが便利

MastodonをHerokuで動かしてみる

Mastodonとは

(ウィキペディア「マストドン (ミニブログ)」))に詳しく説明されているがMastodonというものはTwitterによく似ている短文投稿型のSNSである。最近流行っているそうだ。Mastodonはオープンソースで公開されているので自由に活用できる。

使い方

使い方としてはいろいろあるけど、今回はgithubに乗せているDeploy to Herokuボタンを利用する方法とgit cloneして自らHerokuにpushする方法、2つを試してみる。

Deploy to Herokuボタンを利用する方法

以外とすごく簡単。https://github.com/tootsuite/documentation/blob/master/Running-Mastodon/Heroku-guide.md のHeroku Buttonを押し、

  • App Nameを希望のアプリ名に
  • S3_ENABLEDをtrueに
  • LOCAL_DOMAINを<アプリ名>.herokuapp.comに
  • SMTP_FROM_ADDRESSをnotifications@localhostに

変更して、Deployボタンを押す。Mastodonはデフォルトではdynoのファイルシステムにアイコンやメディアなどのファイルを格納する。これらのファイルは1日に1度のdynoの再起動の際に消えてしまう。なのでS3を使ってファイルを保存する方が良い。S3を使う場合は

  • S3_BUCKET
  • S3_REGION
  • AWS_ACCESS_KEY_ID
  • AWS_SECRET_ACCESS_KEY

これらを記入する。
メールの機能のためにSMTP設定もしなきゃいけないが今回は一旦gmailで設定しておいた。
Manage appボタンからダッシュボードを開き、Dyno formationからConfigure Dynosをたどり、worker dynoをONにする。
これでセッティングは終わり。
今までの設定をしたApplicationここで確認できる。

git clone

git cloneするのもすぐできる。
適当なDirectoryに

1
$ git clone git@github.com:tootsuite/mastodon.git

を入力する。それから
- Xcode Command Line ツール

1
$ xcode-select --install
  • HomeBrewで下記のものをインストール
1
$ brew install imagemagick ffmpeg yarn postgresql redis rbenv nodejs protobuf
  • rbenv 2.4.1をインストール
1
$ rbenv install 2.4.1
  • bundlerインストール
1
$ gem install bundler

全部インストールできたら、実際のapplicationのためのインストールをする。

1
2
3
4
5
6
$ brew install libidn
$ bundle install --with development
$ yarn install --pure-lockfile
$ gem install foreman --no-ri --no-rdoc
$ bundle exec rails db:setup
$ bin/rails assets:precompile

ここまでしたらセッティングは終わり。こうしたらローカルで動くようになる。
そこで

1
2
$ heroku login
$ heroku create <App名>

を実行してheroku appを作って、

1
2
3
$ git add .
$ git commit -m "first commit"
$ git push heroku master

を実行してpushする。そしたら

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
remote:        To see why this extension failed to compile, please check the mkmf.log which can
remote:        be found here:
remote:        
remote:        /tmp/build_65348a656aac448eb5aea36afa80f6c0/vendor/bundle/ruby/2.4.0/extensions/x86_64-linux/2.4.0/cld3-3.1.3/mkmf.log
remote:        
remote:        extconf failed, exit code 1
remote:        
remote:        Gem files will remain installed in
remote:        /tmp/build_65348a656aac448eb5aea36afa80f6c0/vendor/bundle/ruby/2.4.0/gems/cld3-3.1.3
remote:        for inspection.
remote:        Results logged to
remote:        /tmp/build_65348a656aac448eb5aea36afa80f6c0/vendor/bundle/ruby/2.4.0/extensions/x86_64-linux/2.4.0/cld3-3.1.3/gem_make.out
remote:        
remote:        An error occurred while installing cld3 (3.1.3), and Bundler cannot continue.
remote:        Make sure that `gem install cld3 -v '3.1.3'` succeeds before bundling.
remote:        
remote:        In Gemfile:
remote:        cld3
remote:  !
remote:  !     Failed to install gems via Bundler.
remote:  !
remote:  !     Push rejected, failed to compile Ruby app.
remote:
remote:  !     Push failed

と怒られる。調べてみるとbuildpacksを設置しなきゃいけない。それで

1
2
3
4
5
$ heroku buildpacks:add https://github.com/heroku/heroku-buildpack-apt # v1.3.3かそれ以前ではこの行は不要
$ heroku buildpacks:add heroku/nodejs -a <App名>
$ heroku buildpacks:add heroku/ruby -a <App名>
$ heroku addons:create heroku-postgresql -a <App名>
$ heroku addons:create heroku-redis -a <App名>

ちゃんと設置をしてから

1
$ git push heroku master

をしてみたら、無事に通る。次は

  • データベースのマイグレーション
1
2
$ heroku run rails db:migrate -a <App名>
$ heroku run rails db:seed -a <App名>
  • アプリケーションの設定
1
2
3
4
5
$ heroku config:set HEROKU=true -a <App名>
$ heroku config:set LOCAL_DOMAIN=$APP_NAME.herokuapp.com -a <App名>
$ heroku config:set PAPERCLIP_SECRET=`heroku run rails secret -a <App名>` -a <App名>
$ heroku config:set SECRET_KEY_BASE=`heroku run rails secret -a <App名>` -a <App名>
$ heroku config:set OTP_SECRET=`heroku run rails secret -a <App名>` -a <App名>

そしてHerokuのAppのDashboardでworkerをONにする。
ここまでするとセットアップは全て終わり、Heroku上で動かすことができる。その結果はここで確認できる。

参照

tootsuite/documentation(Development guide)
tootsuite/documentation(Heroku guide)
zunda/mastodon(CreateInstanceOnHeroku)

クラス変数、インスタンス変数、クラスインスタス変数

クラス変数

クラスに変数を持ちたい時にはクラス変数を定義すれば良い。@@というプレフィックスをつけるとクラス変数の定義して使うことができる。

1
2
3
4
5
6
7
8
9
10
11
class ParentVariable
  @@var = "Hello!"

  def class_var
    @@var
  end
end

class Variable < ParentVariable

end

クラス変数はサブクラスや通常のインスタンスメソッドからアクセスすることもできる。上記のVariableクラスのclass_varを実行してみると

1
2
3
4
irb> var = Variable.new
=> #<Variable:0x007f8483c8f730>
irb> var.class_var
=> "Hello!"

普通にクラス変数の@@varを取得できる。しかし、クラス変数には少し使いにくいところがある。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class ParentVariable
  @@var = "Hello!"

  def class_var
    @@var
  end
end

class Variable < ParentVariable
  @@var = "Good morning!"
end

irb> parent_var = ParentVariable.new
=> #<ParentVariable:0x007f8480e4dd90>
irb> prent_var.class_var
=> "Hello!"
irb> var = Variable.new
=> #<Variable:0x007f8480de7388>
irb> var.class_var
=> "Good morning!"
irb> pvar.class_var
=> "Good morning!"

こういうふうにクラス変数を変更する場合、このクラス変数にかかっている全てのオブジェクトに影響を与えるので実際の変数の値が変更されてしまう。ということで多くのRubyistはクラス変数は使ってない(らしい)。

インスタンス変数

インスタンス変数は@というプレフィックスをつけて使う。これはオブジェクトに所属していて、クラスとは何もつながりがない。なので、同じクラスのオブジェクトでもインスタンス変数の値はそれぞれ違う。インスタンス変数は値を代入した時に初めて出現する。だから明示的に定義しなくても良い。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Variable
  def instance_var var
    @var = "variable : '#{var}'."
  end
end

irb> var = Variable.new
=> #<Variable:0x007f8483923e98>
irb> var1 = var.instance_var "Hello"
=> "variable : 'Hello'."
irb> var2 = var.instance_var "Bye"
=> "variable : 'Bye'."
irb> var1
=> "variable : 'Hello'."
irb> var2
=> "variable : 'Bye'."

クラスインスタンス変数

Rubyでは、全てのインスタンス変数はカレントオブジェクトselfに属している。クラスの定義の中ではselfはクラスのことになるから、下記にクラスに定義された@varはクラスに属していることになる。同じインスタンス変数@varだけどどこに定義されているかによって違い、それぞれ異なるスコープに定義されており、別々のオブジェクトに属している。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Variable
  @var = "Class Instance Hello!"

  def initialize(str)
    @var = str
  end

  def instance_var
    @var
  end

  def self.class_instance_var
    @var
  end
end

irb> var = Variable.new "Instance Hello!"
=> #<Variable:0x007f8483c56bd8 @var="Instance Hello!">
irb> var.instance_var
=> "Instance Hello!"
irb> Variable.class_instance_var
=> "Class Instance Hello!"

1つ目instance_varメソッドで呼ばれた@varvarオブジェクトがselfになる。つまり、varオブジェクトのインスタンス変数だ。2つ目のclass_instance_varのクラス変数で呼ばれた@varVariableselfになる。つまり、これはVariableオブジェクトのインスタンス変数だ。これはClassクラスのオブジェクトであるVariableに属しているので通常のインスタンス変数だ。これをクラスインスタンス変数と呼ぶ。
クラスインスタンス変数はクラスからしかアクセスできない。クラスのインスタンスやサブクラスからはアクセスできない。

参照

メタプログラミングRuby 45p - インスタンス変数
メタプログラミングRuby 144 ~ 146p - クラスインスタンス変数

Rubyの値渡しと参照渡し

一般的に、引数は二つの方法がある。一つが値渡しで、もう一つが参照渡しである。
値渡しの概念はとても簡単。

  1. 受け取るデータを格納する変数 (仮引数) を用意する。
  2. データを引数に代入する。
  3. 関数の実行終了後、引数を廃棄する。

値渡しのポイントは2番目。データを引数に代入するとき、データのコピーが行われる。

1
2
3
4
5
6
7
8
9
10
11
irb> def foo(a)
irb> a = 100
irb> print a
irb> end
=> :foo
irb> x = 10
=> 10
irb> foo x
100=> nil
irb> x
=> 10

fooが参照渡しであれば、仮引数aの値を100に書き換えると、実引数であるxの値も100になるはず。foo(x)を呼び出したあと、xの値は10のままなので、Rubyは「値渡し」であることがわかる。

1
2
3
4
5
6
7
8
9
10
11
12
13
irb> def print_obj_id arg
irb> p "before change : #{int.object_id}"
irb> arg = 10
irb> p "after changed : #{int.object_id}"
irb> arg
irb> end

irb> int = 1

irb> p "int before func : #{int}"
irb> p "obj_id outside func : #{int.object_id}"
irb> p "excute func : #{print_obj_id int}"
irb> p "int after excuted func : #{int.object_id}"

このコードに対する結果は下記のようになる。

1
2
3
4
5
6
"int before func : 1"
"obj_id outside func : 3"
"before change : 3"
"after changed : 21"
"excute func : 10"
"int after excuted func : 3"

これは渡された仮引数のargが変わっても実引数の変数intは変わらないことを証明する。
しかし、配列、hashなどはmutable(書き換え可)なオブジェクトなので、関数の引数に配列やhashを渡してそれを修正すると、呼び出し元の変数の値も書き換えられるようになる。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# arrayの場合
irb> def bar(x, y)
irb> x.push y
irb> x[0] = 10
irb> end
=> :bar
irb> a = [1, 2, 3]
=> [1, 2, 3]
irb> a.object_id
=> 70284299180520
irb> bar a, 4
=> 10
irb> a
=> [10, 2, 3, 4]
irb> a.object_id
=> 70284299180520

# hashの場合
irb> def baz x, y
irb> x.store "fourth", y
irb> x[:first] = 10
irb> end
=> :baz
irb> hash = {first: 1, second: 2, third: 3}
=> {:first=>1, :second=>2, :third=>3}
irb> baz hash, 4
=> 10
irb> hash
=> {:first=>10, :second=>2, :third=>3, "fourth"=>4}

上記のコードを見てみるとarrayの場合はarray変数の0番目indexの値が変わり、端っこにデータが追加されたのが確認できる。hashの場合もfirstのオブジェクトが変わり、端っこにデータが追加されたのが確認できる。

追記

オブジェクトはどうなるかについても試してみたので追記。
下記のようなクラスを作成する。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Name
  def initialize(first_name, last_name)
    @first_name = first_name
    @last_name = last_name
  end

  def first_name(name)
    @first_name = name
    @first_name
  end

  def last_name(name)
    @last_name = name
    @last_name
  end

  def full_name
    full_name = "#{@last_name} #{@first_name}"
  end
end

Nameのオブジェクトを作ってfull_nameのメソッドを実行すると

1
2
3
4
irb> create_name = Name.new "Nobunaga", "Oda"
=> #<Name:0x007f8483c1f8b8 @first_name="Nobunaga", @last_name="Oda">
irb> create_name.full_name
=> "Oda Nobunaga"

普通に入力した通りOda Nobunagaというフルネームが取得される。
ここで下記のようなクラスを作成して試してみる。

1
2
3
4
5
6
7
8
9
10
class Change
  def initialize(obj)
    @name_obj = obj
  end

  def change_str
    @name_obj.first_name("hogehoge")
    @name_obj.last_name("foo")
  end
end

このChangeクラスにcreate_nameオブジェクトを渡して実装してみると

1
2
3
4
5
6
7
8
9
10
irb> create_name = Name.new "Nobunaga", "Oda"
=> #<Name:0x007f8483c1f8b8 @first_name="Nobunaga", @last_name="Oda">
irb> create_name.full_name
=> "Oda Nobunaga"
irb> change = Change.new create_name
=> #<Change:0x007f84838aae58 @name_obj=#<Name:0x007f84838c0898 @first_name="Nobunaga", @last_name="Oda">>
irb> change.change_name
=> "foo"
irb> create_name.full_name
=> "foo hogehoge"

create_name.full_nameを叩いた時に値が変わることが確認できる。
では、create_nameChangeクラスの@name_objの変数が同じものなのかな。確認してみよう。

1
2
3
4
5
6
7
8
9
10
11
12
class Change
  attr_accessor :name_obj

  def initialize(obj)
    @name_obj = obj
  end

  def change_name
    @name_obj.first_name("hogehoge")
    @name_obj.last_name("foo")
  end
end

Changeクラスにattr_accessorを追加して@name_objを使えるようにする。

1
2
3
4
5
6
7
8
9
10
11
12
irb> create_name = Name.new "Nobunaga", "Oda"
=> #<Name:0x007f8483b03740 @first_name="Nobunaga", @last_name="Oda">
irb> change = Change.new(create_name)
=> #<Change:0x007f8483ae06c8 @name_obj=#<Name:0x007f8483b03740 @first_name="Nobunaga", @last_name="Oda">>
irb> create_name === change.name_obj
=> true
irb> create_name.equal? change.name_obj
=> true
irb> create_name.object_id
=> 70103560887200
irb> change.name_obj.object_id
=> 70103560887200

create_name@name_objは同じものだったそうだ。
ということで、オブジェクトも参照の値渡しということが分かってきた。

参照

お気楽 Ruby プログラミング入門
Rubyの関数の引数は値渡しであることの証明をする
メソッドの引数(値渡し、参照渡し)

Capybara

capybaraはE2Eテストツールであり、ビューの単位テストができるようにしてくれる。
ちなみに、Rails5.1からはcapybaraがデフォルトのgemに入っている。

準備

今回のテストはrspecとcapybaraを使ってみる。

install

1
2
3
4
5
6
7
group :test do
  gem 'rspec-rails'
  gem 'poltergeist'
  gem 'capybara-screenshot'
  gem 'database_rewinder'
  gem 'factory_girl_rails'
end

gemの説明をしてみると、
poltergeistはcapybara用ドライバーである。poltergeistを使えばphantomJSが提供するブラウザでテストすることができる。それとJSの実装やスクリーンショットも撮ることもできる。
capybara-screenshotはスクリーンショットの便利性のために使う。
database_rewinderはdatabaseのクリーナーであり、factory_girl_railsはモデルオブジェクトを作るために入れる。

  • rspecのinstall

rails generate rspec:install

  • require

spec/rails_helper.rbに以下のように書き込む。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
require 'rspec/rails'
require 'capybara/rails'
require 'capybara/rspec'
require 'capybara/poltergeist'
require 'database_rewinder'

# capybara
Capybara.app_host = "http://example.com"
Capybara.always_include_port = true
Capybara.default_max_wait_time = 5

Capybara::Screenshot.autosave_on_failure = false

Capybara.javascript_driver = :poltergeist

Capybara.register_driver :poltergeist do |app|
  Capybara::Poltergeist::Driver.new(app, {
    inspector: true,
    js_errors: false,
    timeout: 1000,
    phantomjs_options: [
      '--load-images=no',
      '--ignore-ssl-errors=yes',
      '--ssl-protocol=any'
    ]
  })
end

# database_rewinder
RSpec.configure do |config|
  config.before :suite do
    DatabaseRewinder.clean_all
  end

  config.before(:each) do
    DatabaseRewinder.clean
  end

  config.before(:each, js: true) do
    DatabaseRewinder.strategy = :truncation
  end

  config.after :all do
    DatabaseRewinder.clean
  end
end
  • PhantomJSのinstall

poltergeistを使うためにphantomJsをインストールする。
macだとPhantomJSはbrew install phantomjsで簡単に入れられる。

簡単なテスト

まず、テストのため、scaffoldでmodel, controller, viewを作る。

1
$ rails g scaffold post title:string content:text

そしたらこういうビューが作られる。
Alt text

このページでPostを作成するテストをしてみる。
specフォルダーにpostsのフォルダーを追加、その中にindex_spec.rbというファイルを作成する。
そして、そのファイルに

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
require 'rails_helper'

feature 'Post一覧画面の表示' do

  scenario 'Postが作成できること', js: true do
    visit posts_url
    expect(page).to have_css('h1', text: 'Posts')
    screenshot_and_open_image
    click_link('New Post')
    sleep 0.5
    screenshot_and_open_image
    fill_in('post_title', with: 'Title01')
    screenshot_and_open_image
    fill_in('post_content', with: 'Content01')
    screenshot_and_open_image
    click_button('Create Post')
    sleep 0.5
    find('p#notice', text: 'Post was successfully created.').visible?
    screenshot_and_open_image
    click_link('Back')
    sleep 0.5
    screenshot_and_open_image
  end
end

こういうコードを入れてみる。
capybaraはいろんなDSLを提供している。
みたらすぐわかるくらいsimpleでわかりやすく使えるようになっている。
ちなみに、fill_in('post_title', with: 'Title01')find('input[name="post[title]"]').set('Title01')に変えることもできる。同じようにclick_linkfind('hogehoge).clickに変えられる。
このテストの結果は

1
2
3
4
5
Post一覧画面の表示
  Postが作成できること

Finished in 4.42 seconds (files took 3.02 seconds to load)
1 example, 0 failures

こうなっていて、下はスクリーンショット。
Alt text
Alt text
Alt text
Alt text
Alt text
Alt text

参考

RailsでRSpec+Capybara+factory_girl+その他テスト系gemのインストール方法
Rails + Capybara + Rspecをセットアップして簡単なテストが通るまで

'vue-resource'とES6を使ってTodoを作成

環境

  • Rails
    • 5.1.1
  • Vue.js
    • 2.3.4

ES6について

Railsでwebpackerを使ってVue.jsを設置した場合、いろんなライブラリがdefaultでインストールされる。その中でbabel-loaderというライブラリがある。
Package.jsonを確認してみると、

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
  "name": "todo_example",
  "private": true,
  "dependencies": {
    "autoprefixer": "^7.1.1",
    "babel-core": "^6.25.0",
    "babel-loader": "7.x",
    "babel-plugin-syntax-dynamic-import": "^6.18.0",
    "babel-plugin-transform-class-properties": "^6.24.1",
    "babel-polyfill": "^6.23.0",
    "babel-preset-env": "^1.5.2",
    "coffee-loader": "^0.7.3",
    "coffee-script": "^1.12.6",
    "compression-webpack-plugin": "^0.4.0",
    "css-loader": "^0.28.4",
    "extract-text-webpack-plugin": "^2.1.2",
    "file-loader": "^0.11.2",
    "glob": "^7.1.2",
    "js-yaml": "^3.8.4",
    "node-sass": "^4.5.3",
    "path-complete-extname": "^0.1.0",
    "postcss-loader": "^2.0.6",
    "postcss-smart-import": "^0.7.4",
    "precss": "^1.4.0",
    "rails-erb-loader": "^5.0.2",
    "resolve-url-loader": "^2.0.3",
    "sass-loader": "^6.0.6",
    "style-loader": "^0.18.2",
    "vue": "^2.3.4",
    "vue-loader": "^12.2.1",
    "vue-template-compiler": "^2.3.4",
    "webpack": "^2.6.1",
    "webpack-manifest-plugin": "^1.1.0",
    "webpack-merge": "^4.1.0"
  },
  "devDependencies": {
    "webpack-dev-server": "^2.4.5"
  }
}

babel-loaderが置いてることが確認できる。
このbabel-loaderconfig/webpack/loadersにあるbabel.jsで読み込んでES6をトランスパイルしてくれる。
safariはまだES6をサポートしてくれないのでsafariで試してみる。
まず下記のようにbabel.jsのloaderにbabel-loaderを指定したときは

1
2
3
4
5
module.exports = {
  test: /\.js(\.erb)?$/,
  exclude: /node_modules/,
  loader: 'babel-loader'
}


画像のように正しくリストが表示されてる。 しかし、loaderのところを一回消してみると

1
2
3
4
5
module.exports = {
  test: /\.js(\.erb)?$/,
  exclude: /node_modules/,
  // loader: 'babel-loader'
}


JSをES6で書いたので何も表示されてないことを確認できる。

もう一つ試してみる。
ES6的なclassを定義してVueコンポーネントでそのclassの関数を呼び出してみる。

1
2
3
4
5
6
7
class TodoLog {
  constructor(log){
    this.log = log
  }

  printLog = () => console.log(this.log)
}

シンプルにこういうclassを定義してVueコンポーネントのmountedで呼び出すと

1
2
3
4
5
mounted: function() {
  console.log('TodoList is mounted.')
  const todoLog = new TodoLog('constructor is called.')
  todoLog.printLog()
}

TodoLogprinLogという関数が呼び出されてブラウザにlogが出力されたのが確認できる。

アロー関数

vueでアロー関数を使うときは注意しなきゃいけないことがある。
アロー関数は既存のメソッドと違ってthisが変わる。関数自体が親コンテキストにバインディングされているためVueインスタンスにはならない。
thisでVueのコンポーネントを呼ぶ処理をしたいならアロー関数は使ってはならない。
Vueのコンポーネントのmountedをアロー関数を例にして試してみると既存のfunction()だとちゃんとVueコンポーネントが出力されるが

1
2
3
4
5
6
7
8
mounted: function() {
    console.log('TodoList is mounted.')
    console.log(this) # thisを出力してみる
    var that = this
    this.$http.get(this.url).then((response) => {
      that.todos = response.data
    })
},


アロー関数を使った場合はVueコンポーネントが出力されないし、$httpもエラーを出す。

1
2
3
4
5
6
7
8
mounted: () => {
    console.log('TodoList is mounted.')
    console.log(this) # thisを出力してみる
    var that = this
    this.$http.get(this.url).then((response) => {
      that.todos = response.data
    })
},

vue-resource

install

まずnpmでvue-resourceをインストールする。

1
$ npm install vue-resource --save

yarnを使ってもOK。

1
$ yarn add vue-resource

使い方

それからjsimportして使う。

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue/dist/vue.min'
import VueResource from 'vue-resource/dist/vue-resource.min'
Vue.use(VueResource)

var that = this
this.$http.get('何かのurl').then((response) => {
  that.todos = response.data
}, response => {
  // error callback
})

参考

Using ES6 arrow functions in Vue.js
ES6の新機能「class構文」 – 基礎編 –

Webpacker

Webpackとは

最近、フロントエンドの規模が段々大きくなり、Javascriptのコード量が多くなって、コードのメンテナンスのためにモジュール化して管理する場合が多い。また、いろんなJSのフレームワークやNodeのライブラリを使うようになる。でもJSは基本的にこういうモジュール間のimport/includeをサポートしてくれないのでモジュールを管理するツールが必要になる。それのために出てきたものがモジュールバンドリングだ。モジュール間の依存性を理解し、それを元にうまく動ける静的アセットを作ることがモジュールバンドリングであり、それを実行するものがモジュールバンドラーだ。もちろんWebpackは唯一のモジュールバンドラーではない。既にRequireJS,Browserifyなどのモジュールバンドラーがあるけど、最近のフロントエンドにはWebpackが主流になっている。

RailsでのWebpack

Rails5.1ではWebpackを通してReact,Vue.js,Angular,Elmなどのフレームワークがサポートされ、さらに簡単に使えるようになった。webpackerを使えばプロジェクトにJSのフレームワークやライブラリを簡単に入れることができるし、Webpackのconfigを設定してBabelを使ってトランスパイルすることももちろんできる。

webpackerの使い方

インストール

Rails5.1には

1
$ rails new myapp --webpack

のように後ろに--webpackをつけてプロジェクト作成の時インストールするか普通にgemfileに入れてbundle installをしてから./bin/rails webpacker:installをすることでwebpackerを入れることができる。
Rails5.1でサポートするReact,Vue.js,Angular,Elmを指定したい時は

1
2
3
4
$ rails new myapp --webpack=react
$ rails new myapp --webpack=angular
$ rails new myapp --webpack=vue
$ rails new myapp --webpack=elm

のように指定する。

webpack-dev-server

通常のwebpackコマンドも--watchまたは-wオプションつきで実行することによりファイルの変更を検知して自動でリビルドを行うことが可能だが、webpack-dev-serverはそれに加えて、ブラウザも自動的にリロードしてくれる(Automatic Refresh)。
ブラウザ全体のリロードではなく、編集したモジュールのみを更新するHot Module Replacementという仕組みが使える。
といった機能を備えているため、ローカルで開発するにはwebpack-dev-serverを使う方が便利。
それに、rails serverも同時に動かしているならばforemanというgemを使ったら便利。webpackerのgithubページにもそれを書いてある。

1
$ gem install foreman
1
2
3
# Procfile
web: bundle exec rails s
webpacker: ./bin/webpack-dev-server
1
$ foreman start

ブラウザに表示

ビューに

1
2
<%= javascript_pack_tag 'app' %>
<%= stylesheet_pack_tag 'app' %>

のように入れて使う。

参考

WebPackを使ってRailsからJavaScriptを楽に良い感じに分離する
webpacker