virtus 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Changelog.md +11 -2
- data/README.md +33 -0
- data/TODO +15 -4
- data/config/flay.yml +1 -1
- data/config/roodi.yml +3 -3
- data/lib/virtus.rb +3 -0
- data/lib/virtus/attribute/array.rb +1 -15
- data/lib/virtus/attribute/collection.rb +20 -0
- data/lib/virtus/attribute/set.rb +1 -3
- data/lib/virtus/attribute/symbol.rb +21 -0
- data/lib/virtus/coercion/string.rb +14 -0
- data/lib/virtus/coercion/time.rb +14 -0
- data/lib/virtus/instance_methods.rb +37 -3
- data/lib/virtus/value_object.rb +104 -0
- data/lib/virtus/value_object/equalizer.rb +129 -0
- data/lib/virtus/version.rb +1 -1
- data/spec/integration/collection_member_coercion_spec.rb +1 -1
- data/spec/integration/virtus/value_object_spec.rb +95 -0
- data/spec/unit/virtus/attribute/class_methods/determine_type_spec.rb +1 -1
- data/spec/unit/virtus/attribute/collection/class_methods/merge_options_spec.rb +2 -2
- data/spec/unit/virtus/attribute/collection/coerce_spec.rb +3 -3
- data/spec/unit/virtus/attribute/integer/coerce_spec.rb +6 -0
- data/spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb +9 -9
- data/spec/unit/virtus/attribute/symbol/coerce_spec.rb +13 -0
- data/spec/unit/virtus/attributes_accessor/inspect_spec.rb +1 -1
- data/spec/unit/virtus/value_object/class_methods/equalizer_spec.rb +24 -0
- data/spec/unit/virtus/value_object/initialize_spec.rb +19 -0
- data/spec/unit/virtus/value_object/with_spec.rb +31 -0
- data/virtus.gemspec +0 -1
- metadata +80 -40
data/Changelog.md
CHANGED
@@ -1,11 +1,20 @@
|
|
1
|
-
# v0.
|
1
|
+
# v0.2.0 2012-02-08
|
2
2
|
|
3
|
-
[
|
3
|
+
* [feature] Support for Value Objects (emmanuel)
|
4
|
+
* [feature] New Symbol attribute (solnic)
|
5
|
+
* [feature] Time => Integer coercion (solnic)
|
6
|
+
|
7
|
+
[Compare v0.1.0..v0.2.0](https://github.com/solnic/virtus/compare/v0.1.0...v0.2.0)
|
8
|
+
|
9
|
+
# v0.1.0 2012-02-05
|
4
10
|
|
5
11
|
* [feature] New EmbeddedValue attribute (solnic)
|
12
|
+
* [feature] Array and Set attributes support member coercions (emmanuel)
|
6
13
|
* [feature] Support for scientific notation handling in string => integer coercion (dkubb)
|
7
14
|
* [feature] Handling of string => numeric coercion with a leading + sign (dkubb)
|
15
|
+
* [changed] Update Boolean coercion to handle "on", "off", "y", "n", "yes", "no" (dkubb)
|
8
16
|
|
17
|
+
[Compare v0.0.10..v0.1.0](https://github.com/solnic/virtus/compare/v0.0.10...v0.1.0)
|
9
18
|
|
10
19
|
# v0.0.10 2011-11-21
|
11
20
|
|
data/README.md
CHANGED
@@ -160,6 +160,39 @@ user.phone_numbers # => [#<PhoneNumber:0x007fdb2d3bef88 @number="212-555-1212">,
|
|
160
160
|
user.addresses # => #<Set: {#<Address:0x007fdb2d3be448 @address="1234 Any St.", @locality="Anytown", @region="DC", @postal_code="21234">}>
|
161
161
|
```
|
162
162
|
|
163
|
+
**Value Objects**
|
164
|
+
|
165
|
+
``` ruby
|
166
|
+
class GeoLocation
|
167
|
+
include Virtus::ValueObject
|
168
|
+
|
169
|
+
attribute :latitude, Float
|
170
|
+
attribute :longitude, Float
|
171
|
+
end
|
172
|
+
|
173
|
+
class Venue
|
174
|
+
include Virtus
|
175
|
+
|
176
|
+
attribute :name, String
|
177
|
+
attribute :location, GeoLocation
|
178
|
+
end
|
179
|
+
|
180
|
+
venue = Venue.new(
|
181
|
+
:name => 'Pub',
|
182
|
+
:location => { :latitude => 37.160317, :longitude => -98.437500 })
|
183
|
+
|
184
|
+
venue.location.latitude # => 37.160317
|
185
|
+
venue.location.longitude # => -98.4375
|
186
|
+
|
187
|
+
# Supports object's equality
|
188
|
+
|
189
|
+
venue_other = Venue.new(
|
190
|
+
:name => 'Other Pub',
|
191
|
+
:location => { :latitude => 37.160317, :longitude => -98.437500 })
|
192
|
+
|
193
|
+
venue.location === venue_other.location # => true
|
194
|
+
```
|
195
|
+
|
163
196
|
**Adding Coercions**
|
164
197
|
|
165
198
|
Virtus comes with a builtin coercion library.
|
data/TODO
CHANGED
@@ -1,8 +1,15 @@
|
|
1
1
|
* Add missing specs:
|
2
|
-
* Add spec file spec/unit/virtus/
|
2
|
+
* Add spec file spec/unit/virtus/attribute/collection/member_coercion/coerce_and_append_member_spec.rb for Virtus::Attribute::Collection::MemberCoercion#coerce_and_append_member
|
3
|
+
* Add spec file spec/unit/virtus/attribute/collection/coerce_and_append_member_spec.rb for Virtus::Attribute::Collection#coerce_and_append_member
|
4
|
+
* Add spec file spec/unit/virtus/attribute/collection/member_type_spec.rb for Virtus::Attribute::Collection#member_type
|
5
|
+
* Add spec file spec/unit/virtus/attribute/collection/new_collection_spec.rb for Virtus::Attribute::Collection#new_collection
|
6
|
+
* Add spec file spec/unit/virtus/coercion/string/class_methods/to_symbol_spec.rb for Virtus::Coercion::String.to_symbol
|
7
|
+
* Add spec file spec/unit/virtus/coercion/time/class_methods/to_integer_spec.rb for Virtus::Coercion::Time.to_integer
|
3
8
|
* Add spec file spec/unit/virtus/coercion/time_coercions/to_time_spec.rb for Virtus::Coercion::TimeCoercions#to_time
|
4
|
-
* Add spec file spec/unit/virtus/coercion/time_coercions/to_string_spec.rb for Virtus::Coercion::TimeCoercions#to_string
|
5
9
|
* Add spec file spec/unit/virtus/coercion/time_coercions/to_datetime_spec.rb for Virtus::Coercion::TimeCoercions#to_datetime
|
10
|
+
* Add spec file spec/unit/virtus/coercion/time_coercions/to_date_spec.rb for Virtus::Coercion::TimeCoercions#to_date
|
11
|
+
* Add spec file spec/unit/virtus/coercion/time_coercions/to_string_spec.rb for Virtus::Coercion::TimeCoercions#to_string
|
12
|
+
* Add spec file spec/unit/virtus/coercion/array/class_methods/to_set_spec.rb for Virtus::Coercion::Array.to_set
|
6
13
|
* Add spec file spec/unit/virtus/coercion/decimal/class_methods/to_decimal_spec.rb for Virtus::Coercion::Decimal.to_decimal
|
7
14
|
* Add spec file spec/unit/virtus/coercion/float/class_methods/to_float_spec.rb for Virtus::Coercion::Float.to_float
|
8
15
|
* Add spec file spec/unit/virtus/coercion/integer/class_methods/to_integer_spec.rb for Virtus::Coercion::Integer.to_integer
|
@@ -12,9 +19,13 @@
|
|
12
19
|
* Add spec file spec/unit/virtus/coercion/numeric/class_methods/to_decimal_spec.rb for Virtus::Coercion::Numeric.to_decimal
|
13
20
|
* Add spec file spec/unit/virtus/coercion/class_methods/element_reference_spec.rb for Virtus::Coercion.[]
|
14
21
|
* Add spec file spec/unit/virtus/coercion/class_methods/primitive_spec.rb for Virtus::Coercion.primitive
|
22
|
+
* Add spec file spec/unit/virtus/value_object/class_methods/attribute_spec.rb for Virtus::ValueObject::ClassMethods#attribute
|
23
|
+
* Add spec file spec/unit/virtus/value_object/instance_methods/with_spec.rb for Virtus::ValueObject::InstanceMethods#with
|
24
|
+
* Add spec file spec/unit/virtus/value_object/equalizer/compile_spec.rb for Virtus::ValueObject::Equalizer#compile
|
25
|
+
* Add spec file spec/unit/virtus/value_object/equalizer/append_spec.rb for Virtus::ValueObject::Equalizer#<<
|
26
|
+
* Add spec file spec/unit/virtus/value_object/equalizer/host_name_spec.rb for Virtus::ValueObject::Equalizer#host_name
|
27
|
+
* Add spec file spec/unit/virtus/value_object/equalizer/keys_spec.rb for Virtus::ValueObject::Equalizer#keys
|
15
28
|
* Add spec file spec/unit/virtus/attributes_accessor/define_writer_method_spec.rb for Virtus::AttributesAccessor#define_writer_method
|
16
|
-
* Add spec file spec/unit/virtus/attributes_accessor/inspect_spec.rb for Virtus::AttributesAccessor#inspect
|
17
29
|
* Add spec file spec/unit/virtus/attributes_accessor/define_reader_method_spec.rb for Virtus::AttributesAccessor#define_reader_method
|
18
30
|
|
19
31
|
* Make #to_time #to_date and #to_datetime work on Ruby 1.8.7 instead of typecasting to string and parsing the value
|
20
|
-
* Add support for defining attributes on Modules
|
data/config/flay.yml
CHANGED
data/config/roodi.yml
CHANGED
@@ -2,16 +2,16 @@
|
|
2
2
|
AbcMetricMethodCheck: { score: 12.1 }
|
3
3
|
AssignmentInConditionalCheck: { }
|
4
4
|
CaseMissingElseCheck: { }
|
5
|
-
ClassLineCountCheck: { line_count:
|
5
|
+
ClassLineCountCheck: { line_count: 325 }
|
6
6
|
ClassNameCheck: { pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/ }
|
7
7
|
ClassVariableCheck: { }
|
8
8
|
CyclomaticComplexityBlockCheck: { complexity: 4 }
|
9
9
|
CyclomaticComplexityMethodCheck: { complexity: 4 }
|
10
10
|
EmptyRescueBodyCheck: { }
|
11
11
|
ForLoopCheck: { }
|
12
|
-
MethodLineCountCheck: { line_count:
|
12
|
+
MethodLineCountCheck: { line_count: 10 }
|
13
13
|
MethodNameCheck: { pattern: !ruby/regexp /\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|<<|[+*&|-])\z/ }
|
14
|
-
ModuleLineCountCheck: { line_count:
|
14
|
+
ModuleLineCountCheck: { line_count: 331 }
|
15
15
|
ModuleNameCheck: { pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/ }
|
16
16
|
# TODO: decrease parameter_count to 2 or less
|
17
17
|
ParameterNumberCheck: { parameter_count: 3 }
|
data/lib/virtus.rb
CHANGED
@@ -35,6 +35,8 @@ require 'virtus/attributes_accessor'
|
|
35
35
|
require 'virtus/class_methods'
|
36
36
|
require 'virtus/instance_methods'
|
37
37
|
|
38
|
+
require 'virtus/value_object'
|
39
|
+
|
38
40
|
require 'virtus/attribute_set'
|
39
41
|
|
40
42
|
require 'virtus/coercion'
|
@@ -69,6 +71,7 @@ require 'virtus/attribute/decimal'
|
|
69
71
|
require 'virtus/attribute/float'
|
70
72
|
require 'virtus/attribute/hash'
|
71
73
|
require 'virtus/attribute/integer'
|
74
|
+
require 'virtus/attribute/symbol'
|
72
75
|
require 'virtus/attribute/string'
|
73
76
|
require 'virtus/attribute/time'
|
74
77
|
require 'virtus/attribute/embedded_value'
|
@@ -16,21 +16,7 @@ module Virtus
|
|
16
16
|
primitive ::Array
|
17
17
|
coercion_method :to_array
|
18
18
|
|
19
|
-
|
20
|
-
#
|
21
|
-
# @param [Array, Set] collection
|
22
|
-
# target collection to which the coerced member should be appended
|
23
|
-
#
|
24
|
-
# @param [Object] entry
|
25
|
-
# the member that should be coerced and appended
|
26
|
-
#
|
27
|
-
# @return [Array, Set]
|
28
|
-
# collection with the coerced member appended to it
|
29
|
-
#
|
30
|
-
# @api private
|
31
|
-
def coerce_and_append_member(collection, entry)
|
32
|
-
collection << @member_type_instance.coerce(entry)
|
33
|
-
end
|
19
|
+
include Collection::MemberCoercion
|
34
20
|
|
35
21
|
end # class Array
|
36
22
|
end # class Attribute
|
@@ -91,6 +91,26 @@ module Virtus
|
|
91
91
|
"#{self.class}#coerce_and_append_member has not been implemented"
|
92
92
|
end
|
93
93
|
|
94
|
+
# Default coercion method for collection members used by Array and Set
|
95
|
+
module MemberCoercion
|
96
|
+
|
97
|
+
# Coerce a member of a source collection and append it to the target collection
|
98
|
+
#
|
99
|
+
# @param [Array, Set] collection
|
100
|
+
# target collection to which the coerced member should be appended
|
101
|
+
#
|
102
|
+
# @param [Object] entry
|
103
|
+
# the member that should be coerced and appended
|
104
|
+
#
|
105
|
+
# @return [Array, Set]
|
106
|
+
# collection with the coerced member appended to it
|
107
|
+
#
|
108
|
+
# @api private
|
109
|
+
def coerce_and_append_member(collection, entry)
|
110
|
+
collection << @member_type_instance.coerce(entry)
|
111
|
+
end
|
112
|
+
end # module MemberCoercion
|
113
|
+
|
94
114
|
end # class Array
|
95
115
|
end # class Attribute
|
96
116
|
end # module Virtus
|
data/lib/virtus/attribute/set.rb
CHANGED
@@ -16,9 +16,7 @@ module Virtus
|
|
16
16
|
primitive ::Set
|
17
17
|
coercion_method :to_set
|
18
18
|
|
19
|
-
|
20
|
-
collection << @member_type_instance.coerce(entry)
|
21
|
-
end
|
19
|
+
include Collection::MemberCoercion
|
22
20
|
|
23
21
|
end # class Set
|
24
22
|
end # class Attribute
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Virtus
|
2
|
+
class Attribute
|
3
|
+
|
4
|
+
# Symbol
|
5
|
+
#
|
6
|
+
# @example
|
7
|
+
# class Product
|
8
|
+
# include Virtus
|
9
|
+
#
|
10
|
+
# attribute :code, Symbol
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# product = Product.new(:code => :red)
|
14
|
+
#
|
15
|
+
class Symbol < Object
|
16
|
+
primitive ::Symbol
|
17
|
+
coercion_method :to_symbol
|
18
|
+
|
19
|
+
end # class Symbol
|
20
|
+
end # class Attribute
|
21
|
+
end # module Virtus
|
@@ -29,6 +29,20 @@ module Virtus
|
|
29
29
|
::Object.const_get(value)
|
30
30
|
end
|
31
31
|
|
32
|
+
# Coerce give value to a symbol
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# Virtus::Coercion::String.to_symbol('string') # => :string
|
36
|
+
#
|
37
|
+
# @param [String] value
|
38
|
+
#
|
39
|
+
# @return [Symbol]
|
40
|
+
#
|
41
|
+
# @api public
|
42
|
+
def self.to_symbol(value)
|
43
|
+
value.to_sym
|
44
|
+
end
|
45
|
+
|
32
46
|
# Coerce given value to Time
|
33
47
|
#
|
34
48
|
# @example
|
data/lib/virtus/coercion/time.rb
CHANGED
@@ -21,6 +21,20 @@ module Virtus
|
|
21
21
|
value
|
22
22
|
end
|
23
23
|
|
24
|
+
# Creates a Fixnum instance from a Time object
|
25
|
+
#
|
26
|
+
# @example
|
27
|
+
# Virtus::Coercion::Time.to_integer(time) # => Fixnum object
|
28
|
+
#
|
29
|
+
# @param [Time] value
|
30
|
+
#
|
31
|
+
# @return [Fixnum]
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
def self.to_integer(value)
|
35
|
+
value.to_i
|
36
|
+
end
|
37
|
+
|
24
38
|
end # class Time
|
25
39
|
end # class Coercion
|
26
40
|
end # module Virtus
|
@@ -35,7 +35,7 @@ module Virtus
|
|
35
35
|
#
|
36
36
|
# @api public
|
37
37
|
def [](name)
|
38
|
-
|
38
|
+
get_attribute(name)
|
39
39
|
end
|
40
40
|
|
41
41
|
# Sets a value of the attribute with the given name
|
@@ -62,7 +62,7 @@ module Virtus
|
|
62
62
|
#
|
63
63
|
# @api public
|
64
64
|
def []=(name, value)
|
65
|
-
|
65
|
+
set_attribute(name, value)
|
66
66
|
end
|
67
67
|
|
68
68
|
# Returns a hash of all publicly accessible attributes
|
@@ -165,7 +165,41 @@ module Virtus
|
|
165
165
|
#
|
166
166
|
# @api private
|
167
167
|
def set_attributes(attribute_values)
|
168
|
-
attribute_values.each { |
|
168
|
+
attribute_values.each { |pair| set_attribute(*pair) }
|
169
|
+
end
|
170
|
+
|
171
|
+
# Get values of all attributes defined for this class, ignoring privacy
|
172
|
+
#
|
173
|
+
# @return [Hash]
|
174
|
+
#
|
175
|
+
# @api private
|
176
|
+
def get_attributes
|
177
|
+
self.class.attributes.each_with_object({}) do |attribute, attributes|
|
178
|
+
attribute_name = attribute.name
|
179
|
+
attributes[attribute_name] = get_attribute(attribute_name)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
# Returns a value of the attribute with the given name
|
184
|
+
#
|
185
|
+
# @see Virtus::InstanceMethods#[]
|
186
|
+
#
|
187
|
+
# @return [Object]
|
188
|
+
#
|
189
|
+
# @api private
|
190
|
+
def get_attribute(name)
|
191
|
+
__send__(name)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Sets a value of the attribute with the given name
|
195
|
+
#
|
196
|
+
# @see Virtus::InstanceMethods#[]=
|
197
|
+
#
|
198
|
+
# @return [Object]
|
199
|
+
#
|
200
|
+
# @api private
|
201
|
+
def set_attribute(name, value)
|
202
|
+
__send__("#{name}=", value)
|
169
203
|
end
|
170
204
|
|
171
205
|
end # module InstanceMethods
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'virtus'
|
4
|
+
require 'virtus/value_object/equalizer'
|
5
|
+
|
6
|
+
module Virtus
|
7
|
+
# Include this Module for Value Object semantics
|
8
|
+
#
|
9
|
+
# The idea is that instances should be immutable and compared based on state
|
10
|
+
# (rather than identity, as is typically the case)
|
11
|
+
#
|
12
|
+
# @example
|
13
|
+
# class GeoLocation
|
14
|
+
# include Virtus::ValueObject
|
15
|
+
# attribute :latitude, Float
|
16
|
+
# attribute :longitude, Float
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# location = GeoLocation.new(:latitude => 10, :longitude => 100)
|
20
|
+
# same_location = GeoLocation.new(:latitude => 10, :longitude => 100)
|
21
|
+
# location == same_location #=> true
|
22
|
+
# hash = { location => :foo }
|
23
|
+
# hash[same_location] #=> :foo
|
24
|
+
module ValueObject
|
25
|
+
# Callback to configure including Class as a Value Object
|
26
|
+
#
|
27
|
+
# Including Class will include Virtus and have additional
|
28
|
+
# value object semantics defined in this module
|
29
|
+
#
|
30
|
+
# @return [Undefined]
|
31
|
+
#
|
32
|
+
# TODO: stacking modules is getting painful
|
33
|
+
# time for Facets' module_inheritance, ActiveSupport::Concern or the like
|
34
|
+
#
|
35
|
+
# @api private
|
36
|
+
def self.included(base)
|
37
|
+
base.instance_eval do
|
38
|
+
include ::Virtus
|
39
|
+
include InstanceMethods
|
40
|
+
extend ClassMethods
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module InstanceMethods
|
45
|
+
def initialize(attributes = {})
|
46
|
+
set_attributes(attributes)
|
47
|
+
end
|
48
|
+
|
49
|
+
def with(attribute_updates)
|
50
|
+
self.class.new(get_attributes.merge(attribute_updates))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
module ClassMethods
|
55
|
+
# Define an attribute on the receiver
|
56
|
+
#
|
57
|
+
# The Attribute will have private writer methods (eg., immutable instances)
|
58
|
+
# and be used in equality/equivalence comparisons
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# class GeoLocation
|
62
|
+
# include Virtus::ValueObject
|
63
|
+
#
|
64
|
+
# attribute :latitude, Float
|
65
|
+
# attribute :longitude, Float
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# @see Virtus::ClassMethods.attribute
|
69
|
+
#
|
70
|
+
# @return [self]
|
71
|
+
#
|
72
|
+
# @api public
|
73
|
+
def attribute(name, type, options = {})
|
74
|
+
equalizer << name
|
75
|
+
options[:writer] = :private
|
76
|
+
|
77
|
+
super
|
78
|
+
end
|
79
|
+
|
80
|
+
# Define and include a module that provides Value Object semantics
|
81
|
+
#
|
82
|
+
# Included module will have #inspect, #eql?, #== and #hash
|
83
|
+
# methods whose definition is based on the _keys_ argument
|
84
|
+
#
|
85
|
+
# @example
|
86
|
+
# virtus_class.equalizer
|
87
|
+
#
|
88
|
+
# @return [Equalizer]
|
89
|
+
# An Equalizer module which defines #inspect, #eql?, #== and #hash
|
90
|
+
# for instances of this class
|
91
|
+
#
|
92
|
+
# @api public
|
93
|
+
def equalizer
|
94
|
+
return @equalizer if instance_variable_defined?('@equalizer')
|
95
|
+
|
96
|
+
@equalizer = begin
|
97
|
+
equalizer = Equalizer.new(name || inspect)
|
98
|
+
include equalizer
|
99
|
+
equalizer
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end # module ClassMethods
|
103
|
+
end # module ValueObject
|
104
|
+
end # module Virtus
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Virtus
|
2
|
+
module ValueObject
|
3
|
+
# A type of Module for dynamically defining and hosting equality methods
|
4
|
+
class Equalizer < Module
|
5
|
+
# Name of hosting Class or Module that will be used for #inspect
|
6
|
+
#
|
7
|
+
# @return [String]
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
attr_reader :host_name
|
11
|
+
|
12
|
+
# List of methods that will be used to define equality methods
|
13
|
+
#
|
14
|
+
# @return [Array(Symbol)]
|
15
|
+
#
|
16
|
+
# @api private
|
17
|
+
attr_reader :keys
|
18
|
+
|
19
|
+
# Initialize an Equalizer with the given keys
|
20
|
+
#
|
21
|
+
# Will use the keys with which it is initialized to define #eql?, #==,
|
22
|
+
# and #hash
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
def initialize(host_name, keys = [])
|
26
|
+
@host_name = host_name
|
27
|
+
@keys = keys
|
28
|
+
end
|
29
|
+
|
30
|
+
# Append a key and compile the equality methods
|
31
|
+
#
|
32
|
+
# @return [Equalizer] self
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
def <<(key)
|
36
|
+
@keys << key
|
37
|
+
compile
|
38
|
+
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
# Compile the equalizer methods based on #keys
|
43
|
+
#
|
44
|
+
# @return [self]
|
45
|
+
#
|
46
|
+
# @api private
|
47
|
+
def compile
|
48
|
+
define_inspect_method
|
49
|
+
define_eql_method
|
50
|
+
define_equivalent_method
|
51
|
+
define_hash_method
|
52
|
+
|
53
|
+
self
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
# Define an inspect method that reports the values of the instance's keys
|
59
|
+
#
|
60
|
+
# @return [self]
|
61
|
+
#
|
62
|
+
# @api private
|
63
|
+
def define_inspect_method
|
64
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
65
|
+
def inspect
|
66
|
+
"#<#{host_name} #{keys.map { |key| "#{key}=\#{#{key}.inspect}" }.join(' ')}>"
|
67
|
+
end
|
68
|
+
RUBY
|
69
|
+
end
|
70
|
+
|
71
|
+
# Define an #eql? method based on the instance's values identified by #keys
|
72
|
+
#
|
73
|
+
# @return [self]
|
74
|
+
#
|
75
|
+
# @api private
|
76
|
+
def define_eql_method
|
77
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
78
|
+
def eql?(other)
|
79
|
+
return true if equal?(other)
|
80
|
+
instance_of?(other.class) &&
|
81
|
+
#{keys.map { |key| "#{key}.eql?(other.#{key})" }.join(' && ')}
|
82
|
+
end
|
83
|
+
RUBY
|
84
|
+
end
|
85
|
+
|
86
|
+
# Define an #== method based on the instance's values identified by #keys
|
87
|
+
#
|
88
|
+
# @return [self]
|
89
|
+
#
|
90
|
+
# @api private
|
91
|
+
def define_equivalent_method
|
92
|
+
respond_to, equivalent = compile_strings_for_equivalent_method
|
93
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
94
|
+
def ==(other)
|
95
|
+
return true if equal?(other)
|
96
|
+
return false unless kind_of?(other.class) || other.kind_of?(self.class)
|
97
|
+
#{respond_to.join(' && ')} && #{equivalent.join(' && ')}
|
98
|
+
end
|
99
|
+
RUBY
|
100
|
+
end
|
101
|
+
|
102
|
+
# Define a #hash method based on the instance's values identified by #keys
|
103
|
+
#
|
104
|
+
# @return [self]
|
105
|
+
#
|
106
|
+
# @api private
|
107
|
+
def define_hash_method
|
108
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
109
|
+
def hash
|
110
|
+
self.class.hash ^ #{keys.map { |key| "#{key}.hash" }.join(' ^ ')}
|
111
|
+
end
|
112
|
+
RUBY
|
113
|
+
end
|
114
|
+
|
115
|
+
# @api private
|
116
|
+
def compile_strings_for_equivalent_method
|
117
|
+
respond_to = []
|
118
|
+
equivalent = []
|
119
|
+
|
120
|
+
keys.each do |key|
|
121
|
+
respond_to << "other.respond_to?(#{key.inspect})"
|
122
|
+
equivalent << "#{key} == other.#{key}"
|
123
|
+
end
|
124
|
+
|
125
|
+
[ respond_to, equivalent ]
|
126
|
+
end
|
127
|
+
end # class Equalizer
|
128
|
+
end # module ValueObject
|
129
|
+
end # module Virtus
|
data/lib/virtus/version.rb
CHANGED
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::ValueObject do
|
4
|
+
let(:class_under_test) do
|
5
|
+
Class.new do
|
6
|
+
def self.name
|
7
|
+
'GeoLocation'
|
8
|
+
end
|
9
|
+
|
10
|
+
include Virtus::ValueObject
|
11
|
+
|
12
|
+
attribute :latitude, Float
|
13
|
+
attribute :longitude, Float
|
14
|
+
end
|
15
|
+
end
|
16
|
+
let(:attribute_values) { { :latitude => 10.0, :longitude => 20.0 } }
|
17
|
+
let(:instance_with_equal_state) { class_under_test.new(attribute_values) }
|
18
|
+
let(:instance_with_different_state) do
|
19
|
+
class_under_test.new(:latitude => attribute_values[:latitude])
|
20
|
+
end
|
21
|
+
subject { class_under_test.new(attribute_values) }
|
22
|
+
|
23
|
+
describe 'initialization' do
|
24
|
+
it 'sets the attribute values provided to Class.new' do
|
25
|
+
class_under_test.new(:latitude => 10000.001).latitude.should == 10000.001
|
26
|
+
subject.latitude.should eql(attribute_values[:latitude])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'writer visibility' do
|
31
|
+
it 'attributes are configured for private writers' do
|
32
|
+
class_under_test.attributes[:latitude].writer_visibility.should == :private
|
33
|
+
class_under_test.attributes[:longitude].writer_visibility.should == :private
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'writer methods are set to private' do
|
37
|
+
private_methods = class_under_test.private_instance_methods
|
38
|
+
private_methods.map! { |m| m.to_s }
|
39
|
+
private_methods.should include('latitude=', 'longitude=')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'attempts to call attribute writer methods raises NameError' do
|
43
|
+
expect { subject.latitude = 5.0 }.to raise_exception(NameError)
|
44
|
+
expect { subject.longitude = 5.0 }.to raise_exception(NameError)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'equality' do
|
49
|
+
describe '#==' do
|
50
|
+
it 'returns true for different objects with the same state' do
|
51
|
+
subject.should == instance_with_equal_state
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'returns false for different objects with different state' do
|
55
|
+
subject.should_not == instance_with_different_state
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#eql?' do
|
60
|
+
it 'returns true for different objects with the same state' do
|
61
|
+
subject.should eql(instance_with_equal_state)
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'returns false for different objects with different state' do
|
65
|
+
subject.should_not eql(instance_with_different_state)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe '#equal?' do
|
70
|
+
it 'returns false for different objects with the same state' do
|
71
|
+
subject.should_not equal(instance_with_equal_state)
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'returns false for different objects with different state' do
|
75
|
+
subject.should_not equal(instance_with_different_state)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe '#hash' do
|
80
|
+
it 'returns the same value for different objects with the same state' do
|
81
|
+
subject.hash.should eql(instance_with_equal_state.hash)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'returns different values for different objects with different state' do
|
85
|
+
subject.hash.should_not eql(instance_with_different_state.hash)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe '#inspect' do
|
91
|
+
it 'includes the class name and attribute values' do
|
92
|
+
subject.inspect.should == '#<GeoLocation latitude=10.0 longitude=20.0>'
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -26,7 +26,7 @@ describe Virtus::Attribute::Collection, '.merge_options' do
|
|
26
26
|
context 'when size is > 1' do
|
27
27
|
let(:size) { 2 }
|
28
28
|
|
29
|
-
specify { expect { subject }.to raise_error(NotImplementedError) }
|
29
|
+
specify { expect { subject }.to raise_error(NotImplementedError, "build SumType from list of types (#{type.inspect})") }
|
30
30
|
end
|
31
31
|
end
|
32
32
|
|
@@ -37,4 +37,4 @@ describe Virtus::Attribute::Collection, '.merge_options' do
|
|
37
37
|
|
38
38
|
it { should eql(options) }
|
39
39
|
end
|
40
|
-
end
|
40
|
+
end
|
@@ -11,10 +11,10 @@ describe Virtus::Attribute::Collection, '#coerce' do
|
|
11
11
|
before do
|
12
12
|
value.should_receive(:respond_to?).with(:inject).and_return(respond_to_inject)
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
context 'when coerced value responds to #inject' do
|
16
16
|
let(:respond_to_inject) { true }
|
17
|
-
|
17
|
+
|
18
18
|
specify { expect { subject }.to raise_error(NotImplementedError) }
|
19
19
|
end
|
20
20
|
|
@@ -23,4 +23,4 @@ describe Virtus::Attribute::Collection, '#coerce' do
|
|
23
23
|
|
24
24
|
specify { should eql(value) }
|
25
25
|
end
|
26
|
-
end
|
26
|
+
end
|
@@ -95,6 +95,12 @@ describe Virtus::Attribute::Integer, '#coerce' do
|
|
95
95
|
it { should eql(-24) }
|
96
96
|
end
|
97
97
|
|
98
|
+
context 'with a time object' do
|
99
|
+
let(:value) { Time.now }
|
100
|
+
|
101
|
+
it { should eql(value.to_i) }
|
102
|
+
end
|
103
|
+
|
98
104
|
[ Object.new, true, false, '00.0', '0.', '-.0', 'string' ].each do |non_num_value|
|
99
105
|
context 'does not coerce non-numeric value #{non_num_value.inspect}' do
|
100
106
|
let(:value) { non_num_value }
|
@@ -1,17 +1,17 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Virtus::Attribute::Object, '.descendants' do
|
4
|
-
subject { described_class.descendants }
|
4
|
+
subject { described_class.descendants.map { |c| c.to_s }.sort }
|
5
5
|
|
6
6
|
let(:known_descendants) do
|
7
|
-
[ Virtus::Attribute::EmbeddedValue,
|
8
|
-
Virtus::Attribute::Time,
|
9
|
-
Virtus::Attribute::Integer,
|
10
|
-
Virtus::Attribute::Float,
|
11
|
-
Virtus::Attribute::Numeric,
|
12
|
-
Virtus::Attribute::Date,
|
13
|
-
Virtus::Attribute::Set,
|
14
|
-
Virtus::Attribute::Collection,
|
7
|
+
[ Virtus::Attribute::EmbeddedValue, Virtus::Attribute::Symbol,
|
8
|
+
Virtus::Attribute::Time, Virtus::Attribute::String,
|
9
|
+
Virtus::Attribute::Integer, Virtus::Attribute::Hash,
|
10
|
+
Virtus::Attribute::Float, Virtus::Attribute::Decimal,
|
11
|
+
Virtus::Attribute::Numeric, Virtus::Attribute::DateTime,
|
12
|
+
Virtus::Attribute::Date, Virtus::Attribute::Boolean,
|
13
|
+
Virtus::Attribute::Set, Virtus::Attribute::Array,
|
14
|
+
Virtus::Attribute::Collection, Virtus::Attribute::Class ].map { |a| a.to_s }.sort
|
15
15
|
end
|
16
16
|
|
17
17
|
it 'should return all known attribute classes' do
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::Attribute::Symbol, '#coerce' do
|
4
|
+
subject { attribute.coerce(value) }
|
5
|
+
|
6
|
+
let(:attribute) { described_class.new(:code) }
|
7
|
+
|
8
|
+
context 'with a string' do
|
9
|
+
let(:value) { 'foo' }
|
10
|
+
|
11
|
+
it { should be(:foo) }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::ValueObject, '.equalizer' do
|
4
|
+
subject { described_class.equalizer }
|
5
|
+
|
6
|
+
let(:described_class) do
|
7
|
+
Class.new do
|
8
|
+
include Virtus::ValueObject
|
9
|
+
|
10
|
+
attribute :first_name, String
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
specify { subject.should be_instance_of(Virtus::ValueObject::Equalizer) }
|
15
|
+
specify { described_class.included_modules.should include(subject) }
|
16
|
+
|
17
|
+
context 'when equalizer is already initialized' do
|
18
|
+
before { subject; described_class.equalizer }
|
19
|
+
|
20
|
+
let(:equalizer) { subject }
|
21
|
+
|
22
|
+
specify { subject.should be(equalizer) }
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::ValueObject, '#initialize' do
|
4
|
+
subject { described_class.new(attributes) }
|
5
|
+
|
6
|
+
let(:described_class) do
|
7
|
+
Class.new do
|
8
|
+
include Virtus::ValueObject
|
9
|
+
|
10
|
+
attribute :currency, String
|
11
|
+
attribute :amount, Integer
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:attributes) { Hash[:currency => 'USD', :amount => 1] }
|
16
|
+
|
17
|
+
its(:currency) { should eql('USD') }
|
18
|
+
its(:amount) { should eql(1) }
|
19
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Virtus::ValueObject, '#with' do
|
4
|
+
subject { object.with(attributes) }
|
5
|
+
|
6
|
+
let(:described_class) do
|
7
|
+
Class.new do
|
8
|
+
include Virtus::ValueObject
|
9
|
+
|
10
|
+
attribute :first_name, String
|
11
|
+
attribute :last_name, String
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:object) { described_class.new }
|
16
|
+
let(:attributes) { Hash[:first_name => 'John', :last_name => 'Doe'] }
|
17
|
+
|
18
|
+
let(:described_class) do
|
19
|
+
Class.new do
|
20
|
+
include Virtus::ValueObject
|
21
|
+
|
22
|
+
attribute :first_name, String
|
23
|
+
attribute :last_name, String
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
it { should be_instance_of(described_class) }
|
28
|
+
|
29
|
+
its(:first_name) { should eql('John') }
|
30
|
+
its(:last_name) { should eql('Doe') }
|
31
|
+
end
|
data/virtus.gemspec
CHANGED
@@ -5,7 +5,6 @@ require File.expand_path('../lib/virtus/version', __FILE__)
|
|
5
5
|
Gem::Specification.new do |gem|
|
6
6
|
gem.name = "virtus"
|
7
7
|
gem.version = Virtus::VERSION
|
8
|
-
gem.date = "2011-11-21"
|
9
8
|
gem.authors = [ "Piotr Solnica" ]
|
10
9
|
gem.email = [ "piotr.solnica@gmail.com" ]
|
11
10
|
gem.description = "Attributes on Steroids for Plain Old Ruby Objects"
|
metadata
CHANGED
@@ -1,59 +1,82 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: virtus
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
5
|
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
6
11
|
platform: ruby
|
7
|
-
authors:
|
12
|
+
authors:
|
8
13
|
- Piotr Solnica
|
9
14
|
autorequire:
|
10
15
|
bindir: bin
|
11
16
|
cert_chain: []
|
12
|
-
|
13
|
-
|
14
|
-
|
17
|
+
|
18
|
+
date: 2012-02-08 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
15
21
|
name: rake
|
16
|
-
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
17
24
|
none: false
|
18
|
-
requirements:
|
25
|
+
requirements:
|
19
26
|
- - ~>
|
20
|
-
- !ruby/object:Gem::Version
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 63
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
- 9
|
32
|
+
- 2
|
21
33
|
version: 0.9.2
|
22
34
|
type: :development
|
23
|
-
|
24
|
-
|
25
|
-
- !ruby/object:Gem::Dependency
|
35
|
+
version_requirements: *id001
|
36
|
+
- !ruby/object:Gem::Dependency
|
26
37
|
name: backports
|
27
|
-
|
38
|
+
prerelease: false
|
39
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
28
40
|
none: false
|
29
|
-
requirements:
|
41
|
+
requirements:
|
30
42
|
- - ~>
|
31
|
-
- !ruby/object:Gem::Version
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 3
|
45
|
+
segments:
|
46
|
+
- 2
|
47
|
+
- 3
|
48
|
+
- 0
|
32
49
|
version: 2.3.0
|
33
50
|
type: :development
|
34
|
-
|
35
|
-
|
36
|
-
- !ruby/object:Gem::Dependency
|
51
|
+
version_requirements: *id002
|
52
|
+
- !ruby/object:Gem::Dependency
|
37
53
|
name: rspec
|
38
|
-
|
54
|
+
prerelease: false
|
55
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
39
56
|
none: false
|
40
|
-
requirements:
|
57
|
+
requirements:
|
41
58
|
- - ~>
|
42
|
-
- !ruby/object:Gem::Version
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 31
|
61
|
+
segments:
|
62
|
+
- 1
|
63
|
+
- 3
|
64
|
+
- 2
|
43
65
|
version: 1.3.2
|
44
66
|
type: :development
|
45
|
-
|
46
|
-
version_requirements: *70295232215500
|
67
|
+
version_requirements: *id003
|
47
68
|
description: Attributes on Steroids for Plain Old Ruby Objects
|
48
|
-
email:
|
69
|
+
email:
|
49
70
|
- piotr.solnica@gmail.com
|
50
71
|
executables: []
|
72
|
+
|
51
73
|
extensions: []
|
52
|
-
|
74
|
+
|
75
|
+
extra_rdoc_files:
|
53
76
|
- LICENSE
|
54
77
|
- README.md
|
55
78
|
- TODO
|
56
|
-
files:
|
79
|
+
files:
|
57
80
|
- .gitignore
|
58
81
|
- .rvmrc
|
59
82
|
- .travis.yml
|
@@ -87,6 +110,7 @@ files:
|
|
87
110
|
- lib/virtus/attribute/object.rb
|
88
111
|
- lib/virtus/attribute/set.rb
|
89
112
|
- lib/virtus/attribute/string.rb
|
113
|
+
- lib/virtus/attribute/symbol.rb
|
90
114
|
- lib/virtus/attribute/time.rb
|
91
115
|
- lib/virtus/attribute_set.rb
|
92
116
|
- lib/virtus/attributes_accessor.rb
|
@@ -111,6 +135,8 @@ files:
|
|
111
135
|
- lib/virtus/support/descendants_tracker.rb
|
112
136
|
- lib/virtus/support/options.rb
|
113
137
|
- lib/virtus/support/type_lookup.rb
|
138
|
+
- lib/virtus/value_object.rb
|
139
|
+
- lib/virtus/value_object/equalizer.rb
|
114
140
|
- lib/virtus/version.rb
|
115
141
|
- spec/integration/collection_member_coercion_spec.rb
|
116
142
|
- spec/integration/custom_attributes_spec.rb
|
@@ -119,6 +145,7 @@ files:
|
|
119
145
|
- spec/integration/embedded_value_spec.rb
|
120
146
|
- spec/integration/overriding_virtus_spec.rb
|
121
147
|
- spec/integration/virtus/instance_level_attributes_spec.rb
|
148
|
+
- spec/integration/virtus/value_object_spec.rb
|
122
149
|
- spec/rcov.opts
|
123
150
|
- spec/shared/constants_helpers.rb
|
124
151
|
- spec/shared/idempotent_method_behaviour.rb
|
@@ -175,6 +202,7 @@ files:
|
|
175
202
|
- spec/unit/virtus/attribute/set/coerce_spec.rb
|
176
203
|
- spec/unit/virtus/attribute/set_spec.rb
|
177
204
|
- spec/unit/virtus/attribute/string/coerce_spec.rb
|
205
|
+
- spec/unit/virtus/attribute/symbol/coerce_spec.rb
|
178
206
|
- spec/unit/virtus/attribute/time/coerce_spec.rb
|
179
207
|
- spec/unit/virtus/attribute/value_coerced_spec.rb
|
180
208
|
- spec/unit/virtus/attribute/writer_visibility_spec.rb
|
@@ -238,6 +266,9 @@ files:
|
|
238
266
|
- spec/unit/virtus/options/options_spec.rb
|
239
267
|
- spec/unit/virtus/type_lookup/determine_type_spec.rb
|
240
268
|
- spec/unit/virtus/type_lookup/primitive_spec.rb
|
269
|
+
- spec/unit/virtus/value_object/class_methods/equalizer_spec.rb
|
270
|
+
- spec/unit/virtus/value_object/initialize_spec.rb
|
271
|
+
- spec/unit/virtus/value_object/with_spec.rb
|
241
272
|
- tasks/metrics/ci.rake
|
242
273
|
- tasks/metrics/flay.rake
|
243
274
|
- tasks/metrics/flog.rake
|
@@ -251,27 +282,36 @@ files:
|
|
251
282
|
- virtus.gemspec
|
252
283
|
homepage: https://github.com/solnic/virtus
|
253
284
|
licenses: []
|
285
|
+
|
254
286
|
post_install_message:
|
255
287
|
rdoc_options: []
|
256
|
-
|
288
|
+
|
289
|
+
require_paths:
|
257
290
|
- lib
|
258
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
291
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
259
292
|
none: false
|
260
|
-
requirements:
|
261
|
-
- -
|
262
|
-
- !ruby/object:Gem::Version
|
263
|
-
|
264
|
-
|
293
|
+
requirements:
|
294
|
+
- - ">="
|
295
|
+
- !ruby/object:Gem::Version
|
296
|
+
hash: 3
|
297
|
+
segments:
|
298
|
+
- 0
|
299
|
+
version: "0"
|
300
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
265
301
|
none: false
|
266
|
-
requirements:
|
267
|
-
- -
|
268
|
-
- !ruby/object:Gem::Version
|
269
|
-
|
302
|
+
requirements:
|
303
|
+
- - ">="
|
304
|
+
- !ruby/object:Gem::Version
|
305
|
+
hash: 3
|
306
|
+
segments:
|
307
|
+
- 0
|
308
|
+
version: "0"
|
270
309
|
requirements: []
|
310
|
+
|
271
311
|
rubyforge_project:
|
272
|
-
rubygems_version: 1.8.
|
312
|
+
rubygems_version: 1.8.15
|
273
313
|
signing_key:
|
274
314
|
specification_version: 3
|
275
315
|
summary: Attributes on Steroids for Plain Old Ruby Objects
|
276
316
|
test_files: []
|
277
|
-
|
317
|
+
|