Как метод класса объекта 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 - это массив или одиночная запись, он может получить доступ к методу класса.