through references rails new has_one has_many has_and_belongs_to_many example create active ruby-on-rails activerecord model-associations

ruby-on-rails - new - rails model references



¿Cómo funcionan los métodos de asociación de rieles? (4)

Cómo funciona realmente es que el objeto de asociación es un "objeto proxy". La clase específica es AssociationProxy . Si miras la línea 52 de ese archivo, verás:

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

Al hacer esto, métodos como class ya no existen en este objeto. Entonces, si llamas a la class sobre este objeto, perderás el método. Por lo tanto, hay un method_missing implementado para el objeto proxy que reenvía la llamada al método al "objetivo":

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

El objetivo es una matriz, por lo que cuando llamas a la class sobre este objeto, dice que es una matriz, pero eso es solo porque el objetivo es una matriz, la clase real es una AssociationProxy, pero ya no puedes ver eso.

Por lo tanto, todos los métodos que agregue, como of_sector , se agregarán al proxy de la asociación para que se les llame directamente. Los métodos como [] y la class no están definidos en el proxy de asociación, por lo que se envían al destino, que es una matriz.

Para ayudarlo a ver cómo sucede esto, agregue esto a la línea 217 de ese archivo en su copia local de association_proxy.rb:

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

Si no sabe dónde está ese archivo, el comando gem which ''active_record/associations/association_proxy'' . Ahora cuando llame a class en un AssociationProxy, verá un mensaje de registro que le indicará que lo está enviando al destino, lo que debería aclarar lo que está sucediendo. Esto es todo para Rails 2.3.2 y podría cambiar en otras versiones.

¿Cómo funcionan los métodos de asociación de rieles? Consideremos este ejemplo

class User < ActiveRecord::Base has_many :articles end class Article < ActiveRecord::Base belongs_to :user end

Ahora puedo hacer algo como

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

Esto me trae artículos que pertenecen a ese usuario. Hasta aquí todo bien.

Ahora puedo continuar y hacer un descubrimiento sobre estos artículos con algunas condiciones.

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

O simplemente declarar y asociar método como

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

Y hacer

@user.articles.of_sector(3)

Ahora mi pregunta es, ¿cómo funciona esto para find la matriz de objetos ActiveRecord recuperados utilizando el método de asociación? Porque si implementamos nuestro propio método de instancia de User llamado articles y escribimos nuestra propia implementación que nos da exactamente los mismos resultados que el método de asociación, la búsqueda en la matriz de búsqueda de objetos ActiveRecord no funcionará.

Mi suposición es que los métodos de asociación adjuntan ciertas propiedades a la matriz de objetos recuperados que permite realizar consultas adicionales utilizando find y otros métodos ActiveRecord . ¿Cuál es la secuencia de ejecución del código en este caso? ¿Cómo podría validar esto?


Como se mencionó anteriormente, al hacer

@user.articles.class => Array

lo que en realidad obtienes es Array. Eso es porque el método #clase no estaba definido, como también se mencionó anteriormente.

¿Pero cómo se obtiene la clase real de @ user.articles (que debe ser proxy)?

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

¿Y por qué obtuviste a Array en primer lugar? Debido a que el método #clase se delegó en la instancia CollectionProxy @target a través del método missin, que en realidad es una matriz. Podrías mirar detrás de la escena haciendo algo como esto:

@user.articles.proxy_association


Como ya se mencionó, las asociaciones de registros activos crean una carga métrica de métodos de conveniencia. Claro, podrías escribir tus propios métodos para buscar todo. Pero esa no es la forma de Rails.

The Rails Way es la culminación de dos lemas. DRY (No repetir) y "Convención sobre configuración". Esencialmente al nombrar las cosas de una manera que tenga sentido, algunos métodos robustos proporcionados por el marco pueden abstraer todo el código común. El código que colocas en tu pregunta es el ejemplo perfecto de algo que puede ser reemplazado por una sola llamada a un método.

Donde realmente brillan estos métodos de conveniencia son las situaciones más complejas. El tipo de cosa que implica unir modelos, condiciones, validaciones, etc.

Para responder a su pregunta cuando hace algo como @user.articles.find(:all, :conditions => ["created_at > ? ", tuesday]) , Rails prepara dos consultas SQL y luego las fusiona en una. donde como su versión simplemente devuelve la lista de objetos. Los ámbitos con nombre hacen lo mismo, pero generalmente no cruzan los límites del modelo.

Puede validar comprobando las consultas SQL en el archivo de desarrollo .log como lo llama en la consola.

Hablemos de Named Scopes por un momento, ya que dan un excelente ejemplo de cómo los rails manejan el SQL, y creo que son una manera más simple de demostrar lo que sucede detrás de las escenas, ya que no necesitan asociaciones de modelos para presumir.

Los ámbitos con nombre se pueden usar para realizar búsquedas personalizadas de un modelo. Pueden ser encadenados juntos o incluso llamados a través de asociaciones. Puede crear fácilmente buscadores personalizados que devuelven listas idénticas, pero luego se encuentra con los mismos problemas mencionados en la Pregunta.

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.

Aquí es donde las cosas cambian:

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.

Esencialmente, cada ámbito nombrado crea un fragmento de SQL. Rails se fusionará hábilmente con todos los demás fragmentos de SQL de la cadena para generar una única consulta que obtenga exactamente lo que desea. Los métodos agregados por los métodos de asociación funcionan de la misma manera. Por eso se integran perfectamente con named_scopes.

La razón por la que mix & match no funcionó es la misma que el método of_sector definido en la pregunta no funciona. edited_methods devuelve una matriz, donde edited_scope (así como también los métodos de búsqueda y todos los otros métodos de conveniencia de AR llamados como parte de una cadena) pasan su fragmento de SQL hacia adelante al siguiente elemento de la cadena. Si es el último en la cadena, ejecuta la consulta. Del mismo modo, esto tampoco funcionará.

@edited = Article.edited_scope @edited.recent_scope

Intentó usar este código. Esta es la forma correcta de hacerlo:

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

Para lograr esta funcionalidad, desea hacer esto:

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

Entonces puedes hacer cosas como esta:

@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


Cuando hace una asociación ( has_one , has_many , etc.), le dice al modelo que incluya algunos métodos automáticamente por ActiveRecord. Sin embargo, cuando decidió crear un método de instancia que devuelva la asociación usted mismo, no podrá hacer uso de esos métodos.

La secuencia es algo como esto

  1. configurar articles en el modelo de User , es decir, hacer un has_many :articles
  2. ActiveRecord incluye automáticamente métodos convenientes en el modelo (por ejemplo, size , empty? , find , all , first , etc.)
  3. configurar el user en el Article , es decir, hacer un belongs_to :user
  4. ActiveRecord incluye automáticamente métodos convenientes en el modelo (por ejemplo, user= , etc.)

Por lo tanto, está claro que cuando declaras una asociación, ActiveRecord agrega los métodos automáticamente, que es la belleza, ya que maneja una gran cantidad de trabajo, que tendrá que hacerse manualmente de lo contrario =)

Puedes leer más sobre esto aquí: http://guides.rubyonrails.org/association_basics.html#detailed-association-reference

Espero que esto ayude =)