1
/
5

Rails ユーザー必見!Rais 5の注目新機能と変更点まとめ

こんにちは。企業や個人が使っているツールを紹介しあうサービスToolsの開発をしていますWebエンジニアの永田です。

WantedlyではWebアプリケーションの開発にRailsを使っていています。Railsはそれ自体の開発速度が速く、新しい機能や変更をキャッチアップしてアップグレードするのも一苦労です。と同時に新しい機能や改善の恩恵を受けるのもまた使っていて嬉しくもあります。

さて、Rails 5はちょうど一ヶ月前にリリースされましたが、もうプロダクションで使っているよという方はいらっしゃいますでしょうか?実際のところ、まだまだ多くはないのかなと思っています。Wantedlyでは現在Rails 4.1を使っており、まだRails 5ではありません。しかし、Railsのレールを走り続けるには継続的なアップグレードは重要です。その準備として今回はRails 5でチェックすべき新機能や変更点をまとめてみました。本記事で解説する機能は以下のとおりです。

・Ruby 2.2.2+ required
・Action Cable
・API mode
・railsコマンドへの統一
・ActiveRecord Attributes API
・ApplicationRecord
・ActiveRecord::Relation#in_batches
・ActiveRecordの#saveにtouchオプションが追加
・#or
・#left_outer_joinsと#left_joins
・beforeコールバックでの明示的な停止
・belongs_toのrequiredがデフォルトでtrueに
・Enumerable#pluck, #without
・Turbolinks 5
・Sprockets 3
・非推奨

さて、早速いってみましょう!

Ruby 2.2.2+ required

はじめに、確認するべき点はRubyのバージョンです。Rails 5では2.2.2またはそれより新しいバージョンのRubyが求められます。railties/lib/rails/rubyversion check.rbを見てみるとバージョンをチェックしているのがわかります。

if RUBY_VERSION < '2.2.2' && RUBY_ENGINE == 'ruby'
  desc = defined?(RUBY_DESCRIPTION) ? RUBY_DESCRIPTION : "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
  abort <<-end_message
    Rails 5 requires Ruby 2.2.2 or newer.
    You're running
      #{desc}
    Please upgrade to Ruby 2.2.2 or newer to continue.
  end_message
end

Ruby 2.2.0で実装されたSymbol GCとIncremental GCの恩恵をRailsで受けるための制約です。特にSymbolがGCされることによってユーザが入力した値の文字列操作でのメモリ消費問題を解決すると同時に、メソッドのキーワード引数をはじめとするRubyの優れた新しい機能をRails本体のコードで使っていくために必要な変更になっています。

Action Cable

Rails 5の新機能としてAction Cableがあります。Action CableはWebSocketを扱うフレームワークで、バックエンドとフロントエンド両方のフレームワークを提供します。これらのフレームワークはいままでのRailsアーキテクチャと同様に作られているので、既存のRails開発者であれば迷うことなく使用することができるはずです。

また、Action Cableの導入に伴い開発環境のサーバがWEBrickからpumaへ変更されました。理由としてはソケットを使ってHTTPレスポンスを送信するためにRack socket hijacking APIを使用するためで、WEBrickはこのHijacking APIに対応していないため変更になりました。また、アプリケーションと同一プロセスで処理できるため開発環境で複数立ち上げる手間が省けるといったメリットもあります。もちろんこれは本番環境では別にすることができます。この記事ではAction Cableについて詳しく説明しませんが、実際の使い方は公式のサンプルアプリケーションがとてもわかり易いので気になる方はそちらを御覧ください。

API mode

最近のトレンドとしてJSONを返すAPIとしてRailsを使いたいというケースが増えてきました。そのため、APIとして特化したAPI modeが登場しました。通常のRailsとAPI modeでの差分として不要なミドルウェアやモジュールを省いており、スループット(Request/sec)で5%ほどの改善となっているようです。

不要なGemをgemfileから省いている

フロントエンドに関連する以下のGemがデフォルトで省かれています。

・ sass-rails
・ uglier
・ coffee-rails
・ therubyracer
・ jquery-rails
・ turbolinks
・ web-console

