coercible 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. data/.gitignore +18 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +19 -0
  4. data/Changelog.md +4 -0
  5. data/Gemfile +6 -0
  6. data/Gemfile.devtools +44 -0
  7. data/Guardfile +58 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +64 -0
  10. data/Rakefile +2 -0
  11. data/coercible.gemspec +22 -0
  12. data/config/flay.yml +3 -0
  13. data/config/flog.yml +2 -0
  14. data/config/mutant.yml +3 -0
  15. data/config/roodi.yml +17 -0
  16. data/config/site.reek +91 -0
  17. data/config/yardstick.yml +2 -0
  18. data/lib/coercible.rb +42 -0
  19. data/lib/coercible/coercer.rb +155 -0
  20. data/lib/coercible/coercer/array.rb +24 -0
  21. data/lib/coercible/coercer/configurable.rb +54 -0
  22. data/lib/coercible/coercer/date.rb +27 -0
  23. data/lib/coercible/coercer/date_time.rb +27 -0
  24. data/lib/coercible/coercer/decimal.rb +41 -0
  25. data/lib/coercible/coercer/false_class.rb +25 -0
  26. data/lib/coercible/coercer/float.rb +40 -0
  27. data/lib/coercible/coercer/hash.rb +72 -0
  28. data/lib/coercible/coercer/integer.rb +130 -0
  29. data/lib/coercible/coercer/numeric.rb +67 -0
  30. data/lib/coercible/coercer/object.rb +160 -0
  31. data/lib/coercible/coercer/string.rb +251 -0
  32. data/lib/coercible/coercer/symbol.rb +25 -0
  33. data/lib/coercible/coercer/time.rb +41 -0
  34. data/lib/coercible/coercer/time_coercions.rb +87 -0
  35. data/lib/coercible/coercer/true_class.rb +25 -0
  36. data/lib/coercible/configuration.rb +33 -0
  37. data/lib/coercible/version.rb +3 -0
  38. data/lib/support/options.rb +113 -0
  39. data/lib/support/type_lookup.rb +113 -0
  40. data/spec/integration/configuring_coercers_spec.rb +14 -0
  41. data/spec/shared/unit/configurable.rb +27 -0
  42. data/spec/spec_helper.rb +30 -0
  43. data/spec/unit/coercible/coercer/array/to_set_spec.rb +12 -0
  44. data/spec/unit/coercible/coercer/class_methods/new_spec.rb +13 -0
  45. data/spec/unit/coercible/coercer/date/to_date_spec.rb +10 -0
  46. data/spec/unit/coercible/coercer/date/to_datetime_spec.rb +30 -0
  47. data/spec/unit/coercible/coercer/date/to_string_spec.rb +12 -0
  48. data/spec/unit/coercible/coercer/date/to_time_spec.rb +12 -0
  49. data/spec/unit/coercible/coercer/date_time/to_date_spec.rb +30 -0
  50. data/spec/unit/coercible/coercer/date_time/to_datetime_spec.rb +10 -0
  51. data/spec/unit/coercible/coercer/date_time/to_string_spec.rb +12 -0
  52. data/spec/unit/coercible/coercer/date_time/to_time_spec.rb +30 -0
  53. data/spec/unit/coercible/coercer/decimal/to_decimal_spec.rb +9 -0
  54. data/spec/unit/coercible/coercer/decimal/to_float_spec.rb +12 -0
  55. data/spec/unit/coercible/coercer/decimal/to_integer_spec.rb +12 -0
  56. data/spec/unit/coercible/coercer/decimal/to_string_spec.rb +12 -0
  57. data/spec/unit/coercible/coercer/element_reference_spec.rb +19 -0
  58. data/spec/unit/coercible/coercer/false_class/to_string_spec.rb +12 -0
  59. data/spec/unit/coercible/coercer/float/to_decimal_spec.rb +12 -0
  60. data/spec/unit/coercible/coercer/float/to_float_spec.rb +9 -0
  61. data/spec/unit/coercible/coercer/float/to_integer_spec.rb +12 -0
  62. data/spec/unit/coercible/coercer/float/to_string_spec.rb +12 -0
  63. data/spec/unit/coercible/coercer/hash/to_date_spec.rb +38 -0
  64. data/spec/unit/coercible/coercer/hash/to_datetime_spec.rb +38 -0
  65. data/spec/unit/coercible/coercer/hash/to_time_spec.rb +38 -0
  66. data/spec/unit/coercible/coercer/integer/to_boolean_spec.rb +27 -0
  67. data/spec/unit/coercible/coercer/integer/to_decimal_spec.rb +12 -0
  68. data/spec/unit/coercible/coercer/integer/to_float_spec.rb +12 -0
  69. data/spec/unit/coercible/coercer/integer/to_integer_spec.rb +9 -0
  70. data/spec/unit/coercible/coercer/integer/to_string_spec.rb +12 -0
  71. data/spec/unit/coercible/coercer/integer_spec.rb +11 -0
  72. data/spec/unit/coercible/coercer/numeric/to_decimal_spec.rb +10 -0
  73. data/spec/unit/coercible/coercer/numeric/to_float_spec.rb +10 -0
  74. data/spec/unit/coercible/coercer/numeric/to_integer_spec.rb +10 -0
  75. data/spec/unit/coercible/coercer/numeric/to_string_spec.rb +12 -0
  76. data/spec/unit/coercible/coercer/object/to_array_spec.rb +51 -0
  77. data/spec/unit/coercible/coercer/object/to_hash_spec.rb +22 -0
  78. data/spec/unit/coercible/coercer/object/to_integer_spec.rb +22 -0
  79. data/spec/unit/coercible/coercer/object/to_string_spec.rb +22 -0
  80. data/spec/unit/coercible/coercer/string/to_boolean_spec.rb +31 -0
  81. data/spec/unit/coercible/coercer/string/to_constant_spec.rb +49 -0
  82. data/spec/unit/coercible/coercer/string/to_date_spec.rb +25 -0
  83. data/spec/unit/coercible/coercer/string/to_datetime_spec.rb +52 -0
  84. data/spec/unit/coercible/coercer/string/to_decimal_spec.rb +47 -0
  85. data/spec/unit/coercible/coercer/string/to_float_spec.rb +57 -0
  86. data/spec/unit/coercible/coercer/string/to_integer_spec.rb +68 -0
  87. data/spec/unit/coercible/coercer/string/to_symbol_spec.rb +9 -0
  88. data/spec/unit/coercible/coercer/string/to_time_spec.rb +52 -0
  89. data/spec/unit/coercible/coercer/string_spec.rb +11 -0
  90. data/spec/unit/coercible/coercer/symbol/to_string_spec.rb +12 -0
  91. data/spec/unit/coercible/coercer/time/to_integer_spec.rb +10 -0
  92. data/spec/unit/coercible/coercer/time/to_time_spec.rb +10 -0
  93. data/spec/unit/coercible/coercer/time_coercions/to_date_spec.rb +30 -0
  94. data/spec/unit/coercible/coercer/time_coercions/to_datetime_spec.rb +34 -0
  95. data/spec/unit/coercible/coercer/time_coercions/to_string_spec.rb +19 -0
  96. data/spec/unit/coercible/coercer/time_coercions/to_time_spec.rb +34 -0
  97. data/spec/unit/coercible/coercer/true_class/to_string_spec.rb +12 -0
  98. data/spec/unit/coercible/configuration/class_methods/build_spec.rb +15 -0
  99. metadata +235 -0
