Reputation: 251
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
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