Lighthouse has a new layout. Prefer the old one? Return to the old layout, and switch back any time from the link at the top of each page.

This project is archived and is in readonly mode.

find_or_create_by doesn't work with protected attributes

#5983

There's some strange problem with find_or_create_by on has_many associations and protected attributes:
user.posts.find_or_create_by_some_protected_attribute doesn't assign "some_protected_attribute", but user.posts.find_or_initialize_by_some_protected_attribute does.

I'm attaching a failing test case.

Reported by Szymon Nowak · November 16th, 2010 @ 01:16 PM

State: new
Milestone: none
Assigned to: Aaron Patterson Aaron Patterson
Importance: none

Activity

  1. Szymon Nowak
  2. caseyf
    caseyf

    I'm having this problem as well. No luck tracking it down so far - ActiveRecords's association internals are a mystery to me.

    November 16th, 2010 @ 06:27 PM

  3. caseyf
    caseyf

    It looks like find_or_create through an association calls create_by with a Hash:
    https://github.com/rails/rails/commit/f967b352d2abdb88b704266cdb06b...

    ...and attributes are protected when creating from Hashes: https://github.com/rails/rails/commit/f967b352d2abdb88b704266cdb06b...

    November 16th, 2010 @ 07:05 PM

  4. caseyf
    caseyf

    ack. Ignore me, I'm looking in the wrong place.

    November 16th, 2010 @ 07:12 PM

  5. Szymon Nowak
    Szymon Nowak

    I'll take a look at it over the weekend, but the workaround that I'm using for now is find_or_initialize_by + save.

    November 16th, 2010 @ 08:10 PM

  6. 2kan
    2kan
    • Tag cleared.

    I don't think that it is a bug (for 2.3.10 and 3.x). Here: https://github.com/rails/rails/blob/v2.3.10/activerecord/lib/active... says that protected attributes won't be set unless they are given in a block.

    Also the same says about find_or_initialize_ so if it works for such methods it is a bug.

    November 19th, 2010 @ 01:27 PM

  7. 2kan
    2kan
    • Tag set to 2.3.10, active_record

    November 19th, 2010 @ 01:29 PM

  8. 2kan
    2kan

    Also I wrote a test for 2.3.10 and 3.0.3 for find_or_initialize_ and it also fails as it should be. So I don't think that there is any bug.

    November 19th, 2010 @ 01:50 PM

  9. caseyf
    caseyf

    Thanks 2kan.

    (This is a change in behavior from 2.2.x)

    November 19th, 2010 @ 01:56 PM

  10. Szymon Nowak
    Szymon Nowak

    First of all it's not a change in behavior from 2.2.x, but from 2.3.5 - I migrated my app from 2.3.5 directly to 2.3.10 and it stopped working.

    Second thing is that find_or_initialize_by_id works fine. Here's code from my app:

    r = current_user.relationships.find_or_initialize_by_contact_id(1)
    => #<Relationship id: nil, user_id: 15, contact_id: 1, blocked: false, created_at: nil, updated_at: nil, inverse_id: nil, state: "unapproved", requested_at: nil, approved_at: nil>
    r.save
    => true
    
    r = current_user.relationships.find_or_create_by_contact_id(1)
    WARNING: Can't mass-assign these protected attributes: contact_id
    => #<Relationship id: nil, user_id: 15, contact_id: nil, blocked: false, created_at: nil, updated_at: nil, inverse_id: nil, state: "unapproved", requested_at: nil, approved_at: nil>
    

    so the behavior is inconsistent.

    November 20th, 2010 @ 03:15 PM

  11. Szymon Nowak
    Szymon Nowak

    @2kan: thanks for the link! It turns out that the bug is actually that find_or_initialize_by works with protected attributes in 2.3.10, although it shouldn't :)

    November 20th, 2010 @ 03:58 PM

  12. 2kan
    2kan

    @Szymon Nowak I will check again how find_or_initialize_by works in 2.3.10 to be sure that there is a bug and gonna try to patch it so.

    November 20th, 2010 @ 04:07 PM

  13. 2kan
    2kan

    Hm! Look here: https://github.com/rails/rails/blob/v2.3.10/activerecord/test/cases... Funny, isn't it? I think that it is a bug, I think that find_or_initialize_by should work as find_or_create_by and as it says in docs, shouldn't it?

    But look here: https://github.com/rails/rails/blob/v2.3.10/activerecord/test/cases...

    Isn't it messy? I was really surprised reading the code and tests. I can write any patch doing something with it but i have no idea how must it work. Maybe we just should not touch this.

    November 20th, 2010 @ 08:52 PM

  14. Szymon Nowak
    Szymon Nowak
    • Assigned user set to Aaron Patterson

    Ok, so to sum it all up:

    The docs for both methods (https://github.com/rails/rails/blob/v2.3.10/activerecord/lib/active... and https://github.com/rails/rails/blob/v2.3.10/activerecord/lib/active...) state that:

    Protected attributes won't be set unless they are given in a block.
    

    However, in the tests ((https://github.com/rails/rails/blob/v2.3.10/activerecord/test/cases...) the behavior is different:

    test_find_or_initialize_from_one_attribute_should_set_attribute_even_when_protected
    test_find_or_create_from_one_attribute_should_set_attribute_even_when_protected
    

    The other thing is how both methods work with has_many associations:

    r = current_user.relationships.find_or_initialize_by_contact_id(1)
    => #<Relationship id: nil, user_id: 15, contact_id: 1>
    r.save
    => true
    
    r = current_user.relationships.find_or_create_by_contact_id(1)
    WARNING: Can't mass-assign these protected attributes: contact_id
    => #<Relationship id: nil, user_id: 15, contact_id: nil>
    

    While all the links above point to files in 2.3.10 release, it seems that the same mess is in 3.0.2 release. I hope that someone from the Rails core team can decide how it should actually work :)

    November 21st, 2010 @ 09:39 PM

  15. bingbing