virtus 1.0.0.beta8 → 1.0.0.rc1

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 CHANGED
@@ -1,3 +1,30 @@
1
+ # v1.0.0 [to-be-released]
2
+
3
+ This release no longer works with Ruby 1.8.7.
4
+
5
+ * [BREAKING CHANGE] Integrated with axiom-types, most of the attribute sub-classes are gone (solnic)
6
+ * [feature] Configurable coercion via coercible integration (solnic)
7
+ * [feature] Strict mode for coercions via `:strict` option (solnic)
8
+ * [feature] Lazy-loaded default values via `:lazy` option (solnic)
9
+ * [feature] Finalizing models solving circular-dependency issue (see #81) (solnic)
10
+ * [feature] Ability to cherry-pick which extension should be included (solnic)
11
+ * [feature] Ability to inject a custom coercer object via `:coercer` option (solnic)
12
+ * [feature] Extension module builder with pre-defined configuration for attributes (elskwid & solnic)
13
+ * [feature] `Virtus::Attribute` exposes a public API - you can easily build, rename and clone attribute instances and use their coercion power (solnic)
14
+ * [feature] Ability to reset attributes to their default values (pewniak747)
15
+ * [changed] A meaningful error will be raised if a reserved name is used as an attribute name (solnic)
16
+ * [changed] Default value can be set via private and protected methods now (solnic)
17
+ * [changed] New syntax for value objects (solnic)
18
+ * [deprecated] `Virtus::Attribute.coerce` in favor of `Virtus.coerce` or a customized configured module (solnic)
19
+ * [deprecated] `include Virtus` in favor of `include Virtus.model` (for classes) or `Virtus.module` (for modules) (solnic)
20
+ * [deprecated] `include Virtus::ValueObject` in favor of `include Virtus.value_object` (solnic)
21
+ * [deprecated] `Virtus#attributes` in favor of `Virtus#attribute_set` (solnic)
22
+ * [fixed] const missing hook now works correctly in modules too (cored)
23
+ * [fixed] value object with Hash type works correctly (solnic)
24
+ * [fixed] issues with value-object subclasses and `#==` method (solnic)
25
+
26
+ [Compare v0.5.4..master](https://github.com/solnic/virtus/compare/v0.5.4...master)
27
+
1
28
  # v0.5.4 2012-12-20
2
29
 
3
30
  * [feature] Allow *any* enumerable to be a collection attribute (aptinio)
@@ -12,7 +39,7 @@
12
39
  * [feature] Added Hash member type coercion [example](https://github.com/solnic/virtus#hash-attributes-coercion) (greyblake)
13
40
  * [fixed] Fixed issues with String=>Integer coercion and e-notation (greyblake)
14
41
  * [changed] Replaced internal DescendantsTracker with the extracted gem (solnic)
15
- * [interal] Switched to rspec 2 and mutant for mutation testing (mbj)
42
+ * [internal] Switched to rspec 2 and mutant for mutation testing (mbj)
16
43
 
17
44
  [Compare v0.5.2..v0.5.3](https://github.com/solnic/virtus/compare/v0.5.2...v0.5.3)
18
45
 
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2011-2012 Piotr Solnica
1
+ Copyright (c) 2011-2013 Piotr Solnica
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -69,46 +69,36 @@ user.age # => 21
69
69
  ### Cherry-picking extensions
70
70
 
71
71
  ``` ruby
72
- # include just the attribute DSL
72
+ # include attribute DSL + constructor + mass-assignment
73
73
  class User
74
- include Virtus::Model::Core
74
+ include Virtus.model
75
75
 
76
76
  attribute :name, String
77
77
  end
78
78
 
79
- user = User.new
80
- user.name = 'Piotr'
79
+ user = User.new(:name => 'Piotr')
80
+ user.attributes = { :name => 'John' }
81
+ user.attributes
82
+ # => {:name => 'John'}
81
83
 
82
84
  # include attribute DSL + constructor
83
85
  class User
84
- include Virtus::Model::Core
85
- include Virtus::Model::Constructor
86
+ include Virtus.model(:mass_assignment => false)
86
87
 
87
88
  attribute :name, String
88
89
  end
89
90
 
90
91
  User.new(:name => 'Piotr')
91
92
 
92
- # include attribute DSL + constructor + mass-assignment
93
+ # include just the attribute DSL
93
94
  class User
94
- include Virtus::Model::Core
95
- include Virtus::Model::Constructor
96
- include Virtus::Model::MassAssignment
95
+ include Virtus.model(:constructor => false, :mass_assignment => false)
97
96
 
98
97
  attribute :name, String
99
98
  end
100
99
 
101
- user = User.new(:name => 'Piotr')
102
- user.attributes = { :name => 'John' }
103
- user.attributes
104
- # => {:name => 'John'}
105
-
106
- # starting from virtus 1.0.0 preferred way to do this is to use module builder
107
- MyModel = Virtus.model(:constructor => false, :mass_assignment => false)
108
-
109
- class User
110
- include MyModel
111
- end
100
+ user = User.new
101
+ user.name = 'Piotr'
112
102
  ```
113
103
 
114
104
  ### Using Virtus with Modules
@@ -525,7 +515,7 @@ end
525
515
  ## Attribute Finalization and Circular Dependencies
526
516
 
527
517
  If a type references another type which happens to not be available yet you need
528
- to use lazy-finalization of attributes and finalize virtus manually after all
518
+ to use lazy-finalization of attributes and finalize virtus manually after all
529
519
  types have been already loaded:
530
520
 
531
521
  ``` ruby
data/config/flay.yml CHANGED
@@ -1,3 +1,3 @@
1
1
  ---
2
- threshold: 13
3
- total_score: 321
2
+ threshold: 24
3
+ total_score: 439
data/config/flog.yml CHANGED
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 21.4
2
+ threshold: 33
data/config/mutant.yml CHANGED
@@ -3,12 +3,13 @@ name: virtus
3
3
  namespace:
4
4
  - 'Virtus::Attribute'
5
5
  - 'Virtus::AttributeSet'
6
+ - 'Virtus::Builder'
6
7
  - 'Virtus::ClassInclusions'
7
8
  - 'Virtus::ClassMethods'
8
9
  - 'Virtus::Configuration'
9
10
  - 'Virtus::ConstMissingExtensions'
10
11
  - 'Virtus::Extensions'
11
12
  - 'Virtus::InstanceMethods'
12
- - 'Virtus::ModuleBuilder'
13
+ - 'Virtus::Model'
13
14
  - 'Virtus::ModuleExtensions'
14
15
  - 'Virtus::ValueObject'
data/config/reek.yml CHANGED
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  Attribute:
3
- enabled: true
4
- exclude: []
3
+ enabled: false
5
4
  BooleanParameter:
6
5
  enabled: true
7
6
  exclude: []
@@ -10,23 +9,39 @@ ClassVariable:
10
9
  exclude: []
11
10
  ControlParameter:
12
11
  enabled: true
13
- exclude: []
12
+ exclude:
13
+ - 'Virtus::InstanceMethods::Constructor#initialize'
14
14
  DataClump:
15
15
  enabled: true
16
- exclude: []
17
16
  max_copies: 2
18
17
  min_clump_size: 2
18
+ exclude:
19
+ - 'Virtus::AttributeSet'
19
20
  DuplicateMethodCall:
20
21
  enabled: false
21
22
  FeatureEnvy:
22
23
  enabled: true
23
- exclude: []
24
+ exclude:
25
+ - 'Virtus::Attribute::Boolean#value_coerced?'
26
+ - 'Virtus::AttributeSet#get'
27
+ - 'Virtus::AttributeSet#set'
28
+ - 'Virtus::AttributeSet#skip_default?'
29
+ - 'Virtus::Builder#add_extended_hook'
30
+ - 'Virtus::Builder#add_included_hook'
31
+ - 'Virtus::InstanceMethods#assert_valid_name'
32
+ - 'Virtus::TypeLookup#determine_type_from_primitive'
24
33
  IrresponsibleModule:
25
34
  enabled: true
26
35
  exclude: []
27
36
  LongParameterList:
28
37
  enabled: true
29
- exclude: []
38
+ exclude:
39
+ - 'Virtus::AttributeSet#define_reader_method'
40
+ - 'Virtus::AttributeSet#define_writer_method'
41
+ - 'Virtus::Extensions::Methods#attribute'
42
+ - 'Virtus::ModuleExtensions#attribute'
43
+ - 'Virtus::ModuleExtensions#self.setup'
44
+ - 'Virtus::ValueObject::ClassMethods#attribute'
30
45
  max_params: 2
31
46
  LongYieldList:
32
47
  enabled: true
@@ -36,34 +51,65 @@ NestedIterators:
36
51
  enabled: true
37
52
  max_allowed_nesting: 1
38
53
  ignore_iterators: []
39
- exclude: []
54
+ exclude:
55
+ - 'Virtus::Builder#add_extended_hook'
56
+ - 'Virtus::Builder#add_included_hook'
57
+ - 'Virtus::ModuleBuilder#add_included_hook'
58
+ - 'Virtus::Equalizer#define_cmp_method'
59
+ - 'Virtus::Equalizer#define_hash_method'
60
+ - 'Virtus::Equalizer#define_inspect_method'
40
61
  NilCheck:
41
62
  enabled: true
42
- exclude: []
63
+ exclude:
64
+ - 'Virtus::Attribute::EmbeddedValue::FromOpenStruct#call'
65
+ - 'Virtus::Attribute::EmbeddedValue::FromStruct#call'
66
+ - 'Virtus::Attribute::Strict#coerce'
67
+ - 'Virtus::Options#options'
68
+ - 'Virtus::TypeLookup#determine_type_from_primitive'
43
69
  RepeatedConditional:
44
70
  enabled: true
45
- exclude: []
46
71
  max_ifs: 1
72
+ exclude:
73
+ - 'Virtus::AttributeSet'
74
+ - 'Virtus::Builder'
47
75
  TooManyInstanceVariables:
48
76
  enabled: true
49
- exclude: []
50
77
  max_instance_variables: 3
78
+ exclude:
79
+ - 'Virtus::Attribute'
80
+ - 'Virtus::Attribute::Builder'
81
+ - 'Virtus::Configuration'
51
82
  TooManyMethods:
52
83
  enabled: true
53
- exclude: []
54
- max_methods: 10
84
+ max_methods: 6
85
+ exclude:
86
+ - 'Virtus::Equalizer'
87
+ - 'Virtus::Builder'
88
+ - 'Virtus::Attribute'
89
+ - 'Virtus::AttributeSet'
55
90
  TooManyStatements:
56
91
  enabled: true
57
- exclude: []
58
92
  max_statements: 4
93
+ exclude:
94
+ - 'Virtus#self.included'
95
+ - 'Virtus::Attribute::Builder#self.determine_type'
96
+ - 'Virtus::Attribute::Collection#self.infer'
97
+ - 'Virtus::Attribute::Hash#self.infer'
98
+ - 'Virtus::Builder#add_extended_hook'
99
+ - 'Virtus::Builder#add_included_hook'
100
+ - 'Virtus::ModuleBuilder#add_included_hook'
101
+ - 'Virtus::ClassInclusions#self.included'
102
+ - 'Virtus::ClassMethods#self.extended'
103
+ - 'Virtus::Extensions#self.extended'
104
+ - 'Virtus::ModuleExtensions#included'
105
+ - 'Virtus::TypeLookup#determine_type_from_primitive'
106
+ - 'Virtus::ValueObject#self.included'
59
107
  UncommunicativeMethodName:
60
108
  enabled: true
61
- exclude: []
62
109
  reject:
63
110
  - !ruby/regexp /^[a-z]$/
64
111
  - !ruby/regexp /[0-9]$/
65
112
  - !ruby/regexp /[A-Z]/
66
- accept: []
67
113
  UncommunicativeModuleName:
68
114
  enabled: true
69
115
  exclude: []
@@ -78,7 +124,7 @@ UncommunicativeParameterName:
78
124
  - !ruby/regexp /^.$/
79
125
  - !ruby/regexp /[0-9]$/
80
126
  - !ruby/regexp /[A-Z]/
81
- accept: []
127
+ accept: ['_']
82
128
  UncommunicativeVariableName:
83
129
  enabled: true
84
130
  exclude: []
@@ -92,5 +138,9 @@ UnusedParameters:
92
138
  exclude: []
93
139
  UtilityFunction:
94
140
  enabled: true
95
- exclude: []
96
141
  max_helper_calls: 0
142
+ exclude:
143
+ - 'Virtus::Attribute::Boolean#value_coerced?'
144
+ - 'Virtus::AttributeSet#set_default'
145
+ - 'Virtus::AttributeSet#skip_default?'
146
+ - 'Virtus::Extensions::Methods#merge_options'
data/lib/virtus.rb CHANGED
@@ -25,6 +25,8 @@ module Virtus
25
25
  #
26
26
  # @return [undefined]
27
27
  #
28
+ # @deprecated
29
+ #
28
30
  # @api private
29
31
  def self.included(object)
30
32
  super
@@ -44,6 +46,8 @@ module Virtus
44
46
  #
45
47
  # @return [undefined]
46
48
  #
49
+ # @deprecated
50
+ #
47
51
  # @api private
48
52
  def self.extended(object)
49
53
  Virtus.warn("extending with Virtus module is deprecated. Use 'extend(Virtus.model)' instead #{caller.first}")
@@ -124,21 +128,53 @@ module Virtus
124
128
  #
125
129
  # @api public
126
130
  def self.model(options = {}, &block)
127
- ModelExtensionBuilder.call(options, &block)
131
+ ModelBuilder.call(options, &block)
128
132
  end
129
133
 
130
134
  # Builds a module for...modules
131
135
  #
136
+ # @example
137
+ #
138
+ # module Common
139
+ # include Virtus.module
140
+ #
141
+ # attribute :name, String
142
+ # attribute :age, Integer
143
+ # end
144
+ #
145
+ # class User
146
+ # include Common
147
+ # end
148
+ #
149
+ # class Admin
150
+ # include Common
151
+ # end
152
+ #
153
+ # @return [Module]
154
+ #
132
155
  # @api public
133
156
  def self.module(options = {}, &block)
134
- ModuleExtensionBuilder.call(options, &block)
157
+ ModuleBuilder.call(options, &block)
135
158
  end
136
159
 
137
160
  # Builds a module for value object models
138
161
  #
162
+ # @example
163
+ #
164
+ # class GeoLocation
165
+ # include Virtus.value_object
166
+ #
167
+ # values do
168
+ # attribute :lat, Float
169
+ # attribute :lng, Float
170
+ # end
171
+ # end
172
+ #
173
+ # @return [Module]
174
+ #
139
175
  # @api public
140
176
  def self.value_object(options = {}, &block)
141
- ValueObjectExtensionBuilder.call(options, &block)
177
+ ValueObjectBuilder.call(options, &block)
142
178
  end
143
179
 
144
180
  # Global configuration instance
@@ -150,9 +186,28 @@ module Virtus
150
186
  @configuration ||= Configuration.new
151
187
  end
152
188
 
189
+ # Finalize pending attributes
190
+ #
191
+ # @example
192
+ # class User
193
+ # include Virtus.model(:finalize => false)
194
+ #
195
+ # attribute :address, 'Address'
196
+ # end
197
+ #
198
+ # class Address
199
+ # include Virtus.model(:finalize => false)
200
+ #
201
+ # attribute :user, 'User'
202
+ # end
203
+ #
204
+ # Virtus.finalize # this will resolve constant names
205
+ #
206
+ # @return [Array] array of finalized models
207
+ #
153
208
  # @api public
154
209
  def self.finalize
155
- ExtensionBuilder.pending.each do |klass|
210
+ Builder.pending.each do |klass|
156
211
  klass.attribute_set.finalize
157
212
  end
158
213
  end
@@ -180,7 +235,8 @@ require 'virtus/class_inclusions'
180
235
  require 'virtus/module_extensions'
181
236
 
182
237
  require 'virtus/configuration'
183
- require 'virtus/module_builder'
238
+ require 'virtus/builder'
239
+ require 'virtus/builder/hook_context'
184
240
 
185
241
  require 'virtus/class_methods'
186
242
  require 'virtus/instance_methods'
@@ -1,5 +1,20 @@
1
1
  module Virtus
2
2
 
3
+ # Attribute objects handle coercion and provide interface to hook into an
4
+ # attribute set instance that's included into a class or object
5
+ #
6
+ # @example
7
+ #
8
+ # # non-strict mode
9
+ # attr = Virtus::Attribute.build(Integer)
10
+ # attr.coerce('1')
11
+ # # => 1
12
+ #
13
+ # # strict mode
14
+ # attr = Virtus::Attribute.build(Integer, :strict => true)
15
+ # attr.coerce('not really coercible')
16
+ # # => Virtus::CoercionError: Failed to coerce "fsafa" into Integer
17
+ #
3
18
  class Attribute
4
19
  extend DescendantsTracker, Options, TypeLookup
5
20
 
@@ -24,22 +39,28 @@ module Virtus
24
39
  self
25
40
  end
26
41
 
27
- attr_reader :type, :primitive, :options, :default_value, :coercer
42
+ # Return type of this attribute
43
+ #
44
+ # @return [Axiom::Types::Type]
45
+ #
46
+ # @api public
47
+ attr_reader :type
48
+
49
+ # @api private
50
+ attr_reader :primitive, :options, :default_value, :coercer
28
51
 
29
52
  # Builds an attribute instance
30
53
  #
31
- # @param [Symbol] name
32
- # the name of an attribute
33
- #
34
- # @param [Class] type
35
- # optional type class of an attribute
54
+ # @param [Class,Array,Hash,String,Symbol] type
55
+ # this can be an explicit class or an object from which virtus can infer
56
+ # the type
36
57
  #
37
58
  # @param [#to_hash] options
38
59
  # optional extra options hash
39
60
  #
40
61
  # @return [Attribute]
41
62
  #
42
- # @api private
63
+ # @api public
43
64
  def self.build(type, options = {})
44
65
  Builder.call(type, options)
45
66
  end
@@ -52,13 +73,6 @@ module Virtus
52
73
  )
53
74
  end
54
75
 
55
- # @api private
56
- def self.new(*args)
57
- attribute = super
58
- yield(attribute)
59
- attribute
60
- end
61
-
62
76
  # @api private
63
77
  def self.build_type(definition)
64
78
  Axiom::Types.infer(definition.primitive)
@@ -69,16 +83,6 @@ module Virtus
69
83
  # noop
70
84
  end
71
85
 
72
- # Initializes an attribute instance
73
- #
74
- # @param [#to_sym] name
75
- # the name of an attribute
76
- #
77
- # @param [#to_hash] options
78
- # hash of extra options which overrides defaults set on an attribute class
79
- #
80
- # @return [undefined]
81
- #
82
86
  # @api private
83
87
  def initialize(type, options)
84
88
  @type = type
@@ -88,16 +92,37 @@ module Virtus
88
92
  @coercer = options.fetch(:coercer)
89
93
  end
90
94
 
95
+ # Coerce the input into the expected type
96
+ #
97
+ # @example
98
+ #
99
+ # attr = Virtus::Attribute.build(String)
100
+ # attr.coerce(:one) # => 'one'
101
+ #
102
+ # @param [Object] input
103
+ #
91
104
  # @api public
92
- def coerce(value)
93
- coercer.call(value)
105
+ def coerce(input)
106
+ coercer.call(input)
94
107
  end
95
108
 
109
+ # Return a new attribute with the new name
110
+ #
111
+ # @param [Symbol] name
112
+ #
113
+ # @return [Attribute]
114
+ #
96
115
  # @api public
97
116
  def rename(name)
98
117
  self.class.build(type, options.merge(:name => name))
99
118
  end
100
119
 
120
+ # Return if the given value was coerced
121
+ #
122
+ # @param [Object] value
123
+ #
124
+ # @return [Boolean]
125
+ #
101
126
  # @api public
102
127
  def value_coerced?(value)
103
128
  coercer.success?(primitive, value)
@@ -105,6 +130,14 @@ module Virtus
105
130
 
106
131
  # Return if the attribute is coercible
107
132
  #
133
+ # @example
134
+ #
135
+ # attr = Virtus::Attribute.build(String, :coerce => true)
136
+ # attr.coercible? # => true
137
+ #
138
+ # attr = Virtus::Attribute.build(String, :coerce => false)
139
+ # attr.coercible? # => false
140
+ #
108
141
  # @return [Boolean]
109
142
  #
110
143
  # @api public
@@ -112,21 +145,69 @@ module Virtus
112
145
  kind_of?(Coercible)
113
146
  end
114
147
 
148
+ # Return if the attribute has lazy default value evaluation
149
+ #
150
+ # @example
151
+ #
152
+ # attr = Virtus::Attribute.build(String, :lazy => true)
153
+ # attr.lazy? # => true
154
+ #
155
+ # attr = Virtus::Attribute.build(String, :lazy => false)
156
+ # attr.lazy? # => false
157
+ #
158
+ # @return [Boolean]
159
+ #
115
160
  # @api public
116
161
  def lazy?
117
162
  kind_of?(LazyDefault)
118
163
  end
119
164
 
165
+ # Return if the attribute is in the strict coercion mode
166
+ #
167
+ # @example
168
+ #
169
+ # attr = Virtus::Attribute.build(String, :strict => true)
170
+ # attr.strict? # => true
171
+ #
172
+ # attr = Virtus::Attribute.build(String, :strict => false)
173
+ # attr.strict? # => false
174
+ #
175
+ # @return [Boolean]
176
+ #
120
177
  # @api public
121
178
  def strict?
122
179
  kind_of?(Strict)
123
180
  end
124
181
 
182
+ # Return if the attribute is accepts nil values as valid coercion output
183
+ #
184
+ # @example
185
+ #
186
+ # attr = Virtus::Attribute.build(String, :required => true)
187
+ # attr.required? # => true
188
+ #
189
+ # attr = Virtus::Attribute.build(String, :required => false)
190
+ # attr.required? # => false
191
+ #
192
+ # @return [Boolean]
193
+ #
125
194
  # @api public
126
195
  def required?
127
196
  options[:required]
128
197
  end
129
198
 
199
+ # Return if the attribute was already finalized
200
+ #
201
+ # @example
202
+ #
203
+ # attr = Virtus::Attribute.build(String, :finalize => true)
204
+ # attr.finalized? # => true
205
+ #
206
+ # attr = Virtus::Attribute.build(String, :finalize => false)
207
+ # attr.finalized? # => false
208
+ #
209
+ # @return [Boolean]
210
+ #
130
211
  # @api public
131
212
  def finalized?
132
213
  frozen?