{"id":13879455,"url":"https://github.com/umbrellio/polist","last_synced_at":"2025-05-06T19:22:59.557Z","repository":{"id":49258635,"uuid":"96342113","full_name":"umbrellio/polist","owner":"umbrellio","description":"Ruby gem for creating simple service classes.","archived":false,"fork":false,"pushed_at":"2023-01-23T07:35:59.000Z","size":50,"stargazers_count":17,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-31T02:05:13.089Z","etag":null,"topics":["functional-objects","operation-object","ruby","ruby-service-object","service-object","services"],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/umbrellio.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-07-05T16:59:55.000Z","updated_at":"2024-09-25T14:32:58.000Z","dependencies_parsed_at":"2023-02-12T21:16:22.229Z","dependency_job_id":null,"html_url":"https://github.com/umbrellio/polist","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/umbrellio%2Fpolist","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/umbrellio%2Fpolist/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/umbrellio%2Fpolist/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/umbrellio%2Fpolist/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/umbrellio","download_url":"https://codeload.github.com/umbrellio/polist/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249076544,"owners_count":21208812,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["functional-objects","operation-object","ruby","ruby-service-object","service-object","services"],"created_at":"2024-08-06T08:02:21.642Z","updated_at":"2025-04-19T07:33:29.816Z","avatar_url":"https://github.com/umbrellio.png","language":"Ruby","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)\n\n## DEPRECATION NOTICE\n\nThis 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.\n\n## Description\n\nPolist is a set of simple tools for creating business logic layer of your applications:\n\n- `Polist::Service` is a simple class designed for creating service classes.\n- `Polist::Builder` is a builder system based on `Uber::Builder`.\n- `Polist::Struct` is a small utility that helps generating simple `Struct`-like object initializers.\n\n## Installation\n\nSimply add `gem \"polist\"` to your Gemfile.\n\n## Using Polist::Service\n\n```ruby\nclass MyService \u003c Polist::Service\n  def call\n    if params[:ok]\n      success!(code: :cool)\n    else\n      fail!(code: :not_cool)\n    end\n  end\nend\n\nservice = MyService.run(ok: true)\nservice.success? #=\u003e true\nservice.response #=\u003e { code: :cool }\n\nservice = MyService.run(ok: false)\nservice.success? #=\u003e false\nservice.response #=\u003e { code: :not_cool }\n```\n\nThe 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:\n\n```ruby\nclass MyService \u003c Polist::Service\n  def initialize(a, b, c)\n    # ...\n  end\nend\n\nMyService.call(1, 2, 3)\n```\n\nUnlike `.run`, `.call` will raise `Polist::Service::Failure` exception on failure:\n\n```ruby\nbegin\n  MyService.call(ok: false)\nrescue Polist::Service::Failure =\u003e error\n  error.response #=\u003e { code: :not_cool }\nend\n```\n\nNote 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.\n\n### Using blocks in #call and #run methods.\n\nYou can use yield in `#call`. And then call `::run` or `::call` class methods with block. For example, we have the class:\n```ruby\nclass BlockFun \u003c Polist::Service\n  def call\n    success!(yield(1, 2))\n  end\nend\n```\n\nThen we can use it like this:\n```ruby\nservice = BlockFun.call { |a, b| a + b }\n\np service.response # =\u003e 3\n```\n\nBehind 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:\n```ruby\nclass AnotherBlockFun \u003c Polist::Service\n  def call(\u0026block)\n    success!(block_caller(\u0026block))\n  end\n\n  private\n\n  def block_caller\n    yield 1, 2\n  end\nend\n\nservice = AnotherBlockFun.call { |a, b| a + b }\n\np service.response # =\u003e 3\n```\n\n### Using Form objects\n\nSometimes 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.\n\n```ruby\nclass MyService \u003c Polist::Service\n  class Form \u003c Polist::Service::Form\n    attribute :param1, :String\n    attribute :param2, :Integer\n    attribute :param3, :String, default: \"smth\"\n    attribute :param4, :String\n\n    validates :param4, presence: true\n  end\n\n  def call\n    p form.valid?\n    p [form.param1, form.param2, form.param3]\n  end\n\n  # The commented code is just the default implementation and can be simply overwritten\n  # private\n\n  # def form\n  #   @form ||= self.class::Form.new(form_attributes.to_snake_keys)\n  # end\n\n  # def form_attributes\n  #   params\n  # end\nend\n\nMyService.call(param1: \"1\", param2: \"2\") # prints false and then [\"1\", 2, \"smth\"]\n```\n\nThe `#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.\n\n## Using Polist::Builder\n\nThe build logic is based on [Uber::Builder](https://github.com/apotonick/uber#builder) but it allows recursive builders. See the example:\n\nCan be used with `Polist::Service` or any other Ruby class.\n\n```ruby\nclass User\n  include Polist::Builder\n\n  builds do |role|\n    case role\n    when /admin/\n      Admin\n    end\n  end\n\n  attr_accessor :role\n\n  def initialize(role)\n    self.role = role\n  end\nend\n\nclass Admin \u003c User\n  builds do |role|\n    SuperAdmin if role == \"super_admin\"\n  end\n\n  class SuperAdmin \u003c Admin\n    def super?\n      true\n    end\n  end\n\n  def super?\n    false\n  end\nend\n\nUser.build(\"user\") # =\u003e #\u003cUser:... @role=\"user\"\u003e\n\nUser.build(\"admin\") # =\u003e #\u003cAdmin:... @role=\"admin\"\u003e\nUser.build(\"admin\").super? # =\u003e false\n\nUser.build(\"super_admin\") # =\u003e #\u003cAdmin::SuperAdmin:... @role=\"super_admin\"\u003e\nUser.build(\"super_admin\").super? # =\u003e true\n\nAdmin.build(\"smth\") # =\u003e #\u003cAdmin:... @role=\"admin\"\u003e\nSuperAdmin.build(\"smth\") # =\u003e #\u003cAdmin::SuperAdmin:... @role=\"admin\"\u003e\n```\n\n## Using Polist::Struct\n\nWorks pretty much the same like Ruby `Struct` class, but you don't have to subclass it.\n\nCan be used with `Polist::Service` or any other class that don't have initializer specified.\n\n```ruby\nclass Point\n  include Polist::Struct\n\n  struct :x, :y\nend\n\na = Point.new(15, 25)\na.x # =\u003e 15\na.y # =\u003e 25\n\nb = Point.new(15, 25, 35) # raises ArgumentError: struct size differs\n\nc = Point.new(15)\nc.x # =\u003e 15\nc.y # =\u003e nil\n```\n\n### Using Middlewares\n\nIf 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.\nEvery 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.\nMiddlewares delegate `#success!`, `#fail!`, `#error!`, `#form`, `#form_attributes` to the service class they are registered in.\nEvery middleware should be a subclass of `Polist::Service::Middleware`. Middlewares are run before the service itself is run.\n\nTo register a middleware one should use `.register_middleware` class method on a service. More than one middleware can be registered for one service.\n\nFor example:\n```ruby\nclass MyMiddleware \u003c Polist::Service::Middleware\n  def call\n    fail!(code: :not_cool) if service.fail_on_middleware?\n  end\nend\n\nclass MyService \u003c Polist::Service\n  register_middleware MyMiddleware\n\n  def call\n    success!(code: :cool)\n  end\n\n  def fail_on_middleware?\n    true\n  end\nend\n\nservice = MyService.run\nservice.success? #=\u003e false\nservice.response #=\u003e { code: :not_cool }\n```\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/umbrellio/polist.\n\n## License\n\nReleased under MIT License.\n\n## Authors\n\nCreated by Yuri Smirnov.\n\n\u003ca href=\"https://github.com/umbrellio/\"\u003e\n\u003cimg style=\"float: left;\" src=\"https://umbrellio.github.io/Umbrellio/supported_by_umbrellio.svg\" alt=\"Supported by Umbrellio\" width=\"439\" height=\"72\"\u003e\n\u003c/a\u003e\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fumbrellio%2Fpolist","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fumbrellio%2Fpolist","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fumbrellio%2Fpolist/lists"}