2009-10-07 2 views
19

Как работают методы объединения рельсов? Давайте рассмотрим этот примерКак работают методы объединения рельсов?

class User < ActiveRecord::Base 
    has_many :articles 
end 

class Article < ActiveRecord::Base 
    belongs_to :user 
end 

Теперь я могу сделать что-то вроде

@user = User.find(:first) 
@user.articles 

Это извлекает мне статьи, принадлежащие этому пользователю. Все идет нормально.

Теперь я могу пойти дальше и найти на этих статьях некоторые условия.

@user.articles.find(:all, :conditions => {:sector_id => 3}) 

Или просто объявить и метод объединения как

class User < ActiveRecord::Base 
    has_many :articles do 
    def of_sector(sector_id) 
     find(:all, :conditions => {:sector_id => sector_id}) 
    end 
    end 
end 

И сделать

@user.articles.of_sector(3) 

Теперь мой вопрос, как же это find работа на массиве ActiveRecord объектов принесенных с помощью ассоциации метод? Потому что, если мы реализуем наш собственный метод экземпляра User под названием articles и напишем нашу собственную реализацию, которая дает нам те же результаты, что и метод ассоциации, поиск в массиве выборки объектов ActiveRecord не будет работать.

Мое предположение заключается в том, что методы ассоциации присоединяют определенные свойства к массиву извлеченных объектов, что позволяет проводить дополнительные запросы с использованием методов find и других методов ActiveRecord. Какова последовательность выполнения кода в этом случае? Как я могу подтвердить это?

ответ

20

Как это на самом деле что объект ассоциации является «прокси-объектом». Конкретный класс - AssociationProxy. Если вы посмотрите на линии 52 этого файла, вы увидите:

instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ } 

Делая это, такие методы, как class больше не существует на этом объекте. Поэтому, если вы вызываете class на этот объект, вы получите метод. Таким образом, там method_missing реализован для прокси-объекта, который перенаправляет вызов метода к «цели»:

def method_missing(method, *args) 
    if load_target 
    unless @target.respond_to?(method) 
     message = "undefined method `#{method.to_s}' for \"#{@target}\":#{@target.class.to_s}" 
     raise NoMethodError, message 
    end 

    if block_given? 
     @target.send(method, *args) { |*block_args| yield(*block_args) } 
    else 
     @target.send(method, *args) 
    end 
    end 
end 

Мишень представляет собой массив, поэтому при вызове class на этом объекте, он говорит, что это массив, но это только потому, что целью является массив, фактический класс - это ассоциация, но вы больше этого не видите.

Так что все методы, которые вы добавляете, такие как of_sector, добавляются в прокси-сервер ассоциации, поэтому они вызываются напрямую. Такие методы, как [] и class, не определены в прокси-соединении, поэтому они отправляются на цель, которая представляет собой массив.

Чтобы помочь вам увидеть, как это происходит, добавьте это в строку 217 из этого файла в локальной копии association_proxy.rb:

Rails.logger.info "AssociationProxy forwarding call to `#{method.to_s}' method to \"#{@target}\":#{@target.class.to_s}" 

Если вы не знаете, где этот файл, команда gem which 'active_record/associations/association_proxy' расскажет вам. Теперь, когда вы вызываете class на AssociationProxy, вы увидите сообщение в журнале, сообщающее вам, что оно отправляет его цели, что должно сделать более ясным, что происходит. Это все для Rails 2.3.2 и может быть изменено в других версиях.

+0

Этот один '/ (^ __ |^nil \? $ |^Send $ | proxy_ |^object_id $) /' так смешно, что я больше не боюсь регулярных выражений. – jibiel

0

Когда вы делаете ассоциацию (has_one, has_many и т. Д.), Она сообщает модели автоматически включать некоторые методы ActiveRecord. Однако, когда вы решили создать метод экземпляра, возвращающий ассоциацию самостоятельно, вы не сможете использовать эти методы.

