virtus 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -2
- data/History.txt +7 -0
- data/README.markdown +7 -7
- data/Rakefile +5 -10
- data/VERSION +1 -1
- data/config/flay.yml +1 -1
- data/config/roodi.yml +3 -3
- data/lib/virtus.rb +5 -31
- data/lib/virtus/attribute.rb +118 -46
- data/lib/virtus/attribute/boolean.rb +19 -15
- data/lib/virtus/attribute/date_time.rb +2 -0
- data/lib/virtus/attribute/decimal.rb +2 -0
- data/lib/virtus/attribute/float.rb +2 -0
- data/lib/virtus/attribute/integer.rb +2 -0
- data/lib/virtus/attribute/string.rb +2 -0
- data/lib/virtus/attribute/time.rb +8 -1
- data/lib/virtus/class_methods.rb +4 -5
- data/lib/virtus/instance_methods.rb +9 -7
- data/lib/virtus/support/descendants_tracker.rb +21 -7
- data/lib/virtus/typecast/boolean.rb +7 -7
- data/lib/virtus/typecast/numeric.rb +8 -8
- data/lib/virtus/typecast/string.rb +2 -2
- data/lib/virtus/typecast/time.rb +49 -21
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/virtus/attribute/class_methods/determine_type_spec.rb +49 -0
- data/spec/unit/virtus/descendants_tracker/{inherited_spec.rb → add_descendant_spec.rb} +2 -2
- data/virtus.gemspec +7 -6
- metadata +27 -53
- data/.gitignore +0 -7
- data/spec/unit/virtus/determine_type_spec.rb +0 -32
data/Gemfile
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
source :rubygems
|
2
2
|
|
3
3
|
group :development do
|
4
|
-
gem '
|
5
|
-
gem '
|
4
|
+
gem 'backports', '~> 2.3.0'
|
5
|
+
gem 'jeweler', '~> 1.6.4'
|
6
|
+
gem 'rspec', '~> 2.6.0'
|
6
7
|
end
|
7
8
|
|
8
9
|
group :metrics do
|
@@ -14,6 +15,7 @@ group :metrics do
|
|
14
15
|
|
15
16
|
platforms :mri_18 do
|
16
17
|
gem 'heckle', '~> 1.4.3'
|
18
|
+
gem 'json', '~> 1.5.3'
|
17
19
|
gem 'metric_fu', '~> 2.1.1'
|
18
20
|
gem 'mspec', '~> 1.5.17'
|
19
21
|
gem 'rcov', '~> 0.9.9'
|
data/History.txt
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
=== v0.0.5 to-be-released
|
2
|
+
|
3
|
+
Details: https://github.com/solnic/virtus/compare/v0.0.4...master
|
4
|
+
|
5
|
+
* [BREAKING CHANGE] Moved Virtus.determine_type to Virtus::Attribute.determine_type (dkubb)
|
6
|
+
* [general] Added backports as a development dependency (dkubb)
|
7
|
+
|
1
8
|
=== v0.0.4 2011-07-08
|
2
9
|
|
3
10
|
* [BREAKING CHANGE] attributes hash has been replaced by a specialized class AttributeSet (dkubb)
|
data/README.markdown
CHANGED
@@ -25,20 +25,20 @@ attributes that require typecasting and/or validations.
|
|
25
25
|
end
|
26
26
|
|
27
27
|
# setting attributes in the constructor
|
28
|
-
user = User.new(:age => 28)
|
28
|
+
user = User.new(:name => 'Piotr', :age => 28)
|
29
29
|
|
30
30
|
# attribute readers
|
31
|
-
user.name
|
31
|
+
user.name # => "Piotr"
|
32
32
|
|
33
33
|
# hash of attributes
|
34
|
-
user.attributes
|
34
|
+
user.attributes # => { :name => "Piotr" }
|
35
35
|
|
36
36
|
# automatic typecasting
|
37
37
|
user.age = '28'
|
38
|
-
user.age
|
38
|
+
user.age # => 28
|
39
39
|
|
40
40
|
user.birthday = 'November 18th, 1983'
|
41
|
-
user.birthday
|
41
|
+
user.birthday # => #<DateTime: 1983-11-18T00:00:00+00:00 (4891313/2,0/1,2299161)>
|
42
42
|
|
43
43
|
## Custom Attributes
|
44
44
|
|
@@ -65,8 +65,8 @@ attributes that require typecasting and/or validations.
|
|
65
65
|
|
66
66
|
user = MyApp::User.new
|
67
67
|
|
68
|
-
user.info = '{"email"
|
69
|
-
user.info
|
68
|
+
user.info = '{"email":"john@domain.com"}'
|
69
|
+
user.info # => {"email"=>"john@domain.com"}
|
70
70
|
|
71
71
|
## Note on Patches/Pull Requests
|
72
72
|
|
data/Rakefile
CHANGED
@@ -4,18 +4,13 @@ require 'jeweler'
|
|
4
4
|
require 'rspec/core/rake_task'
|
5
5
|
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
|
-
gem.name =
|
7
|
+
gem.name = 'virtus'
|
8
8
|
gem.platform = Gem::Platform::RUBY
|
9
|
-
gem.authors = [
|
10
|
-
gem.email = [
|
11
|
-
gem.homepage =
|
12
|
-
gem.summary =
|
9
|
+
gem.authors = [ 'Piotr Solnica' ]
|
10
|
+
gem.email = [ 'piotr@rubyverse.com' ]
|
11
|
+
gem.homepage = 'https://github.com/solnic/virtus'
|
12
|
+
gem.summary = 'Attributes for your plain ruby objects'
|
13
13
|
gem.description = gem.summary
|
14
|
-
|
15
|
-
gem.files = `git ls-files`.split("\n")
|
16
|
-
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
|
-
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
|
-
gem.require_paths = ["lib"]
|
19
14
|
end
|
20
15
|
|
21
16
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.5
|
data/config/flay.yml
CHANGED
data/config/roodi.yml
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
AbcMetricMethodCheck: { score: 9.5 }
|
3
3
|
AssignmentInConditionalCheck: { }
|
4
4
|
CaseMissingElseCheck: { }
|
5
|
-
ClassLineCountCheck: { line_count:
|
5
|
+
ClassLineCountCheck: { line_count: 404 }
|
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 }
|
@@ -10,9 +10,9 @@ CyclomaticComplexityMethodCheck: { complexity: 3 }
|
|
10
10
|
EmptyRescueBodyCheck: { }
|
11
11
|
ForLoopCheck: { }
|
12
12
|
# TODO: decrease line_count to 5 to 10
|
13
|
-
MethodLineCountCheck: { line_count:
|
13
|
+
MethodLineCountCheck: { line_count: 19 }
|
14
14
|
MethodNameCheck: { pattern: !ruby/regexp /\A(?:[a-z\d](?:_?[a-z\d])+[?!=]?|\[\]=?|==|<=>|<<|[+*&|-])\z/ }
|
15
|
-
ModuleLineCountCheck: { line_count:
|
15
|
+
ModuleLineCountCheck: { line_count: 410 }
|
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 }
|
data/lib/virtus.rb
CHANGED
@@ -11,41 +11,15 @@ module Virtus
|
|
11
11
|
|
12
12
|
# Extends base class with class and instance methods
|
13
13
|
#
|
14
|
-
# @param [Class]
|
14
|
+
# @param [Class] descendant
|
15
15
|
#
|
16
16
|
# @return [Class]
|
17
17
|
#
|
18
18
|
# @api private
|
19
|
-
def self.included(
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
end
|
24
|
-
|
25
|
-
# Returns a Virtus::Attribute::Object sub-class based on a name or class
|
26
|
-
#
|
27
|
-
# @example
|
28
|
-
# Virtus.determine_type('String') # => Virtus::Attribute::String
|
29
|
-
#
|
30
|
-
# @param [Class,String] class_or_name
|
31
|
-
# name of a class or a class itself
|
32
|
-
#
|
33
|
-
# @return [Class]
|
34
|
-
# one of the Virtus::Attribute::Object sub-class
|
35
|
-
#
|
36
|
-
# @api semipublic
|
37
|
-
def self.determine_type(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
|
46
|
-
elsif Attribute.const_defined?(name = class_or_name.to_s)
|
47
|
-
Attribute.const_get(name)
|
48
|
-
end
|
19
|
+
def self.included(descendant)
|
20
|
+
descendant.extend(DescendantsTracker)
|
21
|
+
descendant.extend(ClassMethods)
|
22
|
+
descendant.send(:include, InstanceMethods)
|
49
23
|
end
|
50
24
|
|
51
25
|
end # module Virtus
|
data/lib/virtus/attribute.rb
CHANGED
@@ -6,6 +6,77 @@ module Virtus
|
|
6
6
|
class Attribute
|
7
7
|
extend DescendantsTracker
|
8
8
|
|
9
|
+
# Returns a Virtus::Attribute::Object descendant based on a name or class
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# Attribute.determine_type('String') # => Virtus::Attribute::String
|
13
|
+
#
|
14
|
+
# @param [Class, #to_s] class_or_name
|
15
|
+
# name of a class or a class itself
|
16
|
+
#
|
17
|
+
# @return [Class]
|
18
|
+
# one of the Virtus::Attribute::Object descendants
|
19
|
+
#
|
20
|
+
# @return [nil]
|
21
|
+
# nil if the type cannot be determined by the class_or_name
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
def self.determine_type(class_or_name)
|
25
|
+
# first match on the Attribute singleton class first, then match
|
26
|
+
# any class, finally fallback to matching on the string
|
27
|
+
case class_or_name
|
28
|
+
when Attribute::Object.singleton_class then determine_type_from_attribute(class_or_name)
|
29
|
+
when Class then determine_type_from_primitive(class_or_name)
|
30
|
+
else
|
31
|
+
determine_type_from_string(class_or_name.to_s)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Return the Attribute class given an Attribute descendant
|
36
|
+
#
|
37
|
+
# @param [Class<Attribute>] attribute
|
38
|
+
#
|
39
|
+
# @return [Class<Attribute>]
|
40
|
+
#
|
41
|
+
# @api private
|
42
|
+
def self.determine_type_from_attribute(attribute)
|
43
|
+
attribute
|
44
|
+
end
|
45
|
+
|
46
|
+
private_class_method :determine_type_from_attribute
|
47
|
+
|
48
|
+
# Return the Attribute class given a primitive
|
49
|
+
#
|
50
|
+
# @param [Class] primitive
|
51
|
+
#
|
52
|
+
# @return [Class<Attribute>]
|
53
|
+
#
|
54
|
+
# @return [nil]
|
55
|
+
# nil if the type cannot be determined by the primitive
|
56
|
+
#
|
57
|
+
# @api private
|
58
|
+
def self.determine_type_from_primitive(primitive)
|
59
|
+
descendants.detect { |descendant| primitive <= descendant.primitive }
|
60
|
+
end
|
61
|
+
|
62
|
+
private_class_method :determine_type_from_primitive
|
63
|
+
|
64
|
+
# Return the Attribute class given a string
|
65
|
+
#
|
66
|
+
# @param [String] string
|
67
|
+
#
|
68
|
+
# @return [Class<Attribute>]
|
69
|
+
#
|
70
|
+
# @return [nil]
|
71
|
+
# nil if the type cannot be determined by the string
|
72
|
+
#
|
73
|
+
# @api private
|
74
|
+
def self.determine_type_from_string(string)
|
75
|
+
const_get(string) if const_defined?(string)
|
76
|
+
end
|
77
|
+
|
78
|
+
private_class_method :determine_type_from_string
|
79
|
+
|
9
80
|
# Returns default options hash for a given attribute class
|
10
81
|
#
|
11
82
|
# @example
|
@@ -51,29 +122,23 @@ module Virtus
|
|
51
122
|
#
|
52
123
|
# @api public
|
53
124
|
def self.accept_options(*new_options)
|
54
|
-
|
55
|
-
concat_options(new_options)
|
56
|
-
|
57
|
-
# create methods for each new option
|
125
|
+
add_accepted_options(new_options)
|
58
126
|
new_options.each { |option| add_option_method(option) }
|
59
|
-
|
60
|
-
|
61
|
-
descendants.each { |descendant| descendant.concat_options(new_options) }
|
62
|
-
|
63
|
-
accepted_options
|
127
|
+
descendants.each { |descendant| descendant.add_accepted_options(new_options) }
|
128
|
+
self
|
64
129
|
end
|
65
130
|
|
66
131
|
# Adds a reader/writer method for the give option name
|
67
132
|
#
|
68
|
-
# @return [
|
133
|
+
# @return [undefined]
|
69
134
|
#
|
70
135
|
# @api private
|
71
136
|
def self.add_option_method(option)
|
72
137
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
73
|
-
def self.#{option}(value = Undefined)
|
74
|
-
return @#{option} if value.equal?(Undefined)
|
75
|
-
@#{option} = value
|
76
|
-
end
|
138
|
+
def self.#{option}(value = Undefined) # def self.primitive(value = Undefined)
|
139
|
+
return @#{option} if value.equal?(Undefined) # return @primitive if value.equal?(Undefined)
|
140
|
+
@#{option} = value # @primitive = value
|
141
|
+
end # end
|
77
142
|
RUBY
|
78
143
|
end
|
79
144
|
|
@@ -84,14 +149,14 @@ module Virtus
|
|
84
149
|
# @param [#to_hash] new_options
|
85
150
|
# options to be set
|
86
151
|
#
|
87
|
-
# @return [
|
88
|
-
# default options set on the attribute class
|
152
|
+
# @return [self]
|
89
153
|
#
|
90
154
|
# @api private
|
91
155
|
def self.set_options(new_options)
|
92
156
|
new_options.to_hash.each do |option_name, option_value|
|
93
157
|
send(option_name, option_value)
|
94
158
|
end
|
159
|
+
self
|
95
160
|
end
|
96
161
|
|
97
162
|
# Adds new options that an attribute class can accept
|
@@ -99,12 +164,12 @@ module Virtus
|
|
99
164
|
# @param [#to_ary] new_options
|
100
165
|
# new options to be added
|
101
166
|
#
|
102
|
-
# @return [
|
103
|
-
# all accepted options
|
167
|
+
# @return [self]
|
104
168
|
#
|
105
169
|
# @api private
|
106
|
-
def self.
|
107
|
-
accepted_options.concat(new_options.to_ary)
|
170
|
+
def self.add_accepted_options(new_options)
|
171
|
+
accepted_options.concat(new_options.to_ary)
|
172
|
+
self
|
108
173
|
end
|
109
174
|
|
110
175
|
# Adds descendant to descendants array and inherits default options
|
@@ -116,19 +181,18 @@ module Virtus
|
|
116
181
|
# @api private
|
117
182
|
def self.inherited(descendant)
|
118
183
|
super
|
119
|
-
descendant.
|
120
|
-
descendant.set_options(options)
|
184
|
+
descendant.add_accepted_options(accepted_options).set_options(options)
|
121
185
|
self
|
122
186
|
end
|
123
187
|
|
124
188
|
# Returns if the given value's class is an attribute's primitive
|
125
189
|
#
|
126
190
|
# @example
|
127
|
-
# Virtus::Attribute::String.primitive?('String')
|
191
|
+
# Virtus::Attribute::String.primitive?('String') # => true
|
128
192
|
#
|
129
|
-
# @return [
|
193
|
+
# @return [Boolean]
|
130
194
|
#
|
131
|
-
# @api
|
195
|
+
# @api public
|
132
196
|
def self.primitive?(value)
|
133
197
|
value.kind_of?(primitive)
|
134
198
|
end
|
@@ -136,7 +200,7 @@ module Virtus
|
|
136
200
|
# Returns name of the attribute
|
137
201
|
#
|
138
202
|
# @example
|
139
|
-
# User.attributes[:age].name
|
203
|
+
# User.attributes[:age].name # => :age
|
140
204
|
#
|
141
205
|
# @return [Symbol]
|
142
206
|
#
|
@@ -185,6 +249,8 @@ module Virtus
|
|
185
249
|
# @param [#to_hash] options
|
186
250
|
# hash of extra options which overrides defaults set on an attribute class
|
187
251
|
#
|
252
|
+
# @return [undefined]
|
253
|
+
#
|
188
254
|
# @api private
|
189
255
|
def initialize(name, options = {})
|
190
256
|
@name = name
|
@@ -199,12 +265,12 @@ module Virtus
|
|
199
265
|
# Returns if an attribute is a complex one
|
200
266
|
#
|
201
267
|
# @example
|
202
|
-
# Virtus::Attribute::String.complex?
|
203
|
-
# Virtus::Attribute::Array.complex?
|
268
|
+
# Virtus::Attribute::String.complex? # => false
|
269
|
+
# Virtus::Attribute::Array.complex? # => true
|
204
270
|
#
|
205
|
-
# @return [
|
271
|
+
# @return [Boolean]
|
206
272
|
#
|
207
|
-
# @api
|
273
|
+
# @api public
|
208
274
|
def complex?
|
209
275
|
@complex
|
210
276
|
end
|
@@ -228,6 +294,8 @@ module Virtus
|
|
228
294
|
|
229
295
|
# Converts the given value to the primitive type
|
230
296
|
#
|
297
|
+
# @return [Object]
|
298
|
+
#
|
231
299
|
# @api private
|
232
300
|
def typecast_to_primitive(value)
|
233
301
|
value
|
@@ -275,7 +343,7 @@ module Virtus
|
|
275
343
|
|
276
344
|
# Creates an attribute reader method
|
277
345
|
#
|
278
|
-
# @return [
|
346
|
+
# @return [self]
|
279
347
|
#
|
280
348
|
# @api private
|
281
349
|
def add_reader_method(model)
|
@@ -283,22 +351,24 @@ module Virtus
|
|
283
351
|
method_name = name
|
284
352
|
|
285
353
|
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
286
|
-
module AttributeMethods
|
287
|
-
def #{method_name}
|
288
|
-
return #{instance_variable_name} if defined?(#{instance_variable_name})
|
289
|
-
attribute = self.class.attributes[#{method_name.inspect}]
|
290
|
-
#{instance_variable_name} = attribute ? attribute.get(self) : nil
|
291
|
-
end
|
292
|
-
end
|
293
|
-
include AttributeMethods
|
354
|
+
module AttributeMethods # module AttributeMethods
|
355
|
+
def #{method_name} # def name
|
356
|
+
return #{instance_variable_name} if defined?(#{instance_variable_name}) # return @name if defined?(@name)
|
357
|
+
attribute = self.class.attributes[#{method_name.inspect}] # attribute = self.class.attributes[:name]
|
358
|
+
#{instance_variable_name} = attribute ? attribute.get(self) : nil # @name = attribute ? attribute.get(self) : nil
|
359
|
+
end # end
|
360
|
+
end # end
|
361
|
+
include AttributeMethods # include AttributeMethods
|
294
362
|
RUBY
|
295
363
|
|
296
364
|
model.send(reader_visibility, method_name)
|
365
|
+
|
366
|
+
self
|
297
367
|
end
|
298
368
|
|
299
369
|
# Creates an attribute writer method
|
300
370
|
#
|
301
|
-
# @return [
|
371
|
+
# @return [self]
|
302
372
|
#
|
303
373
|
# @api private
|
304
374
|
def add_writer_method(model)
|
@@ -306,15 +376,17 @@ module Virtus
|
|
306
376
|
method_name = "#{name}="
|
307
377
|
|
308
378
|
model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
309
|
-
module AttributeMethods
|
310
|
-
def #{method_name}(value)
|
311
|
-
self.class.attributes[#{name.inspect}].set(self, value)
|
312
|
-
end
|
313
|
-
end
|
314
|
-
include AttributeMethods
|
379
|
+
module AttributeMethods # module AttributeMethods
|
380
|
+
def #{method_name}(value) # def name=(value)
|
381
|
+
self.class.attributes[#{name.inspect}].set(self, value) # self.class.attributes[:name].set(self, value)
|
382
|
+
end # end
|
383
|
+
end # end
|
384
|
+
include AttributeMethods # include AttributeMethods
|
315
385
|
RUBY
|
316
386
|
|
317
387
|
model.send(writer_visibility, method_name)
|
388
|
+
|
389
|
+
self
|
318
390
|
end
|
319
391
|
|
320
392
|
private
|