Collection has a different setter logic for persistent and new record.
New record writes changes to the database only after save is triggered, persisted record on the contrary writes changes to the database once ids or array of records assigned via setter.
This inconsistency leads to a different issues, even not related to the inconsistency between new and persisted record.
I just list some of them I already faced with in a production:
-
Validation raises an error. Lets asume we have has_many records, which should create a has_many through associations while passing ids to the parent record via collection_ids. In case when has_many through will have a validation error - this will raise an error, which only could be rescued, even if record is saved with soft
*save*
, not*save!*
. -
Collection assignment writes changes at the moment of passing data via setter, not while
save
is invoked. Lets asume I have a model with a lot of different associations. This model feeded via large form with different ids and other fields. When I set a parameters through a*assign_attributes*
method (without any transaction block), and save returns false because of the validation failed, collection ids will be changed anyway. One of the solutions would be use transactions and raise an exception with ActiveRecord::Rollback. Other solution would be use update_attributes or update in the latest version of rails. Other one is to use undocumented with_transaction_returning_status**.** - Difference between new and persisted record. I believe there are not many developers who work with rails framework know about the difference between new and persisted record with has_many and has_many through records.
- has_one works differently. Means you can update _id of a has_many or belongs_to via setter and it will not modify anything in the database.
Main case to reproduce an issue:
begin require “bundler/inline” rescue LoadError => e $stderr.puts “Bundler version 1.10 or later is required. Please update your Bundler” raise e end
gemfile(true) do source “https://rubygems.org” gem “rails”, path: “…/projects/rails/” gem “sqlite3” end
require “active_record” require “minitest/autorun” require “logger”
This connection will do for database-independent bug reports.
ActiveRecord::Base.establish_connection(adapter: “sqlite3”, database: “:memory:”) ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do create_table :posts, force: true do |t| end
create_table :comments, force: true do |t| t.integer :post_id end end
class Post < ActiveRecord::Base has_many :comments end
class Comment < ActiveRecord::Base belongs_to :post end
class BugTest < Minitest::Test def test_association_stuff comment = Comment.create! attributes = {comment_ids: [comment.id]}
#new_post = Post.new(attributes)
#assert_equal 1, new_post.comments.size
post = Post.create!
post.assign_attributes(attributes)
assert_equal 1, post.comments.size
assert_equal 0, post.comments.count
post.save!
assert_equal 1, post.comments.size
assert_equal 1, post.comments.count
end end
``
Please let me know if you would like to see the other cases I’ve listed above.
Solution would be to change behaviour for the autosave has_many records, so the associations of the autosave records will never modify database on data passed to a setter.