virtus 0.5.2 → 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/.rspec +2 -0
  2. data/.travis.yml +3 -0
  3. data/Changelog.md +9 -0
  4. data/Gemfile +2 -22
  5. data/Gemfile.devtools +44 -0
  6. data/README.md +78 -1
  7. data/Rakefile +3 -4
  8. data/config/flay.yml +2 -2
  9. data/config/mutant.yml +3 -0
  10. data/lib/virtus.rb +2 -1
  11. data/lib/virtus/attribute.rb +1 -1
  12. data/lib/virtus/attribute/array.rb +1 -0
  13. data/lib/virtus/attribute/class.rb +1 -1
  14. data/lib/virtus/attribute/hash.rb +84 -0
  15. data/lib/virtus/attribute/set.rb +1 -0
  16. data/lib/virtus/coercion/string.rb +6 -2
  17. data/lib/virtus/instance_methods.rb +8 -1
  18. data/lib/virtus/version.rb +1 -1
  19. data/spec/integration/default_values_spec.rb +22 -2
  20. data/spec/integration/hash_attributes_coercion_spec.rb +50 -0
  21. data/spec/shared/freeze_method_behavior.rb +1 -1
  22. data/spec/spec_helper.rb +6 -14
  23. data/spec/unit/virtus/attribute/hash/class_methods/merge_options_spec.rb +33 -0
  24. data/spec/unit/virtus/attribute/hash/coerce_spec.rb +20 -0
  25. data/spec/unit/virtus/attribute/hash/key_type_spec.rb +10 -0
  26. data/spec/unit/virtus/attribute/hash/value_type_spec.rb +10 -0
  27. data/spec/unit/virtus/coercion/string/class_methods/to_float_spec.rb +12 -0
  28. data/spec/unit/virtus/coercion/string/class_methods/to_integer_spec.rb +12 -0
  29. data/spec/unit/virtus/extensions/attribute_spec.rb +26 -0
  30. data/spec/unit/virtus/instance_methods/attributes_spec.rb +3 -1
  31. data/spec/unit/virtus/instance_methods/initialize_spec.rb +11 -5
  32. data/spec/unit/virtus/options/accept_options_spec.rb +1 -1
  33. data/spec/unit/virtus/options/accepted_options_spec.rb +1 -1
  34. data/spec/unit/virtus/options/options_spec.rb +1 -1
  35. data/spec/unit/virtus/type_lookup/determine_type_spec.rb +1 -1
  36. data/virtus.gemspec +4 -4
  37. metadata +76 -100
  38. data/lib/virtus/support/descendants_tracker.rb +0 -44
  39. data/spec/rcov.opts +0 -7
  40. data/spec/unit/virtus/attribute/object/class_methods/descendants_spec.rb +0 -22
  41. data/spec/unit/virtus/descendants_tracker/add_descendant_spec.rb +0 -24
  42. data/spec/unit/virtus/descendants_tracker/descendants_spec.rb +0 -22
  43. data/tasks/metrics/ci.rake +0 -7
  44. data/tasks/metrics/flay.rake +0 -41
  45. data/tasks/metrics/flog.rake +0 -43
  46. data/tasks/metrics/heckle.rake +0 -208
  47. data/tasks/metrics/metric_fu.rake +0 -29
  48. data/tasks/metrics/reek.rake +0 -9
  49. data/tasks/metrics/roodi.rake +0 -15
  50. data/tasks/metrics/yardstick.rake +0 -23
  51. data/tasks/spec.rake +0 -45
  52. data/tasks/yard.rake +0 -9
