Rails, суммирует совокупность атрибутов всех экземпляров модели

#ruby-on-rails

#ruby-on-rails

Вопрос:

У меня есть канал модели. Соответствующая таблица содержит несколько столбцов, например, клики.

Так Channel.all.sum(:clicks) что дает мне сумму кликов по всем каналам.

В моей модели я добавил новый метод

 def test
 123 #this is just an example
end
  

Итак, теперь Channel.first.test возвращает 123

То, что я хочу сделать, это что-то вроде Channel.all.sum(:test) , которое суммирует тестовое значение всех каналов.

Ошибка, которую я получаю, заключается в том, что test не является столбцом, что, конечно, не так, но я надеялся, что смогу собрать эту сумму.

Как я мог этого добиться?

Комментарии:

1. можем ли мы увидеть реализацию метода тестирования? Возможно, это можно было бы сделать на уровне БД и сэкономить много производительности

Ответ №1:

Вы могли бы попробовать:

 Channel.all.map(amp;:test).sum
  

Где clicks — это столбец таблицы модели, используйте:

 Channel.sum(:clicks)
  

Комментарии:

1. Потрясающе, что это уже сработало. Я отредактировал свой вопрос, потому что на самом деле он даже немного сложнее.

Ответ №2:

Чтобы решить вашу проблему, вы можете сделать

 Channel.all.sum(amp;:test)
  

Но было бы лучше попытаться достичь этого на уровне базы данных, потому что обработка с помощью Ruby может быть тяжелой для памяти и эффективности.

Редактировать

Если вы хотите суммировать с помощью метода, который принимает аргументы:

 Channel.all.sum { |channel| channel.test(start_date, end_date) }
  

Комментарии:

1. Я хотел бы, но значение, которое возвращает test, является вычислением с некоторыми другими факторами и более простым значением.

Ответ №3:

То, о чем вы здесь говорите, — это две очень разные вещи:

ActiveRecord::Calculations.sum суммирует значения столбца в базе данных:

 SELECT SUM("table_name"."column_name") FROM "column_name"
  

Это то, что происходит, если вы вызываете Channel.sum(:column_name) .

ActiveSupport также расширяет перечислимый модуль с .sum помощью метода:

 module Enumerable
  def sum(identity = nil, amp;block)
    if block_given?
      map(amp;block).sum(identity)
    else
      sum = identity ? inject(identity, : ) : inject(: )
      sum || identity || 0
    end
  end 
end
  

Это перебирает все значения в памяти и складывает их вместе.

 Channel.all.sum(amp;:test)
  

Эквивалентно:

 Channel.all.inject(0) { |sum, c| sum   c.test }
  

Использование более позднего может привести к серьезным проблемам с производительностью, поскольку оно извлекает все данные из базы данных.

Ответ №4:

В качестве альтернативы вы делаете это.

 Channel.all.inject(0) {|sum,x| sum   x.test }
  

Вы можете изменить 0 на любое значение, с которого вы хотите, чтобы сумма начиналась.