Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/umbrellio/polist
Ruby gem for creating simple service classes.
https://github.com/umbrellio/polist
functional-objects operation-object ruby ruby-service-object service-object services
Last synced: about 1 month ago
JSON representation
Ruby gem for creating simple service classes.
- Host: GitHub
- URL: https://github.com/umbrellio/polist
- Owner: umbrellio
- License: mit
- Created: 2017-07-05T16:59:55.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2023-01-23T07:35:59.000Z (almost 2 years ago)
- Last Synced: 2024-10-31T14:26:29.617Z (about 2 months ago)
- Topics: functional-objects, operation-object, ruby, ruby-service-object, service-object, services
- Language: Ruby
- Homepage:
- Size: 48.8 KB
- Stars: 17
- Watchers: 5
- Forks: 2
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.txt
Awesome Lists containing this project
README
# Polist [![Gem Version](https://badge.fury.io/rb/polist.svg)](https://badge.fury.io/rb/polist) [![Build Status](https://travis-ci.org/umbrellio/polist.svg?branch=master)](https://travis-ci.org/umbrellio/polist) [![Coverage Status](https://coveralls.io/repos/github/umbrellio/polist/badge.svg?branch=master)](https://coveralls.io/github/umbrellio/polist?branch=master)
## DEPRECATION NOTICE
This gem is no longer actively maintained. As a replacement, you can use [Resol](https://github.com/umbrellio/resol) as well as gems from [smart-rb](https://github.com/smart-rb) family.
## Description
Polist is a set of simple tools for creating business logic layer of your applications:
- `Polist::Service` is a simple class designed for creating service classes.
- `Polist::Builder` is a builder system based on `Uber::Builder`.
- `Polist::Struct` is a small utility that helps generating simple `Struct`-like object initializers.## Installation
Simply add `gem "polist"` to your Gemfile.
## Using Polist::Service
```ruby
class MyService < Polist::Service
def call
if params[:ok]
success!(code: :cool)
else
fail!(code: :not_cool)
end
end
endservice = 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:
```ruby
class MyService < Polist::Service
def initialize(a, b, c)
# ...
end
endMyService.call(1, 2, 3)
```Unlike `.run`, `.call` will raise `Polist::Service::Failure` exception on failure:
```ruby
begin
MyService.call(ok: false)
rescue Polist::Service::Failure => error
error.response #=> { code: :not_cool }
end
```Note 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.
### Using blocks in #call and #run methods.
You can use yield in `#call`. And then call `::run` or `::call` class methods with block. For example, we have the class:
```ruby
class BlockFun < Polist::Service
def call
success!(yield(1, 2))
end
end
```Then we can use it like this:
```ruby
service = BlockFun.call { |a, b| a + b }p service.response # => 3
```Behind 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:
```ruby
class AnotherBlockFun < Polist::Service
def call(&block)
success!(block_caller(&block))
endprivate
def block_caller
yield 1, 2
end
endservice = AnotherBlockFun.call { |a, b| a + b }
p service.response # => 3
```### Using Form objects
Sometimes 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](https://github.com/enthrops/tainbox) gem under the hood.
```ruby
class MyService < Polist::Service
class Form < Polist::Service::Form
attribute :param1, :String
attribute :param2, :Integer
attribute :param3, :String, default: "smth"
attribute :param4, :Stringvalidates :param4, presence: true
enddef 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
endMyService.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.
## Using Polist::Builder
The build logic is based on [Uber::Builder](https://github.com/apotonick/uber#builder) but it allows recursive builders. See the example:
Can be used with `Polist::Service` or any other Ruby class.
```ruby
class User
include Polist::Builderbuilds do |role|
case role
when /admin/
Admin
end
endattr_accessor :role
def initialize(role)
self.role = role
end
endclass Admin < User
builds do |role|
SuperAdmin if role == "super_admin"
endclass SuperAdmin < Admin
def super?
true
end
enddef super?
false
end
endUser.build("user") # => #
User.build("admin") # => #
User.build("admin").super? # => falseUser.build("super_admin") # => #
User.build("super_admin").super? # => trueAdmin.build("smth") # => #
SuperAdmin.build("smth") # => #
```## Using Polist::Struct
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.
```ruby
class Point
include Polist::Structstruct :x, :y
enda = Point.new(15, 25)
a.x # => 15
a.y # => 25b = Point.new(15, 25, 35) # raises ArgumentError: struct size differs
c = Point.new(15)
c.x # => 15
c.y # => nil
```### Using Middlewares
If 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:
```ruby
class MyMiddleware < Polist::Service::Middleware
def call
fail!(code: :not_cool) if service.fail_on_middleware?
end
endclass MyService < Polist::Service
register_middleware MyMiddlewaredef call
success!(code: :cool)
enddef fail_on_middleware?
true
end
endservice = MyService.run
service.success? #=> false
service.response #=> { code: :not_cool }
```## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/umbrellio/polist.
## License
Released under MIT License.
## Authors
Created by Yuri Smirnov.