不要なRack middlewareを省いている

Rack middlewareも同様にフロントエンド関連の以下のミドルウェアが省かれています。

Rack::MethodOverride リクエストパラメータ_methodにPUTをつけるとPUTリクエストとなる

Sprockets::Rails::QuietAssets アセットへのリクエストログを省いている

WebConsole::Middleware 標準のデバッグツール

ActionDispatch::Cookies クッキー管理

ActionDispatch::Session::CookieStore クッキーセッション管理

ActionDispatch::Flash フラッシュメッセージを扱う

Generatorでviewやhelperを生成しない

当たり前ですが、viewやhelperは不要なのでGeneratorで作成されないようになっています。以下のとおりgenerate scaffoldやgenerate controllerでviewやhelperが生成されていないのが確認できます。

$ rails g scaffold Project title:string started_at:datetime
Running via Spring preloader in process 95950
      invoke  active_record
      create    db/migrate/20160719152731_create_projects.rb
      create    app/models/project.rb
      invoke    test_unit
      create      test/models/project_test.rb
      create      test/fixtures/projects.yml
      invoke  resource_route
       route    resources :projects
      invoke  scaffold_controller
      create    app/controllers/projects_controller.rb
      invoke    test_unit
      create      test/controllers/projects_controller_test.rb
$ rails g controller issues index show 
Running via Spring preloader in process 97967
      create  app/controllers/issues_controller.rb
       route  get 'issues/show'
       route  get 'issues/index'
      invoke  test_unit
      create    test/controllers/issues_controller_test.rb

railsコマンドへの統一

rakeコマンドがrailsコマンドに統一されます。rake db:migrateはrails db:migrateで実行できるようになります。すでにみなさんはrailsを使いこなしていて特に感じないかとは思いますが、この変更は初学者にとってrailsコマンドとrakeコマンドをそれぞれ覚えるコストを下げるために大きく貢献する変更となっています。もちろんrakeタスクが削除されたわけではありませんので、引き続き実行することはできます。

ActiveRecord Attributes API

この新しいAPIは大きく2つの機能を提供します。一つ目はDBの型とActiveRecordでのクラスとの対応を指定する機能で、二つ目はデフォルト値の設定を可能にするものです。

DBの型とActiveRecordのクラスとの対応を変更

まず、対応関係の変更について説明したいと思います。以下のようなbooksテーブルおよびBookモデルがある場合について考えます。pricein centsはdecimal型のカラムで、何かしらの理由で対応するモデルBookのpricein centsをIntegerで扱いたいというケースを想定します。

# migration class
class CreateBooks < ActiveRecord::Migration[5.0]
  def change
    create_table :books do |t|
      t.string :title
      t.decimal :price_in_cents

      t.timestamps
    end
  end
end


# model
class Book < ActiveRecord::Base
end

もちろんbook.pricein centsはBigDecimalクラスのインスタンスが返って来ます。

book = Book.new(title: 'Hello Ruby', price_in_cents: 10.1)
book.price_in_cents #=> #<BigDecimal:7ffb42d7db48,'0.101E2',18(36)>

そこで新しく追加されたattributeメソッドを使います。第一引数に属性名のシンボルを、第二引数に対応させたいクラスをシンボルで指定します。今回の場合第一引数は:pricein centsで第二引数は:integerとなります。また、オプションには配列やレンジに対応するarrayオプションやrangeオプションがあります(PostgreSQLのみ対応)。今回は関係ないので飛ばしますが、詳しく知りたい方はドキュメントを参照してみてください。

# app/models/book.rb
class Book < ActiveRecord::Base
  # attribute(name, cast_type, **options)
  attribute :price_in_cents, :integer
end

ここでもう一度rails consoleを開いて確認してみます。見事Integrerが返って来ていることが確認できました。とても簡単ですね!

book = Book.new(title: 'Hello Ruby', price_in_cents: 10.1)
book.price_in_cents #=> 10

cast type

さて、ここまでで変更方法を説明してきましたが、そもそも指定できるクラスが何なのかという疑問をお持ちの方もいらっしゃるかと思います。ここでは指定できるクラスと独自のクラスについて説明します。

