attributor 5.0.2 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +30 -0
  3. data/.travis.yml +6 -4
  4. data/CHANGELOG.md +6 -1
  5. data/Gemfile +1 -1
  6. data/Guardfile +14 -8
  7. data/Rakefile +4 -5
  8. data/attributor.gemspec +34 -29
  9. data/lib/attributor.rb +23 -29
  10. data/lib/attributor/attribute.rb +108 -127
  11. data/lib/attributor/attribute_resolver.rb +12 -26
  12. data/lib/attributor/dsl_compiler.rb +17 -21
  13. data/lib/attributor/dumpable.rb +1 -2
  14. data/lib/attributor/example_mixin.rb +5 -8
  15. data/lib/attributor/exceptions.rb +5 -6
  16. data/lib/attributor/extensions/randexp.rb +3 -5
  17. data/lib/attributor/extras/field_selector.rb +4 -4
  18. data/lib/attributor/extras/field_selector/transformer.rb +6 -7
  19. data/lib/attributor/families/numeric.rb +0 -2
  20. data/lib/attributor/families/temporal.rb +1 -4
  21. data/lib/attributor/hash_dsl_compiler.rb +22 -25
  22. data/lib/attributor/type.rb +24 -32
  23. data/lib/attributor/types/bigdecimal.rb +7 -14
  24. data/lib/attributor/types/boolean.rb +5 -8
  25. data/lib/attributor/types/class.rb +9 -10
  26. data/lib/attributor/types/collection.rb +34 -44
  27. data/lib/attributor/types/container.rb +9 -15
  28. data/lib/attributor/types/csv.rb +7 -10
  29. data/lib/attributor/types/date.rb +20 -25
  30. data/lib/attributor/types/date_time.rb +7 -14
  31. data/lib/attributor/types/float.rb +4 -6
  32. data/lib/attributor/types/hash.rb +171 -196
  33. data/lib/attributor/types/ids.rb +2 -6
  34. data/lib/attributor/types/integer.rb +12 -17
  35. data/lib/attributor/types/model.rb +39 -48
  36. data/lib/attributor/types/object.rb +2 -4
  37. data/lib/attributor/types/polymorphic.rb +118 -0
  38. data/lib/attributor/types/regexp.rb +4 -5
  39. data/lib/attributor/types/string.rb +6 -7
  40. data/lib/attributor/types/struct.rb +8 -15
  41. data/lib/attributor/types/symbol.rb +3 -6
  42. data/lib/attributor/types/tempfile.rb +5 -6
  43. data/lib/attributor/types/time.rb +11 -11
  44. data/lib/attributor/types/uri.rb +9 -10
  45. data/lib/attributor/version.rb +1 -1
  46. data/spec/attribute_resolver_spec.rb +57 -78
  47. data/spec/attribute_spec.rb +174 -216
  48. data/spec/attributor_spec.rb +11 -15
  49. data/spec/dsl_compiler_spec.rb +19 -33
  50. data/spec/dumpable_spec.rb +6 -7
  51. data/spec/extras/field_selector/field_selector_spec.rb +1 -1
  52. data/spec/families_spec.rb +1 -3
  53. data/spec/hash_dsl_compiler_spec.rb +65 -74
  54. data/spec/spec_helper.rb +9 -3
  55. data/spec/support/hashes.rb +2 -3
  56. data/spec/support/models.rb +30 -36
  57. data/spec/support/polymorphics.rb +10 -0
  58. data/spec/type_spec.rb +38 -61
  59. data/spec/types/bigdecimal_spec.rb +11 -15
  60. data/spec/types/boolean_spec.rb +12 -39
  61. data/spec/types/class_spec.rb +10 -11
  62. data/spec/types/collection_spec.rb +72 -81
  63. data/spec/types/container_spec.rb +22 -26
  64. data/spec/types/csv_spec.rb +15 -16
  65. data/spec/types/date_spec.rb +16 -33
  66. data/spec/types/date_time_spec.rb +16 -33
  67. data/spec/types/file_upload_spec.rb +1 -2
  68. data/spec/types/float_spec.rb +7 -14
  69. data/spec/types/hash_spec.rb +285 -289
  70. data/spec/types/ids_spec.rb +5 -7
  71. data/spec/types/integer_spec.rb +37 -46
  72. data/spec/types/model_spec.rb +111 -128
  73. data/spec/types/polymorphic_spec.rb +134 -0
  74. data/spec/types/regexp_spec.rb +4 -7
  75. data/spec/types/string_spec.rb +17 -21
  76. data/spec/types/struct_spec.rb +40 -47
  77. data/spec/types/tempfile_spec.rb +1 -2
  78. data/spec/types/temporal_spec.rb +9 -0
  79. data/spec/types/time_spec.rb +16 -32
  80. data/spec/types/type_spec.rb +15 -0
  81. data/spec/types/uri_spec.rb +6 -7
  82. metadata +77 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1b19e77ce8b1772454840886e1a1bda5bc248c63
