virtus 0.0.3 → 0.0.4

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 (67) hide show
  1. data/Gemfile +11 -15
  2. data/History.txt +10 -0
  3. data/TODO +4 -0
  4. data/VERSION +1 -1
  5. data/config/flay.yml +1 -1
  6. data/config/flog.yml +1 -1
  7. data/config/roodi.yml +6 -6
  8. data/config/site.reek +5 -5
  9. data/lib/virtus.rb +21 -11
  10. data/lib/virtus/attribute.rb +92 -59
  11. data/lib/virtus/attribute/array.rb +4 -3
  12. data/lib/virtus/attribute/boolean.rb +21 -9
  13. data/lib/virtus/attribute/date.rb +5 -3
  14. data/lib/virtus/attribute/date_time.rb +5 -3
  15. data/lib/virtus/attribute/decimal.rb +5 -3
  16. data/lib/virtus/attribute/float.rb +5 -3
  17. data/lib/virtus/attribute/hash.rb +4 -3
  18. data/lib/virtus/attribute/integer.rb +5 -3
  19. data/lib/virtus/attribute/numeric.rb +5 -3
  20. data/lib/virtus/attribute/object.rb +4 -4
  21. data/lib/virtus/attribute/string.rb +7 -9
  22. data/lib/virtus/attribute/time.rb +5 -3
  23. data/lib/virtus/attribute_set.rb +151 -0
  24. data/lib/virtus/class_methods.rb +19 -10
  25. data/lib/virtus/instance_methods.rb +51 -27
  26. data/lib/virtus/support/descendants_tracker.rb +30 -0
  27. data/lib/virtus/typecast/boolean.rb +7 -5
  28. data/lib/virtus/typecast/numeric.rb +13 -8
  29. data/lib/virtus/typecast/string.rb +24 -0
  30. data/lib/virtus/typecast/time.rb +7 -5
  31. data/spec/integration/virtus/class_methods/attribute_spec.rb +17 -5
  32. data/spec/integration/virtus/class_methods/attributes_spec.rb +2 -5
  33. data/spec/shared/idempotent_method_behaviour.rb +5 -0
  34. data/spec/spec_helper.rb +15 -0
  35. data/spec/unit/shared/attribute.rb +6 -155
  36. data/spec/unit/shared/attribute/accept_options.rb +55 -0
  37. data/spec/unit/shared/attribute/accepted_options.rb +11 -0
  38. data/spec/unit/shared/attribute/complex.rb +15 -0
  39. data/spec/unit/shared/attribute/get.rb +29 -0
  40. data/spec/unit/shared/attribute/options.rb +7 -0
  41. data/spec/unit/shared/attribute/set.rb +42 -0
  42. data/spec/unit/virtus/attribute/boolean_spec.rb +1 -2
  43. data/spec/unit/virtus/attribute/date_spec.rb +1 -2
  44. data/spec/unit/virtus/attribute/date_time_spec.rb +1 -2
  45. data/spec/unit/virtus/attribute/decimal_spec.rb +1 -2
  46. data/spec/unit/virtus/attribute/float_spec.rb +1 -2
  47. data/spec/unit/virtus/attribute/integer_spec.rb +1 -2
  48. data/spec/unit/virtus/attribute/numeric/class_methods/descendants_spec.rb +2 -2
  49. data/spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb +6 -4
  50. data/spec/unit/virtus/attribute/string_spec.rb +1 -2
  51. data/spec/unit/virtus/attribute/time_spec.rb +1 -2
  52. data/spec/unit/virtus/attribute_set/append_spec.rb +35 -0
  53. data/spec/unit/virtus/attribute_set/each_spec.rb +60 -0
  54. data/spec/unit/virtus/attribute_set/element_reference_spec.rb +13 -0
  55. data/spec/unit/virtus/attribute_set/element_set_spec.rb +35 -0
  56. data/spec/unit/virtus/attribute_set/merge_spec.rb +36 -0
  57. data/spec/unit/virtus/attribute_set/parent_spec.rb +11 -0
  58. data/spec/unit/virtus/attribute_set/reset_spec.rb +60 -0
  59. data/spec/unit/virtus/class_methods/attribute_spec.rb +11 -0
  60. data/spec/unit/virtus/descendants_tracker/descendants_spec.rb +22 -0
  61. data/spec/unit/virtus/descendants_tracker/inherited_spec.rb +24 -0
  62. data/spec/unit/virtus/determine_type_spec.rb +21 -9
  63. data/spec/unit/virtus/instance_methods/{attribute_get_spec.rb → element_reference_spec.rb} +4 -2
  64. data/spec/unit/virtus/instance_methods/{attribute_set_spec.rb → element_set_spec.rb} +5 -7
  65. data/virtus.gemspec +35 -13
  66. metadata +96 -14
  67. data/lib/virtus/support/chainable.rb +0 -13
