sam
sam

Reputation: 251

Save calculated price in database - Rails best practise

I have a form that takes in the price and a discount code. When saved, it then calculates the discount price. This all works just fine. However, I don't know which is the Rails way to save the discount price in the database. Do I do it in the model or in the controller?

I currently have this in the model, but I don't know if this is the correct way to do it:

class Product < ApplicationRecord
  before_save :set_discount_price

  def set_discount_price
    discount_price_calculator = DiscountPriceCalculator.new(price, discount)
    self.discount_price = discount_price_calculator.calculate_discount_price
  end
end

Is this the correct way? And if no, then how else am I supposed to do it?

Upvotes: 0

Views: 232

Answers (1)

max
max

Reputation: 102240

Is this the correct way?

No, there are some pretty big problems with that approach.

If you actually want to keep proper records what you want is to setup a separate join model where you record the original price of the product at the time of sale, the discount applied, and the quantity (and often more).

class Product < ApplicationRecord
  has_many :line_items
  has_many :orders, through: :line_items
  has_many :discounts
end

# rails g model line_item quantity:integer unit_price:decimal discount:references gross_price:decimal  
class LineITem < ApplicationRecord
  belongs_to :order
  belongs_to :product
  belongs_to :discount # this could aslo be a many to many association
end

class Discount  < ApplicationRecord
  belongs_to :product
end

class Order < ApplicationRecord
  has_many :line_items
  has_many :products, through: :line_items
end

This ensures that existing orders will not be altered if the price on a product changes as the prices on the line items table are "frozen".

When you "ring up an order" you're actually tallying up the line items and additional discounts may be applied.

Also its usually a better idea to record the available discounts per product on a seperate table so that you can model stuff like quantity based discounts or combined discounts.

Do I do it in the model or in the controller?

Thats a very opinionated question but I would say that the answer is neither. Placing it in the model means you will have to deal with distributing the responsibilites ("is it the responisbility of Product or Order?") and you're burdoning classes that already have tons of responsibilits with yet more responsibilites. Remember that models already do persistence, querying, assocations, validations, integrations with the route helpers, I18n, etc.

Callbacks also come with a boatload of problems of their own since they are fired implicitly instead of explicitly and using them for complex problems can be a nightmare when it comes to testing and just in general getting it to happen when actually intended. Models also don't have any actual context when the callbacks are fired.

Controllers are very hard to test and are already the gluecode holding your entire application together. "Fat controllers" is widely accepted to be an anti-pattern.

Instead you can look at patterns such as form objects, service objects, jobs etc. that let you redestribute the responsibilites and keep your code testable.

Upvotes: 1

Related Questions