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.

Model.has_many_through_association.find(id) returns a read-only record

#5442

I have the following models:

class Provider < AR::Base
  has_many :dish_types
  has_many :dishes, :through => :dish_types
end

class DishType < AR::Base
  has_many :dishes
  belongs_to :provider
end

class Dish < AR::Base
  belongs_to :dish_type
end

Now load dish with #find:

ree-1.8.7-2010.02 > Provider.first.dishes.find(1).readonly?
 => true 
ree-1.8.7-2010.02 > Provider.first.dish_types.find(1).readonly?
 => false 
ree-1.8.7-2010.02 > DishType.first.dishes.find(555).readonly?
 => false

Simple has_many associations work as they should, but HMT association returns a read-only record.

Reported by oleg dashevskii · August 24th, 2010 @ 02:49 PM

State: resolved
Milestone: 3.1
Assigned to: Aaron Patterson Aaron Patterson
Importance: Low

Activity

  1. Paul Rosania
    Paul Rosania

    Confirmed under Rails 3 RC2.

    August 24th, 2010 @ 08:05 PM

  2. oleg dashevskii
  3. Neeraj Singh
    Neeraj Singh
    • Importance changed from to Low

    This is not a bug. It is an undocumented feature. :-)

    Look at following test cases which is already there in ActiveRecord.

      def test_habtm_find_readonly
        dev = Developer.find(1)
        assert !dev.projects.empty?
        assert dev.projects.all?(&:readonly?)
        assert dev.projects.find(:all).all?(&:readonly?)
        assert dev.projects.readonly(true).all?(&:readonly?)
      end
    
      def test_has_many_find_readonly
        post = Post.find(1)
        assert !post.comments.empty?
        assert !post.comments.any?(&:readonly?)
        assert !post.comments.find(:all).any?(&:readonly?)
        assert post.comments.readonly(true).all?(&:readonly?)
      end
    

    As you can see by default with habtm all the retrieved records are marked as readonly.

    Although it is not there in the test but you can pass readonly(false) and you will get non readonly records.

    Check this out

    class Post < ActiveRecord::Base
      has_many :taggings
      has_many :tags, :through => :taggings
    
      def self.lab
        tmp = Post.first.tags.readonly(true).find(1).readonly?
        puts tmp #=> true
      end
    end
    
    
    class Post < ActiveRecord::Base
      has_many :taggings
      has_many :tags, :through => :taggings
    
      def self.lab
        tmp = Post.first.tags.readonly(false).find(1).readonly?
        puts tmp #=> false
      end
    end
    

    August 25th, 2010 @ 04:17 PM

  4. Neeraj Singh
    Neeraj Singh

    I should have tested with habtm rather than has_many through. But the ActiveRecord test is with habtm.

    August 25th, 2010 @ 04:18 PM

  5. Paul Rosania
    Paul Rosania

    Are there safety consequences to using #readonly(false) during retrieval?

    Why is it defaulted to readonly in this case? Results retrieved through HMT have been readonly? #=> false for a long time (since first beta?). (I believe this has been the case since HMT was introduced.) Is readonly(true) necessary for some reason now?

    August 25th, 2010 @ 04:42 PM

  6. Neeraj Singh
    Neeraj Singh

    Good questions. And I do not have answer.

    With final release of rails 3 so close I believe we should not change API much. I would try to dig deeper into this issue later.

    August 25th, 2010 @ 04:45 PM

  7. Paul Rosania
    Paul Rosania

    Thanks for the quick reply! I took one more peek at the Rails source, and I actually think that the current behavior (readonly? #=> true) contradicts this test, introduced in Rails 1.15:

    activerecord/test/cases/readonly_test.rb:69

    def test_has_many_with_through_is_not_implicitly_marked_readonly
      assert people = Post.find(1).people
      assert !people.any?(&:readonly?)
    end
    

    See commit http://github.com/rails/rails/commit/3f049b0b6b5a338786c3dfafb31edf...

    I spent some time tracing execution yesterday, but I haven't been able to identify the change between RC and RC2 that causes the regression.

    August 25th, 2010 @ 05:02 PM

  8. oleg dashevskii
    oleg dashevskii

    Paul, this test is noop, since :people fixture isn't loaded and empty array passes the test. I've changed it (see the patch) and it still passes! So the problem is only with find(id) call, which returns a readonly record for some reason.

    Returning back to my IRB session:

    ree-1.8.7-2010.02 > Provider.first.dishes.find(1).readonly?
     => true 
    ree-1.8.7-2010.02 > Provider.first.dishes.map(&:readonly?).uniq
     => [false]
    

    This is clearly a bug.

    August 25th, 2010 @ 05:21 PM

  9. Paul Rosania
    Paul Rosania

    I just noticed the same thing. To clarify, readonly_test.rb currently specifies the following:

    • Model.habtm.find(1).readonly? #=> true
    • Model.has_many.find(1).readonly? #=> false

    There is no test of has_many :through behavior, which has changed from readonly #=> false to readonly #=> true between RC and RC2.

    This change is likely an unintended side-effect of changes to the Relation class since RC, but I have not been able to isolate it.

    August 25th, 2010 @ 05:28 PM

  10. oleg dashevskii
    oleg dashevskii
    • Title changed from Model.has_many_through_association.find(id) returns a read-only record to [PATCH] Model.has_many_through_association.find(id) returns a read-only record

    This is a rather obscure bug. I'm glad to say I finally traced it down. ActiveRecord::QueryMethods#build_select wasn't resetting @implicit_readonly since it was passed empty selects argument. The RC version was using selects.present? check which returned true even for empty array. And the selects were empty because of the finder options slicing.

    The fix is attached.

    August 26th, 2010 @ 05:30 AM

  11. oleg dashevskii
    oleg dashevskii
    • Tag changed from activerecord rails3, find_one, has_many_through_association, readonly to activerecord rails3, find_one, has_many_through_association, patch, readonly

    August 26th, 2010 @ 05:31 AM

  12. Justin Smestad
    Justin Smestad

    Running into this bug as well. Quite annoying. +1 for this being fixed in 3.0.0 final.

    August 28th, 2010 @ 08:31 AM

  13. Santiago Pastorino
    Santiago Pastorino
    • Milestone cleared.
    • State changed from new to open

    August 28th, 2010 @ 05:31 PM

  14. Jeremy Kemper
  15. Ryan Bigg
    Ryan Bigg
    • Tag cleared.

    Automatic cleanup of spam.

    October 11th, 2010 @ 12:12 PM

  16. Jeff Kreeftmeijer
    Jeff Kreeftmeijer
    • Title changed from [PATCH] Model.has_many_through_association.find(id) returns a read-only record to Model.has_many_through_association.find(id) returns a read-only record
    • Tag set to has_many_through, patch

    Using the "patch" tag instead of prefixing the ticket title with "[PATCH]" to make sure patched tickets end up in the open patches bin. :)

    October 12th, 2010 @ 06:57 AM

  17. Espen Antonsen
    Espen Antonsen

    I assume this is not fixed in 3.0.1? Can reproduce on 3.0.1 in my project.

    October 15th, 2010 @ 01:36 PM

  18. Jeremy Kemper
  19. Santiago Pastorino
  20. Jason King
  21. Jason King
    Jason King

    You can work around it declaratively too:

    has_many :foos, :through => :bars, :readonly => false
    

    November 17th, 2010 @ 06:34 PM

  22. Jon Leighton
    Jon Leighton
    • Assigned user set to Aaron Patterson

    Hi,

    This patch didn't apply cleanly for me initially. I merged in the tests and ran them, and they passed on master and on 3-0-stable. I've attached an updated patch containing these tests which I think should be applied to guard against regressions.

    I'm not sure whether the change that was proposed in AssociationCollection#find should still be applied, but if so presumably that's a different issue as these tests not pass. I've left it out for now.

    Thanks

    December 21st, 2010 @ 12:48 PM

  23. Jon Leighton
    Jon Leighton
    • Milestone set to 3.1

    Contrary to my earlier assertion, this is not fixed in 3-0-stable. It is, however, fixed in master.

    I've looked into fixing it in 3-0-stable but my personal opinion is that it's too complicated and therefore not worth it. The root of the problem is that 3-0-stable generates strings for the INNER JOINs it performs, which then causes the @implicit_readonly flag to get set when building the Arel query. In master, the joins are built as Arel objects instead, so this flag is not set.

    I've also attached a new patch against master which cleans up the tests a little, and also fixes the third one (which was not actually testing what it said it was testing).

    December 23rd, 2010 @ 04:39 PM

  24. Jon Leighton
    Jon Leighton
    • State changed from open to resolved

    December 23rd, 2010 @ 11:55 PM

  25. Geoffrey Hichborn
    Geoffrey Hichborn

    Can you confirm that the suggested patch for 3.0-stable doesn't fix the issue/produces unexpected behavior?

    April 12th, 2011 @ 10:50 PM

  26. Justin Smestad
    Justin Smestad

    This is marked as resolved however there is no commit referenced and the last comment from Jon said this could not be fixed till 3.1. Is this fixed or not?

    April 20th, 2011 @ 08:36 PM

  27. Jason King
    Jason King

    Actually, the last comment from Jon says that (a) it's already fixed in master (ie. 3.1), and (b) it's too complicated to fix it in 3.0

    April 20th, 2011 @ 09:20 PM