Как метод класса объекта ActiveRecord:: Relation
Как объект класса ActiveRecord:: Relation вызывает методы класса?
class Project < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :project
def self.initial_tasks # class methods
# here return initial tasks
end
end
Теперь мы можем позвонить:
Project.first.tasks.initial_tasks # how it works
initial_tasks
- метод класса, и мы не можем вызвать методы класса для объекта.
Project.first.tasks
возвращает объект ActiveRecord:: Relation, поэтому как он мог бы вызвать initial_tasks
?
Пожалуйста, объясните.
Ответы
Ответ 1
Там не так много документации по применению методов класса для объектов ActiveRecord::Relation
, но мы можем понять это поведение, посмотрев, как работают области действия ActiveRecord.
Во-первых, область модели Rails вернет объект ActiveRecord::Relation
. Из документов:
Методы класса на вашей модели автоматически доступны в областях. Предполагая следующую настройку:
class Article < ActiveRecord::Base
scope :published, -> { where(published: true) }
scope :featured, -> { where(featured: true) }
def self.latest_article
order('published_at desc').first
end
def self.titles
pluck(:title)
end
end
Во-первых, вызов области возвращает объект ActiveRecord::Relation
:
Article.published.class
#=> ActiveRecord::Relation
Article.featured.class
#=> ActiveRecord::Relation
Затем вы можете работать с объектом ActiveRecord::Relation
с помощью соответствующих методов класса модели:
Article.published.featured.latest_article
Article.featured.titles
Это немного окольный способ понять взаимосвязь между методами класса и ActiveRecord::Relation
, но суть такова:
- По определению области модели возвращают объекты
ActiveRecord::Relation
- По определению областям доступ к методам класса
- Поэтому,
ActiveRecord::Relation
объекты имеют доступ к методам класса
Ответ 2
Это очень легко изучить. Вы просто так:
class Project < ActiveRecord::Base
has_many :tasks
end
class Task < ActiveRecord::Base
belongs_to :project
def self.initial_tasks #class methods
1 / 0
end
end
Затем вызовите Project.first.tasks.initial_tasks
, и вы получите:
Division by zero
...
.../gems/activerecord-4.1.0/lib/active_record/relation/delegation.rb:70:in `block in re
.../gems/activerecord-4.1.0/lib/active_record/associations/collection_proxy.rb:872:in `
.../gems/activerecord-4.1.0/lib/active_record/relation.rb:286:in `scoping'",
.../gems/activerecord-4.1.0/lib/active_record/associations/collection_proxy.rb:872:in `
.../gems/activerecord-4.1.0/lib/active_record/relation/delegation.rb:70:in `initial_tasks'",
И это все, что вам нужно в основном. Легко исследовать, но не так легко понять.
Теперь я объясню, что это значит.
Когда вы вызываете метод Project#tasks
, он не возвращает объект ActiveRecord:: Relation. Фактически он возвращает вам экземпляр класса, созданного во время выполнения, с именем Task:: ActiveRecord_Associations_CollectionProxy, унаследованным от ActiveRecord:: Associations:: CollextionProxy, который, в свою очередь, унаследован от ActiveRecord:: Relation. Этот класс, созданный во время выполнения, связан с классом Task и содержит динамически определенные (через method_missing) прокси-методы, которые делегируют вызовы методам класса Task и объединительной области объединения с классом, определенным классами класса.
Как это работает (действительно нетривиально):
- Существует класс ActiveRecord:: Base. Объект класса, расширенный
ActiveRecord:: Delegation:: DelegateCache здесь.
-
Функция делегирования имеет DelegateCache.inherited
обратный вызов, который определяет атрибут @relation_delegate_cache
каждый раз, когда вы наследуете ActiveRecord:: Base. Это означает, что все классы AR:: Base потомков будут иметь такой атрибут. Обратный вызов вызывает метод DelegateCache#initialize_relation_delegate_cache
, который заполняет атрибут кеша с помощью классов, созданных во время выполнения:
[
ActiveRecord::Relation,
ActiveRecord::Associations::CollectionProxy,
ActiveRecord::AssociationRelation
].each do |klass|
delegate = Class.new(klass) {
include ClassSpecificRelation
}
const_set klass.name.gsub('::', '_'), delegate
cache[klass] = delegate
end
Здесь эти классы получают необычные имена a-la Task::ActiveRecord_Associations_CollectionProxy
упомянутых ранее.
- Итак, теперь наша модель задач имеет ссылки на классы отношения, определенные во время выполнения. У этих классов определенная проблема называется ClassSpecificRelation (здесь здесь). Концерн добавляет метод method_missing, который обнаруживает вызовы метода класса для объекта отношения (например, вызывает
#initial_tasks
on Project.tasks
). При таком вызове динамически определяет новые методы экземпляра класса времени выполнения, которые передают методы класса. Теперь у вас есть класс задач, связанный с классом Task:: ActiveRecord_Associations_CollectionProxy, содержащий все методы уровня экземпляра, которые прокси-вызовы методам класса на уровне задачи получают результат области и объединяют его с текущей областью связи (здесь).
То, как AR предпочитает динамически определяемые методы в классах, созданных во время выполнения, используя неэффективные вызовы method_missing для ActiveRecord:: Relation.
Я думаю, это нормально, если вы не понимаете все это. Просто вызовите методы уровня класса в ассоциациях:)
Ответ 3
В вашем случае вам нужно выбрать ОДНУ задачу, такую как Project.first.tasks.first.initial_tasks или
tasks = Project.tasks
tasks.each do |t|
t.initial_tasks
end
Надеюсь, что это поможет!
Ответ 4
Здесь, в ActiveRecord:: Relation, Relation представляет собой целую таблицу
и ваш класс Post - это карта с таблицей,
Итак, ActiveRecord:: Relation - это массив или одиночная запись, он может получить доступ к методу класса.