yadm 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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +8 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +273 -0
  8. data/Rakefile +10 -0
  9. data/examples/basic.rb +43 -0
  10. data/examples/migration.rb +65 -0
  11. data/lib/yadm.rb +39 -0
  12. data/lib/yadm/adapters.rb +32 -0
  13. data/lib/yadm/adapters/common_sql.rb +120 -0
  14. data/lib/yadm/adapters/memory.rb +175 -0
  15. data/lib/yadm/adapters/mysql.rb +17 -0
  16. data/lib/yadm/adapters/postgresql.rb +17 -0
  17. data/lib/yadm/adapters/sqlite.rb +17 -0
  18. data/lib/yadm/criteria.rb +32 -0
  19. data/lib/yadm/criteria/argument.rb +22 -0
  20. data/lib/yadm/criteria/attribute.rb +15 -0
  21. data/lib/yadm/criteria/condition.rb +31 -0
  22. data/lib/yadm/criteria/expression.rb +19 -0
  23. data/lib/yadm/criteria/limit.rb +25 -0
  24. data/lib/yadm/criteria/order.rb +48 -0
  25. data/lib/yadm/criteria_parser.rb +62 -0
  26. data/lib/yadm/criteria_parser/expression_parser.rb +77 -0
  27. data/lib/yadm/entity.rb +53 -0
  28. data/lib/yadm/identity_map.rb +51 -0
  29. data/lib/yadm/mapper.rb +16 -0
  30. data/lib/yadm/mapping.rb +71 -0
  31. data/lib/yadm/mapping/attribute.rb +31 -0
  32. data/lib/yadm/query.rb +28 -0
  33. data/lib/yadm/repository.rb +103 -0
  34. data/lib/yadm/version.rb +3 -0
  35. data/spec/spec_helper.rb +26 -0
  36. data/spec/support/criteria_helpers.rb +33 -0
  37. data/spec/support/sequel_helpers.rb +25 -0
  38. data/spec/support/shared_examples_for_a_sequel_adapter.rb +173 -0
  39. data/spec/yadm/adapters/common_sql_spec.rb +89 -0
  40. data/spec/yadm/adapters/memory_spec.rb +230 -0
  41. data/spec/yadm/adapters/mysql_spec.rb +9 -0
  42. data/spec/yadm/adapters/postgresql_spec.rb +9 -0
  43. data/spec/yadm/adapters/sqlite_spec.rb +5 -0
  44. data/spec/yadm/adapters_spec.rb +32 -0
  45. data/spec/yadm/criteria/condition_spec.rb +50 -0
  46. data/spec/yadm/criteria/limit_spec.rb +45 -0
  47. data/spec/yadm/criteria/order_spec.rb +50 -0
  48. data/spec/yadm/criteria_parser/expression_parser_spec.rb +47 -0
  49. data/spec/yadm/criteria_parser_spec.rb +55 -0
  50. data/spec/yadm/criteria_spec.rb +40 -0
  51. data/spec/yadm/entity_spec.rb +76 -0
  52. data/spec/yadm/identity_map_spec.rb +128 -0
  53. data/spec/yadm/mapper_spec.rb +23 -0
  54. data/spec/yadm/mapping/attribute_spec.rb +35 -0
  55. data/spec/yadm/mapping_spec.rb +122 -0
  56. data/spec/yadm/query_spec.rb +45 -0
  57. data/spec/yadm/repository_spec.rb +175 -0
  58. data/spec/yadm_spec.rb +45 -0
  59. data/yadm.gemspec +33 -0
  60. metadata +254 -0
