ActiveRecord query changing when a dot/period is in condition value

前端 未结 2 1092

See the updates at the bottom. I\'ve narrowed this down significantly.

I\'ve also created a barebones app demonstrating this bug: https://github.com/coreywa

2条回答
  •  野趣味
    野趣味 (楼主)
    2020-12-16 05:54

    The difference between the two strategies for eager loading are discussed in the comments here

    https://github.com/rails/rails/blob/3-0-stable/activerecord/lib/active_record/association_preload.rb

    From the documentation:

    # The second strategy is to use multiple database queries, one for each
    # level of association. Since Rails 2.1, this is the default strategy. In
    # situations where a table join is necessary (e.g. when the +:conditions+
    # option references an association's column), it will fallback to the table
    # join strategy.
    

    I believe that the dot in "foo.bar" is causing active record to think that you are putting a condition on a table that is outside of the originating model which prompts the second strategy discussed in the documentation.

    The two separate queries runs one with the Person model and the second with the Item model.

     Person.includes(:items).where(:name => 'fubar')
    
    Person Load (0.2ms)  SELECT "people".* FROM "people" WHERE "people"."name" = 'fubar'
    Item Load (0.4ms)  SELECT "items".* FROM "items" WHERE ("items".person_id = 1) ORDER BY items.ordinal
    

    Because you run the second query against the Item model, it inherits the default scope where you specified order(:ordinal).

    The second query, which it attempts eager loading with the full runs off the person model and will not use the default scope of the association.

     Person.includes(:items).where(:name => 'foo.bar')
    
    Person Load (0.4ms)  SELECT "people"."id" AS t0_r0, "people"."name" AS t0_r1, 
    "people"."created_at" AS t0_r2, "people"."updated_at" AS t0_r3, "items"."id" AS t1_r0, 
    "items"."person_id" AS t1_r1, "items"."name" AS t1_r2, "items"."ordinal" AS t1_r3, 
    "items"."created_at" AS t1_r4, "items"."updated_at" AS t1_r5 FROM "people" LEFT OUTER JOIN 
    "items" ON "items"."person_id" = "people"."id" WHERE "people"."name" = 'foo.bar'
    

    It is a little buggy to think that, but I can see how it would be with the several different ways you can present a list of options, the way to be sure that you catch all of them would be to scan the completed "WHERE" conditions for a dot and use the second strategy, and they leave it that way because both strategies are functional. I would actually go as far as saying that the aberrant behavior is in the first query, not the second. If you would like the ordering to persist for this query, I recommend one of the following:

    1) If you want the association to have an order by when it is called, then you can specify that with the association. Oddly enough, this is in the documentation, but I could not get it to work.

    Source: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#method-i-has_many

    class Person < ActiveRecord::Base
      has_many :items, :order => 'items.ordinal'
    end
    

    2) Another method would be to just add the order statement to the query in question.

    Person.includes(:items).where(:name => 'foo.bar').order('items.ordinal')
    

    3) Along the same lines would be setting up a named scope

    class Person < ActiveRecord::Base
      has_many :items
      named_scope :with_items, includes(:items).order('items.ordinal')
    end
    

    And to call that:

    Person.with_items.where(:name => 'foo.bar')
    

提交回复
热议问题