virtus 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|