virtus 0.0.3 → 0.0.4

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