Последовательность что-то вроде этого

  1. установка articles в User модели, то есть сделать has_many :articles
  2. ActiveRecord автоматически включает в себя удобные методы в модели (например, size, empty?, find, all, first и т.д.)
  3. установка user в Article, т.е. сделать belongs_to :user
  4. ActiveRecord автоматически включает в себя удобные методы в модели (например,user= и т.д.)

Таким образом, становится ясно, что, когда вы объявляете ассоциации, методы добавляются автоматически ActiveRecord, что красота, как она обрабатывается огромное количество работы, которую нужно будет сделать вручную в противном случае =)

вы можете прочитать об этом здесь: http://guides.rubyonrails.org/association_basics.html#detailed-association-reference

надеюсь, что это помогает =)

+0

Это я знал. Но мой вопрос заключается в том, что если я вообще буду реализовывать то, что AR делает для меня, как бы добавить эти размеры, пустые?, Найти, все, первые и т. Д. Методы в массив возвращаемых объектов. Кроме того, мне нужна последовательность, в которой будет выполняться последовательность @ user.articles.find (: all,: conditions => {: sector_id => 3}). Используются ли условия для извлечения объектов ассоциации? Или все объекты ассоциаций выбираются первым и повторно запрашиваются, применяя условия для ограниченного набора объектов? – Chirantan

+0

Основная модель, в вашем случае, пользователь, сначала загружается (даже в случае с usign: include). и последующие запросы с использованием user.articles.find выдается после этого, добавив user_id (поскольку ar уже знает, какой user_id использовать). Вы можете попробовать, глядя на лог-файл, выполнив: User.find (1) user.articles.find (1) и проверить, что выдается SQL запросов =) я не понимаю, почему вы бы хотели реализовать свой собственный, хотя ... если вы не пытаетесь сделать то же самое с другими фреймворками, и просто хотите знать, как это происходит ... – Staelen

+0

Я просто хочу это знать. :) – Chirantan

9

Как уже упоминалось выше, активные записи ассоциации создать метрику buttload методов удобства. Конечно, вы могли бы написать свои собственные методы, чтобы получить все. Но это не путь Rails.

The Rails Way является кульминацией двух девизов. DRY (не повторяйте себя) и «Конвенция по конфигурации». По сути, обозначая вещи таким образом, что имеет смысл, некоторые надежные методы, предоставляемые структурой, могут абстрагировать весь общий код. Код, который вы размещаете в своем вопросе, является прекрасным примером того, что можно заменить одним вызовом метода.

Где эти удобные методы действительно блестят, это более сложные ситуации. То, что связано с моделями присоединения, условиями, валидациями и т. Д.

Чтобы ответить на ваш вопрос, когда вы делаете что-то вроде @user.articles.find(:all, :conditions => ["created_at > ? ", tuesday]), Rails готовит два SQL-запроса, а затем объединяет их в один. где, когда ваша версия просто возвращает список объектов. Именованные области делают то же самое, но обычно не пересекают границы модели.

Вы можете проверить его, проверив SQL-запросы в development.log, как вы это называете в консоли.

Так что давайте поговорим о Named Scopes на мгновение, потому что они дают отличный пример того, как рельсы обрабатывают SQL, и я думаю, что это более простой способ продемонстрировать, что происходит за кулисами, поскольку они не нуждаются в каких-либо модельных ассоциаций.

Именованные области могут использоваться для выполнения пользовательских запросов модели. Их можно связать вместе или даже позвонить через ассоциации. Вы можете легко создавать пользовательские поисковые устройства, которые возвращают идентичные списки, но затем вы сталкиваетесь с теми же проблемами, что и в Вопросе.

class Article < ActiveRecord::Base 
    belongs_to :user 
    has_many :comments 
    has_many :commentators, :through :comments, :class_name => "user" 
    named_scope :edited_scope, :conditions => {:edited => true} 
    named_scope :recent_scope, lambda do 
    { :conditions => ["updated_at > ? ", DateTime.now - 7.days]} 

    def self.edited_method 
    self.find(:all, :conditions => {:edited => true}) 
    end 

    def self.recent_method 
    self.find(:all, :conditions => ["updated_at > ?", DateTime.now - 7 days]) 
    end 
