2010-01-08 5 views
1

Je suis nouveau sur Rails (et ruby). Quelle est la méthode standard d'itération à travers un tableau pour totaliser une variable.Ruby on rails array itération

par exemple. pour les dépenses totales en un mois, d'abord un tableau:

expenses_this_month = expenses.find :all, 
            :conditions => ['date >= ? and date <= ?', 
             Date.today.beginning_of_month, Date.today.end_of_month] 

je l'ai déjà de savoir de deux façons de le faire:

total = 0.0 
for expense in expenses_this_month 
    total += expense.cost 
end 
return total 

ou avec un bloc

total = 0.0 
expenses_this_month.each do |expense| 
    total += expense.cost 
end 
return total 

I » Je suis conscient que la dernière ligne d'une méthode ruby ​​sera retournée par défaut, donc il doit y avoir une meilleure façon de l'écrire?

Répondre

3

Vous êtes à la recherche de la méthode Enumerable#inject:

expenses_this_month.inject(0.0) {|total, expense| total + expense } 

Cette méthode (empruntée à Smalltalk) prend la valeur qui lui est passé (0,0 dans ce cas) et définit une variable interne à ce sujet. Il appelle ensuite le bloc avec la valeur de cette variable (comme total) et chaque élément successif (comme expense), et définit la variable à tout ce que le bloc retourne (dans ce cas la somme du total et de l'élément courant).

Vous pouvez transférer ce calcul à la base de données, bien que, comme le suggère kejadlen, en utilisant la méthode #sum.

2

Une fois que vous avez retourné les données, utilisez la méthode inject:

total = expenses_this_month.inject { |total, expense| total + expense.cost } 

Cependant, vous devez simplement réécrire votre requête:

total = expenses.sum(:cost, :conditions => ['date >= ? and date <= ?', 
            Date.today.beginning_of_month, Date.today.end_of_month]) 
+0

Merci pour la suggestion.Je n'utilise pas la somme, car le coût est une méthode sur le modèle (pas une colonne dans la base de données), car elle est calculée en fonction du taux d'imposition. Inject semble que cela fonctionnera pour moi – Ryan

1

Si vous utilisez Rails, vous pouvez utiliser la méthode de classe sum intégrée (en supposant que Expense est le nom de classe).

expenses_this_month = Expense.sum('cost', 
            :conditions => ['date >= ? and date <= ?', 
                Date.today.beginning_of_month, 
                Date.today.end_of_month]) 
4

La méthode inject fonctionnera très bien, comme l'a suggéré Doug. Cependant, il est généralement préférable de faire ce genre de choses dans la base de données quand vous le pouvez. ActiveRecord fournit une interface simple pour cela.

total = Expenses.sum :cost, :conditions => { 
    :date => (Date.today.beginning_of_month..Date.today.end_of_month) 
} 

Notez que vous pouvez également utiliser un objet Range au lieu d'interpolation SQL.

Si vous chargez tous les objets Dépenses pour une autre raison, la méthode d'injection est, bien sûr, correcte.

+0

+1 pour montrer une façon beaucoup plus propre de passer la plage de dates. Très bonne réponse! –

3
expenses_this_month.map(&:cost).sum 

(plus courte, bien qu'il crée un tableau en mémoire contrairement à réduire)

expenses_this_month.reduce(BigDecimal.new('0')) { |total, expense| total + expense.cost } 

vous devez vous rappeler de passer une valeur initiale à réduire (sinon elle retournera nul pour tableau vide) et utilisez BigDecimal au lieu de flottants réguliers lorsqu'il s'agit de l'argent.

+0

J'ai utilisé le type décimal dans la base de données pour les montants en argent, mais si je passe (0.0) à la méthode (réduire/injecter), est-ce que ça va flotter par défaut? En outre, quelles sont les différences spécifiques entre injecter et réduire? – Ryan

+0

réduire est un alias pour injecter, bien que je pense qu'il a été introduit dans ruby ​​1.8.7, donc pour la rétrocompatibilité, vous pouvez coller avec injecter. C'est ma préférence personnelle d'utiliser map/reduce au lieu de collection/injecter car cette nomenclature est utilisée dans une grande variété de langages de programmation. Si vous passez un flottant en tant que valeur initiale pour réduire les nombres décimaux/BigDecimal, vous allez effectuer une opération d'ajout de float + BigDecimal qui résulte en un flottement. – psyho