separates the domain model (Data) from use cases (Context) and Roles that objects play (Interaction)
- Wikipedia
# app/controllers/transfers_controller.rb
class TransfersController < ApplicationController
def transfer
@source_account = Account.find(params[:source_id])
@destination_account = Account.find(params[:destination_id])
amount = params[:transfer_amount]
if @source_account.transfer_to(@destination_account, amount)
flash[:success] = 'Transfer completed successfully!'
redirect_to @source_account
else
flash[:error] = 'Something went wrong'
render :transfer
end
end
end
# app/models/account.rb
class Account < ActiveRecord::Base
# == Schema Information
#
# Table name: accounts
# id :integer not null, primary key
# balance :integer default(0)
def transfer_to(destination, amount)
self.decrement(amount)
destination.increment(amount)
end
def increment(amount)
self.balance += amount
self.save
end
def decrement(amount)
self.balance -= amount
self.save
end
end
# app/models/concerns/transferrable.rb
module Transferrable
extend ActiveSupport::Concern
def transfer_to(destination, amount)
self.decrement(amount)
destination.increment(amount)
end
end
# app/models/account.rb
class Account < ActiveRecord::Base
# == Schema Information
#
# Table name: accounts
# id :integer not null, primary key
# balance :integer default(0)
include Transferrable
include Depositable
include Notifiable
def increment(amount)
self.balance += amount
self.save
end
def decrement(amount)
self.balance -= amount
self.save
end
end
# app/controllers/transfers_controller.rb
class TransfersController < ApplicationController
def transfer
@source_account = Account.find(params[:source_id])
@destination_account = Account.find(params[:destination_id])
amount = params[:transfer_amount]
if TransferringMoney.new(@source_account, @destination_account).execute_transfer(amount)
flash[:success] = 'Transfer completed successfully!'
redirect_to @source_account
else
flash[:error] = 'Something went wrong'
render :transfer
end
end
end
# app/models/account.rb
class Account < ActiveRecord::Base
# == Schema Information
#
# Table name: accounts
# id :integer not null, primary key
# balance :integer default(0)
def increment(amount)
self.balance += amount
self.save
end
def decrement(amount)
self.balance -= amount
self.save
end
end
# app/contexts/transferring_money.rb
class TransferringMoney
attr_reader :source, :destination
def initialize(source, destination)
@source = source
@destination = destination
assign_transferrable(@source)
end
def execute_transfer(amount)
source.transfer_to(destination, amount)
end
private
def assign_transferrable(source)
source.extend(Transferrable)
end
module Transferrable
def transfer_to(destination, amount)
self.decrement(amount)
destination.increment(amount)
end
end
private_constant :Transferrable
end
Context
Data
Interaction
Role
class TransferringMoney
attr_reader :source, :destination
def initialize(source, destination)
@source = source
@destination = destination
assign_transferrable
end
def execute_transfer(amount)
source.transfer_to(destination, amount)
end
private
def assign_transferrable
@source = Transferrable.new(@source)
end
class Transferrable < SimpleDelegator
def transfer_to(destination, amount)
self.decrement(amount)
destination.increment(amount)
end
end
private_constant :Transferrable
end
Pros
Cons
# app/models/account.rb
class Account < ActiveRecord::Base
# == Schema Information
#
# Table name: accounts
# id :integer not null, primary key
# balance :integer default(0)
def increment(amount)
self.balance += amount
self.save
end
def decrement(amount)
self.balance -= amount
self.save
end
def add_role(mod)
@role = mod
end
def roles
@role
end
def method_missing(method_name, *args, &block)
if role.instance_methods.include?(method_name)
role.instance_method(method_name).bind(self).call(*args, &block)
else
super
end
end
end
# app/contexts/transferring_money.rb
class TransferringMoney
attr_reader :source, :destination
def initialize(source, destination)
@source = source
@destination = destination
assign_transferrable
end
def execute_transfer(amount)
source.transfer_to(destination, amount)
end
private
def assign_transferrable
@source.add_role(Transferrable)
end
module Transferrable
def transfer_to(destination, amount)
self.decrement(amount)
destination.increment(amount)
end
end
private_constant :Transferrable
end
Pros
Cons