GoodServices
This is a very simple gem I've created to keep the logic I've been frequently using in my apps to standardize my Service Objects. I intend to keep it as simple as possible so it can be versatile and easy to use, but ideas to improve it and pull-requests are welcome :)
Installation
Add this line to your application's Gemfile:
gem 'good_services'
And then execute:
$ bundle
Or install it yourself as:
$ gem install good_services
Usage
The best way to understand this gem is to have a look at the GoodServices::Base file. It is the only file that matters for this gem (really).
To get started just create a new class inheriting from GoodServices::Base
Sample Service Object:
module Pet
#
# Errors
#
InvalidPetError = Class.new(StandardError)
NoNewNameError = Class.new(StandardError)
#
# Service
#
class Renamer < GoodServices::Base
rescuable_from InvalidPetError, NoNewNameError
attr_reader :new_name
def initialize(pet: pet, new_name: new_name)
@record = pet
@new_name = new_name
end
def perform
@record.name = new_name
end
def validate
raise InvalidPetError unless record.respond_to? :name
raise NoNewNameError unless new_name
end
end
end
By default the following variables are available as attr_readers:
# Used when the service operates on only one object
@record
# Used when the service operates on a collection of objects
@collection
# If you run the service with the safe ".run" method, the @error
# variable will be filled with any rescuable error defined in the service.
@error
--
Rescuable Exceptions
You can define a list of exceptions that are actually expected to happen eventually and the safe ".run" method will rescue you out of them returning false instaead.
The exception will be saved in the @error variable and accessible to the outside for custom error messages and etc. Here is how you use it:
class Renamer < GoodServices::Base
rescuable_from InvalidPetError, NoNewNameError
(...)
end
--
Methods
Perform
The only required method for your service is the perform method. It will have the logic called by your .run and .run! methods.
def perform
@record.name = new_name
end
Validate
This method is optional, but will be called before your perform method when you ".run" or ".run!" the service. It should raise predictable errors based on your validations, for example:
def validate
raise InvalidPetError unless record.respond_to? :name
raise NoNewNameError unless new_name
end
*This syntax is not my masterpiece, I appreciate any ideas of a better way to do this.
Development
To-do
- Improve the way validations are done (right now the "raise x if y" stuff is kinda ugly)
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/victoryam/good_services.
License
The gem is available as open source under the terms of the MIT License.