ormivore 0.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.
- data/Gemfile +14 -0
- data/Gemfile.lock +77 -0
- data/Guardfile +17 -0
- data/README.md +135 -0
- data/Rakefile +11 -0
- data/app/adapters/account_storage_ar_adapter.rb +16 -0
- data/app/adapters/account_storage_memory_adapter.rb +7 -0
- data/app/adapters/address_storage_ar_adapter.rb +8 -0
- data/app/adapters/address_storage_memory_adapter.rb +7 -0
- data/app/connection_manager.rb +17 -0
- data/app/console.rb +15 -0
- data/app/converters/account_sql_storage_converter.rb +33 -0
- data/app/converters/address_sql_storage_converter.rb +51 -0
- data/app/converters/noop_converter.rb +15 -0
- data/app/entities/account.rb +20 -0
- data/app/entities/address.rb +24 -0
- data/app/ports/account_storage_port.rb +5 -0
- data/app/ports/address_storage_port.rb +5 -0
- data/app/repos/account_repo.rb +7 -0
- data/app/repos/address_repo.rb +7 -0
- data/app/require_helpers.rb +34 -0
- data/db/database.yml +7 -0
- data/lib/console.rb +13 -0
- data/lib/init.rb +9 -0
- data/lib/ormivore/ar_adapter.rb +111 -0
- data/lib/ormivore/entity.rb +199 -0
- data/lib/ormivore/errors.rb +21 -0
- data/lib/ormivore/memory_adapter.rb +99 -0
- data/lib/ormivore/port.rb +95 -0
- data/lib/ormivore/repo.rb +58 -0
- data/lib/ormivore/version.rb +3 -0
- data/spec/adapters/account_storage_ar_adapter_spec.rb +13 -0
- data/spec/adapters/account_storage_memory_adapter_spec.rb +12 -0
- data/spec/adapters/address_storage_ar_adapter_spec.rb +14 -0
- data/spec/adapters/address_storage_memory_adapter_spec.rb +13 -0
- data/spec/adapters/ar_helpers.rb +9 -0
- data/spec/adapters/memory_helpers.rb +5 -0
- data/spec/adapters/shared.rb +146 -0
- data/spec/adapters/shared_account.rb +15 -0
- data/spec/adapters/shared_address.rb +21 -0
- data/spec/converters/account_sql_storage_converter_spec.rb +28 -0
- data/spec/converters/address_sql_storage_converter_spec.rb +48 -0
- data/spec/entities/account_spec.rb +13 -0
- data/spec/entities/address_spec.rb +17 -0
- data/spec/entities/shared.rb +114 -0
- data/spec/factories.rb +18 -0
- data/spec/integration/account_repo_ar_integration_spec.rb +12 -0
- data/spec/integration/account_repo_memory_integration_spec.rb +11 -0
- data/spec/integration/address_repo_ar_integration_spec.rb +13 -0
- data/spec/integration/address_repo_memory_integration_spec.rb +13 -0
- data/spec/integration/shared.rb +74 -0
- data/spec/integration/shared_account.rb +17 -0
- data/spec/integration/shared_address.rb +23 -0
- data/spec/ports/account_storage_port_spec.rb +6 -0
- data/spec/ports/address_storage_port_spec.rb +6 -0
- data/spec/ports/shared.rb +50 -0
- data/spec/repos/account_repo_spec.rb +6 -0
- data/spec/repos/address_repo_spec.rb +6 -0
- data/spec/repos/shared.rb +92 -0
- data/spec/spec.opts +3 -0
- data/spec/spec_db_helper.rb +54 -0
- data/spec/spec_helper.rb +8 -0
- metadata +187 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
require_relative 'shared'
|
2
|
+
|
3
|
+
shared_examples_for 'an account adapter' do
|
4
|
+
let(:attrs) do
|
5
|
+
v = test_value
|
6
|
+
{ firstname: v, lastname: v, email: v, status: :active }
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:test_attr) { :firstname }
|
10
|
+
|
11
|
+
let(:factory_name) { :account }
|
12
|
+
let(:factory_attrs) { {} }
|
13
|
+
|
14
|
+
it_behaves_like 'an adapter'
|
15
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative 'shared'
|
2
|
+
|
3
|
+
shared_examples_for 'an address adapter' do
|
4
|
+
let(:account_id) { FactoryGirl.create(:account, adapter: account_adapter).id }
|
5
|
+
|
6
|
+
let(:attrs) do
|
7
|
+
v = test_value
|
8
|
+
{
|
9
|
+
street_1: v, city: v, postal_code: v,
|
10
|
+
country_code: v, region_code: v,
|
11
|
+
type: :shipping, account_id: account_id
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:test_attr) { :street_1 }
|
16
|
+
|
17
|
+
let(:factory_name) { :shipping_address }
|
18
|
+
let(:factory_attrs) { { account_id: account_id } }
|
19
|
+
|
20
|
+
it_behaves_like 'an adapter'
|
21
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe App::AccountSqlStorageConverter do
|
4
|
+
let(:attrs) do
|
5
|
+
v = 'Foo'
|
6
|
+
{ firstname: v, lastname: v, email: v }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#from_storage' do
|
10
|
+
it 'converts status attribute' do
|
11
|
+
subject.from_storage(attrs.merge(status: 1)).should include(status: :active)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'passes through other attributes' do
|
15
|
+
subject.from_storage(attrs.merge(status: 1)).should include(attrs)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#to_storage' do
|
20
|
+
it 'converts status attribute' do
|
21
|
+
subject.to_storage(attrs.merge(status: :inactive)).should include(status: 2)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'passes through other attributes' do
|
25
|
+
subject.to_storage(attrs.merge(status: :inactive)).should include(attrs)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe App::AddressSqlStorageConverter do
|
4
|
+
let(:attrs) do
|
5
|
+
v = 'Foo'
|
6
|
+
{
|
7
|
+
street_1: v, city: v, postal_code: v,
|
8
|
+
country_code: v, region_code: v
|
9
|
+
}
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#from_storage' do
|
13
|
+
it 'converts type attribute' do
|
14
|
+
subject.from_storage(attrs.merge(type: 'ShippingAddress')).should include(type: :shipping)
|
15
|
+
subject.from_storage(attrs.merge(type: 'BillingAddress')).should include(type: :billing)
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'converts addressable id/type attributes to account_id' do
|
19
|
+
subject.from_storage(attrs.merge(addressable_type: 'Account', addressable_id: 123)).should include(account_id: 123)
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'raises error converting unknown addressable_type' do
|
23
|
+
expect {
|
24
|
+
subject.from_storage(attrs.merge(addressable_type: 'Foo', addressable_id: 123))
|
25
|
+
}.to raise_error ORMivore::BadAttributesError
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'passes through other attributes' do
|
29
|
+
subject.from_storage(attrs).should == attrs
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe '#to_storage' do
|
34
|
+
it 'converts status attribute' do
|
35
|
+
subject.to_storage(attrs.merge(type: :shipping)).should include(type: 'ShippingAddress')
|
36
|
+
subject.to_storage(attrs.merge(type: :billing)).should include(type: 'BillingAddress')
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'converts account_id to addressable_id/type attributes' do
|
40
|
+
subject.to_storage(attrs.merge(account_id: 123)).should include(addressable_type: 'Account', addressable_id: 123)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'passes through other attributes' do
|
44
|
+
subject.to_storage(attrs).should == attrs
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'shared'
|
3
|
+
|
4
|
+
describe App::Account do
|
5
|
+
it_behaves_like 'an entity' do
|
6
|
+
let(:attrs) do
|
7
|
+
v = test_value
|
8
|
+
{ firstname: v, lastname: v, email: v, status: :active }
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:test_attr) { :firstname }
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'shared'
|
3
|
+
|
4
|
+
describe App::Address do
|
5
|
+
it_behaves_like 'an entity' do
|
6
|
+
let(:attrs) do
|
7
|
+
v = test_value
|
8
|
+
{
|
9
|
+
street_1: v, city: v, postal_code: v,
|
10
|
+
country_code: v, region_code: v,
|
11
|
+
type: :shipping, account_id: 1
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:test_attr) { :street_1 }
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
shared_examples_for 'an entity' do
|
2
|
+
subject { described_class.new(attrs) }
|
3
|
+
|
4
|
+
let(:test_value) { 'Foo' }
|
5
|
+
|
6
|
+
describe '#initialize' do
|
7
|
+
it 'fails if no attributes are provided' do
|
8
|
+
expect {
|
9
|
+
described_class.new
|
10
|
+
}.to raise_error ArgumentError
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'fails if not enough attributes are provided' do
|
14
|
+
expect {
|
15
|
+
described_class.new(test_attr => test_value)
|
16
|
+
}.to raise_error ORMivore::BadAttributesError
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'fails if unknown attributes are specified' do
|
20
|
+
expect {
|
21
|
+
described_class.new(attrs.merge(foo: 'Foo'))
|
22
|
+
}.to raise_error ORMivore::BadAttributesError
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'allows specifying id' do
|
26
|
+
o = described_class.construct(attrs, 123)
|
27
|
+
o.id.should == 123
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'allows string id that is convertable to integer' do
|
31
|
+
o = described_class.construct(attrs, '123')
|
32
|
+
o.id.should == 123
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'refuses non-integer id' do
|
36
|
+
expect {
|
37
|
+
described_class.construct(attrs, '123a')
|
38
|
+
}.to raise_error ORMivore::BadArgumentError
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'when all mandatory attributes are specified' do
|
42
|
+
it 'succeeds' do
|
43
|
+
o = described_class.new(attrs)
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when some of them are keyed by strings (not symbols)' do
|
47
|
+
it 'succeeds' do
|
48
|
+
attrs.except!(test_attr).merge!(test_attr.to_s => test_value)
|
49
|
+
o = described_class.new(attrs)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'when id is specified' do
|
55
|
+
it 'assumes first param attributes to be "clean" attributes' do
|
56
|
+
o = described_class.construct(attrs, 123)
|
57
|
+
o.changes.should be_empty
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe '#attributes' do
|
63
|
+
it 'returns hash with all the model attributes keyed as symbols' do
|
64
|
+
subject.attributes.should == attrs
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'combines clean and dirty attributes' do
|
68
|
+
o = described_class.construct(attrs, 123).apply(test_attr => 'dirty')
|
69
|
+
o.attributes.should == attrs.merge(test_attr => 'dirty')
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe 'attribute methods' do
|
74
|
+
it 'return value of attribute' do
|
75
|
+
subject.public_send(test_attr).should == test_value
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'return dirty value of attribute if available' do
|
79
|
+
o = described_class.construct(attrs, 123).apply(test_attr => 'dirty')
|
80
|
+
o.changes.should == { test_attr => 'dirty' }
|
81
|
+
o.public_send(test_attr).should == 'dirty'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
describe '#apply' do
|
86
|
+
subject { described_class.construct(attrs, 123) }
|
87
|
+
|
88
|
+
it 'creates copy of this entity' do
|
89
|
+
proto = subject.apply({})
|
90
|
+
proto.should_not == subject
|
91
|
+
proto.attributes.should == subject.attributes
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'adds changes to the copy it makes' do
|
95
|
+
proto = subject.apply(test_attr => 'dirty')
|
96
|
+
proto.attributes.should == attrs.merge(test_attr => 'dirty')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe '#changes' do
|
101
|
+
it 'returns all attributes on new entity' do
|
102
|
+
subject.changes.should == attrs
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'returns no attributes on "persisted" entity' do
|
106
|
+
described_class.construct(attrs, 123).changes.should be_empty
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'returns incremental changes added by applying attributes' do
|
110
|
+
o = described_class.construct(attrs, 123)
|
111
|
+
o.apply(test_attr => 'dirty').changes.should == { test_attr => 'dirty' }
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
data/spec/factories.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
FactoryGirl.define do
|
2
|
+
factory :account, class: App::Account::Builder do
|
3
|
+
firstname 'John'
|
4
|
+
lastname 'Doe'
|
5
|
+
email 'test@test.com'
|
6
|
+
status :active
|
7
|
+
end
|
8
|
+
|
9
|
+
factory :shipping_address, class: App::Address::Builder do
|
10
|
+
street_1 'Some street 123'
|
11
|
+
street_2 'appartment 1'
|
12
|
+
city 'Test'
|
13
|
+
postal_code '12345'
|
14
|
+
country_code :US
|
15
|
+
region_code :PA
|
16
|
+
type :shipping
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'shared_account'
|
3
|
+
require_relative '../adapters/ar_helpers'
|
4
|
+
|
5
|
+
describe App::AccountRepo, :relational_db do
|
6
|
+
include ArHelpers
|
7
|
+
|
8
|
+
let(:entity_table) { 'accounts' }
|
9
|
+
let(:adapter) { App::AccountStorageArAdapter.new }
|
10
|
+
|
11
|
+
it_behaves_like 'an integrated account repo'
|
12
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'shared_account'
|
3
|
+
require_relative '../adapters/memory_helpers'
|
4
|
+
|
5
|
+
describe App::AccountRepo do
|
6
|
+
include MemoryHelpers
|
7
|
+
|
8
|
+
let(:adapter) { App::AccountStorageMemoryAdapter.new }
|
9
|
+
|
10
|
+
it_behaves_like 'an integrated account repo'
|
11
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'shared_address'
|
3
|
+
require_relative '../adapters/ar_helpers'
|
4
|
+
|
5
|
+
describe App::AddressRepo, :relational_db do
|
6
|
+
include ArHelpers
|
7
|
+
|
8
|
+
let(:account_adapter) { App::AccountStorageArAdapter.new }
|
9
|
+
let(:entity_table) { 'addresses' }
|
10
|
+
let(:adapter) { App::AddressStorageArAdapter.new }
|
11
|
+
|
12
|
+
it_behaves_like 'an integrated address repo'
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require_relative 'shared_address'
|
3
|
+
require_relative '../adapters/memory_helpers'
|
4
|
+
|
5
|
+
describe App::AddressRepo do
|
6
|
+
include MemoryHelpers
|
7
|
+
|
8
|
+
let(:account_adapter) { App::AccountStorageMemoryAdapter.new }
|
9
|
+
|
10
|
+
let(:adapter) { App::AddressStorageMemoryAdapter.new }
|
11
|
+
|
12
|
+
it_behaves_like 'an integrated address repo'
|
13
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
shared_examples_for 'an integrated repo' do
|
2
|
+
let(:test_value) { 'Foo' }
|
3
|
+
|
4
|
+
def create_entity
|
5
|
+
FactoryGirl.create(
|
6
|
+
factory_name, factory_attrs.merge(adapter: adapter, test_attr => test_value)
|
7
|
+
).attributes.symbolize_keys
|
8
|
+
end
|
9
|
+
|
10
|
+
subject { described_class.new(port) }
|
11
|
+
|
12
|
+
describe '#find_by_id' do
|
13
|
+
it 'loads entity if found' do
|
14
|
+
account = create_entity
|
15
|
+
subject.find_by_id(account[:id]).public_send(test_attr).should == test_value
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'raises error if entity is not found' do
|
19
|
+
expect {
|
20
|
+
subject.find_by_id(123)
|
21
|
+
}.to raise_error ORMivore::RecordNotFound
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'in quiet mode' do
|
25
|
+
it 'returns nil if entity is not found' do
|
26
|
+
subject.find_by_id(123, quiet: true).should be_nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#persist' do
|
32
|
+
context 'when entity is new' do
|
33
|
+
it 'creates and returns new entity' do
|
34
|
+
entity = entity_class.new(attrs)
|
35
|
+
saved_entity = subject.persist(entity)
|
36
|
+
saved_entity.should_not be_nil
|
37
|
+
saved_entity.attributes.should == attrs
|
38
|
+
saved_entity.id.should be_kind_of(Integer)
|
39
|
+
|
40
|
+
load_test_value(saved_entity.id).should == attrs[test_attr]
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'creates entity with no "changes" recorded on it' do
|
44
|
+
entity = entity_class.new(attrs)
|
45
|
+
entity.changes.should == attrs
|
46
|
+
saved_entity = subject.persist(entity)
|
47
|
+
saved_entity.changes.should be_empty
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when entity is not new' do
|
52
|
+
let(:existing_entity_id) {
|
53
|
+
create_entity[:id]
|
54
|
+
}
|
55
|
+
|
56
|
+
it 'updates record in database' do
|
57
|
+
entity = entity_class.construct(attrs, existing_entity_id).apply(attrs)
|
58
|
+
saved_entity = subject.persist(entity)
|
59
|
+
saved_entity.should_not be_nil
|
60
|
+
saved_entity.attributes.should == attrs
|
61
|
+
saved_entity.id.should == existing_entity_id
|
62
|
+
|
63
|
+
load_test_value(saved_entity.id).should == attrs[test_attr]
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'creates entity with no "changes" recorded on it' do
|
67
|
+
entity = entity_class.construct(attrs, existing_entity_id).apply(attrs)
|
68
|
+
entity.changes.should == attrs
|
69
|
+
saved_entity = subject.persist(entity)
|
70
|
+
saved_entity.changes.should be_empty
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'shared'
|
2
|
+
|
3
|
+
shared_examples_for 'an integrated account repo' do
|
4
|
+
let(:attrs) do
|
5
|
+
v = test_value
|
6
|
+
{ firstname: v, lastname: v, email: v, status: :active }
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:test_attr) { :firstname }
|
10
|
+
let(:entity_class) { App::Account }
|
11
|
+
let(:port) { App::AccountStoragePort.new(adapter) }
|
12
|
+
|
13
|
+
let(:factory_name) { :account }
|
14
|
+
let(:factory_attrs) { {} }
|
15
|
+
|
16
|
+
it_behaves_like 'an integrated repo'
|
17
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require_relative 'shared'
|
2
|
+
|
3
|
+
shared_examples_for 'an integrated address repo' do
|
4
|
+
let(:account_id) { FactoryGirl.create(:account, adapter: account_adapter).id }
|
5
|
+
|
6
|
+
let(:attrs) do
|
7
|
+
v = test_value
|
8
|
+
{
|
9
|
+
street_1: v, city: v, postal_code: v,
|
10
|
+
country_code: v, region_code: v,
|
11
|
+
type: :shipping, account_id: account_id
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:test_attr) { :street_1 }
|
16
|
+
let(:entity_class) { App::Address }
|
17
|
+
let(:port) { App::AddressStoragePort.new(adapter) }
|
18
|
+
|
19
|
+
let(:factory_name) { :shipping_address }
|
20
|
+
let(:factory_attrs) { { account_id: account_id } }
|
21
|
+
|
22
|
+
it_behaves_like 'an integrated repo'
|
23
|
+
end
|