Created
December 20, 2011 17:50
-
-
Save mbj/1502485 to your computer and use it in GitHub Desktop.
my custom unit implementation with datamapper_load and datamapper_dump methods
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class AModel | |
include DataMapper::Mongo::Resource | |
property :id,ObjectId | |
property :unit, EmbeddedDocument, :document_class => MyUnits::Unit | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module DataMapper | |
class Property | |
class EmbeddedDocument < Property | |
coercion_method nil | |
dump_as Hash | |
accept_options :document_class | |
protected | |
def initialize(model, name, options = {}) | |
super | |
@document_class = @options.fetch(:document_class) { raise ArgumentError,'missing :document_class' } | |
@load_as = @document_class | |
end | |
public | |
def load(value) | |
if value.nil? | |
nil | |
else | |
@document_class.datamapper_load value | |
end | |
end | |
def typecast(value) | |
case value | |
when @document_class | |
value | |
when NilClass | |
nil | |
else | |
raise ArgumentError,"+value+ must be of kind #{@document_class} or nil was: #{value.class}" | |
end | |
end | |
def dump(value) | |
if value.nil? | |
nil | |
else | |
@document_class.datamapper_dump value | |
end | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'rational' | |
module MyUnits | |
class Unit | |
attr_reader :scalar | |
attr_reader :numerators | |
attr_reader :denominators | |
def self.units | |
@units ||= { | |
:none => [1], | |
:item => [1,:item], | |
:pack => [1,:pack], | |
:can => [1,:can], | |
:kilogramm => [1,:kilogramm], | |
:euro => [1,:euro], | |
:meter => [1,:meter], | |
:kilometer => [1000,:meter] | |
} | |
end | |
def self.find_unit(value) | |
unit = case value | |
when String | |
if units.keys.map(&:to_s).include? value | |
find_unit(value.to_sym) | |
end | |
when Symbol | |
units[value] | |
else | |
raise ArgumentError,"can only find units by symbol or string not by: #{value.class}" | |
end | |
if unit | |
unit | |
else | |
raise ArgumentError,"unit for: #{value.inspect} could not be found" | |
end | |
end | |
def initialize(*arguments) | |
value,numerators,denominators = | |
if arguments.length == 1 and arguments.first.kind_of?(Hash) | |
hash = arguments.first.dup | |
scalar = hash.delete('scalar') || hash.delete(:scalar) | |
unless scalar | |
raise ArgumentError,'hash is missung :scalar or "scalar"' | |
end | |
numerators = hash.delete('numerators') || hash.delete(:numerators) | |
denominators = hash.delete('denominators') || hash.delete(:denominators) | |
# Remove database searchable version | |
hash.delete('value') | |
hash.delete(:value) | |
unless hash.empty? | |
raise ArgumentError,"Illegal keys in hash: #{hash.keys}" | |
end | |
[scalar,numerators,denominators] | |
elsif arguments.length <= 3 | |
value,numerators,denominators = arguments | |
else | |
raise ArgumentError,'I do not take more than 3 arguments!' | |
end | |
numerators = [*numerators] | |
denominators = [*denominators] | |
@scalar = | |
case value | |
when Fixnum | |
Rational(value,1) | |
when Array | |
if value.length != 2 | |
raise ArgumentError,'scalar array must be of length two' | |
end | |
numerator,denominator = value | |
unless numerator.kind_of?(Fixnum) | |
raise ArgumentError,'numerator of scalar muste be of kind Fixnum' | |
end | |
unless denominator.kind_of?(Fixnum) | |
raise ArgumentError,"denominator of scalar muste be of kind Fixnum was :#{denominator.class}" | |
end | |
Rational(numerator,denominator) | |
when String | |
match = /\A(\d+)(?:\.(\d+))?\Z/.match value | |
unless match | |
raise ArgumentError,'+scalar+ is not in a compatible string format' | |
end | |
full,fraction = match[1],match[2] | |
denominator = 10 ** (fraction ? fraction.length : 0) | |
Rational((full.to_i*denominator)+fraction.to_i,denominator) | |
when Rational | |
value | |
else | |
raise ArgumentError,'+scalar+ must be kind of Rational, Fixnum or in a compatible String format' | |
end | |
numerators.map! do |numerator| | |
scalar,numerator = self.class.find_unit numerator | |
@scalar *= scalar | |
numerator | |
end | |
denominators.map! do |denominator| | |
scalar,denominator = self.class.find_unit denominator | |
@scalar /= scalar | |
denominator | |
end | |
numerators = numerators. delete_if { |numerator | denominators.delete_at(denominators.index(numerator) || denominators.length) } | |
denominators = denominators.delete_if { |denominator| numerators. delete_at(numerators. index(denominator) || numerators. length) } | |
@numerators = numerators. sort.freeze | |
@denominators = denominators.sort.freeze | |
freeze | |
end | |
def to_hash | |
hash = {} | |
hash[:scalar] = [scalar.numerator,scalar.denominator] | |
mod = scalar.numerator % scalar.denominator | |
hash[:value] = mod.zero? ? scalar.to_i : scalar.to_f | |
hash[:numerators] = @numerators unless @numerators.empty? | |
hash[:denominators] = @denominators unless @denominators.empty? | |
hash | |
end | |
def self.datamapper_load(value) | |
self.new value | |
end | |
def self.datamapper_dump(value) | |
value.to_hash | |
end | |
def unit | |
[@numerators,@denominators] | |
end | |
def inspect | |
"<#{self.class.name} @scalar=#{"%0.5f" % @scalar.to_f} #{self.class.make_pretty(@numerators)}/#{self.class.make_pretty(@denominators)}>" | |
end | |
def +(operand) | |
operand = self.class.convert operand | |
unless operand.unit == self.unit | |
raise ArgumentError,'cannot add incompatible units' | |
end | |
self.class.new( | |
operand.scalar + @scalar, | |
numerators.dup, | |
denominators.dup | |
) | |
end | |
def -(operand) | |
self.+(self.class.convert(operand) * -1) | |
end | |
def *(operand) | |
operand = self.class.convert operand | |
self.class.new( | |
operand.scalar * @scalar, | |
numerators + operand.numerators, | |
denominators + operand.denominators | |
) | |
end | |
def /(operand) | |
operand = self.class.convert operand | |
self * self.class.new( | |
1 / operand.scalar, | |
operand.denominators.dup, | |
operand.numerators.dup | |
) | |
end | |
def ==(operand) | |
operand = self.class.convert operand | |
operand.scalar == @scalar && operand.unit == self.unit | |
end | |
def unitless? | |
numerators.empty? and denominators.empty? | |
end | |
private | |
def self.make_pretty(base) | |
base.group_by { |item| item }.map do |x,y| | |
length = y.length; | |
if length > 1 | |
"#{x}^#{length}" | |
else | |
x.to_s | |
end | |
end.join('') | |
end | |
def self.convert(operand) | |
case operand | |
when self | |
operand | |
when Fixnum | |
self.new(operand) | |
when Rational | |
self.new(operand) | |
else | |
raise ArgumentError,"+operand+ must be kind of Unit, Rational or Fixnum was #{operand.class}" | |
end | |
end | |
def check_unit(operand) | |
unless operand.kind_of?(self.class) | |
raise "+operand+ is of incompatible type: #{operand.class}" | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment