This gem is no longer actively maintained. As a replacement, you can use Resol as well as gems from smart-rb family.
Polist is a set of simple tools for creating business logic layer of your applications:
Polist::Serviceis a simple class designed for creating service classes.Polist::Builderis a builder system based onUber::Builder.Polist::Structis a small utility that helps generating simpleStruct-like object initializers.
Simply add gem "polist" to your Gemfile.
class MyService < Polist::Service
def call
if params[:ok]
success!(code: :cool)
else
fail!(code: :not_cool)
end
end
end
service = MyService.run(ok: true)
service.success? #=> true
service.response #=> { code: :cool }
service = MyService.run(ok: false)
service.success? #=> false
service.response #=> { code: :not_cool }The only parameter that is passed to the service is called params by default. If you want more params, feel free to define your own initializer and call the service accordingly:
class MyService < Polist::Service
def initialize(a, b, c)
# ...
end
end
MyService.call(1, 2, 3)Unlike .run, .call will raise Polist::Service::Failure exception on failure:
begin
MyService.call(ok: false)
rescue Polist::Service::Failure => error
error.response #=> { code: :not_cool }
endNote that .run and .call are just shortcuts for MyService.new(...).run and MyService.new(...).call with the only difference that they always return the service instance instead of the result of #run or #call. Unlike #call though, #run is not intended to be overwritten in subclasses.
You can use yield in #call. And then call ::run or ::call class methods with block. For example, we have the class:
class BlockFun < Polist::Service
def call
success!(yield(1, 2))
end
endThen we can use it like this:
service = BlockFun.call { |a, b| a + b }
p service.response # => 3Behind the scenes it just catches passed block in class methods ::run and ::call, converts it to proc and then passes proc to instance method #call and #run by converting it back to block. So, for example, if you want to pass this block to private methods, you can write code like this:
class AnotherBlockFun < Polist::Service
def call(&block)
success!(block_caller(&block))
end
private
def block_caller
yield 1, 2
end
end
service = AnotherBlockFun.call { |a, b| a + b }
p service.response # => 3Sometimes you want to use some kind of params parsing and/or validation, and you can do that with the help of Polist::Service::Form class. It uses tainbox gem under the hood.
class MyService < Polist::Service
class Form < Polist::Service::Form
attribute :param1, :String
attribute :param2, :Integer
attribute :param3, :String, default: "smth"
attribute :param4, :String
validates :param4, presence: true
end
def call
p form.valid?
p [form.param1, form.param2, form.param3]
end
# The commented code is just the default implementation and can be simply overwritten
# private
# def form
# @form ||= self.class::Form.new(form_attributes.to_snake_keys)
# end
# def form_attributes
# params
# end
end
MyService.call(param1: "1", param2: "2") # prints false and then ["1", 2, "smth"]The #form method is there just for convinience and by default it uses what #form_attributes returns as the attributes for the default form class which is the services' Form class. You are free to use as many different form classes as you want in your service.
The build logic is based on Uber::Builder but it allows recursive builders. See the example:
Can be used with Polist::Service or any other Ruby class.
class User
include Polist::Builder
builds do |role|
case role
when /admin/
Admin
end
end
attr_accessor :role
def initialize(role)
self.role = role
end
end
class Admin < User
builds do |role|
SuperAdmin if role == "super_admin"
end
class SuperAdmin < Admin
def super?
true
end
end
def super?
false
end
end
User.build("user") # => #<User:... @role="user">
User.build("admin") # => #<Admin:... @role="admin">
User.build("admin").super? # => false
User.build("super_admin") # => #<Admin::SuperAdmin:... @role="super_admin">
User.build("super_admin").super? # => true
Admin.build("smth") # => #<Admin:... @role="admin">
SuperAdmin.build("smth") # => #<Admin::SuperAdmin:... @role="admin">Works pretty much the same like Ruby Struct class, but you don't have to subclass it.
Can be used with Polist::Service or any other class that don't have initializer specified.
class Point
include Polist::Struct
struct :x, :y
end
a = Point.new(15, 25)
a.x # => 15
a.y # => 25
b = Point.new(15, 25, 35) # raises ArgumentError: struct size differs
c = Point.new(15)
c.x # => 15
c.y # => nilIf you have some common things to be done in more than one service, you can define a middleware and register it inside the said services.
Every middleware takes the service into it's constructor and executes #call. Thus every middleware has to implement #call method and has a #service attribute reader.
Middlewares delegate #success!, #fail!, #error!, #form, #form_attributes to the service class they are registered in.
Every middleware should be a subclass of Polist::Service::Middleware. Middlewares are run before the service itself is run.
To register a middleware one should use .register_middleware class method on a service. More than one middleware can be registered for one service.
For example:
class MyMiddleware < Polist::Service::Middleware
def call
fail!(code: :not_cool) if service.fail_on_middleware?
end
end
class MyService < Polist::Service
register_middleware MyMiddleware
def call
success!(code: :cool)
end
def fail_on_middleware?
true
end
end
service = MyService.run
service.success? #=> false
service.response #=> { code: :not_cool }Bug reports and pull requests are welcome on GitHub at https://github.com/umbrellio/polist.
Released under MIT License.
Created by Yuri Smirnov.