Rails 4 Relation is not being sorted: 'has_man

2019-08-05 17:11发布

问题:

I am trying to use a default scope to impose a sort order on the model QuizCategoryWeight. The goal is to get @possible_answer.quiz_category_weights to return the weights in sorted order.

Update: I have narrowed the problem down to the fact that default scopes seem to work for me as long as they just have an 'order' method but not when the 'includes' method is chained with the 'order' method. However, this chaining does work for named scopes.

Could it be my development environment? Or is this a bug in Rails perhaps?

I am using windows, so maybe that's the problem. Currently on ruby 2.0.0p645 (2015-04-13) [i386-mingw32] and Rails 4.2.4...

The following, using a default scope on QuizCategoryWeight, does not seem to work:

class QuizCategoryWeight < ActiveRecord::Base
    #trying to use a default scope, but does not work
    default_scope { includes(:quiz_category).order("quiz_categories.sort_order") }

    belongs_to :possible_answer, inverse_of: :quiz_category_weights,
        class_name: 'QuizPossibleAnswer', foreign_key: 'possible_answer_id'

    belongs_to :quiz_category

end

class QuizPossibleAnswer < PossibleAnswer
    has_many :quiz_category_weights, 
        #does not work whether the line below is used or not
        ->{ includes(:quiz_category).order("quiz_categories.sort_order") }, 
        inverse_of: :possible_answer, 
        dependent: :destroy, 
        foreign_key: 'possible_answer_id' 
end

class QuizCategory < ActiveRecord::Base
    default_scope { order :sort_order }
end  

With a named scope, it does work. However, this means that I have to add an argument to my form builder to use the collection 'f.object.quiz_category_weights.sorted'.

class QuizCategoryWeight < ActiveRecord::Base
    # named scope works...
    scope :sorted,  ->{ includes(:quiz_category).order("quiz_categories.sort_order") }

    belongs_to :possible_answer, inverse_of: :quiz_category_weights,
        class_name: 'QuizPossibleAnswer', foreign_key: 'possible_answer_id'

    belongs_to :quiz_category

end

class QuizPossibleAnswer < PossibleAnswer
    has_many :quiz_category_weights, 
        inverse_of: :possible_answer, 
        dependent: :destroy, 
        foreign_key: 'possible_answer_id' 
end

回答1:

I think there is a bug with using 'includes' with a default scope, either in the Rails framework generally or in my windows version.

However, I've found that using 'joins' does work. I'm not using any of other the attributes from QuizCategory so it's more appropriate to my use case as well: I only want to sort using the 'sort_order' attribute from the joined table.

The fixed code is:

class QuizCategoryWeight < ActiveRecord::Base
    default_scope { joins(:quiz_category).order("quiz_categories.sort_order") }

    belongs_to :quiz_category
end


回答2:

The includes method was introduces for relations to give Rails a hint to reduce database queries. It says: When you fetch Objects of type A, also fetch associated objects, because I need them later, and they should not fetched one by one (the N+1 queries problem)

The includes was first implemented with two database queries. First all A, then all B with one of the ids from A. Now includes often uses a sql join to have only one database query. But this is an internal optimisation. The concept is object oriented, you want objects from A, then you retrieve the B through the A. So I think, if you set the order from the included B back to A, you are doing more than was meant for the original includes.