Search code examples
ruby-on-railsserviceconsoledevelopment-environmentcloud9-ide

inconsistent results from rails console v. dev UX


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.


Solution

  • 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!