virtus 1.0.0.beta8 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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?