Skip to content

Commit

Permalink
Created and integrated type conversion subsystem
Browse files Browse the repository at this point in the history
  • Loading branch information
spohlenz committed Sep 29, 2009
1 parent c1430eb commit a13e54d
Show file tree
Hide file tree
Showing 25 changed files with 335 additions and 267 deletions.
2 changes: 2 additions & 0 deletions lib/recliner.rb
Expand Up @@ -18,6 +18,8 @@
module Recliner
VERSION = '0.0.1'

autoload :Conversions, 'recliner/conversions'

autoload :Document, 'recliner/document'
autoload :Database, 'recliner/database'

Expand Down
7 changes: 3 additions & 4 deletions lib/recliner/associations/reference.rb
Expand Up @@ -19,10 +19,6 @@ def self.parse(id)
new(id)
end

def to_couch
id
end

def to_s
id.to_s
end
Expand All @@ -49,4 +45,7 @@ def target
end
end
end

Conversions.register(Associations::Reference, :couch) { |ref| ref.id }
Conversions.register(String, Associations::Reference) { |str| Associations::Reference.new(str) }
end
60 changes: 60 additions & 0 deletions lib/recliner/conversions.rb
@@ -0,0 +1,60 @@
module Recliner
module Conversions
extend self

class ConversionError < TypeError
end

def clear!
conversions.clear
end

def convert(from, to)
return nil if from.nil?
return from if to.is_a?(Class) && from.kind_of?(to)

if block = conversion(from.class, to)
from.instance_eval(&block) rescue nil
else
raise ConversionError, "No registered conversion from #{from.class} to #{to.inspect}"
end
end

def convert!(from, to)
return nil if from.nil?
return from if to.is_a?(Class) && from.kind_of?(to)

if block = conversion(from.class, to)
begin
from.instance_eval(&block)
rescue => e
raise ConversionError, "Conversion block raised exception"
end
else
raise ConversionError, "No registered conversion from #{from.class} to #{to.inspect}"
end
end

def register(from, to, &block)
conversions[from][to] = block
end

private
def conversion(from, to)
while from
return conversions[from][to] if conversions[from] && conversions[from][to]
from = from.superclass
end
nil
end

def conversions
@conversions ||= Hash.new { |hash, key| hash[key] = {} }
end
end
end

Dir[File.dirname(__FILE__) + "/conversions/*.rb"].sort.each do |path|
filename = File.basename(path)
require "recliner/conversions/#{filename}"
end
17 changes: 17 additions & 0 deletions lib/recliner/conversions/boolean.rb
@@ -0,0 +1,17 @@
Recliner::Conversions.register(TrueClass, Boolean) { true }
Recliner::Conversions.register(FalseClass, Boolean) { false }

Recliner::Conversions.register(String, Boolean) do |str|
case str.downcase
when '1', 't', 'y', 'true', 'yes'
true
when '0', 'f', 'n', 'false', 'no'
false
else
nil
end
end

Recliner::Conversions.register(Fixnum, Boolean) do |num|
num > 0
end
38 changes: 38 additions & 0 deletions lib/recliner/conversions/couch.rb
@@ -0,0 +1,38 @@
# To couch format

Recliner::Conversions.register(Object, :couch) { self }

Recliner::Conversions.register(Date, :couch) { strftime('%Y/%m/%d') }

Recliner::Conversions.register(Time, :couch) { strftime('%Y/%m/%d %T %z') }

Recliner::Conversions.register(Array, :couch) do |array|
array.map { |item| item.to_couch }
end

Recliner::Conversions.register(Hash, :couch) do |hash|
hash.inject({}) { |result, (key, value)|
result[key.to_s] = value.to_couch
result
}
end


# From couch format

# Recliner::Conversions.register(:couch, Boolean) do
# case self
# when true, 'true', '1', 1
# true
# else
# false
# end
# end
#
# Recliner::Conversions.register(:couch, Date) do
# Date.parse(self)
# end
#
# Recliner::Conversions.register(:couch, Time) do
# Time.parse(self)
# end
8 changes: 8 additions & 0 deletions lib/recliner/conversions/date.rb
@@ -0,0 +1,8 @@
Recliner::Conversions.register(Date, String) { |date| date.to_s }
Recliner::Conversions.register(Time, Date) { |time| time.to_date }

Recliner::Conversions.register(String, Date) do |str|
parts = Date._parse(str)
Date.new(parts[:year], parts[:mon], parts[:mday]) rescue nil
end

22 changes: 22 additions & 0 deletions lib/recliner/conversions/number.rb
@@ -0,0 +1,22 @@
Recliner::Conversions.register(Numeric, String) { |num| num.to_s }

Recliner::Conversions.register(String, Integer) do |str|
result = str.to_i

# string.to_i returns 0 if the string does not contain a number - we want nil
result = nil if result == 0 && str !~ /^\s*\d/

result
end

Recliner::Conversions.register(String, Float) do |str|
result = str.to_f

# string.to_f returns 0 if the string does not contain a number - we want nil
result = nil if result == 0.0 && str !~ /^\s*[\d\.]/

result
end

