{"id":13878704,"url":"https://github.com/Fretadao/f_service","last_synced_at":"2025-07-16T14:32:45.702Z","repository":{"id":39970212,"uuid":"255621299","full_name":"Fretadao/f_service","owner":"Fretadao","description":"Simpler, safer and more composable operations","archived":false,"fork":false,"pushed_at":"2024-10-28T20:53:37.000Z","size":150,"stargazers_count":10,"open_issues_count":4,"forks_count":3,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-05-11T02:53:26.562Z","etag":null,"topics":["chaining-services","hacktoberfest","hacktoberfest2023","interactor","library","monads","operations","ruby","service-objects"],"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/Fretadao.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-04-14T13:47:09.000Z","updated_at":"2024-10-28T20:53:40.000Z","dependencies_parsed_at":"2024-02-18T23:37:10.562Z","dependency_job_id":"57406015-9b2a-45d3-b01e-cc62978a49fc","html_url":"https://github.com/Fretadao/f_service","commit_stats":{"total_commits":124,"total_committers":7,"mean_commits":"17.714285714285715","dds":0.3145161290322581,"last_synced_commit":"22d3e1c8bd4aa83b14f34b1af19aa4cd22b8ad8b"},"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/Fretadao/f_service","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fretadao%2Ff_service","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fretadao%2Ff_service/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fretadao%2Ff_service/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fretadao%2Ff_service/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Fretadao","download_url":"https://codeload.github.com/Fretadao/f_service/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Fretadao%2Ff_service/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259835487,"owners_count":22918990,"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":["chaining-services","hacktoberfest","hacktoberfest2023","interactor","library","monads","operations","ruby","service-objects"],"created_at":"2024-08-06T08:01:57.229Z","updated_at":"2025-07-16T14:32:45.351Z","avatar_url":"https://github.com/Fretadao.png","language":"Ruby","readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/Fretadao/f_service/master/logo.png\" height=\"150\"\u003e\n\n  \u003ch1 align=\"center\"\u003eFService\u003c/h1\u003e\n\n  \u003cp align=\"center\"\u003e\n    \u003ci\u003eSimpler, safer and more composable operations\u003c/i\u003e\n    \u003cbr\u003e\n    \u003cbr\u003e\n    \u003cimg src=\"https://img.shields.io/gem/v/f_service\"\u003e\n    \u003cimg src=\"https://github.com/Fretadao/f_service/workflows/Ruby/badge.svg\"\u003e\n    \u003ca href=\"https://github.com/Fretadao/f_service/blob/master/LICENSE\"\u003e\n      \u003cimg src=\"https://img.shields.io/github/license/Fretadao/f_service.svg\" alt=\"License\"\u003e\n    \u003c/a\u003e\n  \u003c/p\u003e\n\u003c/p\u003e\n\nFService is a small gem that provides a base class for your services (aka operations).\nThe goal is to make services simpler, safer, and more composable.\nIt uses the Result monad for handling operations.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'f_service'\n```\n\nAnd then execute:\n\n    $ bundle\n\nOr install it yourself as:\n\n    $ gem install f_service\n\n## Usage\n\n### Creating your service\n\nTo start using it, you have to create your service class inheriting from FService::Base.\n\n```ruby\nclass User::Create \u003c FService::Base\nend\n```\n\nNow, define your initializer to setup data.\n\n```ruby\nclass User::Create \u003c FService::Base\n  def initialize(name:)\n    @name = name\n  end\nend\n```\n\nThe next step is writing the `#run` method, which is where the work should be done.\nUse the methods `#Success` and `#Failure` to handle your return values.\nYou can optionally specify a list of types which represents that result and a value for your result.\n\n```ruby\nclass User::Create \u003c FService::Base\n  # ...\n  def run\n    return Failure(:no_name, :invalid_attribute) if @name.nil?\n\n    user = UserRepository.create(name: @name)\n    if user.save\n      Success(:success, :created, data: user)\n    else\n      Failure(:creation_failed, data: user.errors)\n    end\n  end\nend\n```\n\n\u003e Remember, you **have** to return an `FService::Result` at the end of your services.\n\n### Using your service\n\nTo run your service, use the method `#call` provided by `FService::Base`. We like to use the [implicit call](https://stackoverflow.com/a/19108981/8650655), but you can use it in the form you like most.\n\n```ruby\nUser::Create.(name: name)\n# or\nUser::Create.call(name: name)\n```\n\n\u003e We do **not** recommend manually initializing and running your service because it **will not**\n\u003e type check your result (and you could lose nice features like [pattern\n\u003e matching](#pattern-matching) and [service chaining](#chaining-services))!\n\n### Using the result\n\nUse the methods `#successful?` and `#failed?` to check the status of your result. If it is successful, you can access the value with `#value`, and if your service fails, you can access the error with `#error`.\n\nA hypothetical controller action using the example service could look like this:\n\n```ruby\nclass UsersController \u003c BaseController\n  def create\n    result = User::Create.(user_params)\n\n    if result.successful?\n      json_success(result.value)\n    else\n      json_error(result.error)\n    end\n  end\nend\n```\n\n\u003e Note that you're not limited to using services inside controllers. They're just PORO's (Play Old Ruby Objects), so you can use in controllers, models, etc. (even other services!).\n\n### Pattern matching\n\nThe code above could be rewritten using the `#on_success` and `#on_failure` hooks. They work similar to pattern matching:\n\n```ruby\nclass UsersController \u003c BaseController\n  def create\n    User::Create.(user_params)\n                .on_success { |value| return json_success(value) }\n                .on_failure { |error| return json_error(error) }\n  end\nend\n```\n\nOr else it is possible to specify an unhandled option to ensure that the callback will process that message anyway the\nerror.\n\n```ruby\nclass UsersController \u003c BaseController\n  def create\n    User::Create.(user_params)\n                .on_success(unhandled: true) { |value| return json_success(value) }\n                .on_failure(unhandled: true) { |error| return json_error(error) }\n  end\nend\n```\n\n```ruby\nclass UsersController \u003c BaseController\n  def create\n    User::Create.(user_params)\n                .on_success { |value| return json_success(value) }\n                .on_failure { |error| return json_error(error) }\n  end\nend\n```\n\n\u003e You can ignore any of the callbacks, if you want to.\n\nGoing further, you can match the Result type, in case you want to handle them differently:\n\n```ruby\nclass UsersController \u003c BaseController\n  def create\n    User::Create.(user_params)\n                .on_success(:user_created) { |value| return json_success(value) }\n                .on_success(:user_already_exists) { |value| return json_success(value) }\n                .on_failure(:invalid_data) { |error| return json_error(error) }\n                .on_failure(:critical_error) do |error|\n                  MyLogger.report_failure(error)\n\n                  return json_error(error)\n                end\n  end\nend\n```\n\nIt's possible to provide multiple types to the hooks too. If the result type matches any of the given types,\nthe hook will run.\n\n```ruby\nclass UsersController \u003c BaseController\n  def create\n    User::Create.(user_params)\n                .on_success(:user_created, :user_already_exists) { |value| return json_success(value) }\n                .on_failure(:invalid_data) { |error| return json_error(error) }\n                .on_failure(:critical_error) do |error|\n                  MyLogger.report_failure(error)\n\n                  return json_error(error)\n                end\n  end\nend\n```\n\n### Type precedence\n\nFService matches the service's types from left to right, from more specific to more generic.\nFor example, the following result `Failure(:unprocessable_entity, :client_error, :http_response)` will match in the following order:\n1. `:unprocessable_entity`;\n2. `:client_error`;\n3. `:http_response`;\n4. unmatched block;\n\n### Chaining services\n\nSince all services return Results, you can chain service calls making a data pipeline.\nIf some step fails, it will short circuit the call chain.\n\n```ruby\nclass UsersController \u003c BaseController\n  def create\n    result = User::Create.(user_params)\n                         .and_then { |user| User::Login.(user) }\n                         .and_then { |user| User::SendWelcomeEmail.(user) }\n\n    if result.successful?\n      json_success(result.value)\n    else\n      json_error(result.error)\n    end\n  end\nend\n```\n\nYou can use the `.to_proc` method on FService::Base to avoid explicit inputs when chaining services:\n\n```ruby\nclass UsersController \u003c BaseController\n  def create\n    result = User::Create.(user_params)\n                         .and_then(\u0026User::Login)\n                         .and_then(\u0026User::SendWelcomeEmail)\n    # ...\n  end\nend\n```\n\n### `Check` and `Try`\n\nYou can use `Check` to converts a boolean to a Result, truthy values map to `Success`, and falsey values map to `Failures`:\n\n```ruby\nCheck(:math_works) { 1 \u003c 2 }\n# =\u003e #\u003cSuccess @value=true, @types=[:math_works]\u003e\n\nCheck(:math_works) { 1 \u003e 2 }\n# =\u003e #\u003cFailure @error=false, @types=[:math_works]\u003e\n```\n\n`Try` transforms an exception into a `Failure` if some exception is raised for the given block. You can specify which exception class to watch for\nusing the parameter `catch`.\n\n```ruby\nclass IHateEvenNumbers \u003c FService::Base\n  def run\n    Try(:rand_int) do\n      n = rand(1..10)\n      raise \"Yuck! It's a #{n}\" if n.even?\n\n      n\n    end\n  end\nend\n\nIHateEvenNumbers.call\n# =\u003e #\u003cSuccess @value=9, @types=[:rand_int]\u003e\n\nIHateEvenNumbers.call\n# =\u003e #\u003cFailure @error=#\u003cRuntimeError: Yuck! It's a 4\u003e, @types=[:rand_int]\u003e\n```\n\n## Testing\n\nWe provide some helpers and matchers to make ease to test code envolving Fservice services.\n\nTo make available in the system, in the file 'spec/spec_helper.rb' or 'spec/rails_helper.rb'\n\nadd the folowing require:\n\n```rb\nrequire 'f_service/rspec'\n```\n\n### Mocking a result\n\n```rb\nmock_service(Uer::Create)\n# =\u003e Mocks a successful result with all values nil\n\nmock_service(Uer::Create, result: :success)\n# =\u003e Mocks a successful result with all values nil\n\nmock_service(Uer::Create, result: :success, types: [:created, :success])\n# =\u003e Mocks a successful result with type created\n\nmock_service(Uer::Create, result: :success, types: :created, value: instance_spy(User))\n# =\u003e Mocks a successful result with type created and a value\n\nmock_service(Uer::Create, result: :failure)\n# =\u003e Mocs a failure with all nil values\n\nmock_service(User::Create, result: :failure, types: [:unprocessable_entity, :client_error])\n# =\u003e Mocs a failure with a failure type\n\nmock_service(User::Create, result: :failure, types: [:unprocessable_entity, :client_error], value: { name: [\"can't be blank\"] })\n# =\u003e Mocs a failure with a failure type and an error value\n```\n\n### Matching a result\n\n```rb\nexpect(User::Create.(name: 'Joe')).to have_succeed_with(:created)\n\nexpect(User::Create.(name: 'Joe')).to have_succeed_with(:created).and_value(an_instance_of(User))\n\nexpect(User::Create.(name: nil)).to have_failed_with(:invalid_attributes)\n\nexpect(User::Create.(name: nil)).to have_failed_with(:invalid_attributes).and_error({ name: [\"can't be blank\"] })\n\nexpect(User::Create.(name: nil)).to have_failed_with(:invalid_attributes).and_error(a_hash_including(name: [\"can't be blank\"]))\n```\n\n## API Docs\n\nYou can access the API docs [here](https://www.rubydoc.info/gems/f_service/).\n\n## Development\n\nAfter checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that allows you to experiment.\n\nTo install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).\n\n## Contributing\n\nBug reports and pull requests are welcome on GitHub at https://github.com/Fretadao/f_service.\n\n## License\n\nThe gem is available as open-source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","funding_links":[],"categories":["Ruby"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFretadao%2Ff_service","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FFretadao%2Ff_service","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FFretadao%2Ff_service/lists"}