4
- data.tar.gz: 32f9bca389ef6c112a7d8edf81cf8adc001ca2fd
3
+ metadata.gz: fe4a2dca77307723d8c18e4f37ded9cba8e54657
4
+ data.tar.gz: 175d9d21dd78aaba4e6c534559290cb79d3bf3a2
5
5
  SHA512:
6
- metadata.gz: dc0fadeddc769124b2b60e717c48d435d88caf15cb6779b0784840c0bdcbbb4efce092fe4648dc8fd14a7b18958c0142dc8e6b52318b44f0b80b0c6f516e4a92
7
- data.tar.gz: 6bfe838ad696d85db73ee1893a1d4f7739958180cb4f0f1a35ca5e8ff5ebb1a11bdde7bd10c23645623390cde56a6b95e72321ec819579068bac7b56037281c6
6
+ metadata.gz: 89de22ecae27da14b59f57b2ea87d3552c6d1dbdd338e07934bc29031fd4e65b664f4faf04fb50f56b2e3687cde7580ba217562a413856fa8cdb403c9bc4a335
7
+ data.tar.gz: d8beb7bba6e226b84d1608d080800e65ce632d3b9af0daeb6fa7415f0477d307d63ac9267b4ffd659fe846b9f1b0b3012d4853d8a1bb6a3d4bad4ff037aaa10b
@@ -0,0 +1,30 @@
1
+ AllCops:
2
+ TargetRubyVersion: 2.2
3
+ Style/Documentation:
4
+ Enabled: false
5
+ Metrics/MethodLength:
6
+ Enabled: false
7
+ Metrics/ClassLength:
8
+ Enabled: false
9
+ Metrics/LineLength:
10
+ Max: 200
11
+ Style/RedundantSelf:
12
+ Enabled: false
13
+ Style/ClassAndModuleChildren:
14
+ Enabled: false
15
+ Lint/Debugger:
16
+ Enabled: false
17
+ Metrics/AbcSize:
18
+ Enabled: false
19
+ Style/CaseEquality:
20
+ Enabled: false
21
+ Lint/UnusedMethodArgument:
22
+ AllowUnusedKeywordArguments: true
23
+
24
+ # Offense count: 13
25
+ Metrics/CyclomaticComplexity:
26
+ Max: 28
27
+
28
+ # Offense count: 11
29
+ Metrics/PerceivedComplexity:
30
+ Max: 23
@@ -1,9 +1,11 @@
1
1
  sudo: false
2
2
  language: ruby
3
3
  rvm:
4
- - 2.1.5
5
- - 2.2.2
6
- script: bundle exec rspec spec
4
+ - 2.2.5
5
+ - 2.3.1
6
+ script:
7
+ - bundle exec rspec spec
8
+ - bundle exec rubocop --format=clang
7
9
  branches:
8
10
  only:
9
- - master
11
+ - master
@@ -2,10 +2,15 @@
2
2
 
3
3
  ## next
4
4
 
5
+ ## 5.1
6
+
7
+ * Added `Polymorphic` type. See [polymorphics.rb](spec/support/polymorphics.rb) for example usage.
8
+
5
9
  ## 5.0.2
6
10
 
7
11
  * Introduce the `Dumpable` (empty) module as an interface to indicate that instances of types that include it
8
- will respond to the `.dump` method, as a way to convert their internal substructure to primitive Ruby objects. * Currently the only two directly dumpable types are Collection and Hash (with the caveat that there are several others that derive from them..i.e., CSV, Model, etc...)
12
+ will respond to the `.dump` method, as a way to convert their internal substructure to primitive Ruby objects.
13
+ * Currently the only two directly dumpable types are Collection and Hash (with the caveat that there are several others that derive from them..i.e., CSV, Model, etc...)
9
14
  * The rest of types have `native_types` that are already Ruby primitive Objects.
10
15
  * Fixed Hash and Model requirements to treat nil values as missing keys (to be compatible with the `required: true` option on an attribute).
11
16
 
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
- gemspec
3
+ gemspec
data/Guardfile CHANGED
@@ -1,12 +1,18 @@
1
1
  # A sample Guardfile
2
2
  # More info at https://github.com/guard/guard#readme
3
3
 