@@ -0,0 +1,25 @@
1
+ module Coercible
2
+ class Coercer
3
+
4
+ # Coerce Symbol values
5
+ class Symbol < Object
6
+ primitive ::Symbol
7
+
8
+ # Coerce given value to String
9
+ #
10
+ # @example
11
+ # coercer[Symbol].to_string(:name) # => "name"
12
+ #
13
+ # @param [Symbol] value
14
+ #
15
+ # @return [String]
16
+ #
17
+ # @api public
18
+ def to_string(value)
19
+ value.to_s
20
+ end
21
+
22
+ end # class Symbol
23
+
24
+ end # class Coercer
25
+ end # module Coercible
@@ -0,0 +1,41 @@
1
+ module Coercible
2
+ class Coercer
3
+
4
+ # Coerce Time values
5
+ class Time < Object
6
+ include TimeCoercions
7
+
8
+ primitive ::Time
9
+
10
+ # Passthrough the value
11
+ #
12
+ # @example
13
+ # coercer[DateTime].to_time(time) # => Time object
14
+ #
15
+ # @param [DateTime] value
16
+ #
17
+ # @return [Date]
18
+ #
19
+ # @api public
20
+ def to_time(value)
21
+ value
22
+ end
23
+
24
+ # Creates a Fixnum instance from a Time object
25
+ #
26
+ # @example
27
+ # Coercible::Coercion::Time.to_integer(time) # => Fixnum object
28
+ #
29
+ # @param [Time] value
30
+ #
31
+ # @return [Fixnum]
32
+ #
33
+ # @api public
34
+ def to_integer(value)
35
+ value.to_i
36
+ end
37
+
38
+ end # class Time
39
+
40
+ end # class Coercer
41
+ end # module Coercible
@@ -0,0 +1,87 @@
1
+ module Coercible
2
+ class Coercer
3
+
4
+ # Common time coercion methods
5
+ module TimeCoercions
6
+
7
+ # Coerce given value to String
8
+ #
9
+ # @example
10
+ # coercer[Time].to_string(time) # => "Wed Jul 20 10:30:41 -0700 2011"
11
+ #
12
+ # @param [Date,Time,DateTime] value
13
+ #
14
+ # @return [String]
15
+ #
16
+ # @api public
17
+ def to_string(value)
18
+ value.to_s
19
+ end
20
+
21
+ # Coerce given value to Time
22
+ #
23
+ # @example
24
+ # coercer[DateTime].to_time(datetime) # => Time object
25
+ #
26
+ # @param [Date,DateTime] value
27
+ #
28
+ # @return [Time]
29
+ #
30
+ # @api public
31
+ def to_time(value)
32
+ coerce_with_method(value, :to_time)
33
+ end
34
+
35
+ # Coerce given value to DateTime
36
+ #
37
+ # @example
38
+ # coercer[Time].to_datetime(time) # => DateTime object
39
+ #
40
+ # @param [Date,Time] value
41
+ #
42
+ # @return [DateTime]
43
+ #
44
+ # @api public
45
+ def to_datetime(value)
46
+ coerce_with_method(value, :to_datetime)
47
+ end
48
+
49
+ # Coerce given value to Date
50
+ #
51
+ # @example
52
+ # coercer[Time].to_date(time) # => Date object
53
+ #
54
+ # @param [Time,DateTime] value
55
+ #
56
+ # @return [Date]
57
+ #
58
+ # @api public
59
+ def to_date(value)
60
+ coerce_with_method(value, :to_date)
61
+ end
62
+
63
+ private
64
+
65
+ # Try to use native coercion method on the given value
66
+ #
67
+ # Falls back to String-based parsing
68
+ #
69
+ # @param [Date,DateTime,Time] value
70
+ # @param [Symbol] method
71
+ #
72
+ # @return [Date,DateTime,Time]
73
+ #
74
+ # @api private
75
+ def coerce_with_method(value, method)
76
+ coerced = super
77
+ if coerced.equal?(value)
78
+ coercers[::String].public_send(method, to_string(value))
79
+ else
80
+ coerced
81
+ end
82
+ end
83
+
84
+ end # module TimeCoercions
85
+
86
+ end # class Coercer
87
+ end # module Coercible
@@ -0,0 +1,25 @@
1
+ module Coercible
2
+ class Coercer
3
+
4
+ # Coerce true values
5
+ class TrueClass < Object
6
+ primitive ::TrueClass
7
+
8
+ # Coerce given value to String
9
+ #
10
+ # @example
11
+ # coercer[TrueClass].to_string(true) # => "true"
12
+ #
13
+ # @param [TrueClass] value
14
+ #
15
+ # @return [String]
16
+ #
17
+ # @api public
18
+ def to_string(value)
19
+ value.to_s
20
+ end
21
+
22
+ end # class TrueClass
23
+
24
+ end # class Coercer
25
+ end # module Coercible
@@ -0,0 +1,33 @@
1
+ module Coercible
2
+
3
+ # Configuration object for global and per coercer type settings
4
+ #
5
+ class Configuration
6
+
7
+ # Build a configuration instance
8
+ #
9
+ # @param [Array] list of accessor keys
10
+ #
11
+ # @return [Configuration]
12
+ #
13
+ # @api private
14
+ def self.build(keys, &block)
15
+ config = new
16
+ keys.each do |key|
17
+ config.instance_eval <<-RUBY
18
+ def #{key}
19
+ @#{key}
20
+ end
21
+
22
+ def #{key}=(value)
23
+ @#{key} = value
24
+ end
25
+ RUBY
26
+ end
27
+ yield(config) if block_given?
28
+ config
29
+ end
30
+
31
+ end # class Configuration
32
+
33
+ end # module Coercible
@@ -0,0 +1,3 @@
1
+ module Coercible
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,113 @@
1
+ module Coercible
2
+
3
+ # A module that adds class and instance level options
4
+ module Options
5
+ Undefined = Class.new.freeze
6
+
7
+ # Returns default options hash for a given attribute class
8
+ #
9
+ # @example
10
+ # Virtus::Attribute::String.options
11
+ # # => {:primitive => String}
12
+ #
13
+ # @return [Hash]
14
+ # a hash of default option values
15
+ #
16
+ # @api public
17
+ def options
18
+ accepted_options.each_with_object({}) do |option_name, options|
19
+ option_value = send(option_name)
20
+ options[option_name] = option_value unless option_value.nil?
21
+ end
22
+ end
23
+
24
+ # Returns an array of valid options
25
+ #
26
+ # @example
27
+ # Virtus::Attribute::String.accepted_options
28
+ # # => [:primitive, :accessor, :reader, :writer]
29
+ #
30
+ # @return [Array]
31
+ # the array of valid option names
32
+ #
33
+ # @api public
34
+ def accepted_options
35
+ @accepted_options ||= []
36
+ end
37
+
38
+ # Defines which options are valid for a given attribute class
39
+ #
40
+ # @example
41
+ # class MyAttribute < Virtus::Attribute::Object
42
+ # accept_options :foo, :bar
43
+ # end
44
+ #
45
+ # @return [self]
46
+ #
47
+ # @api public
48
+ def accept_options(*new_options)
49
+ add_accepted_options(new_options)
50
+ new_options.each { |option| define_option_method(option) }
51
+ descendants.each { |descendant| descendant.add_accepted_options(new_options) }
52
+ self
53
+ end
54
+
55
+ protected
56
+
57
+ # Adds a reader/writer method for the give option name
58
+ #
59
+ # @return [undefined]
60
+ #
61
+ # @api private
62
+ def define_option_method(option)
63
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
64
+ def self.#{option}(value = Undefined) # def self.primitive(value = Undefined)
65
+ return @#{option} if value.equal?(Undefined) # return @primitive if value.equal?(Undefined)
66
+ @#{option} = value # @primitive = value
67
+ self # self
68
+ end # end
69
+ RUBY
70
+ end
71
+
72
+ # Sets default options
73
+ #
74
+ # @param [#each] new_options
75
+ # options to be set
76
+ #
77
+ # @return [self]
78
+ #
79
+ # @api private
80
+ def set_options(new_options)
81
+ new_options.each { |pair| send(*pair) }
82
+ self
83
+ end
84
+
85
+ # Adds new options that an attribute class can accept
86
+ #
87
+ # @param [#to_ary] new_options
88
+ # new options to be added
89
+ #
90
+ # @return [self]
91
+ #
92
+ # @api private
93
+ def add_accepted_options(new_options)
94
+ accepted_options.concat(new_options)
95
+ self
96
+ end
97
+
98
+ private
99
+
100
+ # Adds descendant to descendants array and inherits default options
101
+ #
102
+ # @param [Class] descendant
103
+ #
104
+ # @return [undefined]
105
+ #
106
+ # @api private
107
+ def inherited(descendant)
108
+ super
109
+ descendant.add_accepted_options(accepted_options).set_options(options)
110
+ end
111
+
112
+ end # module Options
113
+ end # module Virtus
@@ -0,0 +1,113 @@
1
+ module Coercible
2
+
3
+ # A module that adds type lookup to a class
4
+ module TypeLookup
5
+
6
+ TYPE_FORMAT = /\A[A-Z]\w*\z/.freeze
7
+
8
+ # Set cache ivar on the model
9
+ #
10
+ # @param [Class] model
11
+ #
12
+ # @return [undefined]
13
+ #
14
+ # @api private
15
+ def self.extended(model)
16
+ model.instance_variable_set('@type_lookup_cache', {})
17
+ end
18
+
19
+ # Returns a descendant based on a name or class
20
+ #
21
+ # @example
22
+ # MyClass.determine_type('String') # => MyClass::String
23
+ #
24
+ # @param [Class, #to_s] class_or_name
25
+ # name of a class or a class itself
26
+ #
27
+ # @return [Class]
28
+ # a descendant
29
+ #
30
+ # @return [nil]
31
+ # nil if the type cannot be determined by the class_or_name
32
+ #
33
+ # @api public
34
+ def determine_type(class_or_name)
35
+ @type_lookup_cache[class_or_name] ||= determine_type_and_cache(class_or_name)
36
+ end
37
+
38
+ # Return the default primitive supported
39
+ #
40
+ # @return [Class]
41
+ #
42
+ # @api private
43
+ def primitive
44
+ raise NotImplementedError, "#{self}.primitive must be implemented"
45
+ end
46
+
47
+ private
48
+
49
+ # Determine type and cache the class
50
+ #
51
+ # @return [Class]
52
+ #
53
+ # @api private
54
+ def determine_type_and_cache(class_or_name)
55
+ case class_or_name
56
+ when singleton_class
57
+ determine_type_from_descendant(class_or_name)
58
+ when Class
59
+ determine_type_from_primitive(class_or_name)
60
+ else
61
+ determine_type_from_string(class_or_name.to_s)
62
+ end
63
+ end
64
+
65
+ # Return the class given a descendant
66
+ #
67
+ # @param [Class] descendant
68
+ #
69
+ # @return [Class]
70
+ #
71
+ # @api private
72
+ def determine_type_from_descendant(descendant)
73
+ descendant if descendant < self
74
+ end
75
+
76
+ # Return the class given a primitive
77
+ #
78
+ # @param [Class] primitive
79
+ #
80
+ # @return [Class]
81
+ #
82
+ # @return [nil]
83
+ # nil if the type cannot be determined by the primitive
84
+ #
85
+ # @api private
86
+ def determine_type_from_primitive(primitive)
87
+ type = nil
88
+ descendants.reverse_each do |descendant|
89
+ descendant_primitive = descendant.primitive
90
+ next unless primitive <= descendant_primitive
91
+ type = descendant if type.nil? or type.primitive > descendant_primitive
92
+ end
93
+ type
94
+ end
95
+
96
+ # Return the class given a string
97
+ #
98
+ # @param [String] string
99
+ #
100
+ # @return [Class]
101
+ #
102
+ # @return [nil]
103
+ # nil if the type cannot be determined by the string
104
+ #
105
+ # @api private
106
+ def determine_type_from_string(string)
107
+ if string =~ TYPE_FORMAT and const_defined?(string, *EXTRA_CONST_ARGS)
108
+ const_get(string, *EXTRA_CONST_ARGS)
109
+ end
110
+ end
111
+
112
+ end # module TypeLookup
113
+ end # module Virtus