data/Gemfile CHANGED
@@ -1,26 +1,22 @@
1
- source "http://rubygems.org"
2
-
3
- group :virtus do
4
- gem 'virtus', File.read('VERSION'), :path => File.dirname(__FILE__)
5
- end
1
+ source :rubygems
6
2
 
7
3
  group :development do
8
- gem "jeweler", "~> 1.5.2"
9
- gem "rspec", "~> 2.6.0"
4
+ gem 'jeweler', '~> 1.6.4'
5
+ gem 'rspec', '~> 2.6.0'
10
6
  end
11
7
 
12
- platforms :mri_18 do
13
- group :metrics do
14
- gem 'flay', '~> 1.4.2'
15
- gem 'flog', '~> 2.5.1'
8
+ group :metrics do
9
+ gem 'flay', '~> 1.4.2'
10
+ gem 'flog', '~> 2.5.1'
11
+ gem 'reek', '~> 1.2.8', :git => 'git://github.com/dkubb/reek.git'
12
+ gem 'roodi', '~> 2.1.0'
13
+ gem 'yardstick', '~> 0.4.0'
14
+
15
+ platforms :mri_18 do
16
16
  gem 'heckle', '~> 1.4.3'
17
- gem 'json', '~> 1.5.1'
18
17
  gem 'metric_fu', '~> 2.1.1'
19
18
  gem 'mspec', '~> 1.5.17'
20
19
  gem 'rcov', '~> 0.9.9'
21
- gem 'reek', '~> 1.2.8', :git => 'git://github.com/dkubb/reek.git'
22
- gem 'roodi', '~> 2.1.0'
23
20
  gem 'ruby2ruby', '= 1.2.2'
24
- gem 'yardstick', '~> 0.4.0'
25
21
  end
26
22
  end
@@ -1,3 +1,13 @@
1
+ === v0.0.4 2011-07-08
2
+
3
+ * [BREAKING CHANGE] attributes hash has been replaced by a specialized class AttributeSet (dkubb)
4
+ * [BREAKING CHANGE] Virtus::ClassMethods.attribute returns self instead of a created attribute
5
+ * [changed] descendants tracking has been extracted into DescendantsTracker module (dkubb)
6
+ * [changed] Instance #primitive? method has been replaced by class utility method Virtus::Attribute.primitive?
7
+ * [changed] Virtus::Attribute::String#typecast_to_primitive delegates to Virtus::Typecast::String.call
8
+
9
+ Details: https://github.com/solnic/virtus/compare/v0.0.3...v0.0.4
10
+
1
11
  === v0.0.3 2011-06-09
2
12
 
3
13
  * [BREAKING CHANGE] Attribute classes were moved to Virtus::Attribute namespace
data/TODO ADDED
@@ -0,0 +1,4 @@
1
+ * Add Virtus::Typecast::Array
2
+ * Add Virtus::Typecast::Hash
3
+ * Add support for default values
4
+ * Add better inspection of attribute instances
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.4
@@ -1,3 +1,3 @@
1
1
  ---
2
2
  threshold: 19.0
3
- total_score: 191
3
+ total_score: 208
@@ -1,2 +1,2 @@
1
1
  ---
2
- threshold: 18.2
2
+ threshold: 17.2
@@ -1,18 +1,18 @@
1
1
  ---
2
- AbcMetricMethodCheck: { score: 11.8 }
2
+ AbcMetricMethodCheck: { score: 9.5 }
3
3
  AssignmentInConditionalCheck: { }
4
4
  CaseMissingElseCheck: { }
5
- ClassLineCountCheck: { line_count: 390 }
5
+ ClassLineCountCheck: { line_count: 345 }
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: 2 }
9
- CyclomaticComplexityMethodCheck: { complexity: 5 }
9
+ CyclomaticComplexityMethodCheck: { complexity: 3 }
10
10
  EmptyRescueBodyCheck: { }
11
11
  ForLoopCheck: { }
12
12
  # TODO: decrease line_count to 5 to 10
