attributor 4.0.0 → 4.0.1

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 64d1a212c179718aacc7837f4484062a4987e4d3
4
- data.tar.gz: 5d2f971e22f191497e2f29bb95e8f84e73a8a52f
3
+ metadata.gz: bcf6ceb7deb1d4db6a3370de07f1af676c4ffd27
4
+ data.tar.gz: 28e0d3d5091ab8c1663275ae4f5c2c46927cd186
5
5
  SHA512:
6
- metadata.gz: f33c468a57b20fd6e91bf58747513b19856d9c62b2c61ddf96e718a91bb0cfa9e3fa1f85c62442e5735df3280707d3adc70bf1b11c4f9c90b7ff5511668affe3
7
- data.tar.gz: 14084572c335799325e89195620520176ff57360bf1732a3eee9c86a00fce331a842ca8790a2aaa2743eccd49cc012a84bc7d5aec5f525659a108885ef54543e
6
+ metadata.gz: 322a5d22a2b1fcc8166c011b88fb09f4367f585e1c1fab94ef457a3132c2e0427b1a1cdf1aaeae67d5c653c813d4a11212f8f2be77250cab15e1bed711981cf4
7
+ data.tar.gz: 83e5bee88b8119a4af387a767ba8a64580cbc79feb72cd7fe3870be707c2a7fb9d78a91ddcc0227a6b05c549980851b5a863b05c47862c312e9cd35b9fb1b075
data/.gitignore CHANGED
@@ -16,3 +16,5 @@ doc
16
16
 
17
17
  Gemfile.lock
18
18
  pkg
19
+
20
+ *.swp
@@ -1,6 +1,11 @@
1
1
  # Attributor Changelog
2
2
 
3
- ## next
3
+ ## next
4
+
5
+
6
+ ## 4.0.1
7
+
8
+ * `Attribute#check_option!` now calls `load` on any provided value.
4
9
 
5
10
 
6
11
  ## 4.0.0 next
data/Rakefile CHANGED
@@ -14,6 +14,14 @@ RSpec::Core::RakeTask.new do |spec|
14
14
  spec.pattern = FileList['spec/**/*_spec.rb']
15
15
  end
16
16
 
17
+ desc "console"
18
+ task :console do
19
+ require 'bundler'
20
+ Bundler.require(:default, :development, :test)
21
+ require_relative 'lib/attributor'
22
+ pry
23
+ end
24
+
17
25
  task :default => :spec
18
26
 
19
27
  require 'yard'
@@ -10,17 +10,18 @@ Gem::Specification.new do |spec|
10
10
  spec.authors = ["Josep M. Blanquer","Dane Jensen"]
11
11
  spec.summary = "A powerful attribute and type management library for Ruby"
12
12
  spec.email = ["blanquer@gmail.com","dane.jensen@gmail.com"]
13
-
13
+
14
14
  spec.homepage = "https://github.com/rightscale/attributor"
15
15
  spec.license = "MIT"
16
16
  spec.required_ruby_version = ">=2.1"
17
-
17
+
18
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
22
  spec.add_runtime_dependency(%q<hashie>, ["~> 3"])
23
23
  spec.add_runtime_dependency(%q<randexp>, ["~> 0"])
24
+ spec.add_runtime_dependency(%q<activesupport>, ['>= 3'])
24
25
  spec.add_development_dependency(%q<rspec>, ["< 2.99"])
25
26
  spec.add_development_dependency(%q<yard>, ["~> 0.8.7"])
26
27
  spec.add_development_dependency(%q<backports>, ["~> 3"])
@@ -45,7 +45,7 @@ module Attributor
45
45
  end
46
46
 
47
47
  def self.type_name(type)
48
- return self.type_name(type.class) unless type.kind_of?(Class)
48
+ return self.type_name(type.class) unless type.kind_of?(::Class)
49
49
 
50
50
  type.ancestors.find { |k| k.name && !k.name.empty? }.name
51
51
  end
@@ -75,7 +75,7 @@ module Attributor
75
75
  end
76
76
 
77
77
  MODULE_PREFIX = "Attributor\:\:".freeze
78
- MODULE_PREFIX_REGEX = Regexp.new(MODULE_PREFIX)
78
+ MODULE_PREFIX_REGEX = ::Regexp.new(MODULE_PREFIX)
79
79
 
80
80
  require_relative 'attributor/families/numeric'
81
81
  require_relative 'attributor/families/temporal'
@@ -91,11 +91,13 @@ module Attributor
91
91
  require_relative 'attributor/types/time'
