Operation

Imagine you have a class like this:

class User
  def delete(id)
    @users.delete id
  end
end

What does it return? True? The User? an ID? What if an error occured? How does anyone calling User.delete 42 know what happened?

This is where Operation comes in. You would just always return an Operation. That Object holds information about what happened, like so:

class User
  def delete(id)
    return Operation.new(code: :id_missing) unless id
    return Operation.new(code: :invalid_id, id: id) unless id.match /[a-f]{8}/

    user = @users[id]
    if @users.delete id
      Operation.new success:true, code: :user_deleted, user: user
    else
      Operation.new code: :deletion_failed
    end
  rescue ConnectionError
      Operation.new code: :deletion_failed_badly
  end
end

This will give you this joyful, consistent, conventional, implementation-unaware programming feeling:

  operation = User.delete 42
  if operation.success?
    puts "It worked! You deleted the user #{operation.meta.user.first_name}"
  else
    puts "Oh, could not delete User with ID #{operation.object} because #{operation.code}"
  end

For your convenience you can use Operations#failure and Operations.success:

class User
  def delete(id)
    return Operations.failure(:id_missing) unless id
    return Operations.failure(:invalid_id, id: id) unless id.match /[a-f]{8}/

    user = @users[id]
    if @users.delete id
      Operations.success :user_deleted, object: user
    else
      Operations.failure :deletion_failed
    end
  rescue ConnectionError
      Operation.failure :deletion_failed_badly
  end
end

#object is just a shortcut to #meta.object:

  operation = User.delete 42
  operation.success? # => true
  operation.object # => <#User id=42>
  operation.meta # => <#Hashie::Mash object: <#User id=42>>
  operation. # => { object: <#User id=42> }
  operation.failure? # => false

Requirements

  • Ruby >= 1.9

MIT 2014 halo. See MIT-LICENSE.