virtus 1.0.2 → 1.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.ruby-version +1 -1
- data/.travis.yml +2 -2
- data/Changelog.md +11 -0
- data/Gemfile +4 -2
- data/Gemfile.devtools +25 -19
- data/README.md +56 -4
- data/lib/virtus.rb +26 -5
- data/lib/virtus/attribute/builder.rb +2 -2
- data/lib/virtus/attribute/collection.rb +7 -3
- data/lib/virtus/attribute/hash.rb +3 -3
- data/lib/virtus/attribute/strict.rb +1 -1
- data/lib/virtus/builder.rb +1 -1
- data/lib/virtus/configuration.rb +7 -36
- data/lib/virtus/instance_methods.rb +1 -0
- data/lib/virtus/module_extensions.rb +8 -2
- data/lib/virtus/support/options.rb +1 -0
- data/lib/virtus/support/type_lookup.rb +1 -1
- data/lib/virtus/version.rb +1 -1
- data/spec/integration/custom_attributes_spec.rb +2 -2
- data/spec/integration/custom_collection_attributes_spec.rb +6 -6
- data/spec/integration/default_values_spec.rb +8 -8
- data/spec/integration/defining_attributes_spec.rb +25 -18
- data/spec/integration/embedded_value_spec.rb +5 -5
- data/spec/integration/extending_objects_spec.rb +5 -5
- data/spec/integration/hash_attributes_coercion_spec.rb +11 -7
- data/spec/integration/mass_assignment_with_accessors_spec.rb +5 -5
- data/spec/integration/overriding_virtus_spec.rb +4 -4
- data/spec/integration/required_attributes_spec.rb +1 -1
- data/spec/integration/struct_as_embedded_value_spec.rb +4 -4
- data/spec/integration/using_modules_spec.rb +8 -8
- data/spec/integration/value_object_with_custom_constructor_spec.rb +4 -4
- data/spec/integration/virtus/instance_level_attributes_spec.rb +1 -1
- data/spec/integration/virtus/value_object_spec.rb +14 -14
- data/spec/shared/freeze_method_behavior.rb +1 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/virtus/attribute/class_methods/build_spec.rb +9 -1
- data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +13 -2
- data/spec/unit/virtus/attribute/collection/coerce_spec.rb +21 -0
- data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +14 -2
- data/spec/unit/virtus/attribute_set/each_spec.rb +21 -16
- data/spec/unit/virtus/attribute_set/element_reference_spec.rb +1 -1
- data/spec/unit/virtus/attribute_set/reset_spec.rb +5 -3
- data/spec/unit/virtus/attribute_spec.rb +4 -3
- data/spec/unit/virtus/attributes_reader_spec.rb +1 -1
- data/spec/unit/virtus/attributes_writer_spec.rb +1 -1
- data/spec/unit/virtus/model_spec.rb +3 -3
- data/spec/unit/virtus/module_spec.rb +59 -2
- data/spec/unit/virtus/value_object_spec.rb +2 -2
- data/virtus.gemspec +2 -2
- metadata +34 -32
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9159a933adbe9e8abeda7386590659134150c554
|
4
|
+
data.tar.gz: cd8c5f3edb39d1dc81d5796ab6a42ba941a1485f
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b57dce024748303a11bb12631e7133b8ac1ee66711bb08b09cd85080552d815c283596b8a9b7af9a87ae77e413592ece743f8059c8293b808d6e4a653bba7837
|
7
|
+
data.tar.gz: f1704e07a82c1de070711b117387fcb2bd067e7bfa95a8f46daf2e1bad818f3faa82500636004d0474e0994ab2a3eb7d1e123ad163b0727f015cdf5ac1ef8326
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1
|
1
|
+
2.1
|
data/.travis.yml
CHANGED
data/Changelog.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
# v1.0.3 2014-07-24
|
2
|
+
|
3
|
+
* [improvement] Expose attribute name in the exception when in strict mode (ntl)
|
4
|
+
* [improvement] Set #to_h as an alias to #to_hash (fnando)
|
5
|
+
* [fixed] Fix handling of nil in collection coercion (edgibbs)
|
6
|
+
* [fixed] Fix issues with using multiple virtus modules (trptcolin)
|
7
|
+
* [fixed] Fix handling of Range type (hampei)
|
8
|
+
* [fixed] Fix strict mode for collection and hash types (solnic)
|
9
|
+
|
10
|
+
[Compare v1.0.2..v1.0.3](https://github.com/solnic/virtus/compare/v1.0.2...v1.0.3)
|
11
|
+
|
1
12
|
# v1.0.2 2014-12-03
|
2
13
|
|
3
14
|
* [improvement] Don’t override already-defined default values when freezing (amarshall)
|
data/Gemfile
CHANGED
@@ -5,6 +5,8 @@ gemspec
|
|
5
5
|
gem 'bogus', '~> 0.1'
|
6
6
|
gem 'devtools', :git => 'https://github.com/rom-rb/devtools', branch: 'master'
|
7
7
|
|
8
|
-
|
8
|
+
group :test do
|
9
|
+
gem 'inflecto', '~> 0.0.2'
|
10
|
+
end
|
9
11
|
|
10
|
-
|
12
|
+
eval_gemfile 'Gemfile.devtools'
|
data/Gemfile.devtools
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
group :development do
|
4
|
-
gem 'rake', '~> 10.
|
5
|
-
gem 'rspec', '~>
|
6
|
-
gem 'rspec-
|
7
|
-
gem 'yard', '~> 0.8.7'
|
4
|
+
gem 'rake', '~> 10.3.2'
|
5
|
+
gem 'rspec', '~> 3.0.0'
|
6
|
+
gem 'rspec-its', '~> 1.0.1'
|
7
|
+
gem 'yard', '~> 0.8.7.4'
|
8
8
|
|
9
9
|
platform :rbx do
|
10
10
|
gem 'rubysl-singleton', '~> 2.0.0'
|
@@ -12,45 +12,51 @@ group :development do
|
|
12
12
|
end
|
13
13
|
|
14
14
|
group :yard do
|
15
|
-
gem 'kramdown', '~> 1.3.
|
15
|
+
gem 'kramdown', '~> 1.3.3'
|
16
16
|
end
|
17
17
|
|
18
18
|
group :guard do
|
19
|
-
gem 'guard', '~> 2.
|
19
|
+
gem 'guard', '~> 2.6.1'
|
20
20
|
gem 'guard-bundler', '~> 2.0.0'
|
21
|
-
gem 'guard-rspec', '~> 4.2.
|
22
|
-
gem 'guard-rubocop', '~> 1.0
|
21
|
+
gem 'guard-rspec', '~> 4.2.9'
|
22
|
+
gem 'guard-rubocop', '~> 1.1.0'
|
23
23
|
|
24
24
|
# file system change event handling
|
25
|
-
gem 'listen', '~> 2.
|
25
|
+
gem 'listen', '~> 2.7.7'
|
26
26
|
gem 'rb-fchange', '~> 0.0.6', require: false
|
27
|
-
gem 'rb-fsevent', '~> 0.9.
|
28
|
-
gem 'rb-inotify', '~> 0.9.
|
27
|
+
gem 'rb-fsevent', '~> 0.9.4', require: false
|
28
|
+
gem 'rb-inotify', '~> 0.9.5', require: false
|
29
29
|
|
30
30
|
# notification handling
|
31
|
-
gem 'libnotify', '~> 0.8.
|
31
|
+
gem 'libnotify', '~> 0.8.3', require: false
|
32
32
|
gem 'rb-notifu', '~> 0.0.4', require: false
|
33
33
|
gem 'terminal-notifier-guard', '~> 1.5.3', require: false
|
34
34
|
end
|
35
35
|
|
36
36
|
group :metrics do
|
37
37
|
gem 'coveralls', '~> 0.7.0'
|
38
|
-
gem 'flay', '~> 2.
|
39
|
-
gem 'flog', '~> 4.2.
|
40
|
-
gem 'reek', '~> 1.3.
|
41
|
-
gem '
|
38
|
+
gem 'flay', '~> 2.5.0'
|
39
|
+
gem 'flog', '~> 4.2.1'
|
40
|
+
gem 'reek', '~> 1.3.7'
|
41
|
+
gem 'rubocop', '~> 0.23.0'
|
42
|
+
gem 'simplecov', '~> 0.7.1'
|
42
43
|
gem 'yardstick', '~> 0.9.9'
|
43
44
|
|
45
|
+
platforms :mri do
|
46
|
+
gem 'mutant', '~> 0.5.23'
|
47
|
+
gem 'mutant-rspec', '~> 0.5.21'
|
48
|
+
end
|
49
|
+
|
44
50
|
platforms :ruby_19, :ruby_20 do
|
45
51
|
gem 'yard-spellcheck', '~> 0.1.5'
|
46
52
|
end
|
47
53
|
|
48
54
|
platform :rbx do
|
49
55
|
gem 'json', '~> 1.8.1'
|
50
|
-
gem 'racc', '~> 1.4'
|
56
|
+
gem 'racc', '~> 1.4.11'
|
51
57
|
gem 'rubysl-logger', '~> 2.0.0'
|
52
58
|
gem 'rubysl-open-uri', '~> 2.0.0'
|
53
|
-
gem 'rubysl-prettyprint', '~> 2.0.
|
59
|
+
gem 'rubysl-prettyprint', '~> 2.0.3'
|
54
60
|
end
|
55
61
|
end
|
56
62
|
|
@@ -60,6 +66,6 @@ end
|
|
60
66
|
|
61
67
|
platform :jruby do
|
62
68
|
group :jruby do
|
63
|
-
gem 'jruby-openssl', '~> 0.
|
69
|
+
gem 'jruby-openssl', '~> 0.9.4'
|
64
70
|
end
|
65
71
|
end
|
data/README.md
CHANGED
@@ -6,14 +6,14 @@ Virtus
|
|
6
6
|
[![Dependency Status](https://gemnasium.com/solnic/virtus.png)][gemnasium]
|
7
7
|
[![Code Climate](https://codeclimate.com/github/solnic/virtus.png)][codeclimate]
|
8
8
|
[![Coverage Status](https://coveralls.io/repos/solnic/virtus/badge.png?branch=master)][coveralls]
|
9
|
-
[![Inline docs](http://inch-
|
9
|
+
[![Inline docs](http://inch-ci.org/github/solnic/virtus.png)][inchpages]
|
10
10
|
|
11
11
|
[gem]: https://rubygems.org/gems/virtus
|
12
12
|
[travis]: https://travis-ci.org/solnic/virtus
|
13
13
|
[gemnasium]: https://gemnasium.com/solnic/virtus
|
14
14
|
[codeclimate]: https://codeclimate.com/github/solnic/virtus
|
15
15
|
[coveralls]: https://coveralls.io/r/solnic/virtus
|
16
|
-
[inchpages]: http://inch-
|
16
|
+
[inchpages]: http://inch-ci.org/github/solnic/virtus
|
17
17
|
|
18
18
|
This is a partial extraction of the DataMapper [Property
|
19
19
|
API](http://rubydoc.info/github/datamapper/dm-core/master/DataMapper/Property)
|
@@ -273,6 +273,22 @@ package = Package.new(:dimensions => { 'width' => "2.2", :height => 2, "length"
|
|
273
273
|
package.dimensions # => { :width => 2.2, :height => 2.0, :length => 4.5 }
|
274
274
|
```
|
275
275
|
|
276
|
+
### IMPORTANT note about Boolean type
|
277
|
+
|
278
|
+
Be aware that some libraries may do a terrible thing and define a global Boolean
|
279
|
+
constant which breaks virtus' constant type lookup, if you see issues with the
|
280
|
+
boolean type you can workaround it like that:
|
281
|
+
|
282
|
+
``` ruby
|
283
|
+
class User
|
284
|
+
include Virtus.model
|
285
|
+
|
286
|
+
attribute :admin, Axiom::Types::Boolean
|
287
|
+
end
|
288
|
+
```
|
289
|
+
|
290
|
+
This will be improved in Virtus 2.0.
|
291
|
+
|
276
292
|
### IMPORTANT note about member coercions
|
277
293
|
|
278
294
|
Virtus performs coercions only when a value is being assigned. If you mutate the value later on using its own
|
@@ -383,7 +399,7 @@ end
|
|
383
399
|
class User
|
384
400
|
include Virtus.model
|
385
401
|
|
386
|
-
attribute :info, Json
|
402
|
+
attribute :info, Json, default: {}
|
387
403
|
end
|
388
404
|
|
389
405
|
user = User.new
|
@@ -429,6 +445,31 @@ user.set_unique_id('1234-1234')
|
|
429
445
|
user.unique_id # => '1234-1234'
|
430
446
|
```
|
431
447
|
|
448
|
+
### Overriding setters
|
449
|
+
|
450
|
+
``` ruby
|
451
|
+
class User
|
452
|
+
include Virtus.model
|
453
|
+
|
454
|
+
attribute :name, String
|
455
|
+
|
456
|
+
def name=(new_name)
|
457
|
+
custom_name = nil
|
458
|
+
if new_name == "Godzilla"
|
459
|
+
custom_name = "Can't tell"
|
460
|
+
end
|
461
|
+
super custom_name || new_name
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
user = User.new(name: "Frank")
|
466
|
+
user.name # => 'Frank'
|
467
|
+
|
468
|
+
user = User.new(name: "Godzilla")
|
469
|
+
user.name # => 'Can't tell'
|
470
|
+
|
471
|
+
```
|
472
|
+
|
432
473
|
## Strict Coercion Mode
|
433
474
|
|
434
475
|
By default Virtus returns the input value even when it couldn't coerce it to the expected type.
|
@@ -453,7 +494,7 @@ You can also build Virtus modules that contain their own configuration.
|
|
453
494
|
```ruby
|
454
495
|
YupNopeBooleans = Virtus.model { |mod|
|
455
496
|
mod.coerce = true
|
456
|
-
mod.string.boolean_map = { '
|
497
|
+
mod.coercer.config.string.boolean_map = { 'nope' => false, 'yup' => true }
|
457
498
|
}
|
458
499
|
|
459
500
|
class User
|
@@ -501,6 +542,17 @@ Blog.attribute_set[:posts].member_type.primitive # => Post
|
|
501
542
|
Post.attribute_set[:blog].type.primitive # => Blog
|
502
543
|
```
|
503
544
|
|
545
|
+
Ruby version support
|
546
|
+
--------------------
|
547
|
+
|
548
|
+
Virtus is known to work correctly with the following rubies:
|
549
|
+
|
550
|
+
* 1.9.3
|
551
|
+
* 2.0.0
|
552
|
+
* 2.1.2
|
553
|
+
* jruby
|
554
|
+
* (probably) rbx
|
555
|
+
|
504
556
|
Credits
|
505
557
|
-------
|
506
558
|
|
data/lib/virtus.rb
CHANGED
@@ -11,11 +11,31 @@ module Virtus
|
|
11
11
|
Undefined = Object.new.freeze
|
12
12
|
|
13
13
|
class CoercionError < StandardError
|
14
|
-
attr_reader :output, :
|
14
|
+
attr_reader :output, :attribute
|
15
15
|
|
16
|
-
def initialize(output,
|
17
|
-
@output, @
|
18
|
-
super(
|
16
|
+
def initialize(output, attribute)
|
17
|
+
@output, @attribute = output, attribute
|
18
|
+
super(build_message)
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_message
|
22
|
+
if attribute_name?
|
23
|
+
"Failed to coerce attribute `#{attribute_name}' from #{output.inspect} into #{target_type}"
|
24
|
+
else
|
25
|
+
"Failed to coerce #{output.inspect} into #{target_type}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def attribute_name
|
30
|
+
attribute.options[:name]
|
31
|
+
end
|
32
|
+
|
33
|
+
def attribute_name?
|
34
|
+
attribute_name ? true : false
|
35
|
+
end
|
36
|
+
|
37
|
+
def target_type
|
38
|
+
attribute.primitive.inspect
|
19
39
|
end
|
20
40
|
end
|
21
41
|
|
@@ -101,7 +121,8 @@ module Virtus
|
|
101
121
|
#
|
102
122
|
# @api public
|
103
123
|
def self.config(&block)
|
104
|
-
configuration
|
124
|
+
yield configuration if block_given?
|
125
|
+
configuration
|
105
126
|
end
|
106
127
|
|
107
128
|
# Provides access to the Virtus module builder
|
@@ -53,7 +53,7 @@ module Virtus
|
|
53
53
|
|
54
54
|
# @api private
|
55
55
|
def pending?
|
56
|
-
@pending
|
56
|
+
@pending if defined?(@pending)
|
57
57
|
end
|
58
58
|
|
59
59
|
private
|
@@ -107,7 +107,7 @@ module Virtus
|
|
107
107
|
determine_type(klass.primitive)
|
108
108
|
elsif EmbeddedValue.handles?(klass)
|
109
109
|
EmbeddedValue
|
110
|
-
elsif klass < Enumerable
|
110
|
+
elsif klass < Enumerable && !(klass <= Range)
|
111
111
|
Collection
|
112
112
|
end
|
113
113
|
end
|
@@ -66,12 +66,16 @@ module Virtus
|
|
66
66
|
|
67
67
|
# @api private
|
68
68
|
def self.merge_options!(type, options)
|
69
|
-
options[:member_type] ||= Attribute.build(type.member_type)
|
69
|
+
options[:member_type] ||= Attribute.build(type.member_type, strict: options[:strict])
|
70
70
|
end
|
71
71
|
|
72
72
|
# @api public
|
73
|
-
def coerce(
|
74
|
-
|
73
|
+
def coerce(value)
|
74
|
+
coerced = super
|
75
|
+
|
76
|
+
return coerced unless coerced.respond_to?(:each_with_object)
|
77
|
+
|
78
|
+
coerced.each_with_object(primitive.new) do |entry, collection|
|
75
79
|
collection << member_type.coerce(entry)
|
76
80
|
end
|
77
81
|
end
|
@@ -70,7 +70,7 @@ module Virtus
|
|
70
70
|
value_type
|
71
71
|
end
|
72
72
|
|
73
|
-
{ :key_type
|
73
|
+
{ :key_type => key_primitive, :value_type => value_primitive}
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
@@ -92,8 +92,8 @@ module Virtus
|
|
92
92
|
|
93
93
|
# @api private
|
94
94
|
def self.merge_options!(type, options)
|
95
|
-
options[:key_type] ||= Attribute.build(type.key_type)
|
96
|
-
options[:value_type] ||= Attribute.build(type.value_type)
|
95
|
+
options[:key_type] ||= Attribute.build(type.key_type, :strict => options[:strict])
|
96
|
+
options[:value_type] ||= Attribute.build(type.value_type, :strict => options[:strict])
|
97
97
|
end
|
98
98
|
|
99
99
|
# Coerce members
|
data/lib/virtus/builder.rb
CHANGED
data/lib/virtus/configuration.rb
CHANGED
@@ -18,49 +18,20 @@ module Virtus
|
|
18
18
|
# Access the mass-assignment setting for this instance
|
19
19
|
attr_accessor :mass_assignment
|
20
20
|
|
21
|
-
# Build new configuration instance using the passed block
|
22
|
-
#
|
23
|
-
# @example
|
24
|
-
# Configuration.build do |config|
|
25
|
-
# config.coerce = false
|
26
|
-
# end
|
27
|
-
#
|
28
|
-
# @return [Configuration]
|
29
|
-
#
|
30
|
-
# @api public
|
31
|
-
def self.build(options = {}, &block)
|
32
|
-
config = new.call(&block)
|
33
|
-
options.each { |key, value| config.public_send("#{key}=", value) }
|
34
|
-
config
|
35
|
-
end
|
36
|
-
|
37
21
|
# Initialized a configuration instance
|
38
22
|
#
|
39
23
|
# @return [undefined]
|
40
24
|
#
|
41
25
|
# @api private
|
42
|
-
def initialize
|
43
|
-
@finalize = true
|
44
|
-
@coerce = true
|
45
|
-
@strict = false
|
46
|
-
@constructor = true
|
47
|
-
@mass_assignment = true
|
26
|
+
def initialize(options={})
|
27
|
+
@finalize = options.fetch(:finalize,true)
|
28
|
+
@coerce = options.fetch(:coerce,true)
|
29
|
+
@strict = options.fetch(:strict,false)
|
30
|
+
@constructor = options.fetch(:constructor,true)
|
31
|
+
@mass_assignment = options.fetch(:mass_assignment,true)
|
48
32
|
@coercer = Coercible::Coercer.new
|
49
|
-
end
|
50
33
|
|
51
|
-
|
52
|
-
#
|
53
|
-
# @example
|
54
|
-
# configuration.call do |config|
|
55
|
-
# config.coerce = false
|
56
|
-
# end
|
57
|
-
#
|
58
|
-
# @return [self]
|
59
|
-
#
|
60
|
-
# @api private
|
61
|
-
def call(&block)
|
62
|
-
block.call(self) if block_given?
|
63
|
-
self
|
34
|
+
yield self if block_given?
|
64
35
|
end
|
65
36
|
|
66
37
|
# Access the coercer for this instance and optional configure a
|
@@ -15,7 +15,9 @@ module Virtus
|
|
15
15
|
# @api private
|
16
16
|
def self.setup(mod, inclusions = [Model], attribute_definitions = [])
|
17
17
|
mod.instance_variable_set('@inclusions', inclusions)
|
18
|
-
mod.
|
18
|
+
existing_attributes = mod.instance_variable_get('@attribute_definitions')
|
19
|
+
new_attributes = (existing_attributes || []) + attribute_definitions
|
20
|
+
mod.instance_variable_set('@attribute_definitions', new_attributes)
|
19
21
|
end
|
20
22
|
|
21
23
|
# Define an attribute in the module
|
@@ -57,7 +59,11 @@ module Virtus
|
|
57
59
|
super
|
58
60
|
|
59
61
|
if Class === object
|
60
|
-
@inclusions.
|
62
|
+
@inclusions.reject do |mod|
|
63
|
+
object.ancestors.include?(mod)
|
64
|
+
end.each do |mod|
|
65
|
+
object.send(:include, mod)
|
66
|
+
end
|
61
67
|
define_attributes(object)
|
62
68
|
else
|
63
69
|
object.extend(ModuleExtensions)
|