2010-01-14 6 views
1

Dans mon application, j'essaie de n'afficher que des éléments uniques d'un tableau d'objets d'enregistrement actif (loss_reports) basés sur deux attributs d'un rapport de perte.Quelle est la meilleure façon d'obtenir des éléments uniques d'un tableau d'objets activerecord en fonction des attributs de l'objet?

Schéma

class Agent < ActiveRecord::Base 
    has_many :loss_reports, :through => policy 
end 

class LossReport < ActiveRecord::Base 
    belongs_to :agent 
end 

J'ai d'abord essayé de remplacer le eql? et la méthode de hachage d'un LossReport pour que je puisse faire quelque chose de similaire à:

Option 1:

class LossReport ... 
    def eql? other 
    self.policy_id == other.policy_id && loss_occurred_on.hash == self.loss_occurred_on 
    end 

    def hash 
    policy_id + loss_occurred_on.hash 
    end 
end 

class Agent ... 
    def unique_losses 
    loss_reports.to_set 
    end 
end 

mais rapidement supprimé le code en raison de ActiveRecord remplaçant déjà les méthodes et mon ne pas être sûr de la répercussions.

Option 2:

class Agent ... 
    def unique_losses 
    loss_reports.sort{|l1,l2| l2.created_at <=> l1.created_at}.group_by{|l| (l.policy_id + l.loss_occurred_on.hash)}.collect{|hl| hl[-1].first} 
    end 
end 

Option 3:

class Agent 
    def unique_losses 
    hsh_array = [] 
    unique = [] 
    loss_reports.sort{|l1,l2| l2.created_at <=> l1.created_at}.each do |l| 
     unique << l unless hsh_array.include?(l.hsh) 
     hsh_array << l.hsh 
    end 
    unique   
    end 
end 

résultats Benchmark:

Benchmark.bmbm do |bm| 
    bm.report("option 2") do 
    losses.sort{|l1,l2| l2.created_at <=> l1.created_at}.group_by{|l| (l.policy_id + l.loss_occurred_on.hash)}.collect{|hl| hl[-1].first} 
    end 
    bm.report("option 3") do 
    hsh_array,unique = [],[] 
    losses.sort{|l1,l2| l2.created_at <=> l1.created_at}.each do |l| 
     unique << l unless hsh_array.include?(l.policy_id+l.loss_occurred_on.hash) 
     hsh_array << l.policy_id + l.loss_occurred_on.hash 
    end 
    end 
end 
Rehearsal -------------------------------------------- 
option 2 0.400000 0.000000 0.400000 ( 0.407615) 
option 3 0.250000 0.000000 0.250000 ( 0.254399) 
----------------------------------- total: 0.650000sec 

       user  system  total  real 
option 2 0.400000 0.000000 0.400000 ( 0.403535) 
option 3 0.250000 0.000000 0.250000 ( 0.262578) 

Ni chemin fe els droite mais les deux fonctionnent. Quelle est la meilleure option ou existe-t-il un moyen encore meilleur?

+0

ne sais pas pourquoi mais agent.loss_reports (: order => 'created_at desc') n'a pas ordonné les résultats – russellkt

+0

agent.loss_reports (: order => 'created_at desc') n'a pas ordonné les résultats car agent.loss_reports ne l'a pas prendre les mêmes arguments qu'ActiveRecord :: Base.find prend. (Cela prend juste un seul argument booléen: reload (re-run DB query) si vrai.) Utilisez agent.loss_reports.find (: all,: order => 'created_at desc') pour le faire. –

Répondre

1

Je ne sais pas sur les points de référence, mais il semble que inject serait la façon la plus simple:

loss_reports.inject([]) do |arr, report| 
    arr << report unless arr.detect{|r| ... } 
end 

Ou peut-être encore mieux serait de définir un named_scope avec un groupe SQL personnalisé par ...

+0

Grande suggestion - trouvé une question similaire à http://stackoverflow.com/questions/2061389 avec un exemple d'utilisation de named_scope. – russellkt

Questions connexes