An easy and intuitive way to add Service Objects to your rails application`.
Services generated with this gem will follow a few simple rules:
- Must operate on a single object (though they can perform trivial updates on other objects)
- Must return a
Result
object (more on that in theUsage
section) - Must only have a single public method
call
all other methods are private.
There are many times where we end up with logic that doesn't necessarily belong to a particular model due to the complextity of the operation, so it ends up in the controller which also is not the best place for it to live. That's where Service Objects come in to the rescue. They allow you to move that logic to it's own object, that is easily testable due to it's nature as a PORO (Plain Old Ruby Object).
Add this line to your application's Gemfile:
gem 'active_services'
And then execute:
$ bundle
Or install it yourself as:
$ gem install active_services
And then to get set up run
$ bundle exec rails generator active_services:install
Running the install task sets you up with some new files. Let's explore how to use them.
The main thing to notice is the new app/services
directory. In this directory you will also see the active_service.rb
file which looks like this:
class ActiveService < ActiveServices::ServiceObject
# Your custom code to be shared across all service objects here
# All your service objects should inherit from the ActiveService class
end
Which is pretty barebones to start off with, but it is here that you can define common behavior between all your Service Objects.
To start using Service Objects in your rails application is as easy as running:
$ bundle exec rails generate active_services:new [MODEL] [SERVICE]
For example, if you're creating a service to create new instances of a Comment
model you would run:
$ bundle exec rails generate active_services:new Comments Create
Which would generate the Create
service in the app/services/comments
directory (and create that directory if it already doesn't exist) like this:
# app/services/comments/create.rb
module Comments
class Create < ActiveService
def initialize
end
def call
end
end
end
And that's it! You've got yourself a brand new Service Object to start working with.
Now that you've got your Service Object let's start adding some functionality to it. Let's say that comments can be nested, so when creating a new one, if it's the child of another comment, we have to update the parent comment to have the child comments id.
# app/services/comments/create.rb
module Comments
class Create < ActiveService
attr_reader :text, :service
attr_accessor :parent
def initialize(text:, author:, parent_id: nil)
@text = text
@author = author
@parent = Comment.find(parent_id) || nil
end
def call
comment = create_comment
result(comment)
end
private
def create_comment
comment = Comment.new(text: text, author: author, parent: parent)
saved = comment.save
update_parent(comment) if can_update_parent(saved)
end
def can_update_parent?(saved)
parent && saved
end
def update_parent(comment)
parent.update_attributes(parent: parent)
end
end
end
And to use this Service Object in your Controller
# app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
service = Comments::Create.new(text: params[:text], author: params[:author], parent_id: params[:parent_id])
result = service.call()
if result.success?
@comment = result.model
redirect :index
else
render :new, errors: result.errors
end
end
end
This code here will create the comment, update the parent comment if necessary, and return the Result Object. The next section will take a look at the Result Object.
Each Service Object should return a Result Object, which is returned by using the result
method which is defined in the base
ServiceObject
model. This object has three public methods available for use:
.model
: This returns the model that was operated on by the Service Object..errors
: This will return the array of full error messages (if any) for the object operated on by the Service Object..success?
: This will return a boolean based on whether the operation was successful or not.
After 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.
To 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.
Bug reports and pull requests are welcome on GitHub at https://github.com/jm96441n/active_services. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct. For more information check out our code of conduct.
The gem is available as open source under the terms of the MIT License.
Everyone interacting in the ActiveServices project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.