Upgrade to Pro — share decks privately, control downloads, hide ads and more …

STOP! Use case time!

STOP! Use case time!

A brief introduction about DCI on Ruby on Rails.

André Medeiros

June 17, 2013
Tweet

More Decks by André Medeiros

Other Decks in Technology

Transcript

  1. How I develop apps •UI first approach •UI directly maps

    to operations within the code •MVC doesn’t always conform to this standard of development
  2. DCI is great for this approach Invented by Trygve Reenskaug

    Comes in where MVC fails: capturing behavior Places interaction in obvious places Models represent entities, not behavior Splits what an object is from what it does
  3. C is for Cookie Context Class which enacts one or

    more use cases Instantiated by a user action Mixes in participating objects with Roles Responsible for acting out the use case
  4. I is for Interaction What the system does Logic is

    contained in Role modules Implemented by mixing in objects with Roles in a given Context or use case
  5. How it works Controller Context starts use case Context Object(s)

    finds or creates the participating objects
  6. How it works Controller Context starts use case Context Object(s)

    mixes in all the roles needed Role Role Role
  7. # app/controllers/transfers_controller.rb (before DCI) class TransfersController def create @source =

    Account.find(params[:source_id]) @destination = Account.find(params[:dest_id]) @source.balance -= params[:amount] @destination.balance += params[:amount] end end
  8. # app/controllers/transfers_controller.rb (after DCI) class TransfersController def create @source =

    Account.find(params[:source_id]) @destination = Account.find(params[:dest_id]) mt = MoneyTransfer.new(@source, @destination) mt.execute(params[:amount]) end end
  9. # app/contexts/money_transfer.rb class MoneyTransfer def initialize(source, destination) @source = source

    @destination = destination assign_transferrer(@source) end def execute(amount) @source.transfer_to(@destination, amount) end private def assign_transferrer(account) account.extend(Transferrer) end module Transferrer def transfer_to(destination, amount) self.balance -= amount destination.balance += amount end end end
  10. # spec/controllers/transfers_controller_spec.rb (before) describe TransfersController do describe 'POST create' do

    before(:each) do @s = FactoryGirl.create :account, balance: 500 @d = FactoryGirl.create :account, balance: 1000 end it 'should transfer money between accounts' do post :create, format: :json, source: @s.id, destination: @d.id, amount: 500 # Yikes :-( [ @s, @d ].map(&:reload) expect(response.status).to eq(201) expect(@s.balance).to eq(0) expect(@d.balance).to eq(1500) end end end
  11. # spec/contexts/money_transfer_spec.rb (after DCI) describe MoneyTransfer do ! before(:each) do

    ! ! @source = OpenStruct.new(balance: 500) ! ! @destination = OpenStruct.new(balance: 1000) ! end ! ! it 'should move money between accounts' do ! ! MoneyTransfer.new(@source,@destination).execute(500) ! ! ! ! expect(@source.balance).to eq(0) ! ! expect(@destination.balance).to eq(1500) ! end end
  12. Advantages Keeps the model slim (fat != obese) Business logic

    is easy to find and track down Facilitates reusability Highly testable