TL;DR
Tron is a minimalistic combination of a monad and value object, implemented in a few lines of code.
- Return 
Tron.success(:it_worked)orTron.failure(:aww_too_bad)from a method, to explain why and how it succeded or failed. That returns an immutable Data (value object) that responds toresult.success?andresult.failure?. - Add metadata as a second argument: 
Tron.failure(:nopes, error_code: 404)which you then can access:result.error_code #=> 404. - The result can also be queried with 
result.success #=> :it_workedandresult.failure #=> nil. - Chaining can make your code cleaner: 
result.on_success { download }.on_failure { show_message } 
Introduction
Imagine you have a class like this:
class User
  def self.delete(id)
    @users.delete id
  end
endIt's not clear from the code what this method returns. true?, a User?, a user ID?. What if a network error occurs, how does anyone calling User.delete 42 know what happened?
Indeed, it is not even clear what "successful" means in the context of this message - if there is no user and you try to delete one, is that considered a "failure"?
Let's rewrite the method using Tron:
class User
  def self.delete(id)
    return Tron.failure(:id_missing) unless id
    return Tron.failure(:invalid_id, id: id) unless id.match /[a-f]{8}/
    user = @users[id]
    if @users.delete id
      Tron.success :user_deleted, user: user
    else
      Tron.success :already_deleted, id: id # Notice the success here
    end
  rescue ConnectionError
    Tron.failure :deletion_failed_badly, id: id
  end
endOne could break the functionality apart into smaller pieces:
class User
  def self.delete(id)
    check_id_syntax(id).on_success { delete_user(id) }
                       .on_success { send_sms }
                       .on_success { redirect }
  end
  def self.check_id_syntax(id)
    return Tron.failure(:id_missing) unless id
    return Tron.failure(:invalid_id, id: id) unless id.match /[a-f]{8}/
    Tron.success :id_looks_good
  end
  def self.delete_user(id)
    user = @users[id]
    if @users.delete id
      Tron.success :user_deleted, user: user
    else
      Tron.success :deletion_failed, id: id
    end
  rescue ConnectionError => ex
    Tron.failure :deletion_failed_badly, id: id, message: ex.message
  end
endOn a side-note, the data object can be passed on further with modifications, that's due to the way Data object work.
result = Tron.success(:api_not_responding, reason: :password_not_accepted)
result.with(code: :could_not_delete_user)
  # => "#<data failure=:could_not_delete_user, reason=:password_not_accepted>"So, what are the benefits?
1. An internal API that doesn't change over time
Tron will give you a consistent, implementation-unaware, programming convention. That means that you can decide later, what constitutes a success or a failure, without changing the way the result is handled. You could also add metadata after-the-fact and the following code would still work fine:
result = User.delete 42
if result.success?
  puts "It worked! You deleted the user #{result.user.first_name}"
else
  puts "Aw, couldn't delete User with ID #{result.id} because #{result.failure}"
endThe result is just an instance of Data:
result = User.delete 42
# Query whether it worked
result.success? # => false
result.failure? # => true
# Query why and how
result.success # => nil
result.failure # => :deletion_failed_badly
# Access immutable metadata
result.message # => "..."
result.inspect # => "#<data failure=:alright, user_id=42, message='...'>"2. If will give you better tests
How would you test this code?
class Product
  def self.delete(id)
    return false if id.blank?
    return false unless product = Products.find(id)
    return false unless permission?
    api.update(id, attributes)
  end
  def self.permission?
    Date.today.sunday?
  end
endYou cannot simply test for false as expected return value, because it could mean anything. Tron helps you to check the response objects for every case. Data objects even support deconstruction for case statements.
3. It gives you documentation
While the code you're writing becomes slightly more verbose, that verbosity translates directly into documentation. You see immediately what each line is doing.
Upgrading from 2.0.0 to 3.0.0
- You will need to use at least Ruby 
3.2 - The result object doesn't respond to collection methods any more, such as 
result[:some_key]orresult.to_a, but it's unlikely that you relied on them in the first place. 
Upgrading from 1.x.x to 2.0.0
- 
1.2.0and2.0.0are identical, except that all deprecations have been removed and don't work any more. 
Upgrading from 0.x.x to 1.x.x
- Don't use 
include Tron, it is not useful any more. There are no subclasses you might want to access. - Replace 
Tron::Success.callwithTron.success(same for failure). The syntax is identical. - The result object is now a Struct and has no 
#metaand#metadatamethods anymore. - The result object does not respond to 
#codeany more. Instead use#successand#failurerespectively. This is so that you can use#codeas metadata, and also so that you can query the code via#successimmediately, without first having to check#success?. 
Background
Tron is a complete rewrite of its predecessor operation. I got inspired by the deterministic gem, which is the follow-up of the monadic gem. There are some complicated structs so I got inspired by this robust implementation and simplified it even more.
Requirements
- Ruby >= 3.2.0
 
Development
Clone the repository, run bundle install and run the tests with bundle exec rake.
Copyright
MIT halo. See LICENSE.txt.
Caveats
- There are no setter methods in the returned Data, so you cannot overwrite the metadata. But you can use 
Data#withto essentially clone the object and change values.