@@ -0,0 +1,55 @@
1
+ RSpec.describe YADM::CriteriaParser do
2
+ describe '.parse' do
3
+ context 'without arguments' do
4
+ let(:block) do
5
+ proc do
6
+ with { age > 12 }.descending_by { age }.ascending_by { id }.first(5)
7
+ end
8
+ end
9
+
10
+ let(:expected_criteria) do
11
+ build_criteria(
12
+ condition: build_condition(
13
+ build_expression(build_attribute(:age), :>, 12)
14
+ ),
15
+ order: build_order(
16
+ [
17
+ build_order_clause(:desc, build_attribute(:age)),
18
+ build_order_clause(:asc, build_attribute(:id))
19
+ ]
20
+ ),
21
+ limit: build_limit(5)
22
+ )
23
+ end
24
+
25
+ it 'returns the parsed YADM::Criteria' do
26
+ expect(described_class.parse(block, :first)).to eq(expected_criteria)
27
+ end
28
+ end
29
+
30
+ context 'with arguments' do
31
+ let(:block) do
32
+ proc do |min_age, limit|
33
+ with { age > min_age }.first(limit)
34
+ end
35
+ end
36
+
37
+ let(:expected_criteria) do
38
+ build_criteria(
39
+ condition: build_condition(
40
+ build_expression(
41
+ build_attribute(:age),
42
+ :>,
43
+ build_argument(:first, 0)
44
+ )
45
+ ),
46
+ limit: build_limit(build_argument(:first, 1))
47
+ )
48
+ end
49
+
50
+ it 'returns the parsed YADM::Criteria' do
51
+ expect(described_class.parse(block, :first)).to eq(expected_criteria)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,40 @@
1
+ RSpec.describe YADM::Criteria do
2
+ describe '#merge' do
3
+ subject do
4
+ described_class.new(
5
+ condition: :first_condition,
6
+ order: :first_order,
7
+ limit: :first_limit
8
+ )
9
+ end
10
+
11
+ let(:other_criteria) do
12
+ described_class.new(
13
+ condition: :second_condition,
14
+ order: :second_order,
15
+ limit: :second_limit
16
+ )
17
+ end
18
+
19
+ before(:each) do
20
+ allow(YADM::Criteria::Condition).to receive(:merge).and_return(:third_condition)
21
+ allow(YADM::Criteria::Order).to receive(:merge).and_return(:third_order)
22
+ allow(YADM::Criteria::Limit).to receive(:merge).and_return(:third_limit)
23
+ end
24
+
25
+ it 'merges conditions' do
26
+ expect(YADM::Criteria::Condition).to receive(:merge).with(:first_condition, :second_condition)
27
+ expect(subject.merge(other_criteria).condition).to eq(:third_condition)
28
+ end
29
+
30
+ it 'merges orders' do
31
+ expect(YADM::Criteria::Order).to receive(:merge).with(:first_order, :second_order)
32
+ expect(subject.merge(other_criteria).order).to eq(:third_order)
33
+ end
34
+
35
+ it 'merges limits' do
36
+ expect(YADM::Criteria::Limit).to receive(:merge).with(:first_limit, :second_limit)
37
+ expect(subject.merge(other_criteria).limit).to eq(:third_limit)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,76 @@
1
+ RSpec.describe YADM::Entity do
2
+ let(:entity_class) do
3
+ Class.new do
4
+ include YADM::Entity
5
+ end
6
+ end
7
+
8
+ describe '.attributes' do
9
+ before(:each) do
10
+ entity_class.class_eval do
11
+ attributes :name
12
+ end
13
+ end
14
+
15
+ let(:entity) { entity_class.new(id: 1, name: 'John') }
16
+
17
+ it 'defines a constructor that takes specified attributes' do
18
+ expect(entity.id).to eq(1)
19
+ expect(entity.name).to eq('John')
20
+ end
21
+
22
+ it 'defines accessors' do
23
+ entity.name = 'Jack'
24
+ expect(entity.name).to eq('Jack')
25
+ end
26
+
27
+ context 'with `readonly` option' do
28
+ before(:each) do
29
+ entity_class.class_eval do
30
+ attribute :password, readonly: true
31
+ end
32
+ end
33
+
34
+ let(:entity) { entity_class.new(id: 1, name: 'John', password: 'secret') }
35
+
36
+ it 'defines just a reader' do
37
+ expect(entity.password).to eq('secret')
38
+
39
+ expect {
40
+ entity.password = 'hello'
41
+ }.to raise_error(NoMethodError)
42
+ end
43
+ end
44
+
45
+ context 'with string parameters' do
46
+ before(:each) do
47
+ entity_class.class_eval do
48
+ attribute 'email'
49
+ end
50
+ end
51
+
52
+ let(:entity) do
53
+ entity_class.new(id: 1, name: 'John', email: 'john@example.com')
54
+ end
55
+
56
+ it 'behaves the same as with symbol parameters' do
57
+ expect(entity.email).to eq('john@example.com')
58
+ end
59
+ end
60
+
61
+ context 'with string keys in contructor' do
62
+ let(:entity) { entity_class.new('id' => 1, 'name' => 'John') }
63
+
64
+ it 'behaves the same as with symbol keys' do
65
+ expect(entity.id).to eq(1)
66
+ expect(entity.name).to eq('John')
67
+ end
68
+ end
69
+
70
+ context 'without arguments' do
71
+ it 'returns all attribute names' do
72
+ expect(entity_class.attributes).to eq([:id, :name].to_set)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,128 @@
1
+ require 'yadm/adapters/memory'
2
+
3
+ RSpec.describe YADM::IdentityMap do
4
+ let(:data_source) { YADM::Adapters::Memory.new }
5
+
6
+ before(:each) do
7
+ data_source.add(:people, name: 'John')
8
+ end
9
+
10
+ subject { YADM::IdentityMap.new(data_source) }
11
+
12
+ describe '#get' do
13
+ context 'without a record in map' do
14
+ it 'gets the record from the data source' do
15
+ expect(subject.get(:people, 1)).to eq(id: 1, name: 'John')
16
+ end
17
+ end
18
+
19
+ context 'with a record in map' do
20
+ before(:each) do
21
+ subject.get(:people, 1)
22
+ data_source.change(:people, 1, name: 'Jack')
23
+ end
24
+
25
+ it 'gets the record from the map' do
26
+ expect(subject.get(:people, 1)).to eq(id: 1, name: 'John')
27
+ end
28
+ end
29
+ end
30
+
31
+ describe '#add' do
32
+ it 'saves the record in the data source' do
33
+ subject.add(:people, name: 'Jack')
34
+
35
+ expect(data_source.get(:people, 2)[:name]).to eq('Jack')
36
+ end
37
+
38
+ it 'saves the record in the map' do
39
+ subject.add(:people, name: 'Jack')
40
+
41
+ expect(data_source).not_to receive(:get)
42
+ expect(subject.get(:people, 2)[:id]).to eq(2)
43
+ end
44
+
45
+ it 'returns the id of the added record' do
46
+ expect(subject.add(:people, name: 'Jack')).to eq(2)
47
+ end
48
+ end
49
+
50
+ describe '#change' do
51
+ context 'without a record in map' do
52
+ before(:each) do
53
+ subject.change(:people, 1, name: 'David')
54
+ end
55
+
56
+ it 'changes the record in the data source' do
57
+ expect(data_source.get(:people, 1)[:name]).to eq('David')
58
+ end
59
+
60
+ it 'adds the record to the map' do
61
+ expect(data_source).not_to receive(:get)
62
+ expect(subject.get(:people, 1)[:id]).to eq(1)
63
+ end
64
+ end
65
+
66
+ context 'with a record in map' do
67
+ before(:each) do
68
+ subject.get(:people, 1)
69
+ subject.change(:people, 1, name: 'David')
70
+ end
71
+
72
+ it 'updates the record in the map' do
73
+ expect(data_source).not_to receive(:get)
74
+ expect(subject.get(:people, 1)[:name]).to eq('David')
75
+ end
76
+ end
77
+ end
78
+
79
+ describe '#remove' do
80
+ it 'removes the record from the data source' do
81
+ subject.remove(:people, 1)
82
+ expect(data_source.count(:people)).to be_zero
83
+ end
84
+
85
+ context 'with a record in map' do
86
+ before(:each) do
87
+ subject.get(:people, 1)
88
+ subject.remove(:people, 1)
89
+ end
90
+
91
+ it 'removes the record from the map' do
92
+ expect(data_source).to receive(:get).with(:people, 1).and_call_original
93
+
94
+ expect {
95
+ subject.get(:people, 1)
96
+ }.to raise_error(KeyError)
97
+ end
98
+ end
99
+ end
100
+
101
+ describe '#count' do
102
+ it 'passes the method call to the data source' do
103
+ expect(subject.count(:people)).to eq(1)
104
+ end
105
+ end
106
+
107
+ describe '#send_query' do
108
+ let(:query) { double('Query') }
109
+ let(:data) { double('Data') }
110
+
111
+ before(:each) do
112
+ allow(data_source).to receive(:send_query).with(:people, query).and_return(data)
113
+ end
114
+
115
+ it 'passes the method call to the data source' do
116
+ expect(subject.send_query(:people, query)).to eq(data)
117
+ end
118
+ end
119
+
120
+ describe '#migrate' do
121
+ let(:block) { double('Block') }
122
+
123
+ it 'passes the method call to the data source' do
124
+ expect(data_source).to receive(:migrate).with(block)
125
+ subject.migrate(block)
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,23 @@
1
+ RSpec.describe YADM::Mapper do
2
+ let(:identity_map) { YADM::IdentityMap.new(nil) }
3
+
4
+ before(:each) do
5
+ YADM.data_sources[:source] = identity_map
6
+ end
7
+
8
+ describe '.repository' do
9
+ let(:repository) do
10
+ Class.new do
11
+ include YADM::Repository
12
+ end
13
+ end
14
+
15
+ it 'adds a new mapping for the given repository' do
16
+ subject.repository(repository) do
17
+ data_source :source
18
+ end
19
+
20
+ expect(subject.mapping_for(repository).data_source).to eq(identity_map)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,35 @@
1
+ RSpec.describe YADM::Mapping::Attribute do
2
+ context 'with an Integer type' do
3
+ subject { described_class.new(Integer) }
4
+
5
+ describe '#coerce' do
6
+ it 'coerces the value to integer' do
7
+ expect(subject.coerce('123')).to eq(123)
8
+ expect(subject.coerce(123)).to eq(123)
9
+ expect(subject.coerce('123abc')).to eq(123)
10
+ expect(subject.coerce('abc')).to eq(0)
11
+ end
12
+
13
+ it 'leaves nil as is' do
14
+ expect(subject.coerce(nil)).to be_nil
15
+ end
16
+ end
17
+ end
18
+
19
+ context 'with a String type' do
20
+ subject { described_class.new(String) }
21
+
22
+ describe '#coerce' do
23
+ it 'coerces the value to String' do
24
+ expect(subject.coerce(123)).to eq('123')
25
+ expect(subject.coerce('123')).to eq('123')
26
+ expect(subject.coerce('123abc')).to eq('123abc')
27
+ expect(subject.coerce(:abc)).to eq('abc')
28
+ end
29
+
30
+ it 'leaves nil as is' do
31
+ expect(subject.coerce(nil)).to be_nil
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,122 @@
1
+ require 'yadm/adapters/memory'
2
+
3
+ RSpec.describe YADM::Mapping do
4
+ let(:data_source) { YADM::Adapters::Memory.new }
5
+ let(:identity_map) { YADM::IdentityMap.new(data_source) }
6
+
7
+ before(:each) do
8
+ data_source.add(:people, name: 'John', age: '35')
9
+ YADM.data_sources[:source] = identity_map
10
+ end
11
+
12
+ let(:people_mapping) do
13
+ YADM::Mapping.new do
14
+ data_source :source
15
+ collection :people
16
+
17
+ attribute :id, Integer
18
+ attribute :name, String
19
+ attribute :age, Integer
20
+ end
21
+ end
22
+
23
+ describe '#get' do
24
+ it 'returns a hash with converted attributes' do
25
+ hash = people_mapping.get(1)
26
+
27
+ expect(hash[:id]).to eq(1)
28
+ expect(hash[:name]).to eq('John')
29
+ expect(hash[:age]).to eq(35)
30
+ end
31
+ end
32
+
33
+ describe '#add' do
34
+ it 'passes the method call to the data source' do
35
+ people_mapping.add(name: 'Jack', age: 27)
36
+ expect(people_mapping.get(2)[:name]).to eq('Jack')
37
+ end
38
+ end
39
+
40
+ describe '#change' do
41
+ it 'passes the method call to the data source' do
42
+ people_mapping.change(1, name: 'Johnny')
43
+ expect(people_mapping.get(1)[:name]).to eq('Johnny')
44
+ end
45
+ end
46
+
47
+ describe '#remove' do
48
+ it 'passes the method call to the data source' do
49
+ people_mapping.remove(1)
50
+ expect { people_mapping.get(1) }.to raise_error(KeyError)
51
+ end
52
+ end
53
+
54
+ describe '#count' do
55
+ it 'passes the method call to the data source' do
56
+ expect(people_mapping.count).to eq(1)
57
+ end
58
+ end
59
+
60
+ describe '#send_query' do
61
+ let(:query) { double('Query') }
62
+
63
+ let(:data) do
64
+ [
65
+ { id: 1, name: 'John', age: '31' },
66
+ { id: 2, name: 'Jack', age: '42' }
67
+ ]
68
+ end
69
+
70
+ before(:each) do
71
+ allow(identity_map).to receive(:send_query).with(:people, query).and_return(data)
72
+ end
73
+
74
+ it 'gets the data from the data source and coerces it' do
75
+ result = people_mapping.send_query(query)
76
+
77
+ expect(result.first[:name]).to eq('John')
78
+ expect(result.first[:age]).to eq(31)
79
+ expect(result.last[:name]).to eq('Jack')
80
+ expect(result.last[:age]).to eq(42)
81
+ end
82
+ end
83
+
84
+ describe YADM::Mapping::DSL do
85
+ describe '#data_source' do
86
+ let(:mapping) do
87
+ YADM::Mapping.new do
88
+ data_source :source
89
+ end
90
+ end
91
+
92
+ it 'specifies the data source identifier' do
93
+ expect(mapping.data_source).to eq(identity_map)
94
+ end
95
+ end
96
+
97
+ describe '#collection' do
98
+ let(:mapping) do
99
+ YADM::Mapping.new do
100
+ collection :people
101
+ end
102
+ end
103
+
104
+ it 'specifies the collection' do
105
+ expect(mapping.collection).to eq(:people)
106
+ end
107
+ end
108
+
109
+ describe '#attribute' do
110
+ let(:mapping) do
111
+ YADM::Mapping.new do
112
+ attribute :id, Integer
113
+ end
114
+ end
115
+
116
+ it 'adds a new attribute' do
117
+ expect(mapping.attributes).to have_key(:id)
118
+ expect(mapping.attributes[:id]).to eq(YADM::Mapping::Attribute.new(Integer))
119
+ end
120
+ end
121
+ end
122
+ end