How I Learned to Stop Worrying and Love the Command Pattern
# looks like command to Create/Update/Delete(Publish, Reject and etc) and Query object | |
class Post < ActiveRecord::Base | |
before_validate :build_from_template # if update don't run | |
before_create :check_content_uniq # extremely long time also don't run on update | |
after_update :send_notification # sometimes don't run | |
after_create :add_editor # sometimes don't run, can touch another record that has callbacks too :D | |
after_save :broadcast # sometimes don't run | |
after_create :run_indexing # sometimes don't run | |
private | |
def build_from_template | |
# ... | |
end | |
def check_content_uniq | |
# ... | |
end | |
def send_notification | |
# ... | |
end | |
def add_editor | |
# ... | |
# update another record, that run callbacks too :D | |
end | |
def broadcast | |
# ... | |
end | |
def run_indexing | |
# ... | |
end | |
end |
# this variant even better than fat model | |
class Post < ActiveRecord::Base | |
end | |
Class OrderController < ApplicationController | |
def edit | |
# many lines of code there | |
end | |
def create | |
# many lines of code there | |
end | |
def publish | |
# many lines of code there | |
end | |
def reject | |
# many lines of code there | |
end | |
end |
class EntityController < ApplicationController | |
def create | |
op = EntityOperation::Create.new(params, current_user) | |
op.process | |
if op.success? | |
render json: op.result, status: :created | |
else | |
render json: { errors: op.result.errors }, status: :unprocessable_entity | |
end | |
end | |
end | |
module EntityOperation | |
class Create < EventSequence::Sequence | |
class SequenceSerializer < EventSequence::Serializer | |
RECORD_FIELDS = %i(id name order_id).freeze | |
end | |
def build_record(params) | |
params | |
end | |
class ChecPermissionsEvent < EventSequence::Event | |
def process(record) | |
end | |
def should? | |
true | |
end | |
end | |
def create_order(record) | |
record.save && record | |
end | |
def refetch_order(record) | |
record.reload | |
end | |
def on_fail(e) | |
puts "FailEvent #{e}" | |
end | |
class BroadcastSequence < EventSequence::Sequence::Low | |
def broadcast(props) | |
BroadcastService.new(props).process | |
end | |
end | |
class NotifySequence < EventSequence::Sequence::Low | |
def notify(props) | |
NotificationService.new(props).process | |
end | |
end | |
class FeedSequece < EventSequence::Sequence::Low | |
def populate_feed(props) | |
ActivityFeedService.new(props).create | |
end | |
end | |
class AfterAllSequence < EventSequence::Sequence::Low | |
def call_me_after_all(props) | |
puts "#{call_me_after_all} props" | |
end | |
def on_fail_after_all(e) | |
puts "After all fail #{e}" | |
end | |
sequence({ | |
events: [:call_me_after_all], | |
fail: [:on_fail_after_all], | |
wait: [BroadcastSequence, FeedSequece, NotifySequence] | |
}) | |
end | |
sequence({ | |
events: [ | |
:build_record, | |
ChecPermissionsEvent, | |
:create_order, | |
:refetch_order, | |
BroadcastSequence, | |
FeedSequece, | |
NotifySequence, | |
AfterAllSequence, | |
], | |
serializer: SequenceSerializer, | |
fail: [:on_fail] | |
}) | |
end | |
end |
module Api | |
class OrdersController < ApiController | |
def create | |
if Order::Create.new(@order, current_user, order_params).process | |
render json: @order, status: :created | |
else | |
render json: { errors: @order.errors }, status: :unprocessable_entity | |
end | |
end | |
end | |
end | |
module Order | |
class Create < Operations::Create | |
class Runner < Operationable::Runners::Separate | |
def initialize_callbacks | |
push_to_queue(:broadcast, :notify, :populate_feed, queue: :low) | |
push_to_queue(:notify, queue: :high) | |
end | |
end | |
class Callback < ::Operations::Callback | |
def broadcast | |
BroadcastService.new(props).process | |
end | |
def notify | |
NotificationService.new(props).process | |
end | |
def populate_feed | |
ActivityFeedService.new(props).process | |
end | |
end | |
class Serializer < ::Operations::Serializer | |
RECORD_FIELDS = %i(id).freeze | |
end | |
end | |
end |
# How can somebody understand what is happend, and insure that circular calls not happens. | |
class One | |
include Pubsub | |
def make_something | |
# make something | |
if success? | |
publish(:one_finished, result) | |
else | |
publish(:one_failed, e) | |
end | |
end | |
end | |
One.subscribe(Two) | |
class Two | |
include Pubsub | |
def one_finished | |
# execute some code, and call another domain | |
publish(:call_three, result) | |
end | |
def one_failed | |
# execute some code, and call another domain | |
publish(:call_four, result) | |
end | |
end | |
Two.subscribe(Three, Four) | |
class Three | |
include Pubsub | |
def call_three | |
# may be publish for One? | |
end | |
end | |
class Four | |
include Pubsub | |
def call_four | |
# may be publish for Two? | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment