virtus 0.4.2 → 0.5.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.
Files changed (35) hide show
  1. data/.gitignore +1 -0
  2. data/Changelog.md +11 -1
  3. data/README.md +47 -8
  4. data/config/flay.yml +1 -1
  5. data/lib/virtus.rb +25 -5
  6. data/lib/virtus/attribute.rb +1 -5
  7. data/lib/virtus/attribute/embedded_value.rb +20 -8
  8. data/lib/virtus/attribute/embedded_value/from_open_struct.rb +17 -0
  9. data/lib/virtus/attribute/embedded_value/from_struct.rb +17 -0
  10. data/lib/virtus/class_inclusions.rb +41 -0
  11. data/lib/virtus/class_methods.rb +18 -60
  12. data/lib/virtus/extensions.rb +93 -0
  13. data/lib/virtus/instance_methods.rb +7 -2
  14. data/lib/virtus/module_extensions.rb +62 -0
  15. data/lib/virtus/value_object.rb +19 -1
  16. data/lib/virtus/version.rb +1 -1
  17. data/spec/integration/default_values_spec.rb +16 -0
  18. data/spec/integration/extending_objects_spec.rb +35 -0
  19. data/spec/integration/struct_as_embedded_value_spec.rb +28 -0
  20. data/spec/integration/using_modules_spec.rb +49 -0
  21. data/spec/integration/value_object_with_custom_constructor_spec.rb +42 -0
  22. data/spec/integration/virtus/value_object_spec.rb +6 -2
  23. data/spec/unit/virtus/attribute/class_methods/determine_type_spec.rb +3 -1
  24. data/spec/unit/virtus/attribute/embedded_value/class_methods/determine_type_spec.rb +23 -0
  25. data/spec/unit/virtus/attribute/embedded_value/coerce_spec.rb +8 -37
  26. data/spec/unit/virtus/attribute/embedded_value/from_open_struct/coerce_spec.rb +28 -0
  27. data/spec/unit/virtus/attribute/embedded_value/from_struct/coerce_spec.rb +28 -0
  28. data/spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb +2 -0
  29. data/spec/unit/virtus/class_methods/{attributes_spec.rb → attribute_set_spec.rb} +2 -2
  30. data/spec/unit/virtus/class_methods/attribute_spec.rb +1 -1
  31. data/spec/unit/virtus/{class_methods → extensions}/allowed_writer_methods_spec.rb +1 -1
  32. data/spec/unit/virtus/value_object/class_methods/attribute_spec.rb +1 -1
  33. data/spec/unit/virtus/value_object/instance_methods/duplicates_spec.rb +22 -0
  34. data/virtus.gemspec +2 -2
  35. metadata +26 -13
data/.gitignore CHANGED
@@ -33,5 +33,6 @@ measurements
33
33
  ## BUNDLER
34
34
  .bundle
35
35
  Gemfile.lock
36
+ bin/
36
37
 
37
38
  ## PROJECT::SPECIFIC
data/Changelog.md CHANGED
@@ -1,4 +1,14 @@
1
- # v0.4.1 2012-05-08
1
+ # v0.5.0 2012-06-08
2
+
3
+ * [feature] Support for extending objects (solnic)
4
+ * [feature] Support for defining attributes in modules (solnic)
5
+ * [feature] Support for Struct as an EmbeddedValue or ValueObject attribute (solnic)
6
+ * [changed] Allow any input for EmbeddedValue and ValueObject constructors (solnic)
7
+ * [changed] ValueObject instances cannot be duped or cloned (senny)
8
+
9
+ [Compare v0.4.2..master](https://github.com/solnic/virtus/compare/v0.4.2...master)
10
+
11
+ # v0.4.2 2012-05-08
2
12
 
3
13
  * [updated] Bump backports dep to ~> 2.5.3 (solnic)
4
14
 
data/README.md CHANGED
@@ -41,7 +41,7 @@ class User
41
41
  end
42
42
 
43
43
  user = User.new(:name => 'Piotr', :age => 28)
44
- user.attributes # => { :name => "Piotr", :age => 28 }
44
+ user.attribute_set # => { :name => "Piotr", :age => 28 }
45
45
 
46
46
  user.name # => "Piotr"
47
47
 
@@ -51,8 +51,48 @@ user.age.class # => Fixnum
51
51
  user.birthday = 'November 18th, 1983' # => #<DateTime: 1983-11-18T00:00:00+00:00 (4891313/2,0/1,2299161)>
52
52
  ```
53
53
 
54
+ ### Using Virtus With Modules
54
55
 
55
- **Default values**
56
+ You can create modules extended with virtus and define attributes for later
57
+ inclusion in your classes:
58
+
59
+ ```ruby
60
+ module Name
61
+ include Virtus
62
+
63
+ attribute :name, String
64
+ end
65
+
66
+ module Age
67
+ include Virtus
68
+
69
+ attribute :age, Integer
70
+ end
71
+
72
+ class User
73
+ include Name, Age
74
+ end
75
+
76
+ user = User.new(:name => 'John', :age => '30')
77
+ ```
78
+
79
+ ### Dynamically Extending Instances
80
+
81
+ It's also possible to dynamically extend an object with Virtus:
82
+
83
+ ```ruby
84
+ class User
85
+ # nothing here
86
+ end
87
+
88
+ user = User.new
89
+ user.extend(Virtus)
90
+ user.attribute :name, String
91
+ user.name = 'John'
92
+ user.name # => 'John'
93
+ ```
94
+
95
+ ### Default Values
56
96
 
57
97
  ``` ruby
58
98
  class Page
@@ -84,7 +124,7 @@ page.published # => false
84
124
  page.editor_title # => "UNPUBLISHED: Virtus README"
85
125
  ```
86
126
 
87
- **Embedded Value**
127
+ ### Embedded Value
88
128
 
89
129
  ``` ruby
90
130
  class City
@@ -115,7 +155,7 @@ user.address.street # => "Street 1/2"
115
155
  user.address.city.name # => "NYC"
116
156
  ```
117
157
 
118
- **Collection Member Coercions**
158
+ ### Collection Member Coercions
119
159
 
120
160
  ``` ruby
121
161
  # Support "primitive" classes
@@ -163,7 +203,7 @@ user.phone_numbers # => [#<PhoneNumber:0x007fdb2d3bef88 @number="212-555-1212">,
163
203
  user.addresses # => #<Set: {#<Address:0x007fdb2d3be448 @address="1234 Any St.", @locality="Anytown", @region="DC", @postal_code="21234">}>
164
204
  ```
165
205
 
166
- **Value Objects**
206
+ ### Value Objects
167
207
 
168
208
  ``` ruby
169
209
  class GeoLocation
@@ -196,7 +236,7 @@ venue_other = Venue.new(
196
236
  venue.location === venue_other.location # => true
197
237
  ```
198
238
 
199
- **Adding Coercions**
239
+ ### Adding Coercions
200
240
 
201
241
  Virtus comes with a builtin coercion library.
202
242
  It's super easy to add your own coercion classes.
@@ -235,7 +275,7 @@ user.name # => 'Piotr'
235
275
  user.password # => '3858f62230ac3c915f300c664312c63f'
236
276
  ```
237
277
 
238
- **Custom Attributes**
278
+ ### Custom Attributes
239
279
 
240
280
  ``` ruby
241
281
  require 'json'
@@ -265,7 +305,6 @@ user.info = '{"email":"john@domain.com"}' # => {"email"=>"john@domain.com"}
265
305
  user.info.class # => Hash
266
306
  ```
267
307
 
268
-
269
308
  Credits
270
309
  -------
271
310
 
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 19
3
- total_score: 365
3
+ total_score: 385
data/lib/virtus.rb CHANGED
@@ -16,21 +16,35 @@ module Virtus
16
16
  # Represents an undefined parameter used by auto-generated option methods
17
17
  Undefined = Object.new.freeze
18
18
 
19
- # Extends base class with class and instance methods
19
+ # Extends base class or a module with virtus methods
20
20
  #
21
21
  # @param [Class] descendant
22
22
  #
23
23
  # @return [undefined]
24
24
  #
25
25
  # @api private
26
- def self.included(descendant)
26
+ def self.included(object)
27
27
  super
28
- descendant.extend(ClassMethods)
29
- descendant.send(:include, InstanceMethods)
28
+ if Class === object
29
+ object.send(:include, ClassInclusions)
30
+ else
31
+ object.extend(ModuleExtensions)
32
+ end
30
33
  end
31
-
32
34
  private_class_method :included
33
35
 
36
+ # Extends an object with virtus extensions
37
+ #
38
+ # @param [Object] object
39
+ #
40
+ # @return [undefined]
41
+ #
42
+ # @api private
43
+ def self.extended(object)
44
+ object.extend(Extensions)
45
+ end
46
+ private_class_method :extended
47
+
34
48
  end # module Virtus
35
49
 
36
50
  require 'virtus/support/descendants_tracker'
@@ -38,6 +52,10 @@ require 'virtus/support/type_lookup'
38
52
  require 'virtus/support/options'
39
53
  require 'virtus/support/equalizer'
40
54
 
55
+ require 'virtus/extensions'
56
+ require 'virtus/class_inclusions'
57
+ require 'virtus/module_extensions'
58
+
41
59
  require 'virtus/attributes_accessor'
42
60
  require 'virtus/class_methods'
43
61
  require 'virtus/instance_methods'
@@ -86,3 +104,5 @@ require 'virtus/attribute/symbol'
86
104
  require 'virtus/attribute/string'
87
105
  require 'virtus/attribute/time'
88
106
  require 'virtus/attribute/embedded_value'
107
+ require 'virtus/attribute/embedded_value/from_struct'
108
+ require 'virtus/attribute/embedded_value/from_open_struct'
@@ -81,11 +81,7 @@ module Virtus
81
81
  def self.determine_type(class_or_name)
82
82
  case class_or_name
83
83
  when ::Class
84
- if class_or_name <= Virtus
85
- Attribute::EmbeddedValue
86
- else
87
- super
88
- end
84
+ Attribute::EmbeddedValue.determine_type(class_or_name) || super
89
85
  when ::Array, ::Set
90
86
  super(class_or_name.class)
91
87
  else
@@ -35,6 +35,24 @@ module Virtus
35
35
  options.merge(:primitive => type)
36
36
  end
37
37
 
38
+ # Determine type based on class
39
+ #
40
+ # Virtus::EmbeddedValue.determine_type(Struct) # => Virtus::EmbeddedValue::FromStruct
41
+ # Virtus::EmbeddedValue.determine_type(VirtusClass) # => Virtus::EmbeddedValue::FromOpenStruct
42
+ #
43
+ # @param [Class] klass
44
+ #
45
+ # @return [Virtus::Attribute::EmbeddedValue]
46
+ #
47
+ # @api public
48
+ def self.determine_type(klass)
49
+ if klass <= Virtus || klass <= OpenStruct
50
+ FromOpenStruct
51
+ elsif klass <= Struct
52
+ FromStruct
53
+ end
54
+ end
55
+
38
56
  # Coerce attributes into a virtus object
39
57
  #
40
58
  # @param [Hash,Virtus]
@@ -42,14 +60,8 @@ module Virtus
42
60
  # @return [Virtus]
43
61
  #
44
62
  # @api private
45
- def coerce(attributes_or_object)
46
- value = if attributes_or_object.kind_of?(::Hash)
47
- @primitive.new(attributes_or_object)
48
- else
49
- attributes_or_object
50
- end
51
-
52
- super(value)
63
+ def coerce(value)
64
+ value if value.kind_of?(@primitive)
53
65
  end
54
66
 
55
67
  end # class EmbeddedValue
@@ -0,0 +1,17 @@
1
+ module Virtus
2
+ class Attribute
3
+ class EmbeddedValue < Object
4
+
5
+ # EmbeddedValue attribute handling OpenStruct primitive or Virtus object
6
+ #
7
+ class FromOpenStruct < EmbeddedValue
8
+
9
+ # @api private
10
+ def coerce(attributes)
11
+ super or @primitive.new(attributes)
12
+ end
13
+
14
+ end # class FromOpenStruct
15
+ end # class EmbeddedValue
16
+ end # class Attribute
17
+ end # module Virtus
@@ -0,0 +1,17 @@
1
+ module Virtus
2
+ class Attribute
3
+ class EmbeddedValue < Object
4
+
5
+ # EmbeddedValue attribute handling Struct primitive
6
+ #
7
+ class FromStruct < EmbeddedValue
8
+
9
+ # @api private
10
+ def coerce(attributes)
11
+ super or @primitive.new(*attributes)
12
+ end
13
+
14
+ end # class FromStruct
15
+ end # class EmbeddedValue
16
+ end # class Attribute
17
+ end # module Virtus
@@ -0,0 +1,41 @@
1
+ module Virtus
2
+
3
+ # Class-level extensions
4
+ module ClassInclusions
5
+
6
+ # Extends a descendant with class and instance methods
7
+ #
8
+ # @param [Class] descendant
9
+ #
10
+ # @return [undefined]
11
+ #
12
+ # @api private
13
+ def self.included(descendant)
14
+ super
15
+ descendant.extend(ClassMethods)
16
+ descendant.send(:include, InstanceMethods)
17
+ end
18
+ private_class_method :included
19
+
20
+ private
21
+
22
+ # Return class' attribute set
23
+ #
24
+ # @return [Virtus::AttributeSet]
25
+ #
26
+ # @api private
27
+ def attribute_set
28
+ self.class.attribute_set
29
+ end
30
+
31
+ # Return a list of allowed writer method names
32
+ #
33
+ # @return [Set]
34
+ #
35
+ # @api private
36
+ def allowed_writer_methods
37
+ self.class.allowed_writer_methods
38
+ end
39
+
40
+ end # module ClassInclusions
41
+ end # module Virtus
@@ -2,8 +2,7 @@ module Virtus
2
2
 
3
3
  # Class methods that are added when you include Virtus
4
4
  module ClassMethods
5
- WRITER_METHOD_REGEXP = /=\z/.freeze
6
- INVALID_WRITER_METHODS = %w[ == != === []= attributes= ].to_set.freeze
5
+ include Extensions
7
6
 
8
7
  # Hook called when module is extended
9
8
  #
@@ -22,39 +21,6 @@ module Virtus
22
21
 
23
22
  private_class_method :extended
24
23
 
25
- # Defines an attribute on an object's class
26
- #
27
- # @example
28
- # class Book
29
- # include Virtus
30
- #
31
- # attribute :title, String
32
- # attribute :author, String
33
- # attribute :published_at, DateTime
34
- # attribute :page_count, Integer
35
- # end
36
- #
37
- # @param [Symbol] name
38
- # the name of an attribute
39
- #
40
- # @param [Class] type
41
- # the type class of an attribute
42
- #
43
- # @param [#to_hash] options
44
- # the extra options hash
45
- #
46
- # @return [self]
47
- #
48
- # @see Attribute.build
49
- #
50
- # @api public
51
- def attribute(*args)
52
- attribute = Attribute.build(*args)
53
- attribute.define_accessor_methods(virtus_attributes_accessor_module)
54
- virtus_add_attribute(attribute)
55
- self
56
- end
57
-
58
24
  # Returns all the attributes defined on a Class
59
25
  #
60
26
  # @example
@@ -72,27 +38,22 @@ module Virtus
72
38
  # @return [AttributeSet]
73
39
  #
74
40
  # @api public
75
- def attributes
76
- return @attributes if defined?(@attributes)
41
+ def attribute_set
42
+ return @attribute_set if defined?(@attribute_set)
77
43
  superclass = self.superclass
78
44
  method = __method__
79
45
  parent = superclass.public_send(method) if superclass.respond_to?(method)
80
- @attributes = AttributeSet.new(parent)
46
+ @attribute_set = AttributeSet.new(parent)
81
47
  end
82
48
 
83
- # The list of writer methods that can be mass-assigned to in #attributes=
49
+ # @see Virtus::ClassMethods.attribute_set
84
50
  #
85
- # @return [Set]
51
+ # @deprecated
86
52
  #
87
- # @api private
88
- def allowed_writer_methods
89
- @allowed_writer_methods ||=
90
- begin
91
- allowed_writer_methods = public_instance_methods.map(&:to_s)
92
- allowed_writer_methods = allowed_writer_methods.grep(WRITER_METHOD_REGEXP).to_set
93
- allowed_writer_methods -= INVALID_WRITER_METHODS
94
- allowed_writer_methods.freeze
95
- end
53
+ # @api public
54
+ def attributes
55
+ warn "#{self}.attributes is deprecated. Use #{self}.attribute_set instead: #{caller.first}"
56
+ attribute_set
96
57
  end
97
58
 
98
59
  protected
@@ -104,8 +65,7 @@ module Virtus
104
65
  # @api private
105
66
  def virtus_setup_attributes_accessor_module
106
67
  @virtus_attributes_accessor_module = AttributesAccessor.new(inspect)
107
- include virtus_attributes_accessor_module
108
- self
68
+ include @virtus_attributes_accessor_module
109
69
  end
110
70
 
111
71
  private
@@ -128,13 +88,6 @@ module Virtus
128
88
  descendant.virtus_setup_attributes_accessor_module
129
89
  end
130
90
 
131
- # Holds the anonymous module which hosts this class's Attribute accessors
132
- #
133
- # @return [Module]
134
- #
135
- # @api private
136
- attr_reader :virtus_attributes_accessor_module
137
-
138
91
  # Hooks into const missing process to determine types of attributes
139
92
  #
140
93
  # @param [String] name
@@ -154,8 +107,13 @@ module Virtus
154
107
  #
155
108
  # @api private
156
109
  def virtus_add_attribute(attribute)
157
- attributes << attribute
158
- descendants.each { |descendant| descendant.attributes.reset }
110
+ super
111
+ descendants.each { |descendant| descendant.attribute_set.reset }
112
+ end
113
+
114
+ # @api private
115
+ def public_method_list
116
+ public_instance_methods
159
117
  end
160
118
 
161
119
  end # module ClassMethods
@@ -0,0 +1,93 @@
1
+ module Virtus
2
+
3
+ # Extensions common for both classes and instances
4
+ module Extensions
5
+ WRITER_METHOD_REGEXP = /=\z/.freeze
6
+ INVALID_WRITER_METHODS = %w[ == != === []= attributes= ].to_set.freeze
7
+
8
+ # A hook called when an object is extended with Virtus
9
+ #
10
+ # @param [Object] object
11
+ #
12
+ # @return [undefined]
13
+ #
14
+ # @api private
15
+ def self.extended(object)
16
+ object.extend(InstanceMethods)
17
+ object.instance_eval do
18
+ @virtus_attributes_accessor_module = AttributesAccessor.new(object.class.inspect)
19
+ extend @virtus_attributes_accessor_module
20
+ end
21
+ end
22
+ private_class_method :extended
23
+
24
+ # Defines an attribute on an object's class
25
+ #
26
+ # @example
27
+ # class Book
28
+ # include Virtus
29
+ #
30
+ # attribute :title, String
31
+ # attribute :author, String
32
+ # attribute :published_at, DateTime
33
+ # attribute :page_count, Integer
34
+ # end
35
+ #
36
+ # @param [Symbol] name
37
+ # the name of an attribute
38
+ #
39
+ # @param [Class] type
40
+ # the type class of an attribute
41
+ #
42
+ # @param [#to_hash] options
43
+ # the extra options hash
44
+ #
45
+ # @return [self]
46
+ #
47
+ # @see Attribute.build
48
+ #
49
+ # @api public
50
+ def attribute(*args)
51
+ attribute = Attribute.build(*args)
52
+ attribute.define_accessor_methods(@virtus_attributes_accessor_module)
53
+ virtus_add_attribute(attribute)
54
+ self
55
+ end
56
+
57
+ # The list of writer methods that can be mass-assigned to in #attributes=
58
+ #
59
+ # @return [Set]
60
+ #
61
+ # @api private
62
+ def allowed_writer_methods
63
+ @allowed_writer_methods ||=
64
+ begin
65
+ allowed_writer_methods = public_method_list.map(&:to_s)
66
+ allowed_writer_methods = allowed_writer_methods.grep(WRITER_METHOD_REGEXP).to_set
67
+ allowed_writer_methods -= INVALID_WRITER_METHODS
68
+ allowed_writer_methods.freeze
69
+ end
70
+ end
71
+
72
+ private
73
+
74
+ # Return an attribute set for that instance
75
+ #
76
+ # @return [AttributeSet]
77
+ #
78
+ # @api private
79
+ def attribute_set
80
+ @attribute_set ||= AttributeSet.new
81
+ end
82
+
83
+ # Add an attribute to the attribute set
84
+ #
85
+ # @return [AttributeSet]
86
+ #
87
+ # @api private
88
+ def virtus_add_attribute(attribute)
89
+ attribute_set << attribute
90
+ end
91
+
92
+ end # module Extensions
93
+ end # module Virtus
@@ -140,7 +140,7 @@ module Virtus
140
140
  #
141
141
  # @api private
142
142
  def get_attributes
143
- self.class.attributes.each_with_object({}) do |attribute, attributes|
143
+ attribute_set.each_with_object({}) do |attribute, attributes|
144
144
  name = attribute.name
145
145
  attributes[name] = get_attribute(name) if yield(attribute)
146
146
  end
@@ -155,7 +155,7 @@ module Virtus
155
155
  # @api private
156
156
  def set_attributes(attributes)
157
157
  ::Hash.try_convert(attributes).each do |name, value|
158
- set_attribute(name, value) if self.class.allowed_writer_methods.include?("#{name}=")
158
+ set_attribute(name, value) if allowed_writer_methods.include?("#{name}=")
159
159
  end
160
160
  end
161
161
 
@@ -181,5 +181,10 @@ module Virtus
181
181
  __send__("#{name}=", value)
182
182
  end
183
183
 
184
+ # @api private
185
+ def public_method_list
186
+ public_methods
187
+ end
188
+
184
189
  end # module InstanceMethods
185
190
  end # module Virtus
@@ -0,0 +1,62 @@
1
+ module Virtus
2
+
3
+ # Virtus module that can define attributes for later inclusion
4
+ #
5
+ module ModuleExtensions
6
+
7
+ def attribute(*args)
8
+ attribute_definitions << args
9
+ end
10
+
11
+ private
12
+
13
+ # Extend an object with Virtus methods and define attributes
14
+ #
15
+ # @param [Object] object
16
+ #
17
+ # @return [undefined]
18
+ #
19
+ # @api private
20
+ def extended(object)
21
+ super
22
+ object.extend(Virtus)
23
+ define_attributes(object)
24
+ end
25
+
26
+ # Extend a class with Virtus methods and define attributes
27
+ #
28
+ # @param [Object] object
29
+ #
30
+ # @return [undefined]
31
+ #
32
+ # @api private
33
+ def included(object)
34
+ super
35
+ object.send(:include, ClassInclusions)
36
+ define_attributes(object)
37
+ end
38
+
39
+ # Return attribute definitions
40
+ #
41
+ # @return [Array<Hash>]
42
+ #
43
+ # @api private
44
+ def attribute_definitions
45
+ @_attribute_definitions ||= []
46
+ end
47
+
48
+ # Define attributes on a class or instance
49
+ #
50
+ # @param [Object,Class] object
51
+ #
52
+ # @return [undefined]
53
+ #
54
+ # @api private
55
+ def define_attributes(object)
56
+ attribute_definitions.each do |attribute_args|
57
+ object.attribute(*attribute_args)
58
+ end
59
+ end
60
+
61
+ end # class ModuleExtensions
62
+ end # module Virtus
@@ -41,6 +41,7 @@ module Virtus
41
41
  private_class_method :included
42
42
 
43
43
  module InstanceMethods
44
+
44
45
  # the #get_attributes method accept a Proc object that will filter
45
46
  # out an attribute when the block returns false. the ValueObject
46
47
  # needs all the attributes, so we allow every attribute.
@@ -53,6 +54,23 @@ module Virtus
53
54
  def with(attribute_updates)
54
55
  self.class.new(get_attributes(&FILTER_NONE).merge(attribute_updates))
55
56
  end
57
+
58
+ # ValueObjects are immutable and can't be cloned
59
+ #
60
+ # They always represent the same value
61
+ #
62
+ # @example
63
+ #
64
+ # value_object.clone === value_object # => true
65
+ #
66
+ # @return [self]
67
+ #
68
+ # @api public
69
+ def clone
70
+ self
71
+ end
72
+ alias dup clone
73
+
56
74
  end
57
75
 
58
76
  module ClassMethods
@@ -112,7 +130,7 @@ module Virtus
112
130
  @allowed_writer_methods ||=
113
131
  begin
114
132
  allowed_writer_methods = super
115
- allowed_writer_methods += attributes.map{|attr| "#{attr.name}="}
133
+ allowed_writer_methods += attribute_set.map{|attr| "#{attr.name}="}
116
134
  allowed_writer_methods.to_set.freeze
117
135
  end
118
136
  end
@@ -1,3 +1,3 @@
1
1
  module Virtus
2
- VERSION = '0.4.2'
2
+ VERSION = '0.5.0'
3
3
  end
@@ -4,6 +4,13 @@ describe "default values" do
4
4
 
5
5
  before do
6
6
  module Examples
7
+
8
+ class Reference
9
+ include Virtus::ValueObject
10
+
11
+ attribute :ref, String
12
+ end
13
+
7
14
  class Page
8
15
  include Virtus
9
16
 
@@ -12,6 +19,7 @@ describe "default values" do
12
19
  attribute :view_count, Integer, :default => 0
13
20
  attribute :published, Boolean, :default => false, :accessor => :private
14
21
  attribute :editor_title, String, :default => :default_editor_title
22
+ attribute :reference, String, :default => Reference.new
15
23
 
16
24
  def default_editor_title
17
25
  published? ? title : "UNPUBLISHED: #{title}"
@@ -41,4 +49,12 @@ describe "default values" do
41
49
  subject.editor_title.should == 'UNPUBLISHED: Top Secret'
42
50
  end
43
51
 
52
+ context 'with a ValueObject' do
53
+ it 'should not duplicate the ValueObject' do
54
+ page1 = Examples::Page.new
55
+ page2 = Examples::Page.new
56
+ page1.reference.should equal(page2.reference)
57
+ end
58
+ end
59
+
44
60
  end
@@ -0,0 +1,35 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'I can extend objects' do
4
+ before do
5
+ module Examples
6
+ class User; end
7
+
8
+ class Admin; end
9
+ end
10
+ end
11
+
12
+ specify 'defining attributes on an object' do
13
+ attributes = { :name => 'John', :age => 29 }
14
+
15
+ admin = Examples::Admin.new
16
+ admin.extend(Virtus)
17
+
18
+ admin.attribute :name, String
19
+ admin.attribute :age, Integer
20
+
21
+ admin.name = 'John'
22
+ admin.age = 29
23
+
24
+ admin.name.should eql('John')
25
+ admin.age.should eql(29)
26
+
27
+ admin.attributes.should eql(attributes)
28
+
29
+ new_attributes = { :name => 'Jane', :age => 28 }
30
+ admin.attributes = new_attributes
31
+
32
+ admin.name.should eql('Jane')
33
+ admin.age.should eql(28)
34
+ end
35
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Using Struct as an embedded value attribute' do
4
+ before do
5
+ module Examples
6
+ Point = Struct.new(:x, :y)
7
+
8
+ class Rectangle
9
+ include Virtus
10
+
11
+ attribute :top_left, Point
12
+ attribute :bottom_right, Point
13
+ end
14
+ end
15
+ end
16
+
17
+ subject do
18
+ Examples::Rectangle.new(:top_left => [ 3, 5 ], :bottom_right => [ 8, 7 ])
19
+ end
20
+
21
+ specify 'initialize a struct object with correct attributes' do
22
+ subject.top_left.x.should be(3)
23
+ subject.top_left.y.should be(5)
24
+
25
+ subject.bottom_right.x.should be(8)
26
+ subject.bottom_right.y.should be(7)
27
+ end
28
+ end
@@ -0,0 +1,49 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'I can define attributes within a module' do
4
+ before do
5
+ module Examples
6
+ module Name
7
+ include Virtus
8
+
9
+ attribute :name, String
10
+ end
11
+
12
+ module Age
13
+ include Virtus
14
+
15
+ attribute :age, Integer
16
+ end
17
+
18
+ class User
19
+ include Name
20
+ end
21
+
22
+ class Admin < User
23
+ include Age
24
+ end
25
+
26
+ class Moderator; end
27
+ end
28
+ end
29
+
30
+ specify 'including a module with attributes into a class' do
31
+ Examples::User.attribute_set[:name].should be_instance_of(Virtus::Attribute::String)
32
+
33
+ Examples::Admin.attribute_set[:name].should be_instance_of(Virtus::Attribute::String)
34
+ Examples::Admin.attribute_set[:age].should be_instance_of(Virtus::Attribute::Integer)
35
+
36
+ user = Examples::Admin.new(:name => 'Piotr', :age => 29)
37
+ user.name.should eql('Piotr')
38
+ user.age.should eql(29)
39
+ end
40
+
41
+ specify 'including a module with attributes into an instance' do
42
+ moderator = Examples::Moderator.new
43
+ moderator.extend(Examples::Name, Examples::Age)
44
+
45
+ moderator.attributes = { :name => 'John', :age => 21 }
46
+ moderator.name.should eql('John')
47
+ moderator.age.should eql(21)
48
+ end
49
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Defining a ValueObject with a custom constructor" do
4
+ before do
5
+ module Examples
6
+ class Point
7
+ include Virtus::ValueObject
8
+
9
+ attribute :x, Integer
10
+ attribute :y, Integer
11
+
12
+ def initialize(attributes)
13
+ if attributes.kind_of?(Array)
14
+ self.x = attributes.first
15
+ self.y = attributes.last
16
+ else
17
+ super
18
+ end
19
+ end
20
+ end
21
+
22
+ class Rectangle
23
+ include Virtus
24
+
25
+ attribute :top_left, Point
26
+ attribute :bottom_right, Point
27
+ end
28
+ end
29
+ end
30
+
31
+ subject do
32
+ Examples::Rectangle.new(:top_left => [ 3, 4 ], :bottom_right => [ 5, 8 ])
33
+ end
34
+
35
+ specify "initialize a value object attribute with correct attributes" do
36
+ subject.top_left.x.should be(3)
37
+ subject.top_left.y.should be(4)
38
+
39
+ subject.bottom_right.x.should be(5)
40
+ subject.bottom_right.y.should be(8)
41
+ end
42
+ end
@@ -13,11 +13,15 @@ describe Virtus::ValueObject do
13
13
  attribute :longitude, Float
14
14
  end
15
15
  end
16
+
16
17
  let(:attribute_values) { { :latitude => 10.0, :longitude => 20.0 } }
18
+
17
19
  let(:instance_with_equal_state) { class_under_test.new(attribute_values) }
20
+
18
21
  let(:instance_with_different_state) do
19
22
  class_under_test.new(:latitude => attribute_values[:latitude])
20
23
  end
24
+
21
25
  subject { class_under_test.new(attribute_values) }
22
26
 
23
27
  describe 'initialization' do
@@ -29,8 +33,8 @@ describe Virtus::ValueObject do
29
33
 
30
34
  describe 'writer visibility' do
31
35
  it 'attributes are configured for private writers' do
32
- class_under_test.attributes[:latitude].public_reader?.should be(true)
33
- class_under_test.attributes[:longitude].public_writer?.should be(false)
36
+ class_under_test.attribute_set[:latitude].public_reader?.should be(true)
37
+ class_under_test.attribute_set[:longitude].public_writer?.should be(false)
34
38
  end
35
39
 
36
40
  it 'writer methods are set to private' do
@@ -4,6 +4,8 @@ describe Virtus::Attribute, '.determine_type' do
4
4
  let(:object) { described_class }
5
5
 
6
6
  (described_class.descendants - [ Virtus::Attribute::Collection ]).each do |attribute_class|
7
+ next if attribute_class <= Virtus::Attribute::EmbeddedValue
8
+
7
9
  context "with class #{attribute_class.inspect}" do
8
10
  subject { object.determine_type(attribute_class) }
9
11
 
@@ -36,7 +38,7 @@ describe Virtus::Attribute, '.determine_type' do
36
38
 
37
39
  let(:primitive) { Class.new { include Virtus } }
38
40
 
39
- it { should equal(Virtus::Attribute::EmbeddedValue) }
41
+ it { should equal(Virtus::Attribute::EmbeddedValue::FromOpenStruct) }
40
42
  end
41
43
 
42
44
  context 'when the primitive defaults to Object' do
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute::EmbeddedValue, '.determine_type' do
4
+ subject { described_class.determine_type(value) }
5
+
6
+ context "with Struct" do
7
+ let(:value) { Struct.new(:x) }
8
+
9
+ it { should be(Virtus::Attribute::EmbeddedValue::FromStruct) }
10
+ end
11
+
12
+ context "with OpenStruct" do
13
+ let(:value) { OpenStruct }
14
+
15
+ it { should be(Virtus::Attribute::EmbeddedValue::FromOpenStruct) }
16
+ end
17
+
18
+ context "with a Virtus descendant" do
19
+ let(:value) { Class.new { include Virtus; self } }
20
+
21
+ it { should be(Virtus::Attribute::EmbeddedValue::FromOpenStruct) }
22
+ end
23
+ end
@@ -3,47 +3,18 @@ require 'spec_helper'
3
3
  describe Virtus::Attribute::EmbeddedValue, '#coerce' do
4
4
  subject { object.coerce(value) }
5
5
 
6
- let(:attribute_name) { :attribute_name }
7
- let(:model) { OpenStruct }
8
- let(:instance) { Object.new }
6
+ let(:object) { described_class.new(:name, :primitive => primitive) }
7
+ let(:instance) { Object.new }
8
+ let(:value) { primitive.new }
9
9
 
10
- context 'when the value is a hash' do
11
- let(:value) { Hash[:foo => 'bar'] }
10
+ context 'when the value is a virtus object' do
11
+ let(:primitive) { Class.new { include Virtus } }
12
12
 
13
- context 'when the options include the model' do
14
- let(:object) { described_class.new(attribute_name, :primitive => model) }
15
- let(:model) { mock('model') }
16
- let(:model_instance) { mock('model_instance') }
17
-
18
- before do
19
- model.should_receive(:new).with(value).and_return(model_instance)
20
- end
21
-
22
- it { should be(model_instance) }
23
- end
24
-
25
- context 'with the default OpenStruct model' do
26
- let(:model_instance) { OpenStruct.new(value) }
27
-
28
- context 'when the options are an empty Hash' do
29
- let(:object) { described_class.new(attribute_name, {}) }
30
-
31
- it { should == model_instance }
32
- end
33
-
34
- context 'when the options are not provided' do
35
- let(:object) { described_class.new(attribute_name) }
36
-
37
- it { should == model_instance }
38
- end
39
- end
13
+ it { should be(value) }
40
14
  end
41
15
 
42
- context 'when the value is not a hash' do
43
- let(:object) { described_class.new(attribute_name) }
44
- let(:value) { mock('value') }
45
-
46
- before { model.should_not_receive(:new) }
16
+ context 'when the value is a virtus value object' do
17
+ let(:primitive) { Class.new { include Virtus::ValueObject } }
47
18
 
48
19
  it { should be(value) }
49
20
  end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute::EmbeddedValue::FromOpenStruct, '#coerce' do
4
+ subject { object.coerce(value) }
5
+
6
+ let(:primitive) { OpenStruct }
7
+
8
+ let(:object) do
9
+ described_class.new(:name, :primitive => primitive)
10
+ end
11
+
12
+ context 'when the value is a primitive instance' do
13
+ let(:value) { primitive.new }
14
+
15
+ it { should be(value) }
16
+ end
17
+
18
+ context 'when the value is a hash' do
19
+ let(:value) { Hash[:foo => 'bar'] }
20
+ let(:instance) { mock('instance') }
21
+
22
+ before do
23
+ primitive.should_receive(:new).with(value).and_return(instance)
24
+ end
25
+
26
+ it { should be(instance) }
27
+ end
28
+ end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::Attribute::EmbeddedValue::FromStruct, '#coerce' do
4
+ subject { object.coerce(value) }
5
+
6
+ let(:primitive) { Struct.new(:x, :y) }
7
+
8
+ let(:object) do
9
+ described_class.new(:name, :primitive => primitive)
10
+ end
11
+
12
+ context 'when the value is a primitive instance' do
13
+ let(:value) { primitive.new }
14
+
15
+ it { should be(value) }
16
+ end
17
+
18
+ context 'when the value is an array' do
19
+ let(:value) { [ 1 ,2 ] }
20
+ let(:instance) { mock('instance') }
21
+
22
+ before do
23
+ primitive.should_receive(:new).with(*value).and_return(instance)
24
+ end
25
+
26
+ it { should be(instance) }
27
+ end
28
+ end
@@ -3,6 +3,8 @@ require 'spec_helper'
3
3
  describe Virtus::Attribute::Object, '.descendants' do
4
4
  subject { described_class.descendants.map { |c| c.to_s }.sort }
5
5
 
6
+ before { pending 'Remove this spec in favor of Virtus::DescentantsTracker spec' }
7
+
6
8
  let(:known_descendants) do
7
9
  [ Virtus::Attribute::EmbeddedValue, Virtus::Attribute::Symbol,
8
10
  Virtus::Attribute::Time, Virtus::Attribute::String,
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Virtus::ClassMethods, '#attributes' do
4
- subject { object.attributes }
3
+ describe Virtus::ClassMethods, '#attribute_set' do
4
+ subject { object.attribute_set }
5
5
 
6
6
  let(:object) { Class.new { extend Virtus::ClassMethods } }
7
7
 
@@ -9,7 +9,7 @@ describe Virtus::ClassMethods, '#attribute' do
9
9
  let(:type) { Virtus::Attribute::String }
10
10
 
11
11
  def assert_attribute_added(klass, name, attribute_class)
12
- attributes = klass.attributes
12
+ attributes = klass.attribute_set
13
13
  attributes[name].should be_nil
14
14
  subject
15
15
  attribute = attributes[name]
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Virtus::ClassMethods, '#allowed_writer_methods' do
3
+ describe Virtus::Extensions, '#allowed_writer_methods' do
4
4
  subject { object.allowed_writer_methods }
5
5
 
6
6
  let(:object) do
@@ -6,7 +6,7 @@ describe Virtus::ValueObject, '.attribute' do
6
6
  let(:object) { Class.new { include Virtus::ValueObject } }
7
7
  let(:name) { :latitude }
8
8
  let(:type) { Float }
9
- let(:attribute) { object.attributes[name] }
9
+ let(:attribute) { object.attribute_set[name] }
10
10
 
11
11
  it { should be(object) }
12
12
 
@@ -0,0 +1,22 @@
1
+ require 'spec_helper'
2
+
3
+ describe Virtus::ValueObject::InstanceMethods, 'duplication' do
4
+ let(:described_class) do
5
+ Class.new do
6
+ include Virtus::ValueObject
7
+
8
+ attribute :name, String
9
+ end
10
+ end
11
+
12
+ subject { described_class.new }
13
+
14
+ it '#clone returns the same instance' do
15
+ subject.should equal(subject.clone)
16
+ end
17
+
18
+ it '#dup returns the same instance' do
19
+ subject.should equal(subject.dup)
20
+ end
21
+
22
+ end
data/virtus.gemspec CHANGED
@@ -16,9 +16,9 @@ Gem::Specification.new do |gem|
16
16
  gem.test_files = `git ls-files -- {spec}/*`.split("\n")
17
17
  gem.extra_rdoc_files = %w[LICENSE README.md TODO]
18
18
 
19
- gem.add_runtime_dependency('backports', '~> 2.5.3')
19
+ gem.add_runtime_dependency('backports', '~> 2.6.1')
20
20
 
21
21
  gem.add_development_dependency('rake', '~> 0.9.2')
22
22
  gem.add_development_dependency('rspec', '~> 1.3.2')
23
- gem.add_development_dependency('guard-rspec', '~> 0.6.0')
23
+ gem.add_development_dependency('guard-rspec', '~> 1.0.0')
24
24
  end
metadata CHANGED
@@ -5,9 +5,9 @@ version: !ruby/object:Gem::Version
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 4
9
- - 2
10
- version: 0.4.2
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Piotr Solnica
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2012-05-08 00:00:00 Z
18
+ date: 2012-06-08 00:00:00 Z
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
21
21
  name: backports
@@ -25,12 +25,12 @@ dependencies:
25
25
  requirements:
26
26
  - - ~>
27
27
  - !ruby/object:Gem::Version
28
- hash: 29
28
+ hash: 21
29
29
  segments:
30
30
  - 2
31
- - 5
32
- - 3
33
- version: 2.5.3
31
+ - 6
32
+ - 1
33
+ version: 2.6.1
34
34
  type: :runtime
35
35
  version_requirements: *id001
36
36
  - !ruby/object:Gem::Dependency
@@ -73,12 +73,12 @@ dependencies:
73
73
  requirements:
74
74
  - - ~>
75
75
  - !ruby/object:Gem::Version
76
- hash: 7
76
+ hash: 23
77
77
  segments:
78
+ - 1
78
79
  - 0
79
- - 6
80
80
  - 0
81
- version: 0.6.0
81
+ version: 1.0.0
82
82
  type: :development
83
83
  version_requirements: *id004
84
84
  description: Attributes on Steroids for Plain Old Ruby Objects
@@ -124,6 +124,8 @@ files:
124
124
  - lib/virtus/attribute/default_value/from_clonable.rb
125
125
  - lib/virtus/attribute/default_value/from_symbol.rb
126
126
  - lib/virtus/attribute/embedded_value.rb
127
+ - lib/virtus/attribute/embedded_value/from_open_struct.rb
128
+ - lib/virtus/attribute/embedded_value/from_struct.rb
127
129
  - lib/virtus/attribute/float.rb
128
130
  - lib/virtus/attribute/hash.rb
129
131
  - lib/virtus/attribute/integer.rb
@@ -135,6 +137,7 @@ files:
135
137
  - lib/virtus/attribute/time.rb
136
138
  - lib/virtus/attribute_set.rb
137
139
  - lib/virtus/attributes_accessor.rb
140
+ - lib/virtus/class_inclusions.rb
138
141
  - lib/virtus/class_methods.rb
139
142
  - lib/virtus/coercion.rb
140
143
  - lib/virtus/coercion/array.rb
@@ -152,7 +155,9 @@ files:
152
155
  - lib/virtus/coercion/time.rb
153
156
  - lib/virtus/coercion/time_coercions.rb
154
157
  - lib/virtus/coercion/true_class.rb
158
+ - lib/virtus/extensions.rb
155
159
  - lib/virtus/instance_methods.rb
160
+ - lib/virtus/module_extensions.rb
156
161
  - lib/virtus/support/descendants_tracker.rb
157
162
  - lib/virtus/support/equalizer.rb
158
163
  - lib/virtus/support/options.rb
@@ -164,8 +169,12 @@ files:
164
169
  - spec/integration/default_values_spec.rb
165
170
  - spec/integration/defining_attributes_spec.rb
166
171
  - spec/integration/embedded_value_spec.rb
172
+ - spec/integration/extending_objects_spec.rb
167
173
  - spec/integration/mass_assignment_with_accessors_spec.rb
168
174
  - spec/integration/overriding_virtus_spec.rb
175
+ - spec/integration/struct_as_embedded_value_spec.rb
176
+ - spec/integration/using_modules_spec.rb
177
+ - spec/integration/value_object_with_custom_constructor_spec.rb
169
178
  - spec/integration/virtus/instance_level_attributes_spec.rb
170
179
  - spec/integration/virtus/value_object_spec.rb
171
180
  - spec/rcov.opts
@@ -214,8 +223,11 @@ files:
214
223
  - spec/unit/virtus/attribute/define_accessor_methods_spec.rb
215
224
  - spec/unit/virtus/attribute/define_reader_method_spec.rb
216
225
  - spec/unit/virtus/attribute/define_writer_method_spec.rb
226
+ - spec/unit/virtus/attribute/embedded_value/class_methods/determine_type_spec.rb
217
227
  - spec/unit/virtus/attribute/embedded_value/class_methods/merge_options_spec.rb
218
228
  - spec/unit/virtus/attribute/embedded_value/coerce_spec.rb
229
+ - spec/unit/virtus/attribute/embedded_value/from_open_struct/coerce_spec.rb
230
+ - spec/unit/virtus/attribute/embedded_value/from_struct/coerce_spec.rb
219
231
  - spec/unit/virtus/attribute/float/coerce_spec.rb
220
232
  - spec/unit/virtus/attribute/get_spec.rb
221
233
  - spec/unit/virtus/attribute/inspect_spec.rb
@@ -243,9 +255,8 @@ files:
243
255
  - spec/unit/virtus/attributes_accessor/define_reader_method_spec.rb
244
256
  - spec/unit/virtus/attributes_accessor/define_writer_method_spec.rb
245
257
  - spec/unit/virtus/attributes_accessor/inspect_spec.rb
246
- - spec/unit/virtus/class_methods/allowed_writer_methods_spec.rb
258
+ - spec/unit/virtus/class_methods/attribute_set_spec.rb
247
259
  - spec/unit/virtus/class_methods/attribute_spec.rb
248
- - spec/unit/virtus/class_methods/attributes_spec.rb
249
260
  - spec/unit/virtus/class_methods/const_missing_spec.rb
250
261
  - spec/unit/virtus/class_methods/inherited_spec.rb
251
262
  - spec/unit/virtus/coercion/array/class_methods/to_set_spec.rb
@@ -308,6 +319,7 @@ files:
308
319
  - spec/unit/virtus/equalizer/class_method/new_spec.rb
309
320
  - spec/unit/virtus/equalizer/methods/eql_spec.rb
310
321
  - spec/unit/virtus/equalizer/methods/equal_value_spec.rb
322
+ - spec/unit/virtus/extensions/allowed_writer_methods_spec.rb
311
323
  - spec/unit/virtus/instance_methods/attributes_spec.rb
312
324
  - spec/unit/virtus/instance_methods/element_reference_spec.rb
313
325
  - spec/unit/virtus/instance_methods/element_set_spec.rb
@@ -323,6 +335,7 @@ files:
323
335
  - spec/unit/virtus/value_object/class_methods/attribute_spec.rb
324
336
  - spec/unit/virtus/value_object/class_methods/equalizer_spec.rb
325
337
  - spec/unit/virtus/value_object/initialize_spec.rb
338
+ - spec/unit/virtus/value_object/instance_methods/duplicates_spec.rb
326
339
  - spec/unit/virtus/value_object/instance_methods/with_spec.rb
327
340
  - tasks/metrics/ci.rake
328
341
  - tasks/metrics/flay.rake