まず指定できるクラスは以下のとおりです。左のシンボルがattributeメソッドの第二引数に渡すシンボルになります。

:big_integer, ActiveRecord::Type::BigInteger
:binary, ActiveRecord::Type::Binary
:boolean, ActiveRecord::Type::Boolean
:date, ActiveRecord::Type::Date
:datetime, ActiveRecord::Type::DateTime
:decimal, ActiveRecord::Type::Decimal
:float, ActiveRecord::Type::Float
:integer, ActiveRecord::Type::Integer
:string, ActiveRecord::Type::String
:text, ActiveRecord::Type::Text
:time, ActiveRecord::Type::Time

上記のクラスがどこで登録されているかというとActiveRecord::Typeモジュールを覗いてみるとわかります。また、多くのActiveRecord::Type::クラスの実体がActiveModel::Type:: であることがわかります。

# activerecord/lib/active_record/type.rb
module ActiveRecord
  # ...
  module Type 
    Helpers = ActiveModel::Type::Helpers
    BigInteger = ActiveModel::Type::BigInteger
    Binary = ActiveModel::Type::Binary
    Boolean = ActiveModel::Type::Boolean
    Decimal = ActiveModel::Type::Decimal
    DecimalWithoutScale = ActiveModel::Type::DecimalWithoutScale
    Float = ActiveModel::Type::Float
    Integer = ActiveModel::Type::Integer
    String = ActiveModel::Type::String
    Text = ActiveModel::Type::Text
    UnsignedInteger = ActiveModel::Type::UnsignedInteger
    Value = ActiveModel::Type::Value

    register(:big_integer, Type::BigInteger, override: false)
    register(:binary, Type::Binary, override: false)
    register(:boolean, Type::Boolean, override: false)
    register(:date, Type::Date, override: false)
    register(:datetime, Type::DateTime, override: false)
    register(:decimal, Type::Decimal, override: false)
    register(:float, Type::Float, override: false)
    register(:integer, Type::Integer, override: false)
    register(:string, Type::String, override: false)
    register(:text, Type::Text, override: false)
    register(:time, Type::Time, override: false)
  end
end

もし独自のクラスを作りたい場合は上記のすでにあるクラスを継承したクラスを作るか、基底クラスであるActiveModel::Type::Valueを継承したクラスを作成します。

ActiveRecord::Type::Value

基底クラスであるActiveModel::Type::Valueをみていきましょう。特に見るべき部分は#serializeと#deserializeと#castです。

# activemodel/lib/active_model/type/value.rb
module ActiveModel
  module Type
    class Value
      attr_reader :precision, :scale, :limit

      def initialize(precision: nil, limit: nil, scale: nil)
        @precision = precision
        @scale = scale
        @limit = limit
      end

      def type # :nodoc:
      end

      # Converts a value from database input to the appropriate ruby type. The
      # return value of this method will be returned from
      # ActiveRecord::AttributeMethods::Read#read_attribute. The default
      # implementation just calls Value#cast.
      #
      # +value+ The raw input, as provided from the database.
      def deserialize(value)
        cast(value)
      end

      # Type casts a value from user input (e.g. from a setter). This value may
      # be a string from the form builder, or a ruby object passed to a setter.
      # There is currently no way to differentiate between which source it came
      # from.
      #
      # The return value of this method will be returned from
      # ActiveRecord::AttributeMethods::Read#read_attribute. See also:
      # Value#cast_value.
      #
      # +value+ The raw input, as provided to the attribute setter.
      def cast(value)
        cast_value(value) unless value.nil?
      end

      # Casts a value from the ruby type to a type that the database knows how
      # to understand. The returned value from this method should be a
      # +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
      # +nil+.
      def serialize(value)
        value
      end

      # Type casts a value for schema dumping. This method is private, as we are
      # hoping to remove it entirely.
      def type_cast_for_schema(value) # :nodoc:
        value.inspect
      end

      # These predicates are not documented, as I need to look further into
      # their use, and see if they can be removed entirely.
      def binary? # :nodoc:
        false
      end

      # Determines whether a value has changed for dirty checking. +old_value+
      # and +new_value+ will always be type-cast. Types should not need to
      # override this method.
      def changed?(old_value, new_value, _new_value_before_type_cast)
        old_value != new_value
      end

      # Determines whether the mutable value has been modified since it was
      # read. Returns +false+ by default. If your type returns an object
      # which could be mutated, you should override this method. You will need
      # to either:
      #
      # - pass +new_value+ to Value#serialize and compare it to
      #   +raw_old_value+
      #
      # or
      #
      # - pass +raw_old_value+ to Value#deserialize and compare it to
      #   +new_value+
      #
      # +raw_old_value+ The original value, before being passed to
      # +deserialize+.
      #
      # +new_value+ The current value, after type casting.
      def changed_in_place?(raw_old_value, new_value)
        false
      end

      def map(value) # :nodoc:
        yield value
      end

      def ==(other)
        self.class == other.class &&
          precision == other.precision &&
          scale == other.scale &&
          limit == other.limit
      end
      alias eql? ==

      def hash
        [self.class, precision, scale, limit].hash
      end

      def assert_valid_value(*)
      end

      private

      # Convenience method for types which do not need separate type casting
      # behavior for user and database inputs. Called by Value#cast for
      # values except +nil+.
      def cast_value(value) # :doc:
        value
      end
    end
  end
