rails / protected_attributes

Protect attributes from mass-assignment in ActiveRecord models.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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'

@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!