13
- MethodLineCountCheck: { line_count: 20 }
14
- MethodNameCheck: { pattern: !ruby/regexp /\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|[+*&|-])\z/ }
15
- ModuleLineCountCheck: { line_count: 392 }
13
+ MethodLineCountCheck: { line_count: 17 }
14
+ MethodNameCheck: { pattern: !ruby/regexp /\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|<<|[+*&|-])\z/ }
15
+ ModuleLineCountCheck: { line_count: 351 }
16
16
  ModuleNameCheck: { pattern: !ruby/regexp /\A(?:[A-Z]+|[A-Z][a-z](?:[A-Z]?[a-z])+)\z/ }
17
17
  # TODO: decrease parameter_count to 2 or less
18
18
  ParameterNumberCheck: { parameter_count: 3 }
@@ -8,10 +8,10 @@ UncommunicativeParameterName:
8
8
  - !ruby/regexp /[0-9]$/
9
9
  - !ruby/regexp /[A-Z]/
10
10
  LargeClass:
11
- max_methods: 15 # TODO: decrease max_methods to 10-15 or less
11
+ max_methods: 11
12
12
  exclude: []
13
13
  enabled: true
14
- max_instance_variables: 5
14
+ max_instance_variables: 8
15
15
  UncommunicativeMethodName:
16
16
  accept: []
17
17
  exclude: []
@@ -50,7 +50,7 @@ NestedIterators:
50
50
  enabled: true
51
51
  max_allowed_nesting: 1
52
52
  LongMethod:
53
- max_statements: 7 # TODO: decrease max_statements to 5 or less
53
+ max_statements: 6
54
54
  exclude: []
55
55
  enabled: true
56
56
  Duplication:
@@ -80,12 +80,12 @@ SimulatedPolymorphism:
80
80
  DataClump:
81
81
  exclude: []
82
82
  enabled: true
83
- max_copies: 1
83
+ max_copies: 2
84
84
  min_clump_size: 2
85
85
  ControlCouple:
86
86
  exclude: []
87
87
  enabled: true
88
88
  LongYieldList:
89
- max_params: 2
89
+ max_params: 1
90
90
  exclude: []
91
91
  enabled: true
@@ -1,5 +1,3 @@
1
- require 'pathname'
2
- require 'set'
3
1
  require 'date'
4
2
  require 'time'
5
3
  require 'bigdecimal'
@@ -7,10 +5,11 @@ require 'bigdecimal/util'
7
5
 
8
6
  # Base module which adds Attribute API to your classes
9
7
  module Virtus
8
+
10
9
  # Represents an undefined parameter used by auto-generated option methods
11
- module Undefined; end
10
+ Undefined = Object.new.freeze
12
11
 
13
- # Extends base class with Attributes and Chainable modules
12
+ # Extends base class with class and instance methods
14
13
  #
15
14
  # @param [Class] base
16
15
  #
@@ -18,12 +17,12 @@ module Virtus
18
17
  #
19
18
  # @api private
20
19
  def self.included(base)
20
+ base.extend(DescendantsTracker)
21
21
  base.extend(ClassMethods)
22
22
  base.send(:include, InstanceMethods)
23
- base.extend(Support::Chainable)
24
23
  end
25
24
 
26
- # Returns a Virtus::Attributes::Object sub-class based on a name or class
25
+ # Returns a Virtus::Attribute::Object sub-class based on a name or class
27
26
  #
28
27
  # @example
29
28
  # Virtus.determine_type('String') # => Virtus::Attribute::String
@@ -32,24 +31,35 @@ module Virtus
32
31
  # name of a class or a class itself
33
32
  #
34
33
  # @return [Class]
35
- # one of the Virtus::Attributes::Object sub-class
34
+ # one of the Virtus::Attribute::Object sub-class
36
35
  #
37
36
  # @api semipublic
38
37
  def self.determine_type(class_or_name)
39
- if class_or_name.is_a?(Class) && class_or_name < Attribute::Object
40
- class_or_name
38
+ if class_or_name.kind_of?(Class)
39
+ if class_or_name < Attribute::Object
40
+ class_or_name
41
+ else
42
+ Attribute.descendants.detect do |descendant|
43
+ class_or_name <= descendant.primitive
44
+ end
45
+ end
41
46
  elsif Attribute.const_defined?(name = class_or_name.to_s)
42
47
  Attribute.const_get(name)
43
48
  end
44
49
  end
45
- end
46
50
 
47
- require 'virtus/support/chainable'
51
+ end # module Virtus
52
+
53
+ require 'virtus/support/descendants_tracker'
54
+
48
55
  require 'virtus/class_methods'
49
56
  require 'virtus/instance_methods'
50
57
 
58
+ require 'virtus/attribute_set'
59
+
51
60
  require 'virtus/typecast/boolean'
52
61
  require 'virtus/typecast/numeric'