end

#serializeはAR側からDB側への変換です。引数valueを受け取りDBで受け取れる型の値を返します。値はString, Numeric, Date, Time, Symbol, true, false, nilのいずれかである必要があります。

def serialize(value)
  value
end

#deserializeはDB側からAR側への変換です。引数valueを受け取りAR側で取り扱いたい型に変換して返します。内容としてはcastを呼んでいるだけです。

def deserialize(value)
  cast(value)
end

#castは上の#deserializeで呼ばれるだけでなくフォームビルダーや通常のセッターで呼ばれることもあります。つまり引数valueはstring以外の可能性がある点に注意してください。内容としてはnilチェックをしてcast_valueを呼んでいます。

def cast(value)
  cast_value(value) unless value.nil?
end

cast_valueで実際の変換を行います。基底クラスでは特に変更はしないのでそのままvalueを返しています。

def cast_value(value)
  value
end

以上が基底クラスActiveModel::Type::Valueの主なメソッドでした。

ActiveModel::Type::Integer

実際のクラスもみていきましょう。例としてActiveModel::Type::Integerを取り上げます。ここでも#deserialize、#serialize、#castを見ていきます。

# activemodel/lib/active_model/type/integer.rb
module ActiveModel
  module Type
    class Integer < Value # :nodoc:
      include Helpers::Numeric

      # Column storage size in bytes.
      # 4 bytes means a MySQL int or Postgres integer as opposed to smallint etc.
      DEFAULT_LIMIT = 4

      def initialize(*)
        super
        @range = min_value...max_value
      end

      def type
        :integer
      end

      def deserialize(value)
        return if value.nil?
        value.to_i
      end

      def serialize(value)
        result = cast(value)
        if result
          ensure_in_range(result)
        end
        result
      end

      protected

      attr_reader :range

      private

      def cast_value(value)
        case value
        when true then 1
        when false then 0
        else
          value.to_i rescue nil
        end
      end

      def ensure_in_range(value)
        unless range.cover?(value)
          raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit}"
        end
      end

      def max_value
        1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
      end

      def min_value
        -max_value
      end

      def _limit
        self.limit || DEFAULT_LIMIT
      end
    end
  end
end

#deserializeではnilの場合にnilを返し、nilでない場合にvalueをto_iしてIntegerを返しています。

def deserialize(value)
  return if value.nil?
  value.to_i
end

#serializeでは#castメソッドにvalueを渡して変換をした後にensurein rangeで値のレンジを確認します。ensurein rangeでは値がレンジの外であれば例外を投げるようになっています。

def serialize(value)
  result = cast(value)
  if result
    ensure_in_range(result)
  end
  result
end

