Rails polymorphic with includes на основе типа класса
Скажем, мы имеем эти модели
class Message
belongs_to :messageable, polymorphic: true
end
class Ticket
has_many :messages, as: :messageable
has_many :comments
end
class User
has_many :messages, as: :messageable
has_many :ratings
end
class Rating
belongs_to :user
end
class Comment
belongs_to :ticket
end
Теперь я хочу загрузить все сообщения (которые связаны с tickets
или users
) и загружать загрузку в зависимости от типа класса, либо comments
для tickets
и ratings
для users
Конечно, Message.includes(:messageable).order("created_at desc")
будет включать только связанный с ним объект, но вопрос заключается в том, как включить различные типы ассоциаций, которые производятся от каждого типа модели (т.е. в этом примере, как загружать нагрузку comments for tickets
и ratings for users
)?
Это простой пример, но что еще более сложные случаи, когда я хотел бы добавить что-то еще для user
, другую ассоциацию, и что, если эта ассоциация нуждается в большей части?
Ответы
Ответ 1
Единственный способ, которым я могу это сделать, - дублировать ассоциации для каждой модели с общим именем:
class Ticket
has_many :messages, as: :messageable
has_many :comments
has_many :messageable_includes, class_name: "Comment"
end
class User
has_many :messages, as: :messageable
has_many :ratings
has_many :messageable_includes, class_name: "Rating"
end
Message.includes(:messageable => :messageable_includes) ...
Я не уверен, что буду использовать эту стратегию в качестве широко распространенного решения, но если это сложно по мере вашего дела, это может сработать для вас.
Ответ 2
В моем собственном проекте я использовал следующие вспомогательные методы:
def polymorphic_association_includes(association, includes_association_name, includes_by_type)
includes_by_type.each_pair do |includes_association_type, includes|
polymorphic_association_includes_for_type(association, includes_association_name, includes_association_type, includes)
end
end
def polymorphic_association_includes_for_type(association, includes_association_name, includes_association_type, includes)
id_attr = "#{includes_association_name}_id"
type_attr = "#{includes_association_name}_type"
items = association.select {|item| item[type_attr] == includes_association_type.to_s }
item_ids = items.map {|item| item[id_attr] }
items_with_includes = includes_association_type.where(id: item_ids).includes(includes).index_by(&:id)
items.each do |parent|
parent.send("#{includes_association_name}=", items_with_includes[parent[id_attr]])
end
end
Это позволит вам сказать:
messages = Message.all
polymorhpic_association_includes messages, :messageable, {
Ticket => :comments,
User => :ratings
}
Не особенно плавный интерфейс, но он работает в целом.
Ответ 3
Поместите объекты в область по умолчанию для каждой модели:
class Ticket
has_many :messages, as: :messageable
has_many :comments
default_scope -> { includes(:comments).order('id DESC') }
end
class User
has_many :messages, as: :messageable
has_many :ratings
default_scope -> { includes(:ratings).order('id DESC') }
end
Тогда всякий раз, когда вы вызываете Message.all
, каждая полиморфная ассоциация будет включать в себя ее собственные ресурсы.
Также, если вам нужно вызвать класс без области, просто используйте unscoped
или создайте другую область:
class Ticket
has_many :messages, as: :messageable
has_many :comments
has_many :watchers
default_scope -> { includes(:comments).order('id DESC') }
scope :watched -> {includes(:watchers)}
end
Ticket.unscoped.all # without comments or watchers (or order)
Ticket.watched.all # includes watchers only