62
+ require 'virtus/typecast/string'
53
63
  require 'virtus/typecast/time'
54
64
 
55
65
  require 'virtus/attribute'
@@ -1,8 +1,11 @@
1
1
  module Virtus
2
+
2
3
  # Abstract class implementing base API for attribute types
3
4
  #
4
5
  # @abstract
5
6
  class Attribute
7
+ extend DescendantsTracker
8
+
6
9
  # Returns default options hash for a given attribute class
7
10
  #
8
11
  # @example
@@ -15,9 +18,9 @@ module Virtus
15
18
  # @api public
16
19
  def self.options
17
20
  options = {}
18
- accepted_options.each do |method|
19
- value = send(method)
20
- options[method] = value unless value.nil?
21
+ accepted_options.each do |option_name|
22
+ option_value = send(option_name)
23
+ options[option_name] = option_value unless option_value.nil?
21
24
  end
22
25
  options
23
26
  end
@@ -47,14 +50,15 @@ module Virtus
47
50
  # All accepted options
48
51
  #
49
52
  # @api public
50
- def self.accept_options(*args)
51
- accepted_options.concat(args)
53
+ def self.accept_options(*new_options)
54
+ # add new options to the array
55
+ concat_options(new_options)
52
56
 
53
57
  # create methods for each new option
54
- args.each { |option| add_option_method(option) }
58
+ new_options.each { |option| add_option_method(option) }
55
59
 
56
60
  # add new options to all descendants
57
- descendants.each { |descendant| descendant.accepted_options.concat(args) }
61
+ descendants.each { |descendant| descendant.concat_options(new_options) }
58
62
 
59
63
  accepted_options
60
64
  end
@@ -72,34 +76,61 @@ module Virtus
72
76
  end # end
73
77
  RUBY
74
78
  end
79
+
75
80
  private_class_method :add_option_method
76
81
 
77
- # Returns all the descendant classes
82
+ # Sets default options
78
83
  #
79
- # @example
80
- # Virtus::Attribute::Numeric.descendants
81
- # # => [Virtus::Attribute::Decimal, Virtus::Attribute::Float, Virtus::Attribute::Integer]
84
+ # @param [#to_hash] new_options
85
+ # options to be set
86
+ #
87
+ # @return [Hash]
88
+ # default options set on the attribute class
89
+ #
90
+ # @api private
91
+ def self.set_options(new_options)
92
+ new_options.to_hash.each do |option_name, option_value|
93
+ send(option_name, option_value)
94
+ end
95
+ end
96
+
97
+ # Adds new options that an attribute class can accept
98
+ #
99
+ # @param [#to_ary] new_options
100
+ # new options to be added
82
101
  #
83
102
  # @return [Array]
84
- # the array of descendants
103
+ # all accepted options
85
104
  #
86
- # @api public
87
- def self.descendants
88
- @descendants ||= []
105
+ # @api private
106
+ def self.concat_options(new_options)
107
+ accepted_options.concat(new_options.to_ary).uniq
89
108
  end
90
109
 
91
110
  # Adds descendant to descendants array and inherits default options
92
111
  #
93
- # @param [Class]
112
+ # @param [Class] descendant
94
113
  #
95
- # @return [Class]
114
+ # @return [self]
96
115
  #
97
116
  # @api private
98
117
  def self.inherited(descendant)
99
- descendants << descendant
100
- descendant.accepted_options.concat(accepted_options)
101
- options.each { |key, value| descendant.send(key, value) }
102
- descendant
118
+ super
119
+ descendant.concat_options(accepted_options)
120
+ descendant.set_options(options)
121
+ self
122
+ end
123
+
124
+ # Returns if the given value's class is an attribute's primitive
125
+ #
126
+ # @example
127
+ # Virtus::Attribute::String.primitive?('String') # => true
128
+ #
129
+ # @return [TrueClass, FalseClass]
130
+ #
131
+ # @api semipublic
132
+ def self.primitive?(value)
133
+ value.kind_of?(primitive)
103
134
  end
104
135
 
105
136
  # Returns name of the attribute
@@ -112,13 +143,6 @@ module Virtus
112
143
  # @api public
113
144
  attr_reader :name
114
145
 
115
- # Returns primitive class of the attribute
116
- #
117
- # @return [Class]
118
- #
119
- # @api private
120
- attr_reader :primitive
121
-
122
146
  # Returns options hash for the attribute
123
147
  #
124
148
  # @return [Hash]
@@ -140,7 +164,6 @@ module Virtus
140
164
  # @api private
141
165
  attr_reader :reader_visibility
142
166
 