#castは定義されていないので親クラスから継承されます。親クラスではnilチェックをして#castvalueを呼んでいます。では、#cast valueを見ていきます。valueがtrueの場合は1をfalseの場合は0をそれ以外はtoiをして返してます。また、to iに応答しない場合はnilを返すようになっているのがわかります。

def cast_value(value)
  case value
  when true then 1
  when false then 0
  else
    value.to_i rescue nil
  end
end

以上がActiveModel::Type::Integerでした。この要領でクラスを作成することで、意図するクラスを返すことができます。もし、みなさんのRailsアプリケーションでこのような変換処理をモデルに書いている場合この手段はかなり有効かと思います。

デフォルト値の設定

さて、二つ目の機能の説明はすこしばかり簡単になってしまうのですが、これも強力な機能です。いままでデフォルト値を設定するには自前で実装するか、defaultvalue forを活用する方法がありましたが、Rails5では標準で簡単に書くことができます。これもまた新しく追加されたattributeメソッド使います。defaultオプションにデフォルト値をするだけです。

class Book < ApplicationRecord
  attribute :permited, :boolean, default: false
end
Book.new.permited # => false

またブロックをとることもできるので以下のようなケースでも意図したとおりに動きます。

class Book < ApplicationRecord
  attribute :published_at, :datetime, default: -> { Time.zone.now }
end
irb(main):002:0> Book.new.published_at
=> Sun, 31 Jul 2016 02:53:22 UTC +00:00

ApplicationRecord

もうすでにお気づきかもしれませんが、モデルの継承元がActiveRecord::BaseからApplicationRecordに変更されています。この変更によりすべてのモデルで共有したいロジックをApplicationRecordに書くことができます。

# app/models/book.rb
class Book < ApplicationRecord
end
# app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end

ActiveRecord::Relation#in_batches

#findin batchesではブロックの引数がArrayでしたが、#in_batchesではブロックの引数がActiveRecord::RelationとなるためActiveRecord::Relationのメソッドがつかえます。素晴らしいですね。

User.in_batches.class #=> ActiveRecord::Batches::BatchEnumerator
User.in_batches.update_all(verified: true)
User.in_batches.each do |relation|
  relation.class #=> User::ActiveRecord_Relation
  relation.update_all(verified: true)
  sleep 10
end

https://github.com/rails/rails/pull/20933

ActiveRecordの#saveにtouchオプションが追加

#saveメソッドでtouchオプションを指定するとタイムスタンプを更新しないようにすることができます。タイムスタンプを更新したくないケースは割りと良くあると思いますが、より簡単にできるようになりました。

irb(main):013:0> book = Book.find(1)
  Book Load (0.2ms)  SELECT  "books".* FROM "books" WHERE "books"."id" = ? LIMIT ?  [["id", 1], ["LIMIT", 1]]
=> #<Book id: 1, title: "foo", price_in_cents: nil, created_at: "2016-07-20 02:26:37", updated_at: "2016-07-20 02:26:37", published_at: "2016-07-20 02:26:37", author_id: nil>
irb(main):014:0> book.updated_at
=> Wed, 20 Jul 2016 02:26:37 UTC +00:00
irb(main):015:0> book.title = "new title!"
=> "new title!"
irb(main):016:0> book.save(touch: false)
   (0.2ms)  begin transaction
  SQL (0.4ms)  UPDATE "books" SET "title" = ? WHERE "books"."id" = ?  [["title", "new title!"], ["id", 1]]
   (10.9ms)  commit transaction
=> true

#or

ついにorができます!これは見た通りの挙動をします。

irb(main):025:0> User.where(id: [1,2]).or(User.where(name: "User 0"))
  User Load (1.1ms)  SELECT "users".* FROM "users" WHERE ("users"."id" IN (1, 2) OR "users"."name" = 'User 0')
=> #<ActiveRecord::Relation [#<User id: 1, name: "User 0", email: "email_0@mail.com", created_at: "2016-07-18 14:13:23", updated_at: "2016-07-18 14:13:23">, #<User id: 2, name: "User 1", email: "email_1@mail.com", created_at: "2016-07-18 14:13:23", updated_at: "2016-07-18 14:13:23">]>

