2009-12-23 1 views
0

J'écris une application Rails dans laquelle j'ai deux modèles: un modèle Machine et un modèle MachineUpdate. Le modèle Machine a plusieurs MachineUpdates. MachineUpdate possède un champ date/heure. Je suis en train de récupérer tous les enregistrements de machines qui ont les critères suivants:Dans Rails, comment puis-je renvoyer un ensemble d'enregistrements basé sur un nombre d'éléments dans une relation OU des critères sur la relation?

  1. Le modèle de la machine n'a pas eu un MachineUpdate au cours des 2 dernières semaines, OU
  2. Le modèle de la machine n'a jamais eu toute mises à jour.

Actuellement, je suis l'accomplissement # 1 avec une portée du nom:

named_scope :needs_updates, 
      :include => :machine_updates, 
      :conditions => ['machine_updates.date < ?', UPDATE_THRESHOLD.days.ago] 

Cependant, cela ne se modèles de machines qui ont eu au moins une mise à jour. Je voulais également récupérer les modèles de machine qui ont eu pas de mises à jour. Comment puis-je modifier needs_updates pour que les articles retournés répondent également à ces critères?

Répondre

3

Une solution consiste à introduire un counter_cache:

# add a machine_updates_count integer database column (with default 0) 
# and add this to your Machine model: 
counter_cache :machine_updates_count 

puis ajouter OR machine_updates_count = 0 à vos conditions SQL.

Cependant, vous pouvez également résoudre le problème sans cache du compteur en utilisant un LEFT JOIN:

named_scope :needs_updates, 
    :select => "machines.*, MAX(machine_updates.date) as last_update", 
    :joins => "LEFT JOIN machine_updates ON machine_updates.machine_id = machines.id", 
    :group => "machines.id", 
    :having => ["last_update IS NULL OR last_update < ?", lambda{ UPDATE_THRESHOLD.seconds.ago }] 

La jointure gauche est nécessaire pour que vous êtes sûr que vous êtes à la recherche la plus récente MachineUpdate (celui avec date MAX).

Notez également que vous devez placer votre condition dans un lambda afin qu'il soit évalué chaque fois que la requête est exécutée. Sinon, il sera évalué seulement une fois (lorsque votre modèle est chargé au démarrage de l'application), et vous ne pourrez pas trouver les machines qui ont besoin de mises à jour depuis le démarrage de votre application.

MISE À JOUR:

Cette solution fonctionne dans MySQL et SQLite, mais pas PostgreSQL. Postgres n'autorise pas l'attribution de noms aux colonnes de la clause SELECT qui ne sont pas utilisées dans la clause GROUP BY (voir discussion). Je suis très pas familier avec PostgreSQL, mais je l'ai fait que cela fonctionne comme prévu:

named_scope :needs_updates, lambda{ 
    cols = Machine.column_names.collect{ |c| "\"machines\".\"#{c}\"" }.join(",") 
    { 
    :select => cols, 
    :group => cols, 
    :joins => 'LEFT JOIN "machine_updates" ON "machine_updates"."machine_id" = "machines"."id"', 
    :having => ['MAX("machine_updates"."date") IS NULL OR MAX("machine_updates"."date") < ?', UPDATE_THRESHOLD.days.ago] 
    } 
} 
+0

Quand je d'abord mis en œuvre thi s change, j'ai l'erreur de base de données suivante: 'column" last_update "n'existe pas". J'ai sorti le truc 'last_update' et l'ai remplacé par la ligne' MAX (...) '. J'ai alors eu l'erreur de base de données suivante: 'syntaxe d'entrée invalide pour type timestamp:" ---! Ruby/object: Proc {} '. Par curiosité, j'ai enlevé le lambda, qui a résulté en une autre erreur de base de données: .machine_name "doit apparaître dans la clause GROUP BY ou être utilisé dans une fonction d'agrégat." – mipadi

+0

En outre, en remarque, le lambda ne semble pas nécessaire: en fonction des journaux, l'horodatage envoyé par l'instruction SQL est – mipadi

+0

Je suis sûr que vous avez besoin du lambda, sans cela, les choses fonctionneront dans l'environnement de développement, mais pas dans la production.Quant aux erreurs: (1) quelle version de Rails êtes-vous? en utilisant? (2) quel type de base de données utilisez-vous? (3) avez-vous ajouté des options à la portée nommée? J'ai créé une petite application simulacre et ce code a travaillé sur Rails 2.3.5 avec SQLite3 –

0

Si vous pouvez apporter des modifications dans le tableau, vous pouvez utiliser la: méthode de contact de l'association belongs_to.

Par exemple, ajoutez une colonne datetime à la machine nommée last_machine_update. Par la suite dans le belongs_to de MachineUpdate, ajoutez :touch => :last_machine_update. Cela entraînera la mise à jour de ce champ avec la dernière fois que vous avez ajouté ou modifié un MachineUpdate connecté à cet ordinateur, supprimant ainsi la nécessité de l'étendue nommée.

Sinon, je ferais probablement comme Alex propose.

0

Je viens de rencontrer un problème similaire.Il est en fait assez simple:

Machine.all(
    :include => :machine_updates, 
    :conditions => "machine_updates.machine_id IS NULL OR machine_update.date < ?", UPDATE_THRESHOLD.days.ago]) 

Si vous étiez en train de faire un champ nommé, il suffit d'utiliser lambdas pour faire en sorte que la date est recalculé chaque fois que le champ nommé est appelé

named_scope :needs_updates, lambda { { 
    :include => :machine_updates, 
    :conditions => "machine_updates.machine_id IS NULL OR machine_update.date < ?", UPDATE_THRESHOLD.days.ago] 
} } 

Si vous voulez éviter de retourner tous les MachineUpdate enregistrements dans votre requête, vous devez utiliser l'option :select pour retourner uniquement les colonnes que vous voulez

named_scope :needs_updates, lambda { { 
    :select => "machines.*", 
    :conditions => "machine_updates.machine_id IS NULL OR machine_update.date < ?", UPDATE_THRESHOLD.days.ago] 
} } 
Questions connexes