92
92
  require_relative 'attributor/types/date'
93
93
  require_relative 'attributor/types/date_time'
94
+ require_relative 'attributor/types/regexp'
94
95
  require_relative 'attributor/types/float'
95
96
  require_relative 'attributor/types/collection'
96
97
  require_relative 'attributor/types/hash'
97
98
  require_relative 'attributor/types/model'
98
99
  require_relative 'attributor/types/struct'
100
+ require_relative 'attributor/types/class'
99
101
 
100
102
 
101
103
  require_relative 'attributor/types/csv'
@@ -67,7 +67,7 @@ module Attributor
67
67
  defined_val.call
68
68
  end
69
69
  else
70
- defined_val
70
+ defined_val
71
71
  end
72
72
  value = val #Need to load?
73
73
  end
@@ -124,7 +124,7 @@ module Attributor
124
124
  description[:type] = self.type.describe(shallow, example: example )
125
125
  # Move over any example from the type, into the attribute itself
126
126
  if ( ex = description[:type].delete(:example) )
127
- description[:example] = self.dump( ex ).to_s
127
+ description[:example] = self.dump(ex)
128
128
  end
129
129
 
130
130
  description
@@ -286,6 +286,7 @@ module Attributor
286
286
  raise AttributorException.new("Allowed set of values requires an array. Got (#{definition})") unless definition.is_a? ::Array
287
287
  when :default
288
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)
289
290
  when :description
290
291
  raise AttributorException.new("Description value must be a string. Got (#{definition})") unless definition.is_a? ::String
291
292
  when :required
@@ -0,0 +1,56 @@
1
+ require 'active_support'
2
+
3
+ require_relative '../exceptions'
4
+
5
+
6
+ module Attributor
7
+ class Class
8
+ include Type
9
+
10
+ def self.native_type
11
+ return ::Class
12
+ end
13
+
14
+ def self.load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
15
+ return @klass || nil if value.nil?
16
+
17
+ # Must be given a String object or nil
18
+ unless value.kind_of?(::String) || value.nil?
19
+ raise IncompatibleTypeError, context: context, value_type: value.class, type: self
20
+ end
21
+
22
+ value = "::" + value if value[0..1] != '::'
23
+ result = value.constantize
24
+
25
+ # Class given must match class specified when type created using .of() method
26
+ unless @klass.nil? || result == @klass
27
+ raise LoadError, "Error loading class #{value} for attribute with " +
28
+ "defined class #{@klass} while loading #{Attributor.humanize_context(context)}."
29
+ end
30
+
31
+ result
32
+ end
33
+
34
+ def self.example(context=nil, options:{})
35
+ @klass.nil? ? "MyClass" : @klass.name
36
+ end
37
+
38
+ # Create a Class attribute type of a specific Class.
39
+ #
40
+ # @param klass [Class] optional, defines the class of this attribute, if constant
41
+ #
42
+ # @return anonymous class with specified type of collection members
43
+ #
44
+ # @example Class.of(Factory)
45
+ #
46
+ def self.of(klass)
47
+ ::Class.new(self) do
48
+ @klass = klass
49
+ end
50
+ end
51
+
52
+ def self.family
53
+ 'string'
54
+ end
55
+ end
56
+ end
@@ -16,7 +16,7 @@ module Attributor
16
16
  unless resolved_type.ancestors.include?(Attributor::Type)
17
17
  raise Attributor::AttributorException.new("Collections can only have members that are Attributor::Types")
18
18
  end
19
- Class.new(self) do
19
+ ::Class.new(self) do
20
20
  @member_type = resolved_type
21
21
  end
22
22
  end
@@ -53,7 +53,7 @@ module Attributor
53
53
  def self.member_attribute
54
54
  @member_attribute ||= begin
55
55
  self.construct(nil,{})
56
-
56
+
57
57
  @member_attribute
58
58
  end
59
59
  end
@@ -129,7 +129,7 @@ module Attributor
129
129
 
130
130
  # @example Hash.of(key: String, value: Integer)
131
131
  def self.of(key: @key_type, value: @value_type)
132
- Class.new(self) do
132
+ ::Class.new(self) do
133
133
  self.key_type = key
134
134
  self.value_type = value
135
135
  @keys = {}
@@ -164,8 +164,8 @@ module Attributor
164
164
  example_depth = context.size
165
165
 
166
166
  self.keys.each do |sub_attribute_name, sub_attribute|
167
-
168
-
167
+
168
+
169
169
  if sub_attribute.attributes
170
170
  # TODO: add option to raise an exception in this case?