#leftouter joinsと#left_joins

以下のとおりLEFT OUTER JOINができます。

irb(main):042:0> Author.left_outer_joins(:books)
  Author Load (0.3ms)  SELECT "authors".* FROM "authors" LEFT OUTER JOIN "books" ON "books"."author_id" = "authors"."id"
=> #<ActiveRecord::Relation [#<Author id: 1, name: "Mark", created_at: "2016-07-20 07:26:57", updated_at: "2016-07-20 07:26:57">, #<Author id: 1, name: "Mark", created_at: "2016-07-20 07:26:57", updated_at: "2016-07-20 07:26:57">]>

beforeコールバックでの明示的な停止

rails4まではbeforeコールバックでfalseを返すと停止することができました。rails5では:abortを明示的に投げることで停止させます。

class Book < ApplicationRecord
  before_save :authorize_title

  private

  def authorize_title
    !title.include?('ng') # 停止したいケースでfalseを返す
  end
end

これだとcommitされてしまうことが確認できます。

irb(main):003:0> Book.create(title: 'foo ng bar') 
   (0.1ms)  begin transaction
  SQL (1.3ms)  INSERT INTO "books" ("title", "created_at", "updated_at", "published_at") VALUES (?, ?, ?, ?)  [["title", "foo ng bar"], ["created_at", 2016-07-20 02:42:05 UTC], ["updated_at", 2016-07-20 02:42:05 UTC], ["published_at", 2016-07-20 02:42:05 UTC]]
   (1.0ms)  commit transaction
=> #<Book id: 3, title: "foo ng bar", price_in_cents: nil, created_at: "2016-07-20 02:42:05", updated_at: "2016-07-20 02:42:05", published_at: "2016-07-20 02:42:05">

そこで明示的に:abortを投げます。

class Book < ApplicationRecord
  before_save :authorize_title

  private

  def authorize_title
     throw(:abort) if title.include?('ng') # 停止したいケースでthrow(:abort)する
  end
end

rollback transactionとあり、コミットがされていないことが確認できます。

irb(main):001:0> Book.create(title: 'foo ng bar') 
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> #<Book id: nil, title: "foo ng bar", price_in_cents: nil, created_at: nil, updated_at: nil, published_at: "2016-07-20 02:38:13">

belongs_toのrequiredがデフォルトでtrueに

rails 4.2.0でbelongs_toにrequiredオプションが追加されましたが、rails5ではデフォルトでrequireはtrueになり、optionalオプションが追加されました。この仕様はrails 5でrails newした場合でデフォルトで有効になります。

class Project
  belongs_to :company, required: true
end

上記のコードは下記のコードで十分になります。

class Project
  belongs_to :company
end

後者で実行するとerrorが追加されてコミットできないことが確認できます。

irb(main):001:0> Book.new(title: 'hello ruby', author: nil).save
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> false
irb(main):002:0> book = Book.new(title: 'hello ruby', author: nil)
=> #<Book id: nil, title: "hello ruby", price_in_cents: nil, created_at: nil, updated_at: nil, published_at: "2016-07-20 03:23:15", author_id: nil>
irb(main):003:0> book.save
   (0.1ms)  begin transaction
   (0.1ms)  rollback transaction
=> false
irb(main):004:0> book.errors.full_messages
=> ["Author must exist"]

またnilを許容する場合はoptionalオプションをtrueにする必要があります。

class Project
  belongs_to :company, optional: true
end

以下のとおりoptionalの場合はコミットされていることが確認できます。

irb(main):011:0> book = Book.new(title: 'hello ruby', author: nil)
=> #<Book id: nil, title: "hello ruby", price_in_cents: nil, created_at: nil, updated_at: nil, published_at: "2016-07-20 03:26:36", author_id: nil>
irb(main):012:0> book.save
   (0.2ms)  begin transaction
  SQL (1.1ms)  INSERT INTO "books" ("title", "created_at", "updated_at", "published_at") VALUES (?, ?, ?, ?)  [["title", "hello ruby"], ["created_at", 2016-07-20 03:26:39 UTC], ["updated_at", 2016-07-20 03:26:39 UTC], ["published_at", 2016-07-20 03:26:36 UTC]]
   (4.0ms)  commit transaction
