{"id":19685108,"url":"https://github.com/uxxman/service_record","last_synced_at":"2025-04-29T06:30:42.736Z","repository":{"id":40570990,"uuid":"256537594","full_name":"uxxman/service_record","owner":"uxxman","description":"ActiveRecord lookalike but for business model requirements. a.k.a Service Objects","archived":false,"fork":false,"pushed_at":"2024-04-27T01:27:51.000Z","size":85,"stargazers_count":7,"open_issues_count":2,"forks_count":0,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-04-27T02:29:36.466Z","etag":null,"topics":["activemodel","activerecord","business-logic","rails","service-objects","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/uxxman.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":"CODE_OF_CONDUCT.md","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-17T15:15:34.000Z","updated_at":"2024-04-27T02:29:38.269Z","dependencies_parsed_at":"2024-02-03T08:21:42.256Z","dependency_job_id":"1df91b71-9d6d-495d-8e3b-96fe6fa0731a","html_url":"https://github.com/uxxman/service_record","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uxxman%2Fservice_record","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uxxman%2Fservice_record/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uxxman%2Fservice_record/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/uxxman%2Fservice_record/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/uxxman","download_url":"https://codeload.github.com/uxxman/service_record/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224152133,"owners_count":17264629,"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":["activemodel","activerecord","business-logic","rails","service-objects","services"],"created_at":"2024-11-11T18:20:30.771Z","updated_at":"2024-11-11T18:20:31.434Z","avatar_url":"https://github.com/uxxman.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ServiceRecord\n\n[![Gem](https://img.shields.io/gem/v/service_record)](https://rubygems.org/gems/service_record)\n[![GitHub Workflow Status](https://github.com/uxxman/service_record/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/uxxman/service_record/actions/workflows/ci.yml)\n[![Code Climate coverage](https://img.shields.io/codeclimate/coverage/uxxman/service_record)](https://codeclimate.com/github/uxxman/service_record)\n[![Code Climate maintainability](https://img.shields.io/codeclimate/maintainability/uxxman/service_record)](https://codeclimate.com/github/uxxman/service_record)\n\nAn ActiveRecord lookalike but for business model requirements, a.k.a Service Objects.\n\nRails is packed with amazing tools to get you started with building your new awesome project and enforces reliable and battle-tested guidelines. One of those guideline is \"**thin controllers and fat models**\", but sometimes (actually most of the time) its difficult to follow because most business requirements are not that simple like most CRUD operations. \n\nEnters, ServiceRecord, a tiny wrapper around basic goodies included in Rails. Its similar to ActiveRecord models but their sole purpose is to perform a big/complex/muilt-step task without bloating the controllers or models.\n\n## Installation\n\nAdd this line to your application's Gemfile:\n\n```ruby\ngem 'service_record'\n```\n\nAnd then execute:\n\n```shell\n$ bundle install\n```\n\nOr install it yourself as:\n\n```shell\n$ gem install service_record\n```\n\nNow you can start creating new service classes. Rails generator can be used to create new services, e.g, the following will create a service class file `app/services/authenticate_user.rb`.\n\n```shell\nrails g service authenticate_user\n```\n\n## Usage\n\nA basic Service class looks like the following\n\n```ruby\nclass MyService \u003c ApplicationService\n  attribute :email, :string\n  attribute :password, :string\n\n  validates :email, :password, presence: true\n\n  def perform\n  end\nend\n```\n\nNow you can invoke this service by writting;\n\n```ruby\nresponse = MyService.perform(email: '', password: '')\n```\n\nThe returned response from a service will have the following useful attributes/methods,\n\n* `success?` contains true if service was performed without any errors, false otherwise\n* `failure?` contains opposite of success?\n* `result` contains returned value of service perform function\n* `errors` contains details about issues that occurr while performing the service\n\nThere is also a **perform!** (with a bang !) method which will raise **ServiceRecord::Failure** in case of service failure.\n\n## Example\n\nLet's take a real world example of users controller and the sign_in action that involes JWT authentication.\n\nWithout ServiceRecord 🙈\n\n```ruby\n# Inside controllers/users_controller.rb\ndef sign_in\n  token = nil\n  errors = []\n\n  # Basic validation\n  errors \u003c\u003c 'Email is required'    if params[:email].blank?\n  errors \u003c\u003c 'Email is invalid'     if params[:email].present? \u0026\u0026 /\\A[\\w+\\-.]+@[a-z\\d\\-.]+\\.[a-z]+\\z/i.match?(params[:email])\n  errors \u003c\u003c 'Password is required' if params[:password].blank?\n\n  if errors.size == 0\n    user = User.find_by(email: params[:email])\u0026.try(:authenticate, params[:password])\n\n    if user.present?\n      token = JsonWebToken.encode(user_id: user.id)\n    else\n      errors \u003c\u003c 'Invalid credentials'\n    end\n  end\n\n  if errors.size == 0\n    render json: token\n  else\n    render json: errors, status: :unauthorized\n  end\nend\n```\n\nWith ServiceRecord 😍\n\n```ruby\n# Inside services/authenticate_user.rb\nclass AuthenticateUser \u003c ApplicationService\n  attribute :email, :string\n  attribute :password, :string\n\n  validates :email, :password, presence: true\n  validates :email, format: { with: /\\A[\\w+\\-.]+@[a-z\\d\\-.]+\\.[a-z]+\\z/i }\n\n  def perform\n    user = User.find_by(email: email).try(:authenticate, password)\n\n    if user.present?\n      JsonWebToken.encode(user_id: user.id)\n    else\n      errors.add :authentication, 'invalid credentials'\n    end\n  end\nend\n\n\n# Inside controllers/users_controller.rb\ndef sign_in\n  response = AuthenticateUser.perform(params.permit(:email, :password))\n\n  if response.success?\n    render json: response.result\n  else\n    render json: response.errors, status: :unauthorized\n  end\nend\n```\n\n## Validations\n\nServiceRecord extends on `ActiveModel::Validations`, so, everything that you can do there can be done inside a service class and ServiceRecord will make sure that a service only runs the perform function when all validations are passed, otherwise `errors` will contain details about the validation issues.\n\n\n## Custom Errors\n\nJust like validation errors, you can also add custom errors that you want to be reported. Use them to handle errors which are not related to input parameters validation. E.g.\n\n```ruby\nerrors.add :authentication, 'invalid credentials'\n```\n\n## Callbacks\n\nYou can also add callbacks on the perform function similar to [ActiveJob's](https://edgeguides.rubyonrails.org/active_job_basics.html#callbacks) perform function. E.g.\n\n```ruby\nclass SampleService \u003c ApplicationService\n  before_perform :do_something\n\n  def perform\n  end\n\n  private\n    \n  def do_something\n  end\nend\n```\n\nAvailble callbacks are `before_perform`, `after_perform` and `around_perform`. If a `before_perform` calls `throw :abort`, the callback chain is hallted and perform function will not be called.\n\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 will allow 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\n1. Fork it\n2. Create your feature branch (`git checkout -b my-new-feature`)\n3. Commit your changes (`git commit -am 'Add some feature'`)\n4. Push to the branch (`git push origin my-new-feature`)\n5. Create new Pull Request\n\nBug reports and pull requests are welcome on GitHub at https://github.com/uxxman/service_record.\n\n\n## License\n\nThe gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuxxman%2Fservice_record","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fuxxman%2Fservice_record","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fuxxman%2Fservice_record/lists"}