Skip to content

Instantly share code, notes, and snippets.

@KirillSuhodolov
Last active October 6, 2017 23:07
Show Gist options
  • Save KirillSuhodolov/6e02e7105406459be222fe4bb5808225 to your computer and use it in GitHub Desktop.
Save KirillSuhodolov/6e02e7105406459be222fe4bb5808225 to your computer and use it in GitHub Desktop.
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