virtus 1.0.2 → 1.0.3

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