attributor 5.0.2 → 5.1.0

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 (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