{"id":13878600,"url":"https://github.com/pawurb/smart_init","last_synced_at":"2025-04-05T01:05:33.864Z","repository":{"id":55613145,"uuid":"90264759","full_name":"pawurb/smart_init","owner":"pawurb","description":"A simple gem for eliminating Ruby initializers boilerplate code, and providing unified service objects API","archived":false,"fork":false,"pushed_at":"2023-08-19T17:06:54.000Z","size":58,"stargazers_count":179,"open_issues_count":0,"forks_count":4,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-03-29T00:05:59.685Z","etag":null,"topics":[],"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/pawurb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2017-05-04T13:11:11.000Z","updated_at":"2025-03-15T17:45:07.000Z","dependencies_parsed_at":"2024-01-13T20:27:54.404Z","dependency_job_id":"35bcac72-b782-456d-a8e7-95468ab5e37d","html_url":"https://github.com/pawurb/smart_init","commit_stats":null,"previous_names":[],"tags_count":23,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawurb%2Fsmart_init","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawurb%2Fsmart_init/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawurb%2Fsmart_init/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pawurb%2Fsmart_init/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pawurb","download_url":"https://codeload.github.com/pawurb/smart_init/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247271525,"owners_count":20911587,"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":[],"created_at":"2024-08-06T08:01:54.383Z","updated_at":"2025-04-05T01:05:33.839Z","avatar_url":"https://github.com/pawurb.png","language":"Ruby","funding_links":[],"categories":["Ruby"],"sub_categories":[],"readme":"# Smart Init - Simple service objects in Ruby [![Gem Version](https://badge.fury.io/rb/smart_init.svg)](https://badge.fury.io/rb/smart_init) [![GH Actions](https://github.com/pawurb/smart_init/actions/workflows/ci.yml/badge.svg)](https://github.com/pawurb/smart_init/actions)\n\nDo you find yourself writing a lot of boilerplate code like this?\n\n```ruby\ndef initialize(network_provider, api_token)\n  @network_provider = network_provider\n  @api_token = api_token\nend\n\ndef self.call(network_provider, api_token)\n  new(network_provider, api_token).call\nend\n```\n\nThis gem provides a simple DSL for getting rid of it. It offers an alternative to using `Struct.new` which does not check for number of parameters provided in initializer, exposes getters and instantiates unecessary class instances.\n\n**Smart Init** offers a unified API convention for stateless service objects, accepting values in initializer and exposing one public class method `call` which instantiates new objects and accepts arguments passed to initializer.\n\nCheck out [this blog post](https://pawelurbanek.com/2018/02/12/ruby-on-rails-service-objects-and-testing-in-isolation/) for my reasoning behind this approach to service object pattern.\n\n## Installation\n\nIn your Gemfile\n\n```ruby\ngem 'smart_init'\n```\n\n## API\n\nYou can use it either by extending a module:\n\n```ruby\nrequire 'smart_init'\n\nclass ApiClient\n  extend SmartInit\n\n  initialize_with :network_provider, :api_token\nend\n```\n\nor subclassing:\n\n```ruby\nclass ApiClient \u003c SmartInit::Base\n  initialize_with :network_provider, :api_token\nend\n```\n\nNow you can just:\n\n```ruby\nobject = ApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')\n# \u003cApiClient:0x007fa16684ec20 @network_provider=Faraday\u003c...\u003e, @api_token=\"secret_token\"\u003e\n```\n\nIf you omit a required attribute an `ArgumentError` will be thrown:\n\n```ruby\nclient = ApiClient.new(network_provider: Faraday.new)\n\n# ArgumentError (missing required attribute api_token)\n```\n\n### Making the object callable\n\nYou can use the `is_callable` method:\n\n```ruby\nclass Calculator \u003c SmartInit::Base\n  initialize_with :data\n  is_callable\n\n  def call\n    ...\n    result\n  end\nend\n\nCalculator.call(data: data) =\u003e result\n```\n\nOptionally you can customize a callable method name:\n\n```ruby\nclass Routine \u003c SmartInit::Base\n  initialize_with :params\n  is_callable method_name: :run!\n\n  def run!\n    ...\n  end\nend\n\nRoutine.run!(params: params)\n```\n\n### Default arguments\n\nYou can use hash based, default argument values:\n\n```ruby\nclass Adder \u003c SmartInit::Base\n  initialize_with :num_a, num_b: 2\n  is_callable\n\n  def call\n    num_a + num_b\n  end\nend\n\nAdder.call(num_a: 2) =\u003e 4\nAdder.call(num_a: 2, num_b: 3) =\u003e 5\n```\n\n### Readers access\n\nContrary to using Struct, by default the reader methods are not publicly exposed:\n\n```ruby\nclient = ApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')\nclient.api_token =\u003e # NoMethodError (private method `api_token' called for #\u003cApiClient:0x000..\u003e)\n```\n\nOptionally you can make all or subset of readers public using the `public_readers` config option. It accepts `true` or an array of method names as an argument.\n\n```ruby\nclass PublicApiClient \u003c SmartInit::Base\n  initialize_with :network_provider, :api_token, public_readers: true\nend\n\nclient = PublicApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')\nclient.network_provider =\u003e #\u003cFaraday::Connection:0x000...\u003e\nclient.api_token =\u003e 'secret_token'\n```\n\n```ruby\nclass SemiPublicApiClient \u003c SmartInit::Base\n  initialize_with :network_provider, :api_token, public_readers: [:network_provider]\nend\n\nclient = SemiPublicApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')\nclient.network_provider =\u003e #\u003cFaraday::Connection:0x000...\u003e\nclient.api_token =\u003e 'secret_token' =\u003e # NoMethodError (private method `api_token' called for #\u003cSemiPublicApiClient:0x000...\u003e)\n```\n\n### Accessors access\n\nSimilarly, this is how it would look if you tried to use a writer method:\n\n```ruby\nclient = ApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')\nclient.api_token = 'new_token' =\u003e # NoMethodError (private method `api_token=' called for #\u003cApiClient:0x000..\u003e)\n```\n\nOptionally you can make all or subset of accessors public using the `public_accessors` config option. It accepts `true` or an array of method names as an argument. This will provide both reader and writer methods publicly.\n\n```ruby\nclass PublicApiClient \u003c SmartInit::Base\n  initialize_with :network_provider, :api_token, public_accessors: true\nend\n\nclient = PublicApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')\nclient.network_provider =\u003e #\u003cFaraday::Connection:0x000...\u003e\nclient.network_provider = Typhoeus::Request.new(...) =\u003e #\u003cTyphoeus::Request:0x000...\u003e\nclient.api_token =\u003e 'secret_token'\nclient.api_token = 'new_token' =\u003e 'new_token'\n```\n\n```ruby\nclass SemiPublicApiClient \u003c SmartInit::Base\n  initialize_with :network_provider, :api_token, public_accessors: [:network_provider]\nend\n\nclient = SemiPublicApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')\nclient.network_provider =\u003e #\u003cFaraday::Connection:0x000...\u003e\nclient.network_provider = Typhoeus::Request.new(...) =\u003e #\u003cTyphoeus::Request:0x000...\u003e\nclient.api_token =\u003e # NoMethodError (private method `api_token' called for #\u003cSemiPublicApiClient:0x000...\u003e)\nclient.api_token = 'new_token' =\u003e # NoMethodError (undefined method `api_token=' called for #\u003cSemiPublicApiClient:0x000...\u003e)\n```\n\nFinally, you can mix them together like this:\n\n```ruby\nclass PublicReadersSemiPublicAccessorsApiClient \u003c SmartInit::Base\n  initialize_with :network_provider, :api_token, :timeout,\n                  public_readers: true, public_accessors: [:network_provider]\nend\n\nclient = PublicReadersSemiPublicAccessorsApiClient.new(\n           network_provider: Faraday.new, api_token: 'secret_token', timeout_length: 100\n         )\nclient.network_provider =\u003e #\u003cFaraday::Connection:0x000...\u003e\nclient.network_provider = Typhoeus::Request.new(...) =\u003e #\u003cTyphoeus::Request:0x000...\u003e\nclient.api_token =\u003e 'secret_token'\nclient.api_token = 'new_token' =\u003e # NoMethodError (undefined method `api_token=' called for #\u003cSemiPublicApiClient:0x000...\u003e)\nclient.timeout_length =\u003e 100\nclient.timeout_length = 150 =\u003e # NoMethodError (undefined method `timeout_length=' called for #\u003cSemiPublicApiClient:0x000...\u003e)\n```\n\n```ruby\nclass SemiPublicReadersSemiPublicAccessorsApiClient \u003c SmartInit::Base\n  initialize_with :network_provider, :api_token, :timeout,\n                  public_readers: [:timeout], public_accessors: [:network_provider]\nend\n\nclient = SemiPublicReadersSemiPublicAccessorsApiClient.new(\n           network_provider: Faraday.new, api_token: 'secret_token', timeout_length: 100\n         )\nclient.network_provider =\u003e #\u003cFaraday::Connection:0x000...\u003e\nclient.network_provider = Typhoeus::Request.new(...) =\u003e #\u003cTyphoeus::Request:0x000...\u003e\nclient.api_token =\u003e # NoMethodError (private method `api_token' called for #\u003cSemiPublicReadersSemiPublicAccessorsApiClient:0x000...\u003e)\nclient.api_token = 'new_token' =\u003e # NoMethodError (undefined method `api_token=' called for #\u003cSemiPublicReadersSemiPublicAccessorsApiClient:0x000...\u003e)\nclient.timeout_length =\u003e 100\nclient.timeout_length = 150 =\u003e # NoMethodError (undefined method `timeout_length=' called for #\u003cSemiPublicReadersSemiPublicAccessorsApiClient:0x000...\u003e)\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpawurb%2Fsmart_init","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpawurb%2Fsmart_init","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpawurb%2Fsmart_init/lists"}