4
- guard :rspec, cmd: 'bundle exec rspec' do
5
- watch(%r{^spec/.+_spec\.rb$})
6
- watch(%r{^lib/attributor/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
- watch('spec/spec_helper.rb') { "spec" }
8
- watch('lib/attributor/base.rb') { "spec" }
9
- watch('spec/support/models.rb') { "spec" }
10
- watch('lib/attributor.rb') { 'spec' }
11
- end
4
+ group :red_green_refactor, halt_on_fail: true do
5
+ guard :rspec, cmd: 'bundle exec rspec' do
6
+ watch(%r{^spec/.+_spec\.rb$})
7
+ watch(%r{^lib/attributor/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
8
+ watch('spec/spec_helper.rb') { 'spec' }
9
+ watch('lib/attributor/base.rb') { 'spec' }
10
+ watch('spec/support/models.rb') { 'spec' }
11
+ watch('lib/attributor.rb') { 'spec' }
12
+ end
12
13
 
14
+ guard :rubocop, cli: '--auto-correct --display-cop-names' do
15
+ watch(/.+\.rb$/)
16
+ watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) }
17
+ end
18
+ end
data/Rakefile CHANGED
@@ -7,14 +7,13 @@ require 'rspec/core/rake_task'
7
7
  require 'bundler/gem_tasks'
8
8
  require 'rake/notes/rake_task'
9
9
 
10
-
11
- desc "Run RSpec code examples with simplecov"
10
+ desc 'Run RSpec code examples with simplecov'
12
11
  RSpec::Core::RakeTask.new do |spec|
13
- spec.rspec_opts = ["-c"]
12
+ spec.rspec_opts = ['-c']
14
13
  spec.pattern = FileList['spec/**/*_spec.rb']
15
14
  end
16
15
 
17
- desc "console"
16
+ desc 'console'
18
17
  task :console do
19
18
  require 'bundler'
20
19
  Bundler.require(:default, :development, :test)
@@ -22,7 +21,7 @@ task :console do
22
21
  pry
23
22
  end
24
23
 
25
- task :default => :spec
24
+ task default: :spec
26
25
 
27
26
  require 'yard'
28
27
  YARD::Rake::YardocTask.new
@@ -5,38 +5,43 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'attributor/version'
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = "attributor"
9
- spec.version = Attributor::VERSION
10
- spec.authors = ["Josep M. Blanquer","Dane Jensen"]
11
- spec.summary = "A powerful attribute and type management library for Ruby"
12
- spec.email = ["blanquer@gmail.com","dane.jensen@gmail.com"]
8
+ spec.name = 'attributor'
9
+ spec.version = Attributor::VERSION
10
+ spec.authors = ['Josep M. Blanquer', 'Dane Jensen']
11
+ spec.summary = 'A powerful attribute and type management library for Ruby'
12
+ spec.email = ['blanquer@gmail.com', 'dane.jensen@gmail.com']
13
13
 
14
- spec.homepage = "https://github.com/rightscale/attributor"
15
- spec.license = "MIT"
16
- spec.required_ruby_version = ">=2.1"
14
+ spec.homepage = 'https://github.com/rightscale/attributor'
15
+ spec.license = 'MIT'
16
+ spec.required_ruby_version = '>=2.1'
17
17
 
18
- spec.require_paths = ["lib"]
18
+ spec.require_paths = ['lib']
19
19
  spec.files = `git ls-files -z`.split("\x0")
20
20
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
21
21
 
22
- spec.add_runtime_dependency(%q<hashie>, ["~> 3"])
23
- spec.add_runtime_dependency(%q<randexp>, ["~> 0"])
24
- spec.add_runtime_dependency(%q<activesupport>, ['>= 3'])
25
-
26
- spec.add_development_dependency(%q<rspec>, ["< 2.99"])
27
- spec.add_development_dependency(%q<yard>, ["~> 0.8.7"])
28
- spec.add_development_dependency(%q<backports>, ["~> 3"])
29
- spec.add_development_dependency(%q<yardstick>, ["~> 0"])
30
- spec.add_development_dependency(%q<redcarpet>, ["< 3.0"])
31
- spec.add_development_dependency(%q<bundler>, [">= 0"])
32
- spec.add_development_dependency(%q<rake-notes>, ["~> 0"])
33
- spec.add_development_dependency(%q<coveralls>)
34
- spec.add_development_dependency(%q<guard>, ["~> 2"])
35
- spec.add_development_dependency(%q<guard-rspec>, ["~> 4"])
36
- spec.add_development_dependency(%q<pry>, ["~> 0"])
37
- spec.add_development_dependency(%q<pry-byebug>, ["~> 1"])
38
- spec.add_development_dependency(%q<pry-stack_explorer>, ["~> 0"])
39
- spec.add_development_dependency(%q<fuubar>, ["~> 1"])
40
-
41
- spec.add_development_dependency(%q<parslet>, [">= 0"])
22
+ spec.add_runtime_dependency('hashie', ['~> 3'])
23
+ spec.add_runtime_dependency('randexp', ['~> 0'])
24
+ spec.add_runtime_dependency('activesupport', ['>= 3'])
25
+
26
+ spec.add_development_dependency 'rspec', '~> 3'
27
+ spec.add_development_dependency 'rspec-its'
28
+ spec.add_development_dependency 'rspec-collection_matchers', '~> 1'
29
+ spec.add_development_dependency('yard', ['~> 0.8.7'])
30
+ spec.add_development_dependency('backports', ['~> 3'])
31
+ spec.add_development_dependency('yardstick', ['~> 0'])
32
+ spec.add_development_dependency('bundler', ['>= 0'])
33
+ spec.add_development_dependency('rake-notes', ['~> 0'])
34
+ spec.add_development_dependency('coveralls')
35
+ spec.add_development_dependency('guard', ['~> 2'])
36
+ spec.add_development_dependency('guard-rspec', ['~> 4'])
37
+ spec.add_development_dependency('pry', ['~> 0'])
38
+ if RUBY_PLATFORM !~ /java/
39
+ spec.add_development_dependency('pry-byebug', ['~> 1'])
40
+ spec.add_development_dependency('pry-stack_explorer', ['~> 0'])
41
+ end
42
+ spec.add_development_dependency 'fuubar'
43
+ spec.add_development_dependency 'rubocop'
44
+ spec.add_development_dependency 'guard-rubocop'
45
+
46
+ spec.add_development_dependency('parslet', ['>= 0'])
42
47
  end
@@ -6,7 +6,6 @@ require 'hashie'
6
6
  require 'digest/sha1'
7
7
 
8
8
  module Attributor
9
-
10
9
  require_relative 'attributor/dumpable'
11
10
 
12
11
  require_relative 'attributor/exceptions'
@@ -20,45 +19,41 @@ module Attributor
20
19
 
21
20
  require_relative 'attributor/extensions/randexp'
22
21
 
23
-
24
22
  # hierarchical separator string for composing human readable attributes
25
23
  SEPARATOR = '.'.freeze
26
24
  DEFAULT_ROOT_CONTEXT = ['$'].freeze
27
25
 
28
26
  # @param type [Class] The class of the type to resolve
29
27
  #
30
- def self.resolve_type(attr_type, options={}, constructor_block=nil)
31
- if attr_type < Attributor::Type
32
- klass = attr_type
33
- else
34
- name = attr_type.name.split("::").last # TOO EXPENSIVE?
35
-
36
- klass = const_get(name) if const_defined?(name)
37
- raise AttributorException.new("Could not find class with name #{name}") unless klass
38
- raise AttributorException.new("Could not find attribute type for: #{name} [klass: #{klass.name}]") unless klass < Attributor::Type
39
- end
28
+ def self.resolve_type(attr_type, options = {}, constructor_block = nil)
29
+ klass = self.find_type(attr_type)
40
30
 
41
- if klass.constructable?
42
- return klass.construct(constructor_block, options)
43
- end
31
+ return klass.construct(constructor_block, options) if klass.constructable?
32
+ raise AttributorException, "Type: #{attr_type} does not support anonymous generation" if constructor_block
33
+
34
+ klass
35
+ end
44
36
 
45
- raise AttributorException.new("Type: #{attr_type} does not support anonymous generation") if constructor_block
37
+ def self.find_type(attr_type)
38
+ return attr_type if attr_type < Attributor::Type
39
+ name = attr_type.name.split('::').last # TOO EXPENSIVE?
46
40
 
41
+ klass = const_get(name) if const_defined?(name)
42
+ raise AttributorException, "Could not find class with name #{name}" unless klass
43
+ raise AttributorException, "Could not find attribute type for: #{name} [klass: #{klass.name}]" unless klass < Attributor::Type
47
44
  klass
48
45
  end
49
46
 
50
47
  def self.type_name(type)
51
- return self.type_name(type.class) unless type.kind_of?(::Class)
48
+ return type_name(type.class) unless type.is_a?(::Class)
52
49
 
53
50
  type.ancestors.find { |k| k.name && !k.name.empty? }.name
54
51
  end
55
52
 
56
- def self.humanize_context( context )
57
- return "" unless context
53
+ def self.humanize_context(context)
54
+ return '' unless context
58
55
 
59
- if context.kind_of? ::String
60
- context = Array(context)
61
- end
56
+ context = Array(context) if context.is_a? ::String
62
57
 
63
58
  unless context.is_a? Enumerable
64
59
  raise "INVALID CONTEXT!!! (got: #{context.inspect})"
@@ -66,18 +61,18 @@ module Attributor
66
61
 
67
62
  begin
68
63
  return context.join('.')
69
- rescue Exception => e
64
+ rescue e
70
65
  raise "Error creating context string: #{e.message}"
71
66
  end
72
67
  end
73
68
 
74
- def self.errorize_value( value )
75
- inspection =value.inspect
76
- inspection = inspection[0..500]+ "...[truncated]" if inspection.size>500
69
+ def self.errorize_value(value)
70
+ inspection = value.inspect
71
+ inspection = inspection[0..500] + '...[truncated]' if inspection.size > 500
77
72
  inspection
78
73
  end
79
74
 
80
- MODULE_PREFIX = "Attributor\:\:".freeze
75
+ MODULE_PREFIX = 'Attributor::'.freeze
81
76
  MODULE_PREFIX_REGEX = ::Regexp.new(MODULE_PREFIX)
82
77
 
83
78
  require_relative 'attributor/families/numeric'
@@ -101,7 +96,7 @@ module Attributor
101
96
  require_relative 'attributor/types/model'
102
97
  require_relative 'attributor/types/struct'
103
98
  require_relative 'attributor/types/class'
104
-
99
+ require_relative 'attributor/types/polymorphic'
105
100
 
106
101
  require_relative 'attributor/types/csv'
107
102
  require_relative 'attributor/types/ids'
@@ -110,5 +105,4 @@ module Attributor
110
105
  require_relative 'attributor/types/tempfile'
111
106
  require_relative 'attributor/types/file_upload'
112
107
  require_relative 'attributor/types/uri'
113
-
114
108
  end
@@ -1,13 +1,15 @@
1
1
  # TODO: profile keys for attributes, test as frozen strings
2
2
 
3
3
  module Attributor
4
-
5
4
  class FakeParent < ::BasicObject
5
+ def respond_to_missing?(_method_name)
6
+ true
7
+ end
6
8
 
7
- def method_missing(name, *args)
8
- ::Kernel.warn "Warning, you have tried to access the '#{name}' method of the 'parent' argument of a Proc-defined :default values." +
9
- "Those Procs should completely ignore the 'parent' attribute for the moment as it will be set to an " +
10
- "instance of a useless class (until the framework can provide such functionality)"
9
+ def method_missing(name, *_args) # rubocop:disable Style/MethodMissing
10
+ ::Kernel.warn "Warning, you have tried to access the '#{name}' method of the 'parent' argument of a Proc-defined :default values." \
11
+ "Those Procs should completely ignore the 'parent' attribute for the moment as it will be set to an " \
12
+ 'instance of a useless class (until the framework can provide such functionality)'
11
13
  nil
12
14
  end
13
15
 
@@ -18,58 +20,53 @@ module Attributor
18
20
  # It is the abstract base class to hold an attribute, both a leaf and a container (hash/Array...)
19
21
  # TODO: should this be a mixin since it is an abstract class?
20
22
  class Attribute
21
-
22
23
  attr_reader :type, :options
23
24
 
24
25
  # @options: metadata about the attribute
25
26
  # @block: code definition for struct attributes (nil for predefined types or leaf/simple types)
26
- def initialize(type, options={}, &block)
27
+ def initialize(type, options = {}, &block)
27
28
  @type = Attributor.resolve_type(type, options, block)
28
29
 
29
30
  @options = options
30
- if @type.respond_to?(:options)
31
- @options = @type.options.merge(@options)
32
- end
31
+ @options = @type.options.merge(@options) if @type.respond_to?(:options)
33
32
 
34
33
  check_options!
35
34
  end
36
35
 
37
- def ==(attribute)
38
- raise ArgumentError, "can not compare Attribute with #{attribute.class.name}" unless attribute.kind_of?(Attribute)
36
+ def ==(other)
37
+ raise ArgumentError, "can not compare Attribute with #{other.class.name}" unless other.is_a?(Attribute)
39
38
 
40
- self.type == attribute.type &&
41
- self.options == attribute.options
39
+ type == other.type &&
40
+ options == other.options
42
41
  end
43
42
 
43
+ def parse(value, context = Attributor::DEFAULT_ROOT_CONTEXT)
44
+ object = load(value, context)
44
45
 
45
- def parse(value, context=Attributor::DEFAULT_ROOT_CONTEXT)
46
- object = self.load(value,context)
47
-
48
- errors = self.validate(object,context)
49
- [ object, errors ]
50
- end
51
-
46
+ errors = validate(object, context)
47
+ [object, errors]
48
+ end
52
49
 
53
- def load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
54
- value = type.load(value,context,**options)
50
+ def load(value, context = Attributor::DEFAULT_ROOT_CONTEXT, **options)
51
+ value = type.load(value, context, **options)
55
52
 
56
- if value.nil? && self.options.has_key?(:default)
53
+ if value.nil? && self.options.key?(:default)
57
54
  defined_val = self.options[:default]
58
55
  val = case defined_val
59
- when ::Proc
60
- fake_parent = FakeParent.new
61
- # TODO: we can only support "context" as a parameter to the proc for now, since we don't have the parent...
62
- if defined_val.arity == 2
63
- defined_val.call(fake_parent, context)
64
- elsif defined_val.arity == 1
65
- defined_val.call(fake_parent)
66
- else
67
- defined_val.call
68
- end
69
- else
70
- defined_val
71
- end
72
- value = val #Need to load?
56
+ when ::Proc
57
+ fake_parent = FakeParent.new
58
+ # TODO: we can only support "context" as a parameter to the proc for now, since we don't have the parent...
59
+ if defined_val.arity == 2
60
+ defined_val.call(fake_parent, context)
61
+ elsif defined_val.arity == 1
62
+ defined_val.call(fake_parent)
63
+ else
64
+ defined_val.call
65
+ end
66
+ else
67
+ defined_val
68
+ end
69
+ value = val # Need to load?
73
70
  end
74
71
 
75
72
  value
@@ -83,148 +80,136 @@ module Attributor
83
80
  type.dump(value, **opts)
84
81
  end
85
82
 
86
-
87
83
  def validate_type(value, context)
88
84
  # delegate check to type subclass if it exists
89
- unless self.type.valid_type?(value)
85
+ unless type.valid_type?(value)
90
86
  msg = "Attribute #{Attributor.humanize_context(context)} received value: "
91
87
  msg += "#{Attributor.errorize_value(value)} is of the wrong type "
92
- msg += "(got: #{value.class.name}, expected: #{self.type.name})"
88
+ msg += "(got: #{value.class.name}, expected: #{type.name})"
93
89
  return [msg]
94
90
  end
95
91
  []
96
92
  end
97
93
 
94
+ TOP_LEVEL_OPTIONS = [:description, :values, :default, :example, :required, :required_if, :custom_data].freeze
95
+ INTERNAL_OPTIONS = [:dsl_compiler, :dsl_compiler_options].freeze # Options we don't want to expose when describing attributes
98
96
 
99
- TOP_LEVEL_OPTIONS = [ :description, :values, :default, :example, :required, :required_if, :custom_data ]
100
- INTERNAL_OPTIONS = [:dsl_compiler,:dsl_compiler_options] # Options we don't want to expose when describing attributes
101
-
102
- def describe(shallow=true, example: nil)
103
- description = { }
97
+ def describe(shallow = true, example: nil)
98
+ description = {}
104
99
  # Clone the common options
105
100
  TOP_LEVEL_OPTIONS.each do |option_name|
106
- description[option_name] = self.options[option_name] if self.options.has_key? option_name
101
+ description[option_name] = options[option_name] if options.key? option_name
107
102
  end
108
103
 
109
104
  # Make sure this option definition is not mistaken for the real generated example
110
- if ( ex_def = description.delete(:example) )
105
+ if (ex_def = description.delete(:example))
111
106
  description[:example_definition] = ex_def
112
107
  end
113
108
 
114
- special_options = self.options.keys - TOP_LEVEL_OPTIONS - INTERNAL_OPTIONS
109
+ special_options = options.keys - TOP_LEVEL_OPTIONS - INTERNAL_OPTIONS
115
110
  description[:options] = {} unless special_options.empty?
116
111
  special_options.each do |opt_name|
117
- description[:options][opt_name] = self.options[opt_name]
112
+ description[:options][opt_name] = options[opt_name]
118
113
  end
119
114
  # Change the reference option to the actual class name.
120
- if ( reference = self.options[:reference] )
115
+ if (reference = options[:reference])
121
116
  description[:options][:reference] = reference.name
122
117
  end
123
118
 
124
- description[:type] = self.type.describe(shallow, example: example )
119
+ description[:type] = type.describe(shallow, example: example)
125
120
  # Move over any example from the type, into the attribute itself
126
- if ( ex = description[:type].delete(:example) )
127
- description[:example] = self.dump(ex)
121
+ if (ex = description[:type].delete(:example))
122
+ description[:example] = dump(ex)
128
123
  end
129
124
 
130
125
  description
131
126
  end
132
127
 
133
-
134
128
  def example_from_options(parent, context)
135
- val = self.options[:example]
129
+ val = options[:example]
136
130
  generated = case val
137
- when ::Regexp
138
- val.gen
139
- when ::Proc
140
- if val.arity == 2
141
- val.call(parent, context)
142
- elsif val.arity == 1
143
- val.call(parent)
144
- else
145
- val.call
146
- end
147
- when nil
148
- nil
149
- else
150
- val
151
- end
152
- self.load( generated, context )
131
+ when ::Regexp
132
+ val.gen
133
+ when ::Proc
134
+ if val.arity == 2
135
+ val.call(parent, context)
136
+ elsif val.arity == 1
137
+ val.call(parent)
138
+ else
139
+ val.call
140
+ end
141
+ when nil
142
+ nil
143
+ else
144
+ val
145
+ end
146
+ load(generated, context)
153
147
  end
154
148
 
155
- def example(context=nil, parent: nil, values:{})
156
- raise ArgumentError, "attribute example cannot take a context of type String" if (context.is_a? ::String )
149
+ def example(context = nil, parent: nil, values: {})
150
+ raise ArgumentError, 'attribute example cannot take a context of type String' if context.is_a? ::String
157
151
  if context
158
152
  ctx = Attributor.humanize_context(context)
159
- seed, _ = Digest::SHA1.digest(ctx).unpack("QQ")
153
+ seed, = Digest::SHA1.digest(ctx).unpack('QQ')
160
154
  Random.srand(seed)
161
155
  else
162
156
  context = Attributor::DEFAULT_ROOT_CONTEXT
163
157
  end
164
158
 
165
- if self.options.has_key? :example
159
+ if options.key? :example
166
160
  loaded = example_from_options(parent, context)
167
- errors = self.validate(loaded, context)
161
+ errors = validate(loaded, context)
168
162
  raise AttributorException, "Error generating example for #{Attributor.humanize_context(context)}. Errors: #{errors.inspect}" if errors.any?
169
- loaded
170
- else
171
- if (option_values = self.options[:values])
172
- option_values.pick
173
- else
174
- if type.respond_to?(:attributes)
175
- self.type.example(context, values)
176
- else
177
- self.type.example(context, options: self.options)
178
- end
179
- end
163
+ return loaded
180
164
  end
181
- end
182
165
 
166
+ return options[:values].pick if options.key? :values
183
167
 
184
- def attributes
185
- if (@type_has_attributes ||= type.respond_to?(:attributes))
186
- type.attributes
168
+ if type.respond_to?(:attributes)
169
+ type.example(context, values)
187
170
  else
188
- nil
171
+ type.example(context, options: options)
189
172
  end
190
173
  end
191
174
 
175
+ def attributes
176
+ type.attributes if @type_has_attributes ||= type.respond_to?(:attributes)
177
+ end
192
178
 
193
179
  # Validates stuff and checks dependencies
194
- def validate(object, context=Attributor::DEFAULT_ROOT_CONTEXT )
180
+ def validate(object, context = Attributor::DEFAULT_ROOT_CONTEXT)
195
181
  raise "INVALID CONTEXT!! #{context}" unless context
196
182
  # Validate any requirements, absolute or conditional, and return.
197
183
 
198
184
  if object.nil? # == Attributor::UNSET
199
185
  # With no value, we can only validate whether that is acceptable or not and return.
200
186
  # Beyond that, no further validation should be done.
201
- return self.validate_missing_value(context)
187
+ return validate_missing_value(context)
202
188
  end
203
189
 
204
190
  # TODO: support validation for other types of conditional dependencies based on values of other attributes
205
191
 
206
- errors = self.validate_type(object,context)
192
+ errors = validate_type(object, context)
207
193
 
208
194
  # End validation if we don't even have the proper type to begin with
209
195
  return errors if errors.any?
210
196
 
211
- if self.options[:values] && !self.options[:values].include?(object)
212
- errors << "Attribute #{Attributor.humanize_context(context)}: #{Attributor.errorize_value(object)} is not within the allowed values=#{self.options[:values].inspect} "
197
+ if options[:values] && !options[:values].include?(object)
198
+ errors << "Attribute #{Attributor.humanize_context(context)}: #{Attributor.errorize_value(object)} is not within the allowed values=#{options[:values].inspect} "
213
199
  end
214
200
 
215
- errors + self.type.validate(object,context,self)
201
+ errors + type.validate(object, context, self)
216
202
  end
217
203
 
218
-
219
204
  def validate_missing_value(context)
220
205
  raise "INVALID CONTEXT!!! (got: #{context.inspect})" unless context.is_a? Enumerable
221
206
 
222
207
  # Missing attribute was required if :required option was set
223
- return ["Attribute #{Attributor.humanize_context(context)} is required"] if self.options[:required]
208
+ return ["Attribute #{Attributor.humanize_context(context)} is required"] if options[:required]
224
209
 
225
210
  # Missing attribute was not required if :required_if (and :required)
226
211
  # option was NOT set
227
- requirement = self.options[:required_if]
212
+ requirement = options[:required_if]
228
213
  return [] unless requirement
229
214
 
230
215
  case requirement
@@ -237,7 +222,7 @@ module Attributor
237
222
  predicate = requirement.values.first
238
223
  else
239
224
  # should never get here if the option validation worked...
240
- raise AttributorException.new("unknown type of dependency: #{requirement.inspect} for #{Attributor.humanize_context(context)}")
225
+ raise AttributorException, "unknown type of dependency: #{requirement.inspect} for #{Attributor.humanize_context(context)}"
241
226
  end
242
227
 
243
228
  # chop off the last part
@@ -253,11 +238,11 @@ module Attributor
253
238
  message << "(for #{Attributor.humanize_context(requirement_context)}) "
254
239
  end
255
240
 
256
- if predicate
257
- message << "matches #{predicate.inspect}."
258
- else
259
- message << "is present."
260
- end
241
+ message << if predicate
242
+ "matches #{predicate.inspect}."
243
+ else
244
+ 'is present.'
245
+ end
261
246
 
262
247
  [message]
263
248
  else
@@ -265,48 +250,44 @@ module Attributor
265
250
  end
266
251
  end
267
252
 
268
-
269
253
  def check_options!
270
- self.options.each do |option_name, option_value|
271
- if self.check_option!(option_name, option_value) == :unknown
272
- if self.type.check_option!(option_name, option_value) == :unknown
273
- raise AttributorException.new("unsupported option: #{option_name} with value: #{option_value.inspect} for attribute: #{self.inspect}")
274
- end
254
+ options.each do |option_name, option_value|
255
+ next unless check_option!(option_name, option_value) == :unknown
256
+ if type.check_option!(option_name, option_value) == :unknown
257
+ raise AttributorException, "unsupported option: #{option_name} with value: #{option_value.inspect} for attribute: #{inspect}"
275
258
  end
276
259
  end
277
260
 
278
261
  true
279
262
  end
280
263
 
281
-
282
264
  # TODO: override in type subclass
283
265
  def check_option!(name, definition)
284
266
  case name
285
267
  when :values
286
- raise AttributorException.new("Allowed set of values requires an array. Got (#{definition})") unless definition.is_a? ::Array
268
+ raise AttributorException, "Allowed set of values requires an array. Got (#{definition})" unless definition.is_a? ::Array
287
269
  when :default
288
- raise AttributorException.new("Default value doesn't have the correct attribute type. Got (#{definition.inspect})") unless self.type.valid_type?(definition) || definition.kind_of?(Proc)
289
- self.options[:default] = self.load(definition) unless definition.kind_of?(Proc)
270
+ raise AttributorException, "Default value doesn't have the correct attribute type. Got (#{definition.inspect})" unless type.valid_type?(definition) || definition.is_a?(Proc)
271
+ options[:default] = load(definition) unless definition.is_a?(Proc)
290
272
  when :description
291
- raise AttributorException.new("Description value must be a string. Got (#{definition})") unless definition.is_a? ::String
273
+ raise AttributorException, "Description value must be a string. Got (#{definition})" unless definition.is_a? ::String
292
274
  when :required
293
- raise AttributorException.new("Required must be a boolean") unless !!definition == definition # Boolean check
294
- raise AttributorException.new("Required cannot be enabled in combination with :default") if definition == true && options.has_key?(:default)
275
+ raise AttributorException, 'Required must be a boolean' unless definition == true || definition == false
276
+ raise AttributorException, 'Required cannot be enabled in combination with :default' if definition == true && options.key?(:default)
295
277
  when :required_if
296
- raise AttributorException.new("Required_if must be a String, a Hash definition or a Proc") unless definition.is_a?(::String) || definition.is_a?(::Hash) || definition.is_a?(::Proc)
297
- raise AttributorException.new("Required_if cannot be specified together with :required") if self.options[:required]
278
+ raise AttributorException, 'Required_if must be a String, a Hash definition or a Proc' unless definition.is_a?(::String) || definition.is_a?(::Hash) || definition.is_a?(::Proc)
279
+ raise AttributorException, 'Required_if cannot be specified together with :required' if options[:required]
298
280
  when :example
299
- unless definition.is_a?(::Regexp) || definition.is_a?(::String) || definition.is_a?(::Array) || definition.is_a?(::Proc) || definition.nil? || self.type.valid_type?(definition)
300
- raise AttributorException.new("Invalid example type (got: #{definition.class.name}). It must always match the type of the attribute (except if passing Regex that is allowed for some types)")
281
+ unless definition.is_a?(::Regexp) || definition.is_a?(::String) || definition.is_a?(::Array) || definition.is_a?(::Proc) || definition.nil? || type.valid_type?(definition)
282
+ raise AttributorException, "Invalid example type (got: #{definition.class.name}). It must always match the type of the attribute (except if passing Regex that is allowed for some types)"
301
283
  end
302
284
  when :custom_data
303
- raise AttributorException.new("custom_data must be a Hash. Got (#{definition})") unless definition.is_a?(::Hash)
285
+ raise AttributorException, "custom_data must be a Hash. Got (#{definition})" unless definition.is_a?(::Hash)
304
286
  else
305
287
  return :unknown # unknown option
306
288
  end
307
289
 
308
290
  :ok # passes
309
291
  end
310
-
311
292
  end
312
293
  end