171
171
  next if example_depth > MAX_EXAMPLE_DEPTH
@@ -12,7 +12,7 @@ module Attributor
12
12
  raise AttributorException, "#{type.name} does not have attribute with name '#{identity_name}'"
13
13
  end
14
14
 
15
- Class.new(self) do
15
+ ::Class.new(self) do
16
16
  @member_attribute = identity_attribute
17
17
  @member_type = identity_attribute.type
18
18
  end
@@ -0,0 +1,30 @@
1
+ require_relative '../exceptions'
2
+
3
+ module Attributor
4
+ class Regexp
5
+ include Type
6
+
7
+ def self.native_type
8
+ return ::Regexp
9
+ end
10
+
11
+ def self.load(value, context=Attributor::DEFAULT_ROOT_CONTEXT, **options)
12
+ unless value.kind_of?(::String) || value.nil?
13
+ raise IncompatibleTypeError, context: context, value_type: value.class, type: self
14
+ end
15
+
16
+ value && ::Regexp.new(value)
17
+ rescue
18
+ super
19
+ end
20
+
21
+ def self.example(context=nil, options:{})
22
+ ::Regexp.new(/^pattern\d{0,3}$/).to_s
23
+ end
24
+
25
+ def self.family
26
+ 'string'
27
+ end
28
+
29
+ end
30
+ end
@@ -1,8 +1,8 @@
1
1
 
2
2
  module Attributor
3
3
  class Struct < Attributor::Model
4
-
5
- def self.constructable?
4
+
5
+ def self.constructable?
6
6
  true
7
7
  end
8
8
 
@@ -26,7 +26,7 @@ module Attributor
26
26
  end
27
27
  end
28
28
 
29
- Class.new(self) do
29
+ ::Class.new(self) do
30
30
  attributes options, &attribute_definition
31
31
  end
32
32
 
@@ -39,12 +39,26 @@ module Attributor
39
39
  end
40
40
 
41
41
  def self.validate(value,context=Attributor::DEFAULT_ROOT_CONTEXT,attribute)
42
- []
42
+ errors = []
43
+
44
+ if attribute && (definition = attribute.options[:path])
45
+ unless value.path =~ attribute.options[:path]
46
+ errors << "#{Attributor.humanize_context(context)} value (#{value}) does not match path (#{definition.inspect})"
47
+ end
48
+ end
49
+ errors
43
50
  end
44
51
 
45
- def check_option!(name, definition)
46
- # No options are supported
47
- :unknown
52
+ def self.check_option!(name, definition)
53
+ case name
54
+ when :path
55
+ unless definition.is_a? ::Regexp
56
+ raise AttributorException.new("Value for option :path is not a Regexp object. Got (#{definition.inspect})")
57
+ end
58
+ :ok
59
+ else
60
+ :unknown
61
+ end
48
62
  end
49
63
 
50
64
  end
@@ -1,3 +1,3 @@
1
1
  module Attributor
2
- VERSION = "4.0.0"
2
+ VERSION = "4.0.1"
3
3
  end
@@ -87,3 +87,10 @@ class Address < Attributor::Model
87
87
  end
88
88
 
89
89
 
90
+ class Post < Attributor::Model
91
+ attributes do
92
+ attribute :title, String
93
+ attribute :tags, Attributor::Collection.of(String)
94
+ end
95
+ end
96
+
@@ -0,0 +1,58 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
+ require 'backports'
3
+
4
+ describe Attributor::Class do
5
+
6
+ subject(:type) { Attributor::Class }
7
+
8
+ its(:native_type) { should be(::Class) }
9
+ its(:family) { should == 'string' }
10
+
11
+ context '.example' do
12
+ its(:example) { should be_a(::String) }
13
+
14
+ context 'when created using .of method' do
15
+ let(:klass) { Integer }
16
+ subject(:type) { Attributor::Class.of(klass) }
17
+
18
+ its(:example) { should eq(klass.to_s) }
19
+ end
20
+ end
21
+
22
+ context '.load' do
23
+ let(:value) { nil }
24
+
25
+ context 'for incoming String values' do
26
+ ['Object', '::Object', '::Hash', 'Attributor::Struct'].each do |value|
27
+ it "loads '#{value}' as #{eval(value)}" do
28
+ type.load(value).should eq(value.constantize)
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'when created using .of method' do
34
+ let(:klass) { Integer }
35
+ subject(:type) { Attributor::Class.of(klass) }
36
+
37
+ it "loads 'Integer' as Integer" do
38
+ type.load('Integer').should eq(Integer)
39
+ end
40
+
41
+ it "returns specified class for nil" do
42
+ type.load(nil).should be(klass)
43
+ end
44
+
45
+ it "raises when given a class that doesn't match specified class" do
46
+ expect { type.load('Float') }.to raise_exception(Attributor::LoadError)
47
+ end
48
+ end
49
+
50
+ it 'returns nil for nil' do
51
+ type.load(nil).should be(nil)
52
+ end
53
+
54
+ it 'raises when given a non-String' do
55
+ expect {type.load(1)}.to raise_exception(Attributor::IncompatibleTypeError)
56
+ end
57
+ end
58
+ end
@@ -564,8 +564,8 @@ context 'attributes' do
564
564
  description[:attributes].keys.should =~ type.keys.keys
