2/22(月)に開催されたSendagaya.rb #138 に参加して、 「ActiveRecod CHANGELOG 」 を読んだのでその斜め読みのメモです!
🐝 foreign_key_exists? migrationファイルで使えるメソッド。テーブルに外部キー制約が付いているかを確認するforeign_key_exists? が追加。
# Check a foreign key exists foreign_key_exists?(:accounts ,:branches ) # Check a foreign key on a specified column exists foreign_key_exists?(:accounts ,column: :owner_id ) # Check a foreign key with a custom name exists foreign_key_exists?(:accounts ,name: "special_fk_name" )
アンチパターンにあるらしいから外部キー制約はちゃんと付け足ほうがいい。
🐞 Active Record::Base.suppress class Comment < ActiveRecord::Base belongs_to:commentable ,polymorphic: true after_create -> { Notification.create!comment: self , recipients: commentable.recipients }end
こんな風にComment を作成したら必ずNotification を作るようなパターンはあるけど、 特定のパターンの場合は、Notification を作成しないためには次のように記述する。
module Copyable def copy_to (destination) Notification.suppressdo # この中では Comment を作成しても Notification は作らない。 end end end
とするとNotification.suppress のブロックの中では、Notification が作られなくなる。
🗻 Active Record::Base#accessed_fields こんな感じのリファクタリングができるようになる。
class PostsController < ActionController::Base after_action:print_accessed_fields ,only: :index def index @posts = Post.all end private def print_accessed_fields p @posts.first.accessed_fields#=> :id, :title, :author_id, :updated_at end end
とすると:id, :title, :author_id, :updated_at しか使わないので、 次のように書いて必要なカラムだけを取得するようにリファクタリングできる。
class PostsController < ActionController::Basedef index @posts = Post.select(:id ,:title ,:author_id ,:updated_at ) end end
😼 Active Record::Base.ignored_columns ActiveRecord::Base.ignored_columns でカラムを定義すると、Active Recordでは閲覧できないカラムを定義することができる。 おそらく、DBにあるけどもActiveRecodなどで参照してほしくないようなカラムを定義する。
🏈 Active Record::Relation#update ActiveRecord::Relation#updateの動作が次のように変わったとのこと。
# Before: idとattriubutesを渡す形式だった。1SQLで実行できるけどcallbackが呼ばれない Person.update(15 ,:user_name =>'Samuel' ,:group =>'expert' ) # After: こういった形で呼べる。1件1件更新するのでちょっと遅いけど、callbackが呼ばれる Comment.where(group: 'expert' ).update(body: "Group of Rails Experts" )
🐯 drop_テーブルのオプション :if_exists migrationファイル内のオプションで:if_exists を設定できるようになった。
drop_table(:posts ,if_exists: true )
🎂 Active Record::Relation#or Post.where(‘id = 1’).or(Post.where(‘id = 2’)) を正確に解釈するようになりました
Post.where('id = 1' ).or (Post.where('id = 2' )) #=> SELECT * FROM posts WHERE (id = 1) OR (id = 2)
Added #or to ActiveRecord::Relation by matthewd · Pull Request #16052 · rails/rails
🤔 Active Record::Relation#left_outer_joins(#left_joins) 外部結合のための#left_outer_joins(#left_joins) が追加。
User.left_outer_joins(:posts ) #=> SELECT "users".* FROM "users" LEFT OUTER JOIN "posts" ON #=> "posts"."user_id" = "users"."id"
added ActiveRecord::Relation#outer_joins by Crunch09 · Pull Request #12071 · rails/rails
😎 findで与えたidsと同じ順序でActive Recordを返す records = Topic.find([4 ,2 ,5 ]) assert_equal'The Fourth Topic of the day' , records[0 ].title assert_equal'The Second Topic of the day' , records[1 ].title assert_equal'The Fifth Topic of the day' , records[2 ].title
🚜 after_commitのcallback名が変わった #### Before #### after_commit:add_to_index_later ,on: :create after_commit:update_in_index_later ,on: :update after_commit:remove_from_index_later ,on: :destroy #### After #### after_create_commit:add_to_index_later after_update_commit:update_in_index_later after_destroy_commit:remove_from_index_later
🎉 Active Record::Relation#in_batches People.in_batches(of: 100 )do |people| people.where('id % 2 = 0' ).update_all(sleep: true ) people.where('id % 2 = 1' ).each(&:party_all_night! ) end
ちなみにちょっとおもしろかったのはパフォーマンスです。 idを指定しつつ検索をしていく場合のほうが、OFFSETを使うよりも早いという結果になりました。
SELECT "posts" ."id" FROM "posts" ORDER BY "posts" ."id" ASC LIMIT 2 SELECT "posts" ."id" FROM "posts" WHERE ("posts" ."id" >2 )ORDER BY "posts" ."id" ASC LIMIT 2 SELECT "posts" ."id" FROM "posts" WHERE ("posts" ."id" >4 )ORDER BY "posts" ."id" ASC LIMIT 2 # ↑ のパフォーマンス Benchmark times (50 Mrows ,no index , batchsize 1000 ):batchtime 1 194.7 s2 0.016 s3 0.006 s4 0.008 s5 0.007 s...
SELECT COUNT (count_column)FROM (SELECT 1 AS count_columnFROM "posts" LIMIT 2 OFFSET 0 ) subquery_for_countSELECT COUNT (count_column)FROM (SELECT 1 AS count_columnFROM "posts" LIMIT 2 OFFSET 2 ) subquery_for_countSELECT COUNT (count_column)FROM (SELECT 1 AS count_columnFROM "posts" LIMIT 2 OFFSET 4 ) subquery_for_count# ↑ のパフォーマンス Benchmark times (50 Mrows ,no index , batchsize 1000 ):batchtime 1 192.5 s2 242.0 s3 257.5 s4 256.8 s5 258.7 s... (average times increaseon each iteration, becauseof higheroffset )
🍮 none? / one? の実装の変更 none? や、one? の実装が改善。
# Before: users.none? # SELECT "users".* FROM "users" users.one? # SELECT "users".* FROM "users" # After: users.none? # SELECT 1 AS one FROM "users" LIMIT 1 users.one? # SELECT COUNT(*) FROM "users"
🏀 belongs_toでアソシエーションがなければバリデーションエラー belongs_to でアソシエーションがなければ、バリデーションエラーになるようになった。optional: trueをつけるとエラーが出ないようになる。
🎃 Tips 以下は今回のCHANGE LOGとは直接は関係ない部分
(Tips) revertを使うとrollbackできるmigrationファイルの中で以下のようなことができるrevert というのがあるそう。何がうれしいかというとロールバックができるそう。
class FixupExampleMigration < ActiveRecord::Migration[5.0]def change revertdo create_table(:apples )do |t| t.string:variety end end end end
Reverting Previous Migrations - Rails Guide
(Tips) RubyのEnumerable#any?%w{ant bear cat} .any? {|word| word.length >=3 }#=> true %w{ant bear cat} .any? {|word| word.length >=4 }#=> true [nil ,true ,99 ].any?#=> true
(Tips) Active Record::Relation#any?person.pets# => [#] person.pets.any?do |pet| pet.group =='cats' end # => false person.pets.any?do |pet| pet.group =='dogs' end # => true
(Tips) define_attribute_methodsdefine_attribute_methods を使うと次のようなことができる。
class Person include ActiveModel::AttributeMethodsattr_accessor :name ,:age ,:address attribute_method_prefix'clear_' private def clear_attribute (attr) send("#{attr} =" ,nil ) end end person = Person.new person.name ='John Due' person.clear_name puts person.name#=> nil
DRYを実現するのに便利そう
ActiveModel::AttributeMethods で重複をなくす - Qiita
🗽 あとがき CHANGELOGを集中して読んだり、いろんな人の意見を聞く機会っていなかなかなかので、本当にいい勉強になりました!Sendagaya.rb #138 楽しかったです^^
🐹 参考リンク
🖥 VULTRおすすめ 「VULTR 」はVPSサーバのサービスです。日本にリージョンがあり、最安は512MBで2.5ドル/月($0.004/時間)で借りることができます。4GBメモリでも月20ドルです。 最近はVULTR のヘビーユーザーになので、「ここ 」から会員登録してもらえるとサービス開発が捗ります!