class Bid < ActiveRecord::Base
belongs_to :buyer, class_name: Customer
def self.from_form(form)
new(form.attributes)
end
endOur 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.
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
endWe create a "form" with which to capture, validate and sanatize user input. This will be used to create a new Bid entity.
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
endThe service will take the form and use it to create a new entity. It will broadcast an event to signal the outcome.
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
endThis 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')class Orders::Notifier
def on_create_bid_successful(bid_id)
bid = Bid.find(bid_id)
Mailer.bid_created(bid).deliver
end
endclass Orders::Notifier::Mailer < ActionMailer
# the usual...
end