=> true
irb(main):013:0> book.author
=> nil

Enumerable#pluck, #without

Enumerableモジュールにpluckとwithoutが追加されました。#pluckはActiveRecordではおなじみでしたが、Enumerableにも追加されました。個人的にはインターフェイスが揃うことは好ましいことだと思っています。

[{name: 'foo', point: 12}, {name: 'bar', point: 55}].pluck(:name)
#=> ["foo", "bar"]

#withoutは引数で渡したオブジェクトを取り除いて返します。

["apple", "chocolate", "milk", "cookie"].without("apple", "milk")
#=> ["chocolate", "cookie"]

Turbolinks 5

Turobolinksはページ間の遷移をXHR化しbodyの書き換えだけにして高速化を図る機能ですが、Turbolinks 5で大きく実装が書き換えられており、導入されている方はAPIの変更に注意する必要があります。また他の言語やフレームワークでも導入できるようにrailsに依存しない形になったことやjQueryにも依存しない形になったことが大きな変更点と言えそうです。さらに、iOSや Androidのweb viewで導入できるように各種ライブラリが用意されました。

Sprockets 3

プリコンパイル対象の指定方法が変更になりました。Rails4まではconfig/initializers/assets.rbでしたが、Rails5ではapp/assets/config/manifest.jsとなります。

// app/assets/config/manifest.js
//
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css

link_directoryディレクティブ

指定したパスのディレクトリ直下のファイルすべてをプリコンパイル対象にします。

2番目の引数として許可する拡張子を指定します。.jsであればcoffeeも、.cssであればscssも対象に入ります。

//= link_directory <path> <accepted_extension>

実装

# lib/sprockets/directive_processor.rb
def process_link_directory_directive(path = ".", accept = nil)
  path = expand_relative_dirname(:link_directory, path)
  accept = expand_accept_shorthand(accept)
  link_paths(*@environment.stat_directory_with_dependencies(path), accept)
end

link_treeディレクティブ

link_directoryと同様ですが、こちらは再帰的です。サブティレクトリも含まれます。

//= link_tree <path> <accepted_extension>

非推奨

rails 5.1で廃止される機能も少しご紹介したいと思います。

renderメソッドの:nothingオプションが非推奨に

何も返さない場合は以下の2通りの書き方をすることができます。しかしnothingオプションは廃止されheadメソッドに統一されます。

# :nothingオプションを使う
render nothing: true, status: 204
# headメソッドを使う
head status: 204

理由としては以下のコードがデフォルトでテンプレートを探しに行くことがわかりづらいからです。

render status: 204

filterコールバックをすべて非推奨に指定。今後は actionコールバックを使用

もしまだ*_filterを使っている場合は書き換えてください。

class UsersControler
  before_filter :authorize # これです

  def show
  end

  private

  def authorize
    reject_if_not_authorized
  end
end
class UsersControler
  before_action :authorize # 書き換えるだけ

  # ...
end

その他

その他個人的に気になった変更点も簡単にリストアップしてみました。

・ protected_attributes gemがrails 5よりサポート外に

・ イニシャライザを読み込み順に表示してくれるrails initializersコマンドが追加

hassecure tokenが本体にマージ

・ #on_weekend?, #nextweekday, #prev weekdayがDate, Datetimeに追加

・ActionMailerの#deliverおよび#deliver!が非推奨に

まとめ

最後までお読みいただきありがとうございます!さて、みなさんいかがでしたか?この記事では全体での新機能を紹介しつつ特にActiveRecordまわりの変更を詳しく説明していきました。もちろん他にもたくさんの変更があるので気になる方はCHANGELOGRelease Noteも是非よんでみてください!

参考

http://old.blog.phusion.nl/2013/01/23/the-new-rack-socket-hijacking-api/

Wantedly, Inc.では一緒に働く仲間を募集しています
34 いいね!
34 いいね!
今週のランキング