concatenating to hmt causes mass-assignment error with AR 4.1.2.rc1
seanwalbran opened this issue · comments
In ActiveRecord 4.1.2.rc1, I'm seeing mass assignment errors from concatenating to a has_many_through collection like the below; these errors do not occur in 4.1.1.
class Group < ActiveRecord::Base
...
has_many :members, :through => :memberships, :source => :user
end
group.members << user
Standalone reproduction script: https://gist.github.com/seanwalbran/4f8670b2afc5b3f1e912
1) Error:
BugTest#test_concat:
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes for Membership: group_id
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/protected_attributes-1.0.7/lib/active_model/mass_assignment_security/sanitizer.rb:60:in `process_removed_attributes'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/protected_attributes-1.0.7/lib/active_model/mass_assignment_security/sanitizer.rb:10:in `sanitize'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/protected_attributes-1.0.7/lib/active_model/mass_assignment_security.rb:346:in `sanitize_for_mass_assignment'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/protected_attributes-1.0.7/lib/active_record/mass_assignment_security/attribute_assignment.rb:58:in `assign_attributes'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/protected_attributes-1.0.7/lib/active_record/mass_assignment_security/core.rb:8:in `init_attributes'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/core.rb:198:in `initialize'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/inheritance.rb:30:in `new'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/inheritance.rb:30:in `new'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/protected_attributes-1.0.7/lib/active_record/mass_assignment_security/reflection.rb:5:in `build_association'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/protected_attributes-1.0.7/lib/active_record/mass_assignment_security/associations.rb:5:in `build_record'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/protected_attributes-1.0.7/lib/active_record/mass_assignment_security/associations.rb:19:in `build'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/has_many_through_association.rb:86:in `build_through_record'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/has_many_through_association.rb:97:in `save_through_record'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/has_many_through_association.rb:64:in `insert_record'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/collection_association.rb:522:in `block (2 levels) in concat_records'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/collection_association.rb:389:in `add_to_target'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/collection_association.rb:521:in `block in concat_records'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/collection_association.rb:519:in `each'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/collection_association.rb:519:in `concat_records'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/has_many_through_association.rb:42:in `concat_records'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/collection_association.rb:153:in `block in concat'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/collection_association.rb:168:in `block in transaction'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/connection_adapters/abstract/database_statements.rb:201:in `block in transaction'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/connection_adapters/abstract/database_statements.rb:209:in `within_new_transaction'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/connection_adapters/abstract/database_statements.rb:201:in `transaction'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/transactions.rb:208:in `transaction'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/collection_association.rb:167:in `transaction'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/collection_association.rb:153:in `concat'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/has_many_through_association.rb:36:in `concat'
/Users/swalbran/.rvm/gems/ruby-2.1.2/gems/activerecord-4.1.2.rc1/lib/active_record/associations/collection_proxy.rb:972:in `<<'
./protected.rb:49:in `test_concat'
Thank you.
@rafaelfranca Thanks for the fix -- unfortunately, it looks like there's still a regression here that was hidden by the previous issue, in the related case where there's a scope involved in the hmt. Updated gist: https://gist.github.com/seanwalbran/011b48b18e5da2531fba
has_many :admins, -> { where(:memberships => { :admin => true }) }, :through => :memberships, :source => :user
group.admins << user
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes for Membership: admin
@seanwalbran I don't think this is an issue. If you are using scope attributes to build the object you should allow it.
BTW, there is no way to fix both issues so it is better to keep only the common case (without scope attributes) working.
Also:
group.admins << user
Before didn't created the membership with the admin flag as true so there no reason for setting it on the admins
association if you don't want to set the admin flag.
Great point about it not having been set before -- with that clarified, I completely agree this isn't a bug. Thanks again!
Thank you for pointing the issue and testing the RC. Sorry for the delay on fixing it
I'm hitting a couple more mass assignment failures with polymorphism scenarios that worked in 4.1.1: https://gist.github.com/seanwalbran/5d229bc03606c7abccef
While it looks like one could be mitigated by also excepting through_association.reflection.type
from the where_values_hash, that doesn't seem to help the other one.
Would it maybe make more sense to assign the record first, and then only assign the through_scope_attributes afterwards if they've not already been set by that assignment?
+ def build_through_record(record)
+ @through_records[record.object_id] ||= begin
+ ensure_mutable
+
+ # PATCH: assign record first, then only assign new/changed attributes afterwards
+ through_record = through_association.build
+ through_record.send("#{source_reflection.name}=", record)
+ through_scope_attributes.each do |k, v|
+ through_record[k] = v unless through_record[k] == v
+ end
+ through_record
+ end
+ end
Maybe we should disable the mass assignment check when building through record.
Fix #36 makes everything work for me -- many thanks @zzak and @rafaelfranca!