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.
Files changed (63) hide show
  1. data/Gemfile +14 -0
  2. data/Gemfile.lock +77 -0
  3. data/Guardfile +17 -0
  4. data/README.md +135 -0
  5. data/Rakefile +11 -0
  6. data/app/adapters/account_storage_ar_adapter.rb +16 -0
  7. data/app/adapters/account_storage_memory_adapter.rb +7 -0
  8. data/app/adapters/address_storage_ar_adapter.rb +8 -0
  9. data/app/adapters/address_storage_memory_adapter.rb +7 -0
  10. data/app/connection_manager.rb +17 -0
  11. data/app/console.rb +15 -0
  12. data/app/converters/account_sql_storage_converter.rb +33 -0
  13. data/app/converters/address_sql_storage_converter.rb +51 -0
  14. data/app/converters/noop_converter.rb +15 -0
  15. data/app/entities/account.rb +20 -0
  16. data/app/entities/address.rb +24 -0
  17. data/app/ports/account_storage_port.rb +5 -0
  18. data/app/ports/address_storage_port.rb +5 -0
  19. data/app/repos/account_repo.rb +7 -0
  20. data/app/repos/address_repo.rb +7 -0
  21. data/app/require_helpers.rb +34 -0
  22. data/db/database.yml +7 -0
  23. data/lib/console.rb +13 -0
  24. data/lib/init.rb +9 -0
  25. data/lib/ormivore/ar_adapter.rb +111 -0
  26. data/lib/ormivore/entity.rb +199 -0
  27. data/lib/ormivore/errors.rb +21 -0
  28. data/lib/ormivore/memory_adapter.rb +99 -0
  29. data/lib/ormivore/port.rb +95 -0
  30. data/lib/ormivore/repo.rb +58 -0
  31. data/lib/ormivore/version.rb +3 -0
  32. data/spec/adapters/account_storage_ar_adapter_spec.rb +13 -0
  33. data/spec/adapters/account_storage_memory_adapter_spec.rb +12 -0
  34. data/spec/adapters/address_storage_ar_adapter_spec.rb +14 -0
  35. data/spec/adapters/address_storage_memory_adapter_spec.rb +13 -0
  36. data/spec/adapters/ar_helpers.rb +9 -0
  37. data/spec/adapters/memory_helpers.rb +5 -0
  38. data/spec/adapters/shared.rb +146 -0
  39. data/spec/adapters/shared_account.rb +15 -0
  40. data/spec/adapters/shared_address.rb +21 -0
  41. data/spec/converters/account_sql_storage_converter_spec.rb +28 -0
  42. data/spec/converters/address_sql_storage_converter_spec.rb +48 -0
  43. data/spec/entities/account_spec.rb +13 -0
  44. data/spec/entities/address_spec.rb +17 -0
  45. data/spec/entities/shared.rb +114 -0
  46. data/spec/factories.rb +18 -0
  47. data/spec/integration/account_repo_ar_integration_spec.rb +12 -0
  48. data/spec/integration/account_repo_memory_integration_spec.rb +11 -0
  49. data/spec/integration/address_repo_ar_integration_spec.rb +13 -0
  50. data/spec/integration/address_repo_memory_integration_spec.rb +13 -0
  51. data/spec/integration/shared.rb +74 -0
  52. data/spec/integration/shared_account.rb +17 -0
  53. data/spec/integration/shared_address.rb +23 -0
  54. data/spec/ports/account_storage_port_spec.rb +6 -0
  55. data/spec/ports/address_storage_port_spec.rb +6 -0
  56. data/spec/ports/shared.rb +50 -0
  57. data/spec/repos/account_repo_spec.rb +6 -0
  58. data/spec/repos/address_repo_spec.rb +6 -0
  59. data/spec/repos/shared.rb +92 -0
  60. data/spec/spec.opts +3 -0
  61. data/spec/spec_db_helper.rb +54 -0
  62. data/spec/spec_helper.rb +8 -0
  63. 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