565
565
  description[:attributes].each do |name,sub_description|
566
566
  sub_description.should have_key(:example)
567
- val = type.attributes[name].dump( example[name] ).to_s
568
- sub_description[:example].should eq( val )
567
+ val = type.attributes[name].dump(example[name])
568
+ sub_description[:example].should eq val
569
569
  end
570
570
  end
571
571
  end
@@ -242,6 +242,30 @@ describe Attributor::Model do
242
242
  #raise_error(Attributor::AttributorException, /Unknown attributes.*#{context.join('.')}/)
243
243
  end
244
244
  end
245
+
246
+ context 'loading with default values' do
247
+ let(:reference) { Post }
248
+ let(:options) { {reference: reference} }
249
+
250
+ let(:attribute_definition) do
251
+ proc do
252
+ attribute :title
253
+ attribute :tags, default: ['stuff', 'things']
254
+ end
255
+ end
256
+
257
+ let(:struct) { Attributor::Struct.construct(attribute_definition, options)}
258
+
259
+ let(:data) { {title: 'my post'} }
260
+
261
+ subject(:loaded) { struct.load(data) }
262
+
263
+
264
+ it 'validates' do
265
+ expect(loaded.validate).to be_empty
266
+ end
267
+
268
+ end
245
269
  end
246
270
 
247
271
  end
@@ -0,0 +1,28 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
+
3
+ describe Attributor::Regexp do
4
+
5
+ subject(:type) { Attributor::Regexp }
6
+
7
+ its(:native_type) { should be(::Regexp) }
8
+ its(:example) { should be_a(::String) }
9
+ its(:family) { should == 'string' }
10
+
11
+ context '.load' do
12
+ let(:value) { nil }
13
+
14
+ it 'returns nil for nil' do
15
+ type.load(nil).should be(nil)
16
+ end
17
+
18
+ context 'for incoming String values' do
19
+
20
+ { 'foo' => /foo/, '^pattern$' => /^pattern$/ }.each do |value, expected|
21
+ it "loads '#{value}' as #{expected.inspect}" do
22
+ type.load(value).should eq(expected)
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -1,12 +1,103 @@
1
+ require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
2
+
1
3
  describe Attributor::URI do
2
4
 
3
5
  subject(:type) { Attributor::URI }
4
6
 
5
7
  its(:native_type) { should be ::URI::Generic }
6
8
 
7
- it 'check_option!' do
8
- # No options supported thus far
9
- expect(type.check_option!(:foo, nil )).to be(:unknown)
9
+ context '.example' do
10
+ it 'returns a valid URI' do
11
+ expect(type.example).to be_kind_of(URI)
12
+ end
10
13
  end
11
14
 
