Brassbound

Brassbound is a simple but strict implementation of the Data, Context, and Interaction (DCI) paradigm for Ruby.

Example

The canonical DCI example is the transfer of funds between a money source and money sink. In this example, source and sink are both roles that are attached to data objects, and the transfer of funds is orchestrated by the context. Here’s how the example looks using Brassbound.

require 'brassbound'

# Domain/data objects are plain old Ruby objects.
class Account
  # Pretend to lookup accounts in a database.
  def self.find()
    case 
    when 1
      Account.new(, 2000)
    when 2
      Account.new(, 1000)
    end
  end

  attr_reader   :id
  attr_accessor :balance

  def initialize(id, balance)
    @id = id
    @balance = balance
  end
end

# Roles are plain old Ruby modules, and automatically have access to
# the invoking context.
module MoneySource
  def transfer_out(amount)
    puts("Transferring #{amount} from account #{self.id} to account #{context.money_sink.id}")
    self.balance -= amount
    puts("Source account new balance: #{self.balance}")
    context.money_sink.transfer_in(amount)
  end
end

module MoneySink
  def transfer_in(amount)
    self.balance += amount
    puts("Destination account new balance: #{self.balance}")
  end
end

# Contexts are Ruby classes that include the Brassbound::Context module.
# The common idiom is for the initialize method to create all of the
# necessary objects and declare how they are bound to roles.
# Then, within the scope of the execute method, the objects will have been
# bound to the declared roles, and can be accessed by the role name
# (convert to lower case with underscores by default).
class TransferFunds
  include Brassbound::Context

  def initialize(, , amount)
    @amount = amount

    role MoneySource, Account.find()
    role MoneySink, Account.find()
  end

  def execute
    # Here, money_source refers to the object bound to the MoneySource role
    # in the initialize method.
    money_source.transfer_out(@amount)
  end
end

# Now let's create and execute our context.
TransferFunds.new(1, 2, 100).call