attrocity 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|