Entity

class Bid < ActiveRecord::Base
  belongs_to :buyer, class_name: Customer
  
  def self.from_form(form)
    new(form.attributes)
  end
end

Our entity has no validations. This is so we don’t restrict ourself with regards to what is considered a valid entiry. It is pretty much a simple data object.

The from_form constructor method is used later and allows us to pass an object which responds to attributes to initalize a new Bid object.

Form

class Bids::Create::Form
  include ActiveModel::Model
  include Virtus
  
  attribute :buyer_id,    Integer
  attribute :auction_id,  Integer
  attribute :maximum_bid, Money
  
  validates :buyer_id,    presence: true
  validates :auction_id,  presence: true
  validates :maximum_bid, presence: true
end

We create a “form” with which to capture, validate and sanatize user input. This will be used to create a new Bid entity.

Service object

class Bids::Create
  include Wisper::Publisher
  
  def initialize(dependencies = {})
    @bid_class = dependencies.fetch(:bid_class) { Bids::Bid }
  end
  
  def call(form)
    if form.valid?
      bid = @bid_class.from_form(form)
      bid.save!
      
      broadcast(:create_bid_successful, bid.id)
    else
      broadcast(:create_bid_failed, bid.id)
    end
  end
end

The service will take the form and use it to create a new entity. It will broadcast an event to signal the outcome.

Controller

class Bids::CreateController < ApplicationController
	def new
	  @form = new_form
	end

	def create
	  @form = new_form
	  create_bid = Bids::Create.new(@form)
	  
	  create_bid.on(:create_bid_successful) { |bid_id| redirect_to bid_path(bid_id) }
	  create_bid.on(:create_bid_failed)     { |bid_id| render action: :new }
	  
	  create_bid.call
	end

	private

	def new_form
	  Bids::Create::Form.new(params[:form])
	end
end

This is the context which brings each part together. I’ve not included the view the user sees but you can imagine the use of form_for to show a HTML form which the user can interact with.

We have nice seperation of concerns and it wasn’t too much work.

Now we want to send an email to the seller to notify them of the new bid using ActionMailer.

We could put the mailing code directly in the service object.

However I see email as a UI and not part of the core business logic, its on the outside of the Hexagon. So instead, in the controller, we can subscribe a listener to our service object which will react to the create_bid_successsful event.

create_bid.subscribe(Orders::Notifier.new, prefix: 'on')

Listener

class Orders::Notifier
  def on_create_bid_successful(bid_id)
    bid = Bid.find(bid_id)
    
    Mailer.bid_created(bid).deliver
  end
end

and Mailer

class Orders::Notifier::Mailer < ActionMailer
  # the usual...
end