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.
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.
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.
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')
class Orders::Notifier
def on_create_bid_successful(bid_id)
bid = Bid.find(bid_id)
Mailer.bid_created(bid).deliver
end
end
class Orders::Notifier::Mailer < ActionMailer
# the usual...
end