In a previous post we saw how to create service objects with CRUD interface. Now let’s see why you should care it is useful.

REST architecture is about CRUD over http. Rails uses RESTful routes. And in every RESTful controller action you may find something like this:

def create
  @post = Post.new(post_params)

  if @post.save
    redirect_to @post, notice: 'Post was successfully updated.' }
  else
    flash[:alert] = "Error updating post: #{@post.errors.full_messages.to_sentence}"
    render :edit
  end
end

def update
  @post = Post.find(params[:id])

  if @post.update(post_params)
    redirect_to @post, notice: 'Post was successfully created.'
  else
    flash[:alert] = "Error creating post: #{@post.errors.full_messages.to_sentence}"
    render :new
  end
end

Not very DRY, isn’t it? The only differences between these actions are:

  • Method called on a model
  • Action on success
  • Action on error
  • Text in flash message on success
  • Text in flash message on error

What if we can extract them? We can decide which method to call on a model, where to redirect/render after, which flash messages to show by inspecting the model itself and a controller’s action.

But this is fine when you are dealing with ActiveRecord (ActiveModel to be precise) models. What if we are using service objects? One of the “canonical” way to use service objects is by calling call method, like this:

# app/services/create_membership_service.rb
class CreateMembershipService
  def call(options)
    # creating associations, sending notifications, etc
  end
end

# app/services/destroy_membership_service.rb
class DestroyMembershipService
  def call(options)
    # destroing associations, sending notifications, etc
  end
end

# app/controllers/memberships_controller.rb
class MembershipsController < ApplicationController
  def create
    if CreateMembershipService.new.call
      redirect_to :back, notice: "Membership has been created"
    else
      flash[:alert] = "Error creating membership"
      render :edit
    end
  end

  def destroy
    if DestroyMembershipService.new.call
      redirect_to :back, notice: "Membership has been destroyed"
    else
      flash[:alert] = "Error destroying membership"
      render :edit
    end
  end
end

Again, not very DRY. And here comes services which behave like ActiveModel and use CRUD interface.

Also, let’s add some magic method which will call appopriate method on an object, redirect and show flashes:

# app/services/membership_service.rb
class MembershipService
  include ActiveModel::Model

  def save
    # creating associations, sending notifications, etc
    # return true if ok, false otherwise and add messages to `errors`
  end

  def destroy
    # destroing associations, sending notifications, etc
    # return true if ok, false otherwise and add messages to `errors`
  end
end

# app/controllers/memberships_controller.rb
class MembershipsController < ApplicationController
  def create
    magic MembershipService.new
  end

  def destroy
    magic MembershipService.new
  end
end

What inside magic function?

  • Check which actions it is called from by inspecting caller
  • Call save or destroy according to the action
  • Redirect to appropriate url or render a template according to result of calling save or destroy
  • Show appropriate flash messages. In case of error - extract messages from errors object

Not so magic, isn’t it? Luckily you don’t have to implement all this logic because I’ve already done this in crud_responder gem.

Now it’s very handy to have all domain objects (models and services) with CRUD interface so you can use them in controllers without worrying whether it’s ActiveRecord model or a service object.