Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/pawurb/smart_init
A simple gem for eliminating Ruby initializers boilerplate code, and providing unified service objects API
https://github.com/pawurb/smart_init
Last synced: 2 days ago
JSON representation
A simple gem for eliminating Ruby initializers boilerplate code, and providing unified service objects API
- Host: GitHub
- URL: https://github.com/pawurb/smart_init
- Owner: pawurb
- License: mit
- Created: 2017-05-04T13:11:11.000Z (over 7 years ago)
- Default Branch: main
- Last Pushed: 2023-08-19T17:06:54.000Z (about 1 year ago)
- Last Synced: 2024-10-31T14:22:33.560Z (11 days ago)
- Language: Ruby
- Homepage:
- Size: 56.6 KB
- Stars: 179
- Watchers: 5
- Forks: 5
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE.txt
Awesome Lists containing this project
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)
Do you find yourself writing a lot of boilerplate code like this?
```ruby
def initialize(network_provider, api_token)
@network_provider = network_provider
@api_token = api_token
enddef self.call(network_provider, api_token)
new(network_provider, api_token).call
end
```This 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.
**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.
Check 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.
## Installation
In your Gemfile
```ruby
gem 'smart_init'
```## API
You can use it either by extending a module:
```ruby
require 'smart_init'class ApiClient
extend SmartInitinitialize_with :network_provider, :api_token
end
```or subclassing:
```ruby
class ApiClient < SmartInit::Base
initialize_with :network_provider, :api_token
end
```Now you can just:
```ruby
object = ApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')
# , @api_token="secret_token">
```If you omit a required attribute an `ArgumentError` will be thrown:
```ruby
client = ApiClient.new(network_provider: Faraday.new)# ArgumentError (missing required attribute api_token)
```### Making the object callable
You can use the `is_callable` method:
```ruby
class Calculator < SmartInit::Base
initialize_with :data
is_callabledef call
...
result
end
endCalculator.call(data: data) => result
```Optionally you can customize a callable method name:
```ruby
class Routine < SmartInit::Base
initialize_with :params
is_callable method_name: :run!def run!
...
end
endRoutine.run!(params: params)
```### Default arguments
You can use hash based, default argument values:
```ruby
class Adder < SmartInit::Base
initialize_with :num_a, num_b: 2
is_callabledef call
num_a + num_b
end
endAdder.call(num_a: 2) => 4
Adder.call(num_a: 2, num_b: 3) => 5
```### Readers access
Contrary to using Struct, by default the reader methods are not publicly exposed:
```ruby
client = ApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')
client.api_token => # NoMethodError (private method `api_token' called for #)
```Optionally 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.
```ruby
class PublicApiClient < SmartInit::Base
initialize_with :network_provider, :api_token, public_readers: true
endclient = PublicApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')
client.network_provider => #
client.api_token => 'secret_token'
``````ruby
class SemiPublicApiClient < SmartInit::Base
initialize_with :network_provider, :api_token, public_readers: [:network_provider]
endclient = SemiPublicApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')
client.network_provider => #
client.api_token => 'secret_token' => # NoMethodError (private method `api_token' called for #)
```### Accessors access
Similarly, this is how it would look if you tried to use a writer method:
```ruby
client = ApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')
client.api_token = 'new_token' => # NoMethodError (private method `api_token=' called for #)
```Optionally 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.
```ruby
class PublicApiClient < SmartInit::Base
initialize_with :network_provider, :api_token, public_accessors: true
endclient = PublicApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')
client.network_provider => #
client.network_provider = Typhoeus::Request.new(...) => #
client.api_token => 'secret_token'
client.api_token = 'new_token' => 'new_token'
``````ruby
class SemiPublicApiClient < SmartInit::Base
initialize_with :network_provider, :api_token, public_accessors: [:network_provider]
endclient = SemiPublicApiClient.new(network_provider: Faraday.new, api_token: 'secret_token')
client.network_provider => #
client.network_provider = Typhoeus::Request.new(...) => #
client.api_token => # NoMethodError (private method `api_token' called for #)
client.api_token = 'new_token' => # NoMethodError (undefined method `api_token=' called for #)
```Finally, you can mix them together like this:
```ruby
class PublicReadersSemiPublicAccessorsApiClient < SmartInit::Base
initialize_with :network_provider, :api_token, :timeout,
public_readers: true, public_accessors: [:network_provider]
endclient = PublicReadersSemiPublicAccessorsApiClient.new(
network_provider: Faraday.new, api_token: 'secret_token', timeout_length: 100
)
client.network_provider => #
client.network_provider = Typhoeus::Request.new(...) => #
client.api_token => 'secret_token'
client.api_token = 'new_token' => # NoMethodError (undefined method `api_token=' called for #)
client.timeout_length => 100
client.timeout_length = 150 => # NoMethodError (undefined method `timeout_length=' called for #)
``````ruby
class SemiPublicReadersSemiPublicAccessorsApiClient < SmartInit::Base
initialize_with :network_provider, :api_token, :timeout,
public_readers: [:timeout], public_accessors: [:network_provider]
endclient = SemiPublicReadersSemiPublicAccessorsApiClient.new(
network_provider: Faraday.new, api_token: 'secret_token', timeout_length: 100
)
client.network_provider => #
client.network_provider = Typhoeus::Request.new(...) => #
client.api_token => # NoMethodError (private method `api_token' called for #)
client.api_token = 'new_token' => # NoMethodError (undefined method `api_token=' called for #)
client.timeout_length => 100
client.timeout_length = 150 => # NoMethodError (undefined method `timeout_length=' called for #)
```