LoginSignup
2
1

More than 5 years have passed since last update.

Rails5へのアップグレードを諦めた話(或いは、has_many :through の片方がreadonlyな場合の問題)

Last updated at Posted at 2017-02-18

2017/3/12追記 : 続きのお話をこちらに書きました。
http://qiita.com/moperon/items/02ee2eef17e2ed2d769f

TL;DR

アソシエーションの片方だけreadonlyとした場合の、Rails 4と5での動作の違い と全く同じシチュエーションに私もハマってしまったので、Rails4.2からRails5へのアップグレードを諦めました。

再現手順

Railsアプリ作成

参考 : システムのgemにrailsをインストールせずrails newする

例によってアプリ作ります。

$ mkdir has_many_through_readonly
$ cd has_many_through_readonly
$ echo 'source "https://rubygems.org"' > Gemfile
$ echo 'gem "rails", "4.2.6"' >> Gemfile
$ bundle install --path vendor/bundle
$ bundle exec rails new .

続いて、モデルを作る。UserとRoleが多対多の関係にあり、Roleはreadonlyであるという状態。

$ bin/rails g model user
$ bin/rails g model role
$ bin/rails g model role_user user:references role:references

以下のようにモデルを修正する。

app/models/user.rb
class User < ActiveRecord::Base
  has_many :role_users
  has_many :roles, through: :role_users
end
app/models/role_user.rb
class RoleUser < ActiveRecord::Base
  belongs_to :user
  belongs_to :role
end
app/models/role.rb
class Role < ActiveRecord::Base
  has_many :role_users
  has_many :roles, through: :role_users
  def readonly?
    true
  end
end

role.rb のreadonly?がtrueなところがポイント。

そして、readonlyなRoleにひとつレコードを追加しておく。

$ bin/rake db:migrate
$ bin/rails dbconsole
sqlite> insert into roles values(1, datetime('now'), datetime('now'));
sqlite> .q

Rails4の挙動

Rails4ではこのようなアソシエーションであってもUserを追加することが可能でした。

irb(main):001:0> role = Role.first
  Role Load (0.6ms)  SELECT  "roles".* FROM "roles"  ORDER BY "roles"."id" ASC LIMIT 1