@@ -1,44 +0,0 @@
1
- module Virtus
2
-
3
- # A module that adds descendant tracking to a class
4
- module DescendantsTracker
5
-
6
- # Return the descendants of this class
7
- #
8
- # @return [Array<Class>]
9
- #
10
- # @api private
11
- def descendants
12
- @descendants ||= []
13
- end
14
-
15
- # Add the descendant to this class and the superclass
16
- #
17
- # @param [Class] descendant
18
- #
19
- # @return [self]
20
- #
21
- # @api private
22
- def add_descendant(descendant)
23
- superclass = self.superclass
24
- superclass.add_descendant(descendant) if superclass.respond_to?(:add_descendant)
25
- descendants.unshift(descendant)
26
- self
27
- end
28
-
29
- private
30
-
31
- # Hook called when class is inherited
32
- #
33
- # @param [Class] descendant
34
- #
35
- # @return [undefined]
36
- #
37
- # @api private
38
- def inherited(descendant)
39
- super
40
- add_descendant(descendant)
41
- end
42
-
43
- end # module DescendantsTracker
44
- end # module Virtus
@@ -1,7 +0,0 @@
1
- --exclude-only "spec/,^/"
2
- --sort coverage
3
- --callsites
4
- --xrefs
5
- --profile
6
- --text-summary
7
- --failure-threshold 100
@@ -1,22 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Virtus::Attribute::Object, '.descendants' do
4
- subject { described_class.descendants.map { |c| c.to_s }.sort }
5
-
6
- before { pending 'Remove this spec in favor of Virtus::DescentantsTracker spec' }
7
-
8
- let(:known_descendants) do
9
- [ Virtus::Attribute::EmbeddedValue, Virtus::Attribute::Symbol,
10
- Virtus::Attribute::Time, Virtus::Attribute::String,
11
- Virtus::Attribute::Integer, Virtus::Attribute::Hash,
12
- Virtus::Attribute::Float, Virtus::Attribute::Decimal,
13
- Virtus::Attribute::Numeric, Virtus::Attribute::DateTime,
14
- Virtus::Attribute::Date, Virtus::Attribute::Boolean,
15
- Virtus::Attribute::Set, Virtus::Attribute::Array,
16
- Virtus::Attribute::Collection, Virtus::Attribute::Class ].map { |a| a.to_s }.sort
17
- end
18
-
19
- it 'should return all known attribute classes' do
20
- subject.should eql(known_descendants)
21
- end
22
- end
@@ -1,24 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Virtus::DescendantsTracker, '#add_descendant' do
4
- subject { object.add_descendant(descendant) }
5
-
6
- let(:described_class) { Class.new { extend Virtus::DescendantsTracker } }
7
- let(:object) { Class.new(described_class) }
8
- let(:descendant) { Class.new }
9
-
10
- it { should equal(object) }
11
-
12
- it 'prepends the class to the descendants' do
13
- object.descendants << original = Class.new
14
- expect { subject }.to change { object.descendants.dup }.
15
- from([ original ]).
16
- to([ descendant, original ])
17
- end
18
-
19
- it 'prepends the class to the superclass descendants' do
20
- expect { subject }.to change { object.superclass.descendants.dup }.
21
- from([ object ]).
22
- to([ descendant, object ])
23
- end
24
- end
@@ -1,22 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Virtus::DescendantsTracker, '#descendants' do
4
- subject { object.descendants }
5
-
6
- let(:described_class) { Class.new { extend Virtus::DescendantsTracker } }
7
- let(:object) { described_class }
8
-
9
- context 'when there are no descendants' do
10
- it_should_behave_like 'an idempotent method'
11
-
12
- it { should be_empty }
13
- end
14
-
15
- context 'when there are descendants' do
16
- let!(:descendant) { Class.new(object) } # trigger the class inhertance
17
-
18
- it_should_behave_like 'an idempotent method'
19
-
20
- it { should eql([ descendant ]) }
21
- end
22
- end
@@ -1,7 +0,0 @@
1
- desc 'Run metrics with Heckle'
2
- task :ci => %w[ ci:metrics heckle ]
3
-
4
- namespace :ci do
5
- desc 'Run metrics'
6
- task :metrics => %w[ verify_measurements flog flay reek roodi metrics:all ]
7
- end
@@ -1,41 +0,0 @@
1
- begin
2
- require 'flay'
3
- require 'yaml'
4
-
5
- config = YAML.load_file(File.expand_path('../../../config/flay.yml', __FILE__)).freeze
6
- threshold = config.fetch('threshold').to_i
7
- total_score = config.fetch('total_score').to_f
8
- files = Flay.expand_dirs_to_files(config.fetch('path', 'lib'))
9
-
10
- # original code by Marty Andrews:
11
- # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
12
- desc 'Analyze for code duplication'
13
- task :flay do
14
- # run flay once without a threshold to ensure the max mass matches the threshold
15
- flay = Flay.new(:fuzzy => false, :verbose => false, :mass => 0)
16
- flay.process(*files)
17
-
18
- max = flay.masses.map { |hash, mass| mass.to_f / flay.hashes[hash].size }.max
19
- unless max >= threshold
20
- raise "Adjust flay threshold down to #{max}"
21
- end
22
-
23
- total = flay.masses.reduce(0.0) { |total, (hash, mass)| total + (mass.to_f / flay.hashes[hash].size) }
24
- unless total == total_score
25
- raise "Flay total is now #{total}, but expected #{total_score}"
26
- end
27
-
28
- # run flay a second time with the threshold set
29
- flay = Flay.new(:fuzzy => false, :verbose => false, :mass => threshold.succ)
30
- flay.process(*files)
31
-
32
- if flay.masses.any?
33
- flay.report
34
- raise "#{flay.masses.size} chunks of code have a duplicate mass > #{threshold}"
35
- end
36
- end
37
- rescue LoadError
38
- task :flay do
39
- abort 'Flay is not available. In order to run flay, you must: gem install flay'
40
- end
41
- end
@@ -1,43 +0,0 @@
1
- begin
2
- require 'flog'
3
- require 'yaml'
4
-
5
- class Float
6
- def round_to(n)
7
- (self * 10**n).round.to_f * 10**-n
8
- end
9
- end
10
-
11
- config = YAML.load_file(File.expand_path('../../../config/flog.yml', __FILE__)).freeze
12
- threshold = config.fetch('threshold').to_f.round_to(1)
13
-
14
- # original code by Marty Andrews:
15
- # http://blog.martyandrews.net/2009/05/enforcing-ruby-code-quality.html
16
- desc 'Analyze for code complexity'
17
- task :flog do
18
- flog = Flog.new
19
- flog.flog Array(config.fetch('path', 'lib'))
20
-
21
- totals = flog.totals.select { |name, score| name[-5, 5] != '#none' }.
22
- map { |name, score| [ name, score.round_to(1) ] }.
23
- sort_by { |name, score| score }
24
-
25
- max = totals.last[1]
26
- unless max >= threshold
27
- raise "Adjust flog score down to #{max}"
28
- end
29
-
30
- bad_methods = totals.select { |name, score| score > threshold }
31
- if bad_methods.any?
32
- bad_methods.reverse_each do |name, score|
33
- puts '%8.1f: %s' % [ score, name ]
34
- end
35
-
36
- raise "#{bad_methods.size} methods have a flog complexity > #{threshold}"
37
- end
38
- end
39
- rescue LoadError
40
- task :flog do
41
- abort 'Flog is not available. In order to run flog, you must: gem install flog'
42
- end
43
- end
@@ -1,208 +0,0 @@
1
- $LOAD_PATH.unshift(File.expand_path('../../../lib', __FILE__))
2
-
3
- # original code by Ashley Moran:
4
- # http://aviewfromafar.net/2007/11/1/rake-task-for-heckling-your-specs
5
-
6
- begin
7
- require 'pathname'
8
- require 'backports'
9
- require 'active_support/inflector'
10
- require 'heckle'
11
- require 'mspec'
12
- require 'mspec/utils/name_map'
13
-
14
- SKIP_METHODS = %w[ blank_slate_method_added ].freeze
15
-
16
- class NameMap
17
- def file_name(method, constant)
18
- map = MAP[method]
19
- name = if map
20
- map[constant] || map[:default]
21
- else
22
- method.gsub(/[?!=]\z/, '')
23
- end
24
- "#{name}_spec.rb"
25
- end
26
- end
27
-
28
- desc 'Heckle each module and class'
29
- task :heckle => :rcov do
30
- unless Ruby2Ruby::VERSION == '1.2.2'
31
- raise "ruby2ruby version #{Ruby2Ruby::VERSION} may not work properly, 1.2.2 *only* is recommended for use with heckle"
32
- end
33
-
34
- require 'virtus'
35
-
36
- root_module_regexp = Regexp.union(
37
- 'Virtus'
38
- )
39
-
40
- spec_dir = Pathname('spec/unit')
41
-
42
- NameMap::MAP.each do |op, method|
43
- next if method.kind_of?(Hash)
44
- NameMap::MAP[op] = { :default => method }
45
- end
46
-
47
- aliases = Hash.new { |h,mod| h[mod] = Hash.new { |h,method| h[method] = method } }
48
-
49
- aliases['Virtus::ValueObject::InstanceMethods']['dup'] = 'clone'
50
-
51
- map = NameMap.new
52
-
53
- heckle_caught_modules = Hash.new { |hash, key| hash[key] = [] }
54
- unhandled_mutations = 0
55
-
56
- ObjectSpace.each_object(Module) do |mod|
57
- next unless mod.name =~ /\A#{root_module_regexp}(?::|\z)/
58
-
59
- spec_prefix = spec_dir.join(mod.name.underscore)
60
-
61
- specs = []
62
-
63
- # get the public class methods
64
- metaclass = class << mod; self end
65
- ancestors = metaclass.ancestors
66
-
67
- spec_class_methods = mod.singleton_methods(false)
68
-
69
- spec_class_methods.reject! do |method|
70
- %w[ yaml_new yaml_tag_subclasses? included nesting constants ].include?(method.to_s)
71
- end
72
-
73
- if mod.ancestors.include?(Singleton)
74
- spec_class_methods.reject! { |method| method.to_s == 'instance' }
75
- end
76
-
77
- # get the protected and private class methods
78
- other_class_methods = metaclass.protected_instance_methods(false) |
79
- metaclass.private_instance_methods(false)
80
-
81
- ancestors.each do |ancestor|
82
- other_class_methods -= ancestor.protected_instance_methods(false) |
83
- ancestor.private_instance_methods(false)
84
- end
85
-
86
- other_class_methods.reject! do |method|
87
- method.to_s == 'allocate' || SKIP_METHODS.include?(method.to_s)
88
- end
89
-
90
- other_class_methods.reject! do |method|
91
- next unless spec_class_methods.any? { |specced| specced.to_s == $1 }
92
-
93
- spec_class_methods << method
94
- end
95
-
96
- # get the instances methods
97
- spec_methods = mod.public_instance_methods(false)
98
-
99
- other_methods = mod.protected_instance_methods(false) |
100
- mod.private_instance_methods(false)
101
-
102
- other_methods.reject! do |method|
103
- next unless spec_methods.any? { |specced| specced.to_s == $1 }
104
-
105
- spec_methods << method
106
- end
107
-
108
- # map the class methods to spec files
109
- spec_class_methods.each do |method|
110
- method = aliases[mod.name][method]
111
- next if SKIP_METHODS.include?(method.to_s)
112
-
113
- spec_file = spec_prefix.join('class_methods').join(map.file_name(method, mod.name))
114
-
115
- unless spec_file.file?
116
- raise "No spec file #{spec_file} for #{mod}.#{method}"
117
- end
118
-
119
- specs << [ ".#{method}", [ spec_file ] ]
120
- end
121
-
122
- # map the instance methods to spec files
123
- spec_methods.each do |method|
124
- method = aliases[mod.name][method]
125
- next if SKIP_METHODS.include?(method.to_s)
126
-
127
- spec_file = spec_prefix.join(map.file_name(method, mod.name))
128
-
129
- unless spec_file.file?
130
- raise "No spec file #{spec_file} for #{mod}##{method}"
131
- end
132
-
133
- specs << [ "##{method}", [ spec_file ] ]
134
- end
135
-
136
- # non-public methods are considered covered if they can be mutated
137
- # and any spec fails for the current or descendant modules
138
- other_methods.each do |method|
139
- descedant_specs = []
140
-
141
- ObjectSpace.each_object(Module) do |descedant|
142
- next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant
143
- descedant_spec_prefix = spec_dir.join(descedant.name.underscore)
144
- descedant_specs << descedant_spec_prefix
145
-
146
- if method.to_s == 'initialize'
147
- descedant_specs.concat(Pathname.glob(descedant_spec_prefix.join('class_methods/new_spec.rb')))
148
- end
149
- end
150
-
151
- specs << [ "##{method}", descedant_specs ]
152
- end
153
-
154
- other_class_methods.each do |method|
155
- descedant_specs = []
156
-
157
- ObjectSpace.each_object(Module) do |descedant|
158
- next unless descedant.name =~ /\A#{root_module_regexp}(?::|\z)/ && mod >= descedant
159
- descedant_specs << spec_dir.join(descedant.name.underscore).join('class_methods')
160
- end
161
-
162
- specs << [ ".#{method}", descedant_specs ]
163
- end
164
-
165
- specs.sort.each do |(method, spec_files)|
166
- puts "Heckling #{mod}#{method}"
167
- IO.popen("spec #{spec_files.join(' ')} --heckle '#{mod}#{method}'") do |pipe|
168
- while line = pipe.gets
169
- case line = line.chomp
170
- when "The following mutations didn't cause test failures:"
171
- heckle_caught_modules[mod.name] << method
172
- when '+++ mutation'
173
- unhandled_mutations += 1
174
- end
175
- end
176
- end
177
- end
178
- end
179
-
180
- if unhandled_mutations > 0
181
- error_message_lines = [ "*************\n" ]
182
-
183
- error_message_lines << "Heckle found #{unhandled_mutations} " \
184
- "mutation#{"s" unless unhandled_mutations == 1} " \
185
- "that didn't cause spec violations\n"
186
-
187
- heckle_caught_modules.each do |mod, methods|
188
- error_message_lines << "#{mod} contains the following " \
189
- 'poorly-specified methods:'
190
- methods.each do |method|
191
- error_message_lines << " - #{method}"
192
- end
193
- error_message_lines << ''
194
- end
195
-
196
- error_message_lines << 'Get your act together and come back ' \
197
- 'when your specs are doing their job!'
198
-
199
- raise error_message_lines.join("\n")
200
- else
201
- puts 'Well done! Your code withstood a heckling.'
202
- end
203
- end
204
- rescue LoadError
205
- task :heckle do
206
- abort 'Heckle or mspec is not available. In order to run heckle, you must: gem install heckle mspec'
207
- end
208
- end
@@ -1,29 +0,0 @@
1
- begin
2
- require 'metric_fu'
3
- require 'json'
4
-
5
- # XXX: temporary hack until metric_fu is fixed
6
- MetricFu::Saikuro.class_eval { include FileUtils }
7
-
8
- MetricFu::Configuration.run do |config|
9
- config.rcov = {
10
- :environment => 'test',
11
- :test_files => %w[ spec/**/*_spec.rb ],
12
- :rcov_opts => %w[
13
- --sort coverage
14
- --no-html
15
- --text-coverage
16
- --no-color
17
- --profile
18
- --exclude spec/,^/
19
- --include lib:spec
20
- ],
21
- }
22
- end
23
- rescue LoadError
24
- namespace :metrics do
25
- task :all do
26
- abort 'metric_fu is not available. In order to run metrics:all, you must: gem install metric_fu'
27
- end
28
- end
29
- end