CivilService

Build Status

CivilService is a tiny framework for service objects in Rails apps. With CivilService, you can use ActiveModel validations to do pre-flight checks before the service runs, and create your own result object classes to capture the results of complex operations.

CivilService was extracted from Intercode, a web app for convention organizers and participants.

Installation

Add this line to your application's Gemfile:

gem 'civil_service'

And then execute:

$ bundle

Or install it yourself as:

$ gem install civil_service

What CivilService does

CivilService::Service is really a pretty tiny class. It does, however, have some opinions that create a potentially-useful abstraction for app developers:

  • When called, services always return a result object that responds to (at least) #success?, #failure?, and #errors. This lets your code paths that call services be consistent and simple. (If you want to return more information as a result of running the service, it's easy to define a custom result class for your service.)
  • Services include ActiveModel::Validations so they can easily do pre-flight checks. That means you can call my_service.valid? and my_service.errors just like you can for a model, and it also means that the service will fail if it's not valid.

Basic example

Here's a simple service that changes a user's password in a hypothetical Rails app, and sends a notification email about it:

class PasswordChangeService < CivilService::Service
  validate :ensure_valid_password

  attr_reader :user, :new_password

  def initialize(user:, new_password:)
    @user = user
    @new_password = new_password
  end

  private

  def inner_call
    user.update!(password: new_password)
    UserMailer.password_changed(user).deliver_later
    success
  end

  def ensure_valid_password
    return if new_password.length >= 8
    errors.add(:base, "Passwords must be at least 8 characters long")
  end
end

You might call this from a controller action like this:

class UsersController < ApplicationController
  def change_password
    service = PasswordChangeService.new(user: current_user, new_password: params[:password])
    result = service.call

    if result.success?
      redirect_to root_url, notice: "Your password has been changed."
    else
      flash[:alert] = result.errors.full_messages.join(', ')
    end
  end
end

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test 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.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/neinteractiveliterature/civil_service.