Upon creation of a List of Quantities, the List attribute total_calories
should be updated with the sum of all calories
associated with the List. Lists and Foods are associated has_many :through
Quantities, and each Food has a calories
attribute.
When I create a List in the rails development user interface hosted by c9.io, it behaves as expected. When I create a List in the console, it does not; total_calories
does not get updated. How could this be?
I would appreciate any tips about what to investigate, as I have never encountered this issue of inconsistency between the console and development user interface.
The service object count_calories.rb
:
class CountCalories
def initialize(list)
@list=list
end
def count
@calories = 0
@list.quantities.each do |q|
@calories += Food.find(q.food_id).calories
end
@list.update!(total_calories: @calories)
@list.save!
end
end
The create method in the List controller:
def create
@list = WriteList.new(list_params).write
if @list.save
flash[:success] = "A list has been created!"
CountCalories.new(@list).count
redirect_to @list
else
render 'new'
end
end
The service object write_list.rb
, in case it is helpful:
class WriteList
def initialize(params)
@params=params
end
def write
ActiveRecord::Base.transaction do
list = List.new(days: @params[:days], name: @params[:name])
list.save!
3.times do
food1 = Food.all.sample.id
Quantity.create!(food_id: food1, list_id: list[:id], amount: 1+rand(6))
end
list.save!
return list
end
rescue
return List.new(days: @params[:days], name: @params[:name])
end
end
The only thing I can think of is that maybe it has something to do with the migration to create total_calories:
class AddTotalCaloriesToLists < ActiveRecord::Migration
def change
add_column :lists, :total_calories, :integer, :default => 0
end
end
But mostly, I am baffled and would appreciate any tips.
I believe you have over engineered your problem. It appears you are coming from a Java web stack and it's done differently in the rails way. Avoid service objects if you don't need them.
class Food < ActiveRecord::Base
validates_presence_of :calories
has_many :lists, through: :food_lists, :dependent: :destroy
end
class FoodList < ActiveRecord::Base
belongs_to :food
belongs_to :list
validates_presence_of: :amount, :food, :list
end
class List < ActiveRecord::Base
has_many :food_lists
has_many :foods, through: :food_lists, dependent: :destroy
def total_calories
self.food_lists.map{|fl| fl.amount * fl.food.calories}.sum
end
end
Things that can be calculated on the fly, should be calculated, only store them as a form of optimization if you really really need to. It makes your business domain much easier to debug and support. Maintaining state is a last resort!