ruby-on-railsrubyrails-apiservice-object

Rails api how to handle service object exceptions in controller


I wanted use Service Object in my Rails API. Inside my Service Object I want to save Model and return true if saving was successful, but if saving was unsuccessful then I want to return false and send error messages. My Service Object looks like that:

class ModelSaver
  include ActiveModel::Validations
  
  def initialize(params)
    @params = params
  end

  def save_model
    model ||= Model.new(params)

    return false unless model.valid?
    model.save!

  rescue ActiveRecord::RecordNotSaved, ActiveRecord::RecordInvalid
    model.errors.add(:base, 'here I want default error message')
    false
  end

  private

  attr_reader :params
end

The problem is that I don't know how to send errors messages in response. When I try to send service_object.errors.messages it displays an empty array to me. It looks like that in Controller:

class ModelController < ApplicationController
...
  def create
    service_object = ModelSaver.new(params)

    if service_object.save_model
       render json: service_object
    else
       render json: { errors: service_object.errors.messages }, status: :unprocessable_entity
    end
  end
...
end

So how can I get Model errors from Service Object inside Controller?


Solution

  • You can solve this by providing methods to your service object that allow returning the model or the errors even after save_model returned with false.

    I would change the service object to

    class ModelSaver
      include ActiveModel::Validations
    
      attr_reader :model
    
      def initialize(params)
        @params = params
      end
    
      def errors
        @model.errors
      end
    
      def save_model
        @model ||= Model.new(params)
    
        return false unless model.valid?
        @model.save!
    
      rescue ActiveRecord::RecordNotSaved, ActiveRecord::RecordInvalid
        @model.errors.add(:base, 'here I want default error message')
        false
      end
    
      private
    
      attr_reader :params
    end
    

    and the controller method to

    def create
      service_object = ModelSaver.new(params)
    
      if service_object.save_model
         render json: service_object.model
      else
         render json: { errors: service_object.errors.messages }, status: :unprocessable_entity
      end
    end