15
+ context '.load' do
16
+ subject(:load) { type.load(value) }
17
+
18
+ context 'given a nil' do
19
+ let(:value) { nil }
20
+ it 'returns a nil' do
21
+ expect(subject).to be_nil
22
+ end
23
+ end
24
+
25
+ context 'given a string' do
26
+ let(:value) { 'string' }
27
+ it 'returns a URI object' do
28
+ expect(subject).to be_kind_of(URI)
29
+ end
30
+ end
31
+
32
+ context 'given a URI object' do
33
+ let(:value) { URI.parse('string') }
34
+ it 'returns itself' do
35
+ expect(subject).to eq(value)
36
+ end
37
+ end
38
+
39
+ context 'given a value not its native_type' do
40
+ let(:value) { Class.new }
41
+ it 'raises an error' do
42
+ expect { subject }.to raise_error(Attributor::CoercionError)
43
+ end
44
+ end
45
+ end
46
+
47
+ context '.validate' do
48
+ let(:uri) { URI.parse('http://www.example.com/something/foo') }
49
+ let(:attribute) { nil }
50
+ subject(:validate) { type.validate(uri, ['root'], attribute) }
51
+
52
+ context 'when given a valid URI' do
53
+ it 'does not return any errors' do
54
+ expect(subject).to be_empty
55
+ end
56
+
57
+ context 'when given a path option' do
58
+ let(:attribute) { Attributor::Attribute.new(type, path: /^\//) }
59
+
60
+ context 'given a URI that matches the path regex' do
61
+ it 'does not return any errors' do
62
+ expect(subject).to be_empty
63
+ end
64
+ end
65
+
66
+ context 'given a URI that does not match the path regex' do
67
+ let(:uri) { URI.parse('www.example.com/something/foo') }
68
+ it 'returns an errors array' do
69
+ expect(subject).to_not be_empty
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ context '.check_option!' do
77
+ let(:options) { {} }
78
+ subject(:attribute) { Attributor::Attribute.new(type, options) }
79
+
80
+ context 'for path option' do
81
+ context 'given a regex definition' do
82
+ let(:options) { {path: Regexp.new('a-z')} }
83
+ it 'checks successfully' do
84
+ expect(subject).to be_kind_of(Attributor::Attribute)
85
+ end
86
+ end
87
+
88
+ context 'given any definition other than regex' do
89
+ let(:options) { {path: 1} }
90
+ it 'raises an exception' do
91
+ expect { subject }.to raise_error(Attributor::AttributorException)
92
+ end
93
+ end
94
+ end
95
+
96
+ context 'for any other option' do
97
+ let(:options) { {something: 1} }
98
+ it 'raises an exception' do
99
+ expect { subject }.to raise_error(Attributor::AttributorException)
100
+ end
101
+ end
102
+ end
12
103
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: attributor
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep M. Blanquer
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-07-08 00:00:00.000000000 Z
12
+ date: 2015-08-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: hashie
@@ -39,6 +39,20 @@ dependencies:
39
39
  - - "~>"
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: activesupport
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '3'
49
+ type: :runtime
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '3'
42
56
  - !ruby/object:Gem::Dependency
43
57
  name: rspec
44
58
  requirement: !ruby/object:Gem::Requirement
@@ -265,6 +279,7 @@ files:
265
279
  - lib/attributor/type.rb
266
280
  - lib/attributor/types/bigdecimal.rb
267
281
  - lib/attributor/types/boolean.rb
282
+ - lib/attributor/types/class.rb
268
283
  - lib/attributor/types/collection.rb
269
284
  - lib/attributor/types/container.rb
270
285
  - lib/attributor/types/csv.rb
@@ -277,6 +292,7 @@ files:
277
292
  - lib/attributor/types/integer.rb
278
293
  - lib/attributor/types/model.rb
279
294
  - lib/attributor/types/object.rb
295
+ - lib/attributor/types/regexp.rb
280
296
  - lib/attributor/types/string.rb
281
297
  - lib/attributor/types/struct.rb
282
298
  - lib/attributor/types/symbol.rb
@@ -295,6 +311,7 @@ files:
295
311
  - spec/type_spec.rb
296
312
  - spec/types/bigdecimal_spec.rb
297
313
  - spec/types/boolean_spec.rb
314
+ - spec/types/class_spec.rb
298
315
  - spec/types/collection_spec.rb
299
316
  - spec/types/container_spec.rb
300
317
  - spec/types/csv_spec.rb
@@ -306,6 +323,7 @@ files:
306
323
  - spec/types/ids_spec.rb
307
324
  - spec/types/integer_spec.rb
308
325
  - spec/types/model_spec.rb
326
+ - spec/types/regexp_spec.rb
309
327
  - spec/types/string_spec.rb
310
328
  - spec/types/struct_spec.rb
311
329
  - spec/types/tempfile_spec.rb
@@ -347,6 +365,7 @@ test_files:
347
365
  - spec/type_spec.rb
348
366
  - spec/types/bigdecimal_spec.rb
349
367
  - spec/types/boolean_spec.rb
368
+ - spec/types/class_spec.rb
350
369
  - spec/types/collection_spec.rb
351
370
  - spec/types/container_spec.rb
352
371
  - spec/types/csv_spec.rb
@@ -358,6 +377,7 @@ test_files:
358
377
  - spec/types/ids_spec.rb
359
378
  - spec/types/integer_spec.rb
360
379
  - spec/types/model_spec.rb
380
+ - spec/types/regexp_spec.rb
361
381
  - spec/types/string_spec.rb
362
382
  - spec/types/struct_spec.rb
363
383
  - spec/types/tempfile_spec.rb