おはようございます!
公私ともに2020年を納めきれるか心配になってきた @shutooike です。
今回から数回に亘って Rails 6.1 の新機能を浅く広くつまみ食いしていこうと思います!
セットアップ
こちらの記事を参考に dip
を使って環境構築をしていきます。
$ git clone https://github.com/hachi8833/rails6_docker_quicksetup_sqlite3.git $ mv rails6_docker_quicksetup_sqlite3/ test_rails_latest/ $ cd test_rails_latest/ $ rm -rf .git $ git init $ dip provision . . . Webpacker successfully installed 🎉 🍰 Creating test_rails_latest_backend_run ... done yarn install v1.22.4 [1/4] Resolving packages... success Already up-to-date. Done in 2.08s. Creating test_rails_latest_runner_run ... done Creating test_rails_latest_backend_run ... done $ dip rails s Creating test_rails_latest_rails_run ... done => Booting Puma => Rails 6.1.0 application starting in development => Run `bin/rails server --help` for more startup options . .
Yay!Rails 6.1 の環境構築がものの10分でできました!dip
良さげ!
次に Post has many comments
なモデルを scaffold
で作成します。
$ dip rails g scaffold post title:string body:string $ dip rails g scaffold comment post:references body:string $ dip rails db:migrate
class Post < ApplicationRecord has_many :comments end class Comment < ApplicationRecord belongs_to :post end
セットアップ完了です!
Destroy Associations Async
今回試すのは「関連レコードをバックグラウンドジョブで destroy
してくれる」新機能です。
アソシエーションが大量にあるレコードを destroy
する場合はタイムアウトやパフォーマンスを気にして、自前でこのような実装しているところも多いと思います。
こういう機能をフレームワークがサポートしてくれるのは嬉しいですね!
では早速、先ほどの Post
モデルにつけて
class Post < ApplicationRecord has_many :comments, dependent: :destroy_async end
Post
レコードを削除してみます。
irb(main):001:0> post = Post.first Post Load (6.3ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" ASC LIMIT ? [["LIMIT", 1]] => #<Post id: 1, title: "test", body: "1", created_at: "2020-12-20 17:07:49.130967000 +0000", updated_at: "2020-12-20 17:07:49.130967000 +0000"> irb(main):002:0> post.comments Comment Load (7.7ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? /* loading for inspect */ LIMIT ? [["post_id", 1], ["LIMIT", 11]] => #<ActiveRecord::Associations::CollectionProxy [#<Comment id: 1, post_id: 1, body: "comment 1", created_at: "2020-12-20 17:08:23.394683000 +0000", updated_at: "2020-12-20 17:08:23.394683000 +0000">, #<Comment id: 2, post_id: 1, body: "comment 2", created_at: "2020-12-20 17:09:03.999279000 +0000", updated_at: "2020-12-20 17:09:03.999279000 +0000">]> irb(main):003:0> post.destroy TRANSACTION (0.8ms) begin transaction Comment Load (15.0ms) SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 1]] Enqueued ActiveRecord::DestroyAssociationAsyncJob (Job ID: 84f9208b-0e6d-46e9-a87e-17600e314c9c) to Async(default) with arguments: {:owner_model_name=>"Post", :owner_id=>1, :association_class=>"Comment", :association_ids=>[1, 2], :association_primary_key_column=>:id, :ensuring_owner_was_method=>nil} Post Destroy (45.6ms) DELETE FROM "posts" WHERE "posts"."id" = ? [["id", 1]] TRANSACTION (5.0ms) rollback transaction Traceback (most recent call last): 1: from (irb):5 ActiveRecord::InvalidForeignKey (SQLite3::ConstraintException: FOREIGN KEY constraint failed) irb(main):006:0> (0.3ms) SELECT sqlite_version(*) Performing ActiveRecord::DestroyAssociationAsyncJob (Job ID: 84f9208b-0e6d-46e9-a87e-17600e314c9c) from Async(default) enqueued at 2020-12-20T17:12:21Z with arguments: {:owner_model_name=>"Post", :owner_id=>1, :association_class=>"Comment", :association_ids=>[1, 2], :association_primary_key_column=>:id, :ensuring_owner_was_method=>nil} (1.7ms) SELECT sqlite_version(*) Post Load (12.1ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] Error performing ActiveRecord::DestroyAssociationAsyncJob (Job ID: 84f9208b-0e6d-46e9-a87e-17600e314c9c) from Async(default) in 137.57ms: ActiveRecord::DestroyAssociationAsyncError (owner record not destroyed): /usr/local/bundle/gems/activerecord-6.1.0/lib/active_record/destroy_association_async_job.rb:23:in `perform' /usr/local/bundle/gems/activejob-6.1.0/lib/active_job/execution.rb:48:in `block in perform_now' . . /usr/local/bundle/gems/concurrent-ruby-1.1.7/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb:334:in `block in create_worker'
親の Post
レコードが外部キー制約により削除できなかったため、ジョブで例外が発生しました。なんたるおバカムーブ、当たり前ですね 🤦♂️ 🤦♀️
外部キー制約を外して
class RemoveForeingKeyOnComment < ActiveRecord::Migration[6.1] def change remove_foreign_key :comments, :posts end end
$ dip rails db:migrate
もう一度
irb(main):004:0> post.destroy (0.9ms) SELECT sqlite_version(*) Enqueued ActiveRecord::DestroyAssociationAsyncJob (Job ID: 8ac3ce06-aaaa-40c0-b40c-600028a99436) to Async(default) with arguments: {:owner_model_name=>"Post", :owner_id=>1, :association_class=>"Comment", :association_ids=>[1, 2], :association_primary_key_column=>:id, :ensuring_owner_was_method=>nil} TRANSACTION (1.6ms) begin transaction Post Destroy (46.7ms) DELETE FROM "posts" WHERE "posts"."id" = ? [["id", 1]] TRANSACTION (52.5ms) commit transaction => #<Post id: 1, title: "test", body: "1", created_at: "2020-12-20 17:07:49.130967000 +0000", updated_at: "2020-12-20 17:07:49.130967000 +0000"> irb(main):010:0> (0.2ms) SELECT sqlite_version(*) Performing ActiveRecord::DestroyAssociationAsyncJob (Job ID: 8ac3ce06-aaaa-40c0-b40c-600028a99436) from Async(default) enqueued at 2020-12-20T17:26:22Z with arguments: {:owner_model_name=>"Post", :owner_id=>1, :association_class=>"Comment", :association_ids=>[1, 2], :association_primary_key_column=>:id, :ensuring_owner_was_method=>nil} (0.3ms) SELECT sqlite_version(*) Post Load (6.6ms) SELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT ? [["id", 1], ["LIMIT", 1]] Comment Load (36.2ms) SELECT "comments".* FROM "comments" WHERE "comments"."id" IN (?, ?) ORDER BY "comments"."id" ASC LIMIT ? [[nil, 1], [nil, 2], ["LIMIT", 1000]] TRANSACTION (3.8ms) begin transaction Comment Destroy (54.9ms) DELETE FROM "comments" WHERE "comments"."id" = ? [["id", 1]] TRANSACTION (31.8ms) commit transaction TRANSACTION (0.6ms) begin transaction Comment Destroy (109.4ms) DELETE FROM "comments" WHERE "comments"."id" = ? [["id", 2]] TRANSACTION (37.8ms) commit transaction Performed ActiveRecord::DestroyAssociationAsyncJob (Job ID: 8ac3ce06-aaaa-40c0-b40c-600028a99436) from Async(default) in 502.18ms
今度はうまくいきました!
さいごに
destroy_async
名前がわかりやすくていいですね〜
もう少し動作の把握が出来たら個人の Rails プロダクトでも使ってみようと思います。
次回は ActiveStorage
の permanent link を試すつもりです!
では良いお年を!
【追記】
第2回書きました!