Model methods that need to return either success or an error message

Any opinions on whether methods that need to return either “success” or a detailed error message should use an Exception to contain the error message or should return nil for success, and a String with the error message in the case of failure.

I’ve used both. Some folks like to only use exceptions for “exceptional” cases. However, raising an exception can be simpler than conditionals.

Thoughts?

I wouldn’t use an exception unless it’s truly an exceptional case. Paraphrasing Avdi Grimm, when you are thinking about raising an exception, ask yourself, “Am I prepared to end the program because of this?”

Would it be possible for your method to return a status object that indicates success or failure along with a message?

What does it look like “a status object” here? could you give an example?

Off the top of my head, here’s an example of a status object.

class CommandStatus
  # Some handy constants to give meaning to some booleans we'll be using
  SUCCESS = true
  FAILURE = false

  attr_reader :message

  # A factory method to easily create a status that represents success
  def self.success
    new(SUCCESS)
  end

  # A factory method to easily create a status that represents failure
  def self.failure(message)
    new(FAILURE, message)
  end

  def initialize(success, message=nil)
    @success = success
    @message = message
  end

  The method users of this class would call to check if a status represents success
  def success?
    @success == SUCCESS
  end

If the method you were talking about, rather than returning nil for success and a string with the failure message for failures, return either CommandStatus.success or CommandStatus.failure(your_failure_message) on failures.

The code that uses receives this status object returned to it, would do something like:

if status.success?
  # handle success
else
  puts status.message
end

I’ve done something very similar to CommandStatus, except that my class MethodResponse is geared toward refactoring controller logic into a service object. It seems to work very nicely. The apply and set_flash methods are helpers to avoid duplicating the same code in multiple controller methods. Here it is. Any suggestions? Opinions?

class MethodResponse
  attr_reader :flash_type
  attr_reader :flash_message
  attr_reader :http_status_code
  attr_reader :data
  attr_reader :redirect_path
  attr_reader :session_data

  # http_status can be either symbol or number
  def initialize(flash_type: :notice,
    flash_now: false,
    flash_message: "",
    http_status: :ok,
    data: {},
    redirect_path: nil,
    session_data: {})
    @flash_type       = flash_type
    @flash_now        = flash_now
    @flash_message    = flash_message
    @http_status_code = Rack::Utils::status_code(http_status)
    @data             = data
    @redirect_path    = redirect_path
    @session_data     = session_data
  end

  def self.symbol_to_status_code(symbol)
    Rack::Utils::SYMBOL_TO_STATUS_CODE[symbol]
  end

  # Currently only supports redirects
  def apply(controller)
    @session_data.each { |k, v| controller.session[k] = v }
    if @flash_message.present?
      if @flash_now
        controller.flash.now[@flash_type] = @flash_message
      else
        controller.flash[@flash_type] = @flash_message
      end
    end
    controller.redirect_to @redirect_path if @redirect_path.present?
  end

  def set_flash(flash)
    flash[flash_type] = flash_message if flash_message.present?
  end
end