Ruby on Rails - domain objects without fat models in ruby on rails - ruby on rails tutorial - rails guides - rails tutorial - ruby rails



  • "Fat Model, Skinny Controller" is a very good first step, but it doesn't scale well once our codebase starts to grow.
  • Think on the Single Responsibility of models. What is the single responsibility of models? Is it to hold business logic? Is it to hold non-response-related logic?
  • No. Its responsibility is to handle the persistence layer and its abstraction.
  • Business logic, as well as any non-response-related logic and non-persistence-related logic, should go in domain objects.
  • Domain objects are classes designed to have only one responsibility in the domain of the problem. Our classes Scream Their Architecture for the problems they solve.
  • Practice, we should strive towards skinny models, skinny views and skinny controllers.
  • The architecture of our solution shouldn't be influenced by the framework we are choosing.

Example

We are marketplace which charges a fixed 15% commission to our customers via Stripe. If we charge a fixed 15% commission, that means that our commission changes depending on the order's amount because Stripe charges 2.9% + 30ยข.

  • The amount we charge as commission should be: amount*0.15 - (amount*0.029 + 0.30).
# app/models/order.rb
class Order < ActiveRecord::Base
  SERVICE_COMMISSION = 0.15
  STRIPE_PERCENTAGE_COMMISSION = 0.029
  STRIPE_FIXED_COMMISSION = 0.30

  ...

  def commission
    amount*SERVICE_COMMISSION - stripe_commission  
  end

  private

  def stripe_commission
    amount*STRIPE_PERCENTAGE_COMMISSION + STRIPE_FIXED_COMMISSION
  end
end
Clicking "Copy Code" button will copy the code into the clipboard - memory. Please paste(Ctrl+V) it in your destination. The code will get pasted. Happy coding from Wikitechy - ruby on rails tutorial - rails guides - ruby rails - rubyonrails - learn ruby on rails - team
  • As soon as we integrate with a new payment method, we won't be able to scale this functionality inside this model.
  • Also, as soon as we start to integrate more business logic, our Order object will start to lose cohesion.

Domain objects, with the calculation of the commission completely abstracted from the responsibility of persisting orders:

# app/models/order.rb
class Order < ActiveRecord::Base
  ...
  # No reference to commission calculation
end

# lib/commission.rb
class Commission
  SERVICE_COMMISSION = 0.15

  def self.calculate(payment_method, model)
    model.amount*SERVICE_COMMISSION - payment_commission(payment_method, model)  
  end

  private

  def self.payment_commission(payment_method, model)
    # There are better ways to implement a static registry,
    # this is only for illustration purposes.
    Object.const_get("#{payment_method}Commission").calculate(model)
  end
end

# lib/stripe_commission.rb
class StripeCommission
  STRIPE_PERCENTAGE_COMMISSION = 0.029
  STRIPE_FIXED_COMMISSION = 0.30

  def self.calculate(model)
    model.amount*STRIPE_PERCENTAGE_COMMISSION
      + STRIPE_PERCENTAGE_COMMISSION
  end
end

# app/controllers/orders_controller.rb
class OrdersController < ApplicationController
  def create
    @order = Order.new(order_params)
    @order.commission = Commission.calculate("Stripe", @order)
    ...
  end
end
Clicking "Copy Code" button will copy the code into the clipboard - memory. Please paste(Ctrl+V) it in your destination. The code will get pasted. Happy coding from Wikitechy - ruby on rails tutorial - rails guides - ruby rails - rubyonrails - learn ruby on rails - team

Using domain objects has the give below architectural advantages:

  • it's extremely easy to unit test, as no fixtures or factories are required to instantiate the objects with the logic.
  • works with everything that accepts the message amount.
  • keeps each domain object small, with clearly defined responsibilities, and with higher cohesion.
  • easily scales with new payment methods by addition, not modification.
  • stops the tendency to have an ever-growing User object in each Ruby on Rails application.
  • We personally like to put domain objects in lib.
  • If we do so, remember to add it to autoload_paths:
# config/application.rb
config.autoload_paths << Rails.root.join('lib')
Clicking "Copy Code" button will copy the code into the clipboard - memory. Please paste(Ctrl+V) it in your destination. The code will get pasted. Happy coding from Wikitechy - ruby on rails tutorial - rails guides - ruby rails - rubyonrails - learn ruby on rails - team
  • We may also prefer to create domain objects more action-oriented, following the Command/Query pattern.
  • Such case, putting these objects in app/commands might be a better place as all app subdirectories are automatically added to the autoload path

This ruby on rails tutorial page provides you the following key areas such as ruby , rail , ruby on rails , rail forum , ruby on rails tutorial , ruby tutorial , rails guides , rails tutorial , learn ruby , rails form_for , ruby rails , ruby class , what is ruby on rails , rails installer , ruby online , learn ruby on rails , ruby on rails jobs , rails find_by , install rails , easyrail , rubyonrails , link_to rails , ruby on rails developer , learn ruby the hard way , railscasts , ruby on rails examples , ruby on rails vs php , rails 4 , rails activerecord , rails generate , ruby and rails , ruby on rails download , install ruby on rails , ruby net http , what is rails , ruby app , ruby vs ruby on rails , ruby on rails windows , rails for zombies , ruby on rails book , ruby on rails development , ruby on rails ide , ruby on rails tutorial pdf

Related Searches to domain objects without fat models in ruby on rails