Operable

An easy way to perform simple operations (addition, subtraction, multiplication, division) on your models. It is tested with ActiveRecord and Mongoid and should work with any ActiveModel compliant class.

travis

A Simple Example

Let's say we're collecting records of Baseball games. A Team model has various stats like W, L, T, etc. Including Operable in your model definition lets you use the +, -, * and / operators on objects just as you would on primatives, returning a new instance of the model class containing the result of your desired operation.

# Create team instances for the 2 NY baseball teams:
yankees = Team.new :w => 5, :l => 4, :t => 1
mets =    Team.new :w => 3, :l => 7, :t => 0

# Get their combined totals
yankees + mets # => #<Team w: 8, l: 11, t: 1>

# How much better are the yankees?
yankees - mets # => #<Team w: 2, l: -3, t: 1>

# If the yankees keep this up for the rest of the season...
yankees * 16.2 # => #<Team w: 81, l: 65, t: 16>

Getting Started

First, add operable to your Gemfile:

gem 'operable'

Next, include the module in your model and specify which fields to operate on:

# ActiveRecord:
class Team < ActiveRecord::Base
  include Operable
  operable_on :w, :l, :t
end

# Mongoid:
class Team
  include Mongoid::Document
  include Operable
  field :w
  field :l
  field :t
  operable_on :w, :l, :t
end

That's it! You can call operable_on multiple times to add more fields.

Operations

Four operations are provided: +, -, *, /.

red =   Color.new :r => 128, :g => 0,   :b => 0
green = Color.new :r => 0,   :g => 128, :b => 0
blue =  Color.new :r => 0,   :g => 0,   :b => 128

yellow =     green + red # => #<Color r: 128, g: 128, b: 0>
grey =       yellow + blue # => #<Color r: 128, g: 128, b: 128>
bright_red = red * 2 # => #<Color r: 255, g: 0,   b: 0>
dark_grey =  grey / 3 # => #<Color r: 43,  g: 43,  b: 43>

Testing Equality

Compare the equality of operable objects with the matches? method:

purple = Color.new :r => 128, :g => 0, :b => 128

purple.matches?(red + blue)  #=> true
purple.matches?(red + green) #=> false

Operate On All (Mongoid only)

Mongoid models define their fields explicitly in the model declaration. We can use this to automatically determine what to operate on:

class Team
  include Mongoid::Document
  include Operable
  field :w
  field :l
  field :t
  operable_on_all # operates on w, l and t.
  operable_on_all_except :t # operates on w and l
end

Operate On Associations

We can include associations in our list of operable fields just as with normal attributes!

To operate on associations:

  1. Include Operable on both document classes.
  2. Manually specify the name of the association on the parent document using operable_on

NOTE: associations are not included by operable_on_all methods, you need to manually add them.

class First
  include Mongoid::Document
  include Operable
  field :a
  field :b
  embeds_one :second
  operable_on_all
  operable_on :second
end

class Second
  include Mongoid::Document
  include Operable
  field :c
  field :d
  embedded_in :first
  operable_on_all
end

f1 = First.new :a => 1, :b => 2, :second => Second.new(:c => 1, :d => 2)
f2 = First.new :a => 2, :b => 3, :second => Second.new(:c => 2, :d => 3)
f3 = f1 + f2  # => #<First a: 3, b: 5>
f3.second     # => #<Second c: 3, d: 5>

This feature now works with ActiveRecord as well.

Enhancements and Pull Requests

If you find the project useful but it doesn't meet all of your needs, feel free to fork it and send a pull request. I'd especially love contributions that enhance behavior with ActiveRecord. Please make sure to include specs!

License

MIT license, go wild.