=> #<Role id: 1, created_at: "2017-02-17 23:21:22", updated_at: "2017-02-17 23:21:22">
irb(main):002:0> user = User.create(roles: [role])
   (0.1ms)  begin transaction
  SQL (0.9ms)  INSERT INTO "users" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", "2017-02-17 23:22:23.717950"], ["updated_at", "2017-02-17 23:22:23.717950"]]
  SQL (0.7ms)  INSERT INTO "role_users" ("role_id", "user_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["role_id", 1], ["user_id", 1], ["created_at", "2017-02-17 23:22:23.724668"], ["updated_at", "2017-02-17 23:22:23.724668"]]
   (1.0ms)  commit transaction
=> #<User id: 1, created_at: "2017-02-17 23:22:23", updated_at: "2017-02-17 23:22:23">
irb(main):003:0>

Rails5へアップグレード

参考 : Rails 5へのアップグレード手順メモ(Rails 4.2.6 => 5.0.0)

Gemfile
gem 'rails', '~> 5.0'
$ bundle update rails
$ bin/rails app:update

モデルクラスの継承関係を変更する。

app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  self.abstract_class = true
end
app/models/user.rb
class User < ApplicationRecord
...snip...
app/models/role_user.rb
class RoleUser < ApplicationRecord
...snip...
app/models/role.rb
class Role < ApplicationRecord
...snip...

Rails5での挙動

irb(main):001:0> role = Role.first
  Role Load (0.7ms)  SELECT  "roles".* FROM "roles" ORDER BY "roles"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Role id: 1, created_at: "2017-02-17 23:21:22", updated_at: "2017-02-17 23:21:22">
irb(main):002:0> user = User.create(roles: [role])
   (0.1ms)  begin transaction
  SQL (1.0ms)  INSERT INTO "users" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", 2017-02-18 00:17:01 UTC], ["updated_at", 2017-02-18 00:17:01 UTC]]
  SQL (0.9ms)  INSERT INTO "role_users" ("user_id", "role_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["user_id", 3], ["role_id", 1], ["created_at", 2017-02-18 00:17:01 UTC], ["updated_at", 2017-02-18 00:17:01 UTC]]
   (0.4ms)  rollback transaction
ActiveRecord::ReadOnlyRecord: Role is marked as readonly
...snip...

pryでbacktraceとってみた結果はこちら。

[1] pry(main)> role = Role.first
  Role Load (0.3ms)  SELECT  "roles".* FROM "roles" ORDER BY "roles"."id" ASC LIMIT ?  [["LIMIT", 1]]
=> #<Role:0x007f95acea3258 id: 1, created_at: Fri, 17 Feb 2017 23:21:22 UTC +00:00, updated_at: Fri, 17 Feb 2017 23:21:22 UTC +00:00>
[2] pry(main)> user = User.create(roles: [role])
   (0.1ms)  begin transaction
  SQL (0.5ms)  INSERT INTO "users" ("created_at", "updated_at") VALUES (?, ?)  [["created_at", 2017-02-18 00:29:14 UTC], ["updated_at", 2017-02-18 00:29:14 UTC]]
  SQL (0.3ms)  INSERT INTO "role_users" ("user_id", "role_id", "created_at", "updated_at") VALUES (?, ?, ?, ?)  [["user_id", 5], ["role_id", 1], ["created_at", 2017-02-18 00:29:14 UTC], ["updated_at", 2017-02-18 00:29:14 UTC]]
   (0.5ms)  rollback transaction
ActiveRecord::ReadOnlyRecord: Role is marked as readonly
from /Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/persistence.rb:539:in `create_or_update'
[3] pry(main)> puts _ex_.backtrace
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/persistence.rb:539:in `create_or_update'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/callbacks.rb:298:in `block in create_or_update'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:126:in `call'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:506:in `block (2 levels) in compile'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:455:in `call'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:101:in `__run_callbacks__'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:750:in `_run_save_callbacks'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/callbacks.rb:298:in `create_or_update'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/persistence.rb:125:in `save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/validations.rb:44:in `save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/attribute_methods/dirty.rb:22:in `save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:319:in `block (2 levels) in save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:395:in `block in with_transaction_returning_status'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/database_statements.rb:230:in `transaction'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:211:in `transaction'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:392:in `with_transaction_returning_status'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:319:in `block in save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:334:in `rollback_active_record_state!'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:318:in `save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/suppressor.rb:41:in `save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/associations/has_many_through_association.rb:44:in `insert_record'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/autosave_association.rb:402:in `block in save_collection_association'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/autosave_association.rb:393:in `each'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/autosave_association.rb:393:in `save_collection_association'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/autosave_association.rb:185:in `block in add_autosave_association_callbacks'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/autosave_association.rb:158:in `instance_eval'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/autosave_association.rb:158:in `block in define_non_cyclic_method'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:382:in `block in make_lambda'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:207:in `block in halting_and_conditional'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:456:in `block in call'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:456:in `each'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:456:in `call'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:101:in `__run_callbacks__'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:750:in `_run_create_callbacks'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/callbacks.rb:302:in `_create_record'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/timestamp.rb:68:in `_create_record'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/persistence.rb:540:in `create_or_update'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/callbacks.rb:298:in `block in create_or_update'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:126:in `call'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:506:in `block (2 levels) in compile'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:455:in `call'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:101:in `__run_callbacks__'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activesupport-5.0.1/lib/active_support/callbacks.rb:750:in `_run_save_callbacks'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/callbacks.rb:298:in `create_or_update'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/persistence.rb:125:in `save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/validations.rb:44:in `save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/attribute_methods/dirty.rb:22:in `save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:319:in `block (2 levels) in save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:395:in `block in with_transaction_returning_status'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/database_statements.rb:232:in `block in transaction'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/transaction.rb:189:in `within_new_transaction'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/connection_adapters/abstract/database_statements.rb:232:in `transaction'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:211:in `transaction'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:392:in `with_transaction_returning_status'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:319:in `block in save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:334:in `rollback_active_record_state!'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/transactions.rb:318:in `save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/suppressor.rb:41:in `save'
/Users/moperon/has_many_through_readonly/vendor/bundle/ruby/2.3.0/gems/activerecord-5.0.1/lib/active_record/persistence.rb:34:in `create'
(pry):2:in `<main>'
...snip...

というわけで、Rails4で動いていたコードがRails5では動かなくなりました。
usersrole_usersにINSERTした後で、なぜrolecreate_or_updateしようとしているのか謎です。

原因を追いかけてみたのですが、追いきれず。心折れたので試合終了です。
しばらく寝かせてからまた調べます。

お疲れ様でした。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1