end 

Article.edited_scope 
=>  # Array of articles that have been flagged as edited. 1 SQL query. 
Article.edited_method 
=>  # Array of Articles that have been flagged as edited. 1 SQL query. 
Array.edited_scope == Array.edited_method 
=> true  # return identical lists. 

Article.recent_scope 
=>  # Array of articles that have been updated in the past 7 days. 
    1 SQL query. 
Article.recent_method 
=>  # Array of Articles that have been updated in the past 7 days. 
    1 SQL query. 
Array.recent_scope == Array.recent_method 
=> true  # return identical lists. 

Вот где все меняется:

Article.edited_scope.recent_scope 
=>  # Array of articles that have both been edited and updated 
    in the past 7 days. 1 SQL query. 
Article.edited_method.recent_method 
=> # no method error recent_scope on Array 

# Can't even mix and match. 
Article.edited_scope.recent_method 
=>  # no method error 
Article.recent_method.edited_scope 
=>  # no method error 

# works even across associations. 
@user.articles.edited.comments 
=>  # Array of comments belonging to Articles that are flagged as 
    edited and belong to @user. 1 SQL query. 

По существу каждый Именованная создает фрагмент SQL. Rails будет умело сливаться со всеми остальными фрагментами SQL в цепочке, чтобы создать один запрос, сохраняющий именно то, что вы хотите. Методы, добавленные методами ассоциации, работают одинаково. Именно поэтому они легко интегрируются с named_scopes.

Причина смешивания & Совпадение не работает, это то же самое, что метод of_sector, определенный в вопросе, не работает. edit_methods возвращает массив, где, как отредактированный_scope (а также поиск и все другие методы удобства AR, называемые частью цепочки), передают свой SQL-фрагмент дальше к следующей вещи в цепочке. Если он последний в цепочке, он выполняет запрос. Точно так же это не сработает.

@edited = Article.edited_scope 
@edited.recent_scope 

Вы пытались использовать этот код.Вот правильный способ сделать это:

class User < ActiveRecord::Base 
    has_many :articles do 
    def of_sector(sector_id) 
     find(:all, :conditions => {:sector_id => sector_id}) 
    end 
    end 
end 

Для достижения этой функции вы хотите сделать это:

class Articles < ActiveRecord::Base 
    belongs_to :user 
    named_scope :of_sector, lambda do |*sectors| 
    { :conditions => {:sector_id => sectors} } 
    end 
end 

class User < ActiveRecord::Base 
    has_many :articles 
end 

Затем вы можете сделать что-то вроде этого:

@user.articles.of_sector(4) 
=> # articles belonging to @user and sector of 4 
@user.articles.of_sector(5,6) 
=> # articles belonging to @user and either sector 4 or 5 
@user.articles.of_sector([1,2,3,]) 
=> # articles belonging to @user and either sector 1,2, or 3 
+0

Я не думаю, что это на самом деле отвечает на вопрос. Это объясняет, как использовать ассоциации и имена областей, но не объясняет, как они на самом деле работают. См. Мой ответ. – pjb3

+0

Рельсы на самом деле не знают, что такое «последняя [область] в цепочке». Фактически, ни один из названных областей фактически не выполняет любые вызовы SQL. Это происходит только тогда, когда вы * делаете * что-то с областью (например. If, если iterating или .to_s как 'puts' неявно делает), что запрошенная база данных. – Gareth

1

Как metioned ранее, при выполнении

@user.articles.class 
=> Array 

, что вы на самом деле получаете массив. Это связано с тем, что метод #class не определен, как упоминалось ранее.

Но как вы получаете фактический класс @ user.articles (который должен быть прокси)?

Object.instance_method(:class).bind(@user.articles).call 
=> ActiveRecord::Associations::CollectionProxy 

И почему вы получили Array в первую очередь? Поскольку метод #class был делегирован экземпляру CollectionProxy @target с помощью метода missin, который на самом деле является массивом. Вы можете заглянуть за сцену, сделав что-то вроде этого:

@user.articles.proxy_association 
Смежные вопросы