6

Tengo los siguientes modelos. Los usuarios tienen UserActions, y una posible UserAction puede ser una ContactAction (UserAction es un polimorfismo). Hay otras acciones como LoginAction etc. AsíRuby on Rails 3: combina los resultados de múltiples asociaciones de has_many o has_many_through

 
class User < AR::Base 
    has_many :contact_requests, :class_name => "ContactAction" 
    has_many :user_actions 
    has_many_polymorphs :user_actionables, :from => [:contact_actions, ...], :through => :user_actions 
end 

class UserAction < AR::Base 
belongs_to :user 
belongs_to :user_actionable, :polymorphic => true 
end 

class ContactAction < AR::Base 
belongs_to :user 
named_scope :pending, ... 
named_scope :active, ... 
end 

La idea es que un ContactAction se une a dos usuarios (con otras consecuencias dentro de la aplicación) y siempre tiene una recepción y un extremo emisor. Al mismo tiempo, una ContactAction puede tener diferentes estados, p. caducado, pendiente, etc.

Puedo decir @user.contact_actions.pending o @user.contact_requests.expired para enumerar todas las solicitudes pendientes/caducadas que un usuario ha enviado o recibido. Esto funciona bien

Lo que ahora me gustaría es una forma de join ambos tipos de ContactAction. Es decir. @user.contact_actions_or_requests. Probé el siguiente:

 
class User 

def contact_actions_or_requests 
    self.contact_actions + self.contact_requests 
end 

# or 
has_many :contact_actions_or_requests, :finder_sql => ..., :counter_sql => ... 

end 

pero todos ellos tienen el problema de que no es posible utilizar buscadores o named_scopes adicionales en la parte superior de la asociación, por ejemplo, @user.contact_actions_or_requests.find(...) o @user.contact_actions_or_requests.expired.

Básicamente, necesito una forma de expresar una asociación 1: n que tiene dos caminos diferentes. Uno es User -> ContactAction.user_id, el otro es User -> UserAction.user_id -> UserAction.user_actionable_id -> ContactAction.id. Y luego unir los resultados (ContactActions) en una sola lista para su posterior procesamiento con named_scopes y/o buscadores.

Como necesito esta asociación en literalmente docenas de lugares, sería una gran molestia escribir (y mantener) SQL personalizados para cada caso.

Preferiría resolver esto en Rails, pero también estoy abierto a otras sugerencias (por ejemplo, un procedimiento de PostgreSQL 8.3 o algo similar). Lo importante es que, al final, puedo usar las funciones de conveniencia de Rails como cualquier otra asociación y, lo que es más importante, también anidarlas.

Cualquier idea sería muy apreciada.

¡Gracias!


Para proporcionar una especie de respuesta a mi propia pregunta:

probablemente voy a resolver este usando una vista de base de datos y añadir asociaciones apropiadas según sea necesario. Por lo anterior, puedo

  • utilizar el SQL en finder_sql para crear la vista,
  • el nombre de "contact_actions_or_requests",
  • modificar la cláusula SELECT para agregar una columna user_id,
  • agregar una aplicación /models/ContactActionsOrRequests.rb,
  • y luego agregue "has_many: contact_actions_or_requests" a user.rb.

No sé cómo voy a manejar la actualización de los registros aún - esto parece que no es posible con una vista - pero tal vez este es un primer comienzo.

+0

Actualmente me tener un caos de llamadas find_by_sql esparcidas por la aplicación. Todavía tengo que actualizar esto para usar ARel y especialmente el método #arel_table (vea el comentario a continuación). Cuando haga esto, publicaré mis resultados aquí. – Jens

+0

¿Cómo te enfrentaste a esto al final? –

+0

Todavía uso finder_sql y counter_sql, pero extraje las cadenas de SQL en un método de clase que luego incluyo a través de un proceso. Esto es mucho más limpio que antes, aunque tengo que volver a pensar de nuevo ahora que nos estamos moviendo a Rails 4 y finder_sql está en desuso. – Jens

Respuesta

2

El método que está buscando es fusionar. Si tiene dos ActiveRecord :: Relations, r1 y r2, puede llamar a r1.merge (r2) para obtener un nuevo objeto ActiveRecord :: Relation que combine los dos.

Si esto funciona para usted, depende en gran medida de cómo se configuran sus ámbitos y si puede cambiarlos para producir un resultado significativo. Veamos algunos ejemplos:

Supongamos que tiene un modelo de página. Tiene created_at normal y atributos updated_at, por lo que podría tener ámbitos como: : actualizado -> {donde ('! Created_at = updated_at')} : not_updated -> {donde ('created_at = updated_at')}

Si tirar de esto fuera de la base de datos que obtendrá:

r1 = Page.updated # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at) 
r2 = Page.not_updated # SELECT `pages`.* FROM `pages` WHERE (created_at = updated_at) 
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE (created_at != updated_at) AND (created_at = updated_at) 
=> [] 

por lo tanto, se combinaron las dos relaciones, pero no de una manera significativa. Otro:

r1 = Page.where(:name => "Test1") # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test1' 
r2 = Page.where(:name => "Test2") # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2' 
r1.merge(r2) # SELECT `pages`.* FROM `pages` WHERE `pages`.`name` = 'Test2' 

Por lo tanto, podría funcionar para usted, pero tal vez no, dependiendo de su situación.

Otro, y recomendó, forma de hacer esto es crear un nuevo ámbito en el que el modelo:

class ContactAction < AR::Base 
    belongs_to :user 
    scope :pending, ... 
    scope :active, ... 
    scope :actions_and_requests, pending.active # Combine existing logic 
    scope :actions_and_requests, -> { ... } # Or, write a new scope with custom logic 
end 

que combina los diferentes rasgos que desea recopilar en una consulta ...

+0

Se formuló la pregunta original cuando todavía estábamos usando Rails 2.3 que no tenía ARel y, por lo tanto, no permitía encadenar llamadas find_by_sql. Ahora, con ARel, en realidad es posible (usando #arel_table) encadenar consultas usando "O" (que es necesario en este caso). Su solución todavía utiliza el encadenamiento "Y", que no es lo que necesito ... – Jens

+0

Puede que entienda mi confusión sobre "seguir usando Rails 2.3" ya que la pregunta está etiquetada con Rails tres. La opción de proporcionar un ámbito separado que combine manualmente las consultas sigue siendo válida, a menos que haya restricciones que aún deba especificar. –

+0

Sí. Lo siento por eso. Estaba buscando una motivación para realizar la actualización. :) Con respecto a su sugerencia, ¿'pending.active' no selecciona todas las entradas que están pendientes Y activas (en lugar de" o ")? Los alcances de encadenamiento, en mi experiencia, usan AND lógico, que no funciona aquí. Usar arel_table (en mi caso) también es difícil ya que necesito usar las funciones de Postgres en la consulta que (afaik) no se puede expresar en ARel. Pero seguiré intentándolo y pondré mi solución aquí una vez que la haya encontrado. – Jens

Cuestiones relacionadas