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.

AssociationCollection#include? ignoring build associated objects

#3472

Consider:

class Post < ActiveRecord::Base
  has_many :comments
end

class Comment < ActiveRecord::Base
  belongs_to :post
end

The following test fails:


def test_build
  post = Post.new
  comment = post.comments.build
  assert post.comments.include?(comment)
end

The logs show:


  Comment Load (0.2ms)   SELECT "comments".id FROM "comments" WHERE ("comments"."id" = NULL) AND ("comments".post_id = NULL) LIMIT 1

It seems that the collection ignores that it has been setup (loaded? #=> false) and reloads it from the database. Regarding that the database is a new record and hence definitely cannot be in the database, that is probably not expected behavior.

Any suggestions?

Reported by Dmitry Ratnikov · November 9th, 2009 @ 05:59 AM

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

Activity

  1. Marcelo Giorgi
    Marcelo Giorgi
    • Tag set to patch

    I've just create a patch for this.

    The idea of the fix is to delegate the job to Array#include? for the collection present in memory even if the object is not stored in the database. Without the fix, Rails only looks in the Array collection if the association collection were loaded previously.

    Please, let me know what do you think about the implementation.

    BTW: I took your test to check the fix worked.

    November 23rd, 2009 @ 02:13 AM

  2. Marcelo Giorgi
    Marcelo Giorgi

    Hey,

    My mistake, I had a typo in the test. Now uploading the correct version.

    Now you can tell me what you think about the PATCH.

    November 23rd, 2009 @ 04:46 PM

  3. Diego Algorta
  4. rabble
  5. Dmitry Ratnikov
    Dmitry Ratnikov
    • Tag cleared.

    Looks good to me :)

    I will apply that to 2.3 stable to check that it works.
    I'll also check that it works if the has_many has finder_sql defined.

    November 23rd, 2009 @ 05:00 PM

  6. Dmitry Ratnikov
  7. Dmitry Ratnikov
    Dmitry Ratnikov
    • Tag changed from patch to for, patch

    Patches passes tests for mysql and sqlite3 adapters for 2-3-stable branch.

    November 23rd, 2009 @ 06:09 PM

  8. Marcelo Giorgi
    Marcelo Giorgi
    • Tag changed from for, patch to verified

    As there are three positive opinions about the patch, I changed it to verified.

    November 30th, 2009 @ 05:24 PM

  9. Marcelo Giorgi
    Marcelo Giorgi
    • Tag changed from verified to patch, verified

    December 1st, 2009 @ 03:27 PM

  10. Michael Koziarski
    Michael Koziarski

    This patch doesn't apply cleanly right now, could you rebase it for me

    However the change doesn't quite look right to me, shouldn't the check be whether the target is loaded or the owner is new, not the argument that's being checked against the collection?

    December 18th, 2009 @ 09:05 AM

  11. Marcelo Giorgi
    Marcelo Giorgi

    Hi Michael,

    IMHO: I think it makes sense to ask the argument in this case, because that's the only object that can tell if it is in memory or already stored, which is the key point to decide if traversing the collection makes sense...as we are looking for an in memory object or we would look into the database (which is being done in the last statement of the method).

    Also, there are cases when @owner is loaded and still the test would fail. For example, without the patch, this code (in which @owner is NOT new_record?) fails:

    p=Post.create(:title=>'title', :body=>'body') => # c=p.comments.build => # p.comments.include?(c) => false

    About checking the @target to see if it is loaded, as you can see from the code @target is already been asked for loaded? condition, to determine whether or not to traverse the collection looking for the object being passed as parameter:

    return @target.include?(record) if loaded?

    And this code (the current code) doesn't handle the test that I added, in which neither the @owner nor the @target are being loaded...

    If we don't ask the in_memory_object passed as parameter the only option left is to ALWAYS traverse the collection in memory just to check if there is an object that matches the one passed as parameter. Something like this:

      def include?(record)
        return false unless record.is_a?(@reflection.klass)
        load_target if @reflection.options[:finder_sql] && !loaded?
        return true if @target.include?(record)
        exists?(record)
      end
    

    BUT there are some test that breaks (i.e. /activerecord/test/cases/associations/has_many_through_associations_test#test_replace_association) with this solution, because they assume that when the collection is 'loaded?', then association_collection#include? method will NOT execute a new query, it would just check in memory and return 'false' if not found (but not look into the database).

    For this reason, I've attached the same patch ('rebased' with the latest code in 2-3-stable), PLEASE let me know what you think...

    December 18th, 2009 @ 08:15 PM

  12. Dmitry Ratnikov
    Dmitry Ratnikov

    To be honest, I was leaning towards something like to underline the differences between new_record and not:

    
    def include?(record)
             return false unless record.is_a?(@reflection.klass)
             if record.new_record?
               @target.include?(record)
             else
               load_target if @reflection.options[:finder_sql] && !loaded?
               return @target.include?(record) if loaded?
               exists?(record)
             end
           end
    

    The main reason is that if the record is a new_record? the DB specific code (load, exists?) really doesn't make much sense to me in the context of trying to see if the record is included in the collection.

    Of course Marcello's patch accomplishes same with with a tad less repetition, hence why I +1 it.

    December 18th, 2009 @ 09:02 PM

  13. Marcelo Giorgi
    Marcelo Giorgi

    Hey,

    I've just made some adjustments according to the Dimity's suggestion, about making the case of record.new_record? more explicit.

    Also, I've added more logic to make it work with HasManyThroughAssociation, which had also the same problem.

    More tests were added, to check that it is working for HasManyThroughAssociation and HasAndBelongsToManyAssociation.

    BTW: This patch has been made from 2-3-stable branch. But I'm also going to start a new ticket for master, as this issue still happens for Rails 3 version.

    Please, let me know if it looks good or have any suggestion so I can improve it

    August 8th, 2010 @ 07:34 PM

  14. Marcelo Giorgi
    Marcelo Giorgi
    • Assigned user set to Santiago Pastorino

    August 18th, 2010 @ 01:06 AM

  15. Santiago Pastorino
    Santiago Pastorino
    • Importance changed from to Low

    This issue is for 2.3 only? or 3.0 has the same problem?

    August 28th, 2010 @ 08:52 PM

  16. Marcelo Giorgi
    Marcelo Giorgi

    Good point, yes in fact Rails 3.0 has the same problem, and I pointed out in this ticket:
    #5336 AssociationCollection#include? ignoring build associated objects (not working...

    Btw: I've provided a similar patch and tests (which surprisingly still applies to master ;)) for this issue on Rails 3.0 on the other ticket.

    September 3rd, 2010 @ 11:35 PM

  17. Marcelo Giorgi
    Marcelo Giorgi

    Here I attach an update of the patch so that it applies cleanly (also a subtle change is introduced) on 2-3-stable.

    September 4th, 2010 @ 04:19 PM

  18. Santiago Pastorino
    Santiago Pastorino
    • State changed from new to open
    • Milestone set to 2.3.10
    • Assigned user changed from Santiago Pastorino to Aaron Patterson

    September 28th, 2010 @ 07:20 PM

  19. Repository
  20. Repository
  21. Repository
  22. Ryan Bigg
    Ryan Bigg
    • Tag cleared.

    Automatic cleanup of spam.

    October 9th, 2010 @ 10:10 PM

  23. Ryan Bigg
    Ryan Bigg

    Automatic cleanup of spam.

    October 11th, 2010 @ 12:12 PM

  24. Ryan Bigg
    Ryan Bigg

    Automatic cleanup of spam.

    October 21st, 2010 @ 03:37 AM

  25. bingbing