ruby-on-railsrails-apiservice-object

Service Object returning status


I am making a rails json api which uses service objects in controllers actions and basing on what happend in service I have to render proper json. The example looks like this.

star_service.rb

class Place::StarService
  def initialize(params, user)
    @place_id = params[:place_id]
    @user = user
  end

  def call
    if UserStaredPlace.find_by(user: user, place_id: place_id)
      return #star was already given
    end

    begin
      ActiveRecord::Base.transaction do
        Place.increment_counter(:stars, place_id)
        UserStaredPlace.create(user: user, place_id: place_id)
      end
    rescue
      return #didn't work
    end

    return #gave a star
  end

  private

  attr_reader :place_id, :user
end

places_controller.rb

def star
  foo_bar = Place::Star.new(params, current_user).call

  if foo_bar == #sth
    render json: {status: 200, message: "sth"}
  elsif foo_bar == #sth
    render json: {status: 200, message: "sth"}
  else
    render json: {status: 400, message: "sth"}
end

And my question is, if I should return plain text from service object or there is some better approach?


Solution

  • It'll be opinionated of course but still...

    Rendering views with data, returning data, redirecting etc are the responsibilities of controllers. So any data, plain text and other things you have to handle in your controller.

    Service object have to provide one single public method for any huge complex operation performing. And obviously that method has to return simple value which tells controller if operation was completed successfully or not. So it must be true or false. Maybe some recognizable result (object, simple value) or errors hash. It's the ideal use case of course but it's the point.

    As for your use case your service may return the message or false. And then controller will render that message as json.

    And your star method must live in your controller, be private probably and looks like that:

    def star
      foo_bar = Place::Star.new(params, current_user).call
    
      if foo_bar
        render json: {status: 200, message: foobar} 
      else
        render json: {status: 400, message: "Failed"}
      end
    end
    

    Your Service:

    class Place::StarService
      def initialize(params, user)
        @place_id = params[:place_id]
        @user = user
      end
    
      def call
        if UserStaredPlace.find_by(user: user, place_id: place_id)
          return "Message when star is already given"
        end
    
        begin
          ActiveRecord::Base.transaction do
            Place.increment_counter(:stars, place_id)
            UserStaredPlace.create(user: user, place_id: place_id)
          end
        rescue
          return false
        end
    
        return "Message if gave a star"
      end
    
      private
    
      attr_reader :place_id, :user
    end