Skip to content

Instantly share code, notes, and snippets.

@elskwid
Created December 3, 2013 19:46
Show Gist options
  • Save elskwid/7776245 to your computer and use it in GitHub Desktop.
Save elskwid/7776245 to your computer and use it in GitHub Desktop.
Service Object WIP
# An attempt to codify how we're using Service Objects now
#
# Most follow this pattern:
#
# class SomeService
#
# def self.call(a, b, &block)
# new(a, b).call(&block)
# end
#
# attr_reader :a, :b
#
# def initialize(a, b)
# @a = a
# @b = b
# end
#
# def call(&block)
# # do work here
# # maybe call block
# end
#
# end
#
# These two modules provide this pattern:
#
# Service -
# creates the initializer, readers, and a `collaborators` class
# method to make it clearer.
#
# CallableService -
# provides the default `.call` class method and a default noop
# `#call` method while also mixing in the Service module.
#
#
# Note: It doesn't use AS::Concern so it's usable outside of Rails.
#
# To use it:
#
# class ExampleService
# include CallableService
#
# collaborators :a, :b, :c
# end
#
# ex = ExampleService.new("A", "B", "C")
#
# ex.a # => "A"
# ex.b # => "B"
# ex.c # => "C"
#
# ex.call
# => #<ExampleService: ...>
#
# ExampleService.call("A", "B", "C")
# => #<ExampleService:...>
#
module Service
def self.included(base)
base.send(:extend, ClassMethods)
super
end
# Instance level access to the collaborators
def collaborators
self.class.collaborators
end
module ClassMethods
# Provides a collaborators class method to set them up in your service.
#
# i.e.
# collaborators :event, :user, :payment
#
def collaborators(*collabs)
return @collaborators if collabs.empty?
@collaborators = collabs
create_readers
create_initializer
end
private
# Creates an attr_reader for each collaborator
def create_readers
collaborators.each { |c| puts c.inspect; attr_reader c.to_sym }
end
# Also adds a simple initializer that takes each argument, maps it to
# one of the declared collaborators, and sets the ivar for that value.
#
# i.e.
# collaborators :event, :user, :payment
#
# s = Service.new(some_event, a_user, the_payment)
#
# s.event # => some_event
# s.user # => a_user
# s.payment # => the_payment
#
def create_initializer
initializer = %Q{
def initialize(#{collaborators.join(", ")})
#{collaborators.map{|c| "@#{c} = #{c}\n"}.join("")}
end
}
module_eval initializer
end
end # ClassMethods
end
module CallableService
def self.included(base)
base.send(:include, Service)
base.send(:extend, ClassMethods)
super
end
# Default call method, returns self
def call
self
end
module ClassMethods
# Default call class method that passes args to the initializer
# and the block to the call method.
def call(*args, &block)
new(*args).call(&block)
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment