FlexCoerce
FlexCoerce - is a gem which allow you create operator-dependent coercion logic. It's useful when your type should be treated in a different way for different binary operations (including arithmetic operators, bitwise operators and comparison operators except equality checks: ==
, ===
).
Installation
Attention: this gem works only with Ruby 2.0 because it uses Module#prepend which has no analogs in Ruby 1.9. If you have an idea how to realize it on 1.9 without too much mess - your pull-requests are welcome.
Add this line to your application's Gemfile:
gem 'flex_coerce'
And then execute:
$ bundle
Or install it yourself as:
$ gem install flex_coerce
Usage
I'll show you an example of (very simplistic) class Unit for physical units. We can multiply numbers with units but can't add them.
require 'flex_coerce'
class Unit
attr_reader :num, :unit
def initialize(num, unit)
@num, @unit = num, unit
end
def +(other)
raise TypeError, 'Can\'t add Unit to a non-Unit' unless other.is_a? Unit
raise ArgumentError, 'Can\'t sum units of different types' unless other.unit == unit
Unit.new(num + other.num, unit)
end
def *(other)
return self * Unit.new(other, '1') if other.is_a? Numeric
Unit.new(num * other.num, "#{unit}*#{other.unit}")
end
def coerce(other, meth = nil)
raise TypeError, 'Coercion error: can\'t coerce non-numeric class to a Unit' unless other.is_a? Numeric
case meth.to_sym
when :*
[Unit.new(other,'1'), self]
when :+
raise TypeError 'Coercion error: can\'t compare Unit to a non-unit anything'
[other, self]
else
raise TypeError, 'Unsupported operation'
end
end
prepend FlexCoerce
end
5 * Unit.new(3,'cm') # ==> Unit(15,'1*cm')
5 + Unit.new(3,'cm') # ==> raises an Error
You define your own #coerce
with two arguments: standard argument other
and new argument meth
which represents the binary operations caused coercion. All the magic is in prepend FlexCoerce
call.
It decorates your #coerce
method with another one, which follow ruby conventions about coerce method. This is done by creating an instance of CoerceableWrapper
class which takes a reference to left-side object. This intermediate class defines its own methods for binary operators such that they play well with #coerce
method of right-side object class. I should mention that this method works good for missing, optional or required second argument of #coerce
, you can find examples in specs.
Possibly this example is too simplistic and can be made in another and more consistent way. But this is a proof of concept that coerce can be made method-specific without touching base ruby classes. You're welcome with more interesting examples and more powerful and clear design.
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Add some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request