virtus 1.0.2 → 1.0.3

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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +2 -2
  4. data/Changelog.md +11 -0
  5. data/Gemfile +4 -2
  6. data/Gemfile.devtools +25 -19
  7. data/README.md +56 -4
  8. data/lib/virtus.rb +26 -5
  9. data/lib/virtus/attribute/builder.rb +2 -2
  10. data/lib/virtus/attribute/collection.rb +7 -3
  11. data/lib/virtus/attribute/hash.rb +3 -3
  12. data/lib/virtus/attribute/strict.rb +1 -1
  13. data/lib/virtus/builder.rb +1 -1
  14. data/lib/virtus/configuration.rb +7 -36
  15. data/lib/virtus/instance_methods.rb +1 -0
  16. data/lib/virtus/module_extensions.rb +8 -2
  17. data/lib/virtus/support/options.rb +1 -0
  18. data/lib/virtus/support/type_lookup.rb +1 -1
  19. data/lib/virtus/version.rb +1 -1
  20. data/spec/integration/custom_attributes_spec.rb +2 -2
  21. data/spec/integration/custom_collection_attributes_spec.rb +6 -6
  22. data/spec/integration/default_values_spec.rb +8 -8
  23. data/spec/integration/defining_attributes_spec.rb +25 -18
  24. data/spec/integration/embedded_value_spec.rb +5 -5
  25. data/spec/integration/extending_objects_spec.rb +5 -5
  26. data/spec/integration/hash_attributes_coercion_spec.rb +11 -7
  27. data/spec/integration/mass_assignment_with_accessors_spec.rb +5 -5
  28. data/spec/integration/overriding_virtus_spec.rb +4 -4
  29. data/spec/integration/required_attributes_spec.rb +1 -1
  30. data/spec/integration/struct_as_embedded_value_spec.rb +4 -4
  31. data/spec/integration/using_modules_spec.rb +8 -8
  32. data/spec/integration/value_object_with_custom_constructor_spec.rb +4 -4
  33. data/spec/integration/virtus/instance_level_attributes_spec.rb +1 -1
  34. data/spec/integration/virtus/value_object_spec.rb +14 -14
  35. data/spec/shared/freeze_method_behavior.rb +1 -1
  36. data/spec/spec_helper.rb +1 -0
  37. data/spec/unit/virtus/attribute/class_methods/build_spec.rb +9 -1
  38. data/spec/unit/virtus/attribute/collection/class_methods/build_spec.rb +13 -2
  39. data/spec/unit/virtus/attribute/collection/coerce_spec.rb +21 -0
  40. data/spec/unit/virtus/attribute/hash/class_methods/build_spec.rb +14 -2
  41. data/spec/unit/virtus/attribute_set/each_spec.rb +21 -16
  42. data/spec/unit/virtus/attribute_set/element_reference_spec.rb +1 -1
  43. data/spec/unit/virtus/attribute_set/reset_spec.rb +5 -3
  44. data/spec/unit/virtus/attribute_spec.rb +4 -3
  45. data/spec/unit/virtus/attributes_reader_spec.rb +1 -1
  46. data/spec/unit/virtus/attributes_writer_spec.rb +1 -1
  47. data/spec/unit/virtus/model_spec.rb +3 -3
  48. data/spec/unit/virtus/module_spec.rb +59 -2
  49. data/spec/unit/virtus/value_object_spec.rb +2 -2
  50. data/virtus.gemspec +2 -2
  51. metadata +34 -32
@@ -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
@@ -1 +1 @@
1
- 1.9.3
1
+ 2.1
@@ -5,8 +5,8 @@ script: "bundle exec rake metrics:coverage spec:integration"
5
5
  rvm:
6
6
  - 1.9.3
7
7
  - 2.0.0
8
- - 2.1.0
9
- - jruby-19mode
8
+ - 2.1.2
9
+ - jruby
10
10
  - rbx
11
11
  - ruby-head
12
12
  matrix:
@@ -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
- eval_gemfile 'Gemfile.devtools'
8
+ group :test do
9
+ gem 'inflecto', '~> 0.0.2'
10
+ end
9
11
 
10
- gem 'mutant', git: 'https://github.com/solnic/mutant.git', branch: 'auto-expand-scope'
12
+ eval_gemfile 'Gemfile.devtools'
@@ -1,10 +1,10 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  group :development do
4
- gem 'rake', '~> 10.1.0'
5
- gem 'rspec', '~> 2.14.1'
6
- gem 'rspec-core', '~> 2.14.8'
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.2'
15
+ gem 'kramdown', '~> 1.3.3'
16
16
  end
17
17
 
18
18
  group :guard do
19
- gem 'guard', '~> 2.4.0'
19
+ gem 'guard', '~> 2.6.1'
20
20
  gem 'guard-bundler', '~> 2.0.0'
21
- gem 'guard-rspec', '~> 4.2.6'
22
- gem 'guard-rubocop', '~> 1.0.2'
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.5.0'
25
+ gem 'listen', '~> 2.7.7'
26
26
  gem 'rb-fchange', '~> 0.0.6', require: false
27
- gem 'rb-fsevent', '~> 0.9.3', require: false
28
- gem 'rb-inotify', '~> 0.9.0', require: false
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.0', require: false
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.4.0'
39
- gem 'flog', '~> 4.2.0'
40
- gem 'reek', '~> 1.3.2'
41
- gem 'simplecov', '~> 0.8.2'
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.2'
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.8.5'
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-pages.github.io/github/solnic/virtus.png)][inchpages]
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-pages.github.io/github/solnic/virtus
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 = { 'yup' => true, 'nope' => false }
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
 
@@ -11,11 +11,31 @@ module Virtus
11
11
  Undefined = Object.new.freeze
12
12
 
13
13
  class CoercionError < StandardError
14
- attr_reader :output, :primitive
14
+ attr_reader :output, :attribute
15
15
 
16
- def initialize(output, primitive)
17
- @output, @primitive = output, primitive
18
- super("Failed to coerce #{output.inspect} into #{primitive.inspect}")
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.call(&block)
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
- super.each_with_object(primitive.new) do |entry, collection|
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 => key_primitive, :value_type => value_primitive}
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
@@ -16,7 +16,7 @@ module Virtus
16
16
  if value_coerced?(output) || !required? && output.nil?
17
17
  output
18
18
  else
19
- raise CoercionError.new(output, primitive)
19
+ raise CoercionError.new(output, self)
20
20
  end
21
21
  end
22
22
 
@@ -25,7 +25,7 @@ module Virtus
25
25
 
26
26
  # @api private
27
27
  def self.call(options, &block)
28
- new(Configuration.build(options, &block)).mod
28
+ new(Configuration.new(options, &block)).mod
29
29
  end
30
30
 
31
31
  # @api private
@@ -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
- # Provide access to the attributes and methods via the passed block
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
@@ -42,6 +42,7 @@ module Virtus
42
42
  attribute_set.get(self)
43
43
  end
44
44
  alias_method :to_hash, :attributes
45
+ alias_method :to_h, :attributes
45
46
 
46
47
  # Mass-assign attribute values
47
48
  #
@@ -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.instance_variable_set('@attribute_definitions', attribute_definitions)
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.each { |mod| object.send(:include, mod) }
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)