Recliner::Conversions.register(Numeric, Integer) { |float| float.to_i }
Recliner::Conversions.register(Numeric, Float) { |float| float.to_f }
1 change: 1 addition & 0 deletions lib/recliner/conversions/string.rb
@@ -0,0 +1 @@
Recliner::Conversions.register(Symbol, String) { |sym| sym.to_s }
7 changes: 7 additions & 0 deletions lib/recliner/conversions/time.rb
@@ -0,0 +1,7 @@
Recliner::Conversions.register(Time, String) { |time| strftime('%Y-%m-%d %T %z') }
Recliner::Conversions.register(Date, Time) { |date| date.to_time }

Recliner::Conversions.register(String, Time) do |str|
parts = Date._parse(str)
Time.time_with_datetime_fallback(:local, parts[:year], parts[:mon], parts[:mday], parts[:hour], parts[:min], parts[:sec]) rescue nil
end
2 changes: 0 additions & 2 deletions lib/recliner/core_ext.rb
Expand Up @@ -2,5 +2,3 @@
require 'recliner/core_ext/time'
require 'recliner/core_ext/date'
require 'recliner/core_ext/boolean'
require 'recliner/core_ext/array'
require 'recliner/core_ext/hash'
7 changes: 0 additions & 7 deletions lib/recliner/core_ext/array.rb

This file was deleted.

5 changes: 0 additions & 5 deletions lib/recliner/core_ext/date.rb
Expand Up @@ -8,9 +8,4 @@ def self.from_couch(val)
val
end
end

# Converts the Date object to a consistent string format (YYYY/MM/DD) for JSON serialization.
def to_couch
strftime('%Y/%m/%d')
end
end
10 changes: 0 additions & 10 deletions lib/recliner/core_ext/hash.rb

This file was deleted.

3 changes: 2 additions & 1 deletion lib/recliner/core_ext/object.rb
Expand Up @@ -2,9 +2,10 @@
class Object
def self.from_couch(val)
val
# Recliner::Conversions.convert(val, self.class, :from => :couch)
end

def to_couch
self
Recliner::Conversions.convert(self, :couch)
end
end
5 changes: 0 additions & 5 deletions lib/recliner/core_ext/time.rb
Expand Up @@ -10,9 +10,4 @@ def self.from_couch(val)
val
end
end

# Converts the Time object to a consistent string format (YYYY/MM/DD HH:MM:SS ZONE) for JSON serialization.
def to_couch
strftime('%Y/%m/%d %T %z')
end
end
20 changes: 2 additions & 18 deletions lib/recliner/properties/map.rb
Expand Up @@ -142,27 +142,11 @@ def replace(hash)

private
def convert_key(key)
convert(key, from)
Conversions.convert(key, from)
end

def convert_value(value)
convert(value, to)
end

def convert(value, type)
return nil if value.nil?

if type == String
value.to_s
elsif type == Integer
value.to_i
elsif type == Float
value.to_f
elsif value.kind_of?(type)
value
else
raise TypeError, "expected #{type} but got #{value.class}"
end
Conversions.convert(value, to)
end
end
end
95 changes: 1 addition & 94 deletions lib/recliner/properties/property.rb
Expand Up @@ -3,9 +3,6 @@

module Recliner
class Property < Struct.new(:name, :type, :as, :default)
TRUE_VALUES = [ true, 1, 'true', 't', 'yes', 'y', '1' ]
FALSE_VALUES = [ false, 0, 'false', 'f', 'no', 'n', '0' ]

def default_value(instance)
if default.respond_to?(:call)
default.arity == 1 ? default.call(instance) : default.call
Expand All @@ -15,97 +12,7 @@ def default_value(instance)
end

def type_cast(value)
return nil if value.nil?

if type == Date
convert_to_date(value)
elsif type == Time
convert_to_time(value)
elsif type == Boolean
convert_to_boolean(value)
elsif type == String
convert_to_string(value)
elsif type == Integer
convert_to_integer(value)
elsif type == Float
convert_to_float(value)
elsif value.kind_of?(type)
value
elsif type.respond_to?(:parse)
type.parse(value)
end
end

private
def convert_to_boolean(value)
value.downcase! if value.is_a?(String)

if TRUE_VALUES.include?(value)
true
elsif FALSE_VALUES.include?(value)
false
else
nil
end
end

def convert_to_string(value)
if value.is_a?(Time) && RUBY_VERSION < '1.9'
# Use Ruby 1.9 Time format for consistency
value.strftime('%Y-%m-%d %T %z')
else
value.to_s if value
end
end

def convert_to_integer(value)
if value
result = value.to_i

# string.to_i returns 0 if the string does not contain a number - we want nil
result = nil if result == 0 && value.is_a?(String) && value !~ /^\s*\d/

result
end
end

def convert_to_float(value)
if value
result = value.to_f

# string.to_f returns 0 if the string does not contain a number - we want nil
result = nil if result == 0.0 && value.is_a?(String) && value !~ /^\s*[\d\.]/

result
end
end

def convert_to_date(value)
case value
when String then parse_date(value)
when Time, DateTime then value.to_date
when Date then value
else nil
end
end

def convert_to_time(value)
case value
when String then parse_time(value)
when Date then value.to_time
when Time then value
else nil
end
end

def parse_time(value)
parts = Date._parse(value)
Time.time_with_datetime_fallback(:local, parts[:year], parts[:mon], parts[:mday], parts[:hour], parts[:min], parts[:sec]) rescue nil
end

def parse_date(value)
parts = Date._parse(value)
Date.new(parts[:year], parts[:mon], parts[:mday]) rescue nil
Conversions.convert(value, type)
end
end
end

0 comments on commit a13e54d

Please sign in to comment.