virtus 0.0.4 → 0.0.5
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/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
|