attrocity 0.0.1 → 0.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.
- checksums.yaml +4 -4
- data/.gitignore +30 -10
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile.lock +37 -0
- data/LICENSE +22 -0
- data/README.md +32 -2
- data/Rakefile +14 -0
- data/TODO.md +36 -0
- data/attrocity.gemspec +5 -2
- data/lib/attrocity.rb +91 -2
- data/lib/attrocity/attributes/attribute.rb +10 -0
- data/lib/attrocity/attributes/attribute_methods_builder.rb +47 -0
- data/lib/attrocity/attributes/attribute_set.rb +24 -0
- data/lib/attrocity/attributes/attribute_template.rb +28 -0
- data/lib/attrocity/attributes/attribute_template_set.rb +16 -0
- data/lib/attrocity/attributes/attributes_hash.rb +9 -0
- data/lib/attrocity/attributes/model_attribute.rb +14 -0
- data/lib/attrocity/attributes/model_attribute_set.rb +6 -0
- data/lib/attrocity/builders/model_builder.rb +60 -0
- data/lib/attrocity/builders/object_extension_builder.rb +18 -0
- data/lib/attrocity/coercer_registry.rb +38 -0
- data/lib/attrocity/coercers/boolean.rb +12 -0
- data/lib/attrocity/coercers/integer.rb +13 -0
- data/lib/attrocity/coercers/string.rb +14 -0
- data/lib/attrocity/mappers/key_mapper.rb +14 -0
- data/lib/attrocity/model.rb +10 -0
- data/lib/attrocity/value_extractor.rb +23 -0
- data/lib/attrocity/version.rb +1 -1
- data/notes.md +253 -0
- data/spec/attrocity/attributes/attribute_methods_builder_spec.rb +57 -0
- data/spec/attrocity/attributes/attribute_set_spec.rb +24 -0
- data/spec/attrocity/attributes/attribute_spec.rb +4 -0
- data/spec/attrocity/attributes/attribute_template_set_spec.rb +6 -0
- data/spec/attrocity/attributes/attribute_template_spec.rb +25 -0
- data/spec/attrocity/attributes/model_attribute_spec.rb +26 -0
- data/spec/attrocity/attrocity_spec.rb +83 -0
- data/spec/attrocity/coercer_registry_spec.rb +14 -0
- data/spec/attrocity/coercers/boolean_spec.rb +32 -0
- data/spec/attrocity/coercers/coercer_with_args_spec.rb +9 -0
- data/spec/attrocity/coercers/integer_spec.rb +25 -0
- data/spec/attrocity/coercers/string_spec.rb +13 -0
- data/spec/attrocity/default_value_spec.rb +22 -0
- data/spec/attrocity/mappers/key_mapper_spec.rb +22 -0
- data/spec/attrocity/mapping_spec.rb +47 -0
- data/spec/attrocity/model_spec.rb +34 -0
- data/spec/attrocity/object_extension_spec.rb +53 -0
- data/spec/attrocity/value_extractor_spec.rb +23 -0
- data/spec/spec_helper.rb +93 -0
- data/spec/support/examples.rb +79 -0
- metadata +99 -8
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Attrocity
|
4
|
+
describe AttributeMethodsBuilder do
|
5
|
+
let(:object) { Object.new }
|
6
|
+
let(:attr_name) { :a_string }
|
7
|
+
let(:attribute) { Attribute.new(attr_name, 'hello') }
|
8
|
+
subject { described_class.new(object, Array(attribute)) }
|
9
|
+
|
10
|
+
describe '#define_reader' do
|
11
|
+
it 'defines a reader method for the attribute' do
|
12
|
+
expect {
|
13
|
+
subject.define_reader(attribute)
|
14
|
+
}.to change { object.respond_to?(attr_name) }.from(false).to(true)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'returns the attribute value from the defined reader method' do
|
18
|
+
subject.define_reader(attribute)
|
19
|
+
expect(object.send(attr_name)).to eq('hello')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#define_writer' do
|
24
|
+
it 'defines a writer method for the attribute' do
|
25
|
+
expect {
|
26
|
+
subject.define_writer(attribute)
|
27
|
+
}.to change { object.respond_to?("#{attr_name}=") }.from(false).to(true)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'sets the attribute value' do
|
31
|
+
subject.define_writer(attribute)
|
32
|
+
object.send("#{attr_name}=", 'foobar')
|
33
|
+
expect(attribute.value).to eq('foobar')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#define_predicate' do
|
38
|
+
it 'defines a predicate method for the attribute' do
|
39
|
+
expect {
|
40
|
+
subject.define_predicate(attribute)
|
41
|
+
}.to change { object.respond_to?("#{attr_name}?") }.from(false).to(true)
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'returns true if the attribute has a truthy value' do
|
45
|
+
subject.define_predicate(attribute)
|
46
|
+
attribute.value = 'xyz'
|
47
|
+
expect(object.send("#{attr_name}?")).to be true
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'returns false if the attribute has a falsy value' do
|
51
|
+
subject.define_predicate(attribute)
|
52
|
+
expect(attribute).to receive(:value).and_return(nil)
|
53
|
+
expect(object.send("#{attr_name}?")).to be false
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Attrocity
|
2
|
+
RSpec.describe AttributeSet do
|
3
|
+
let(:attrs) { [Attribute.new(:foo, 1), Attribute.new(:bar, 2) ] }
|
4
|
+
|
5
|
+
subject(:attribute_set) { described_class.new }
|
6
|
+
|
7
|
+
describe '#<<' do
|
8
|
+
it 'adds an attribute' do
|
9
|
+
attr = attrs.first
|
10
|
+
subject << attr
|
11
|
+
expect(attribute_set.attributes).to include(attr)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'raises error when non-attribute is added'
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#to_h' do
|
18
|
+
it 'returns hash of instance attribute names and values' do
|
19
|
+
attrs.each { |attr| attribute_set << attr }
|
20
|
+
expect(attribute_set.to_h).to eq({ foo: 1, bar: 2 })
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'attrocity/attributes/attribute_template'
|
2
|
+
|
3
|
+
module Attrocity
|
4
|
+
RSpec.describe AttributeTemplate do
|
5
|
+
describe '#mapper_key_for' do
|
6
|
+
it "returns mapper key when attribute name matches" do
|
7
|
+
attribute = AttributeTemplate.new(
|
8
|
+
:street, Coercers::String.new, KeyMapper.new(:addressline1))
|
9
|
+
expect(attribute.mapper_key_for(:street)).to eq(:addressline1)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "returns mapper key when mapper matches" do
|
13
|
+
attribute = AttributeTemplate.new(
|
14
|
+
:zip, Coercers::String.new, KeyMapper.new(:zipcode))
|
15
|
+
expect(attribute.mapper_key_for(:zipcode)).to eq(:zipcode)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'returns nil when no match' do
|
19
|
+
attribute = AttributeTemplate.new(
|
20
|
+
:street, Coercers::String.new, KeyMapper.new(:addressline1))
|
21
|
+
expect(attribute.mapper_key_for(:nomatch)).to be_nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Attrocity
|
2
|
+
RSpec.describe ModelAttribute do
|
3
|
+
|
4
|
+
let(:listing) {
|
5
|
+
Examples::Listing.new({ 'listingid' => '1234',
|
6
|
+
'addressline1' => '100 Elm Street',
|
7
|
+
'zip' => '27571' })
|
8
|
+
}
|
9
|
+
let(:address) { listing.address }
|
10
|
+
|
11
|
+
it 'instantiates composed model object' do
|
12
|
+
expect(address.street).to eq('100 Elm Street')
|
13
|
+
expect(address.zip).to eq('27571')
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'provides model object' do
|
17
|
+
model_attr = ModelAttribute.new(:address, Examples::Address)
|
18
|
+
address = model_attr.model(
|
19
|
+
{ 'addressline1' => '100 Elm Street',
|
20
|
+
'zip' => '27571' })
|
21
|
+
expect(address.street).to eq('100 Elm Street')
|
22
|
+
expect(address.zip).to eq('27571')
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'attrocity'
|
2
|
+
require 'support/examples'
|
3
|
+
|
4
|
+
module Attrocity
|
5
|
+
RSpec.describe 'Attrocity module' do
|
6
|
+
|
7
|
+
describe '.initialize' do
|
8
|
+
it 'accepts a hash' do
|
9
|
+
expect {
|
10
|
+
Examples::Person.new(age: 29)
|
11
|
+
}.not_to raise_error
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:person) { Examples::Person.new(age: 29) }
|
15
|
+
|
16
|
+
it 'creates a reader for instance-scope attribute set' do
|
17
|
+
expect { person.attribute_set }.not_to raise_error
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'creates a reader for attribute' do
|
21
|
+
expect(person.respond_to?(:age)).to be true
|
22
|
+
end
|
23
|
+
|
24
|
+
# TODO: Move this spec to another unit?
|
25
|
+
it 'returns correct value for reader method' do
|
26
|
+
expect(person.age).to eq(29)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '.attribute' do
|
31
|
+
it 'does not raise error with declarative attribute method' do
|
32
|
+
expect {
|
33
|
+
module Examples
|
34
|
+
class SpecialPerson
|
35
|
+
include Attrocity.model
|
36
|
+
attribute :foo, coercer: :integer
|
37
|
+
end
|
38
|
+
end
|
39
|
+
}.not_to raise_error
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'raises error when missing coercer keyword argument' do
|
43
|
+
expect {
|
44
|
+
module Examples
|
45
|
+
class SpecialDog
|
46
|
+
include Attrocity.model
|
47
|
+
attribute :name
|
48
|
+
end
|
49
|
+
end
|
50
|
+
}.to raise_error(ArgumentError)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'raises error when using coercer name that is not on the registry' do
|
54
|
+
expect {
|
55
|
+
module Examples
|
56
|
+
class Person
|
57
|
+
include Attrocity.model
|
58
|
+
attribute :foo, coercer: :xyz
|
59
|
+
end
|
60
|
+
end
|
61
|
+
}.to raise_error(UnknownCoercerError)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#attribute_set' do
|
66
|
+
it 'contains expected attributes' do
|
67
|
+
pending
|
68
|
+
obj = Examples::Person.new(age: 29)
|
69
|
+
attribute_names = obj.attribute_set.map(&:name)
|
70
|
+
expect(attribute_names).to include(:age)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
describe '.model_from_mapped_data' do
|
75
|
+
it 'returns an instantiated model' do
|
76
|
+
data = { street: '1234 Elm', zip: '27517' }
|
77
|
+
address = Examples::Address.model_from_mapped_data(data)
|
78
|
+
expect(address.street).to eq('1234 Elm')
|
79
|
+
expect(address.zip).to eq('27517')
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'attrocity/coercer_registry'
|
2
|
+
require 'support/examples'
|
3
|
+
|
4
|
+
module Attrocity
|
5
|
+
RSpec.describe CoercerRegistry do
|
6
|
+
it 'adds to the registry' do
|
7
|
+
CoercerRegistry.register do
|
8
|
+
add :example, Examples::ExampleCoercer
|
9
|
+
end
|
10
|
+
expect(CoercerRegistry.coercer_for(:example)).
|
11
|
+
to eq(Examples::ExampleCoercer)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'attrocity/coercers/boolean'
|
2
|
+
|
3
|
+
module Attrocity
|
4
|
+
RSpec.describe Coercers::Boolean do
|
5
|
+
|
6
|
+
subject(:coercer) { Coercers::Boolean.new }
|
7
|
+
|
8
|
+
describe '#coerce' do
|
9
|
+
it 'coerces nil to false' do
|
10
|
+
expect(coercer.coerce(nil)).to be false
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'coerces false to false' do
|
14
|
+
expect(coercer.coerce(false)).to be false
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'coerces true to true' do
|
18
|
+
expect(coercer.coerce(true)).to be true
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'coerces 0 to true' do
|
22
|
+
expect(coercer.coerce(0)).to be true
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'coerces an object to true' do
|
26
|
+
expect(coercer.coerce(Object.new)).to be true
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'attrocity/coercers/integer'
|
2
|
+
|
3
|
+
module Attrocity
|
4
|
+
RSpec.describe Coercers::Integer do
|
5
|
+
subject(:coercer) { Coercers::Integer.new }
|
6
|
+
|
7
|
+
describe '#coerce' do
|
8
|
+
it 'coerces to an integer' do
|
9
|
+
expect(coercer.coerce('1')).to eq(1)
|
10
|
+
end
|
11
|
+
|
12
|
+
describe 'given nil' do
|
13
|
+
it 'returns nil' do
|
14
|
+
expect(coercer.coerce(nil)).to be_nil
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe 'given an empty string' do
|
19
|
+
it 'returns nil' do
|
20
|
+
expect(coercer.coerce('')).to be_nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Attrocity
|
2
|
+
RSpec.describe 'Default value' do
|
3
|
+
before do
|
4
|
+
module Examples
|
5
|
+
class DefaultValueExample
|
6
|
+
include Attrocity.model
|
7
|
+
attribute :bool, coercer: :boolean, default: true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'defaults to default value' do
|
13
|
+
example = Examples::DefaultValueExample.new
|
14
|
+
expect(example.bool).to be true
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'overrides default value' do
|
18
|
+
example = Examples::DefaultValueExample.new(bool: false)
|
19
|
+
expect(example.bool).to be false
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Attrocity
|
2
|
+
RSpec.describe KeyMapper do
|
3
|
+
let(:default_value) { nil }
|
4
|
+
subject(:mapper) { KeyMapper.new(:foo, default_value) }
|
5
|
+
|
6
|
+
it 'extracts data via hash key' do
|
7
|
+
expect(mapper.call(double, { foo: 'bar' })).to eq('bar')
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'returns default value when key absent' do
|
11
|
+
expect(mapper.call(double, { biz: 'baz' })).to eq(default_value)
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'override default value' do
|
15
|
+
let(:default_value) { true }
|
16
|
+
|
17
|
+
it 'returns overridden value when key is absent' do
|
18
|
+
expect(mapper.call(double, { biz: 'baz' })).to eq(default_value)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'support/examples'
|
2
|
+
require 'attrocity'
|
3
|
+
|
4
|
+
module Attrocity
|
5
|
+
RSpec.describe "Mapping" do
|
6
|
+
|
7
|
+
describe 'with default key mapper' do
|
8
|
+
let(:data) { { 'listingid' => '1234' } }
|
9
|
+
let(:listing) { Examples::Listing.new(data) }
|
10
|
+
|
11
|
+
it 'maps attribute data to attribute name' do
|
12
|
+
expect(listing.id).to eq('1234')
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'does not respond to mapped name' do
|
16
|
+
expect { listing.listingid }.to raise_error(NoMethodError)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe 'with proc mapper' do
|
21
|
+
before do
|
22
|
+
module Examples
|
23
|
+
class WithProcMapper
|
24
|
+
include Attrocity.model
|
25
|
+
attribute :age,
|
26
|
+
coercer: :integer,
|
27
|
+
from: lambda { |obj, attrs_data| attrs_data['properties']['age'] }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'maps attribute data to attribute name' do
|
33
|
+
object = Examples::WithProcMapper.new({'properties' => { 'age' => 27 } })
|
34
|
+
expect(object.age).to eq(27)
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'without explicit mapper' do
|
40
|
+
it 'maps attribute data to attribute name' do
|
41
|
+
person = Examples::Person.new(age: 44)
|
42
|
+
expect(person.age).to eq(44)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Attrocity
|
2
|
+
RSpec.describe 'Attrocity.model' do
|
3
|
+
describe '#model' do
|
4
|
+
let(:person) { Examples::Person.new(age: 50) }
|
5
|
+
let(:model) { person.model }
|
6
|
+
|
7
|
+
it 'responds to a reader' do
|
8
|
+
expect(model.respond_to?(:age)).to be true
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'does not respond to a writer' do
|
12
|
+
expect(model.respond_to?(:age=)).to be false
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'responds to predicate'do
|
16
|
+
expect(model.respond_to?(:age?)).to be true
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'has correct value for attribute' do
|
20
|
+
expect(model.age).to eq(50)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'has correct value for predicate' do
|
24
|
+
expect(model.age?).to be true
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'includes model attributes' do
|
28
|
+
listing = Examples::Listing.new({ id: '1234', zip: '10101' })
|
29
|
+
model = listing.model
|
30
|
+
expect(model.address).not_to be_nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|