143
-
144
167
  # Returns write visibility
145
168
  #
146
169
  # @return [Symbol]
@@ -148,7 +171,7 @@ module Virtus
148
171
  # @api private
149
172
  attr_reader :writer_visibility
150
173
 
151
- DEFAULT_ACCESSOR = :public.freeze
174
+ DEFAULT_ACCESSOR = :public
152
175
 
153
176
  OPTIONS = [ :primitive, :complex, :accessor, :reader, :writer ].freeze
154
177
 
@@ -159,21 +182,18 @@ module Virtus
159
182
  # @param [Symbol] name
160
183
  # the name of an attribute
161
184
  #
162
- # @param [Hash] options
185
+ # @param [#to_hash] options
163
186
  # hash of extra options which overrides defaults set on an attribute class
164
187
  #
165
188
  # @api private
166
189
  def initialize(name, options = {})
167
190
  @name = name
168
- @options = self.class.options.merge(options).freeze
169
-
170
- @primitive = @options[:primitive]
191
+ @options = self.class.options.merge(options.to_hash).freeze
171
192
 
172
193
  @instance_variable_name = "@#{@name}".freeze
194
+ @complex = @options.fetch(:complex, false)
173
195
 
174
- default_accessor = @options.fetch(:accessor, DEFAULT_ACCESSOR)
175
- @reader_visibility = @options.fetch(:reader, default_accessor)
176
- @writer_visibility = @options.fetch(:writer, default_accessor)
196
+ set_visibility
177
197
  end
178
198
 
179
199
  # Returns if an attribute is a complex one
@@ -186,16 +206,7 @@ module Virtus
186
206
  #
187
207
  # @api semipublic
188
208
  def complex?
189
- options[:complex]
190
- end
191
-
192
- # Returns if the given value's class is an attribute's primitive
193
- #
194
- # @return [TrueClass, FalseClass]
195
- #
196
- # @api private
197
- def primitive?(value)
198
- value.kind_of?(primitive)
209
+ @complex
199
210
  end
200
211
 
201
212
  # Converts the given value to the primitive type
@@ -208,7 +219,7 @@ module Virtus
208
219
  #
209
220
  # @api private
210
221
  def typecast(value)
211
- if value.nil? || primitive?(value)
222
+ if value.nil? || self.class.primitive?(value)
212
223
  value
213
224
  else
214
225
  typecast_to_primitive(value)
@@ -268,17 +279,21 @@ module Virtus
268
279
  #
269
280
  # @api private
270
281
  def add_reader_method(model)
271
- model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
272
- chainable(:attribute) do
273
- #{reader_visibility}
282
+ instance_variable_name = self.instance_variable_name
283
+ method_name = name
274
284
 
275
- def #{name}
285
+ model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
286
+ module AttributeMethods
287
+ def #{method_name}
276
288
  return #{instance_variable_name} if defined?(#{instance_variable_name})
277
- attribute = self.class.attributes[#{name.inspect}]
289
+ attribute = self.class.attributes[#{method_name.inspect}]
278
290
  #{instance_variable_name} = attribute ? attribute.get(self) : nil
279
291
  end
280
292
  end
293
+ include AttributeMethods
281
294
  RUBY
295
+
296
+ model.send(reader_visibility, method_name)
282
297
  end
283
298
 
284
299
  # Creates an attribute writer method
@@ -287,15 +302,33 @@ module Virtus
287
302
  #
288
303
  # @api private
289
304
  def add_writer_method(model)
290
- model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
291
- chainable(:attribute) do
292
- #{writer_visibility}
305
+ name = self.name
306
+ method_name = "#{name}="
293
307
 
294
- def #{name}=(value)
308
+ model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
309
+ module AttributeMethods
310
+ def #{method_name}(value)
295
311
  self.class.attributes[#{name.inspect}].set(self, value)
296
312
  end
297
313
  end
314
+ include AttributeMethods
298
315
  RUBY
316
+
317
+ model.send(writer_visibility, method_name)
299
318
  end
300
- end # Attribute
301
- end # Virtus
319
+
320
+ private
321
+
322
+ # Sets visibility of reader/write methods based on the options hash
323
+ #
324
+ # @return [undefined]
325
+ #
326
+ # @api private
327
+ def set_visibility
328
+ default_accessor = @options.fetch(:accessor, self.class::DEFAULT_ACCESSOR)
329
+ @reader_visibility = @options.fetch(:reader, default_accessor)
330
+ @writer_visibility = @options.fetch(:writer, default_accessor)
331
+ end
332
+
333
+ end # class Attribute
334
+ end # module Virtus