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,230 @@
1
+ require 'yadm/adapters/memory'
2
+
3
+ RSpec.describe YADM::Adapters::Memory do
4
+ let(:person_attributes) do
5
+ { name: 'John', email: 'john@example.com' }
6
+ end
7
+
8
+ describe '#get' do
9
+ before(:each) do
10
+ subject.add(:people, person_attributes)
11
+ end
12
+
13
+ context 'with a valid id' do
14
+ it 'returns the record with the specified id' do
15
+ expect(subject.get(:people, 1)).to eq(person_attributes.merge(id: 1))
16
+ end
17
+ end
18
+
19
+ context 'with an invalid id' do
20
+ it 'raises' do
21
+ expect {
22
+ subject.get(:people, 11)
23
+ }.to raise_error(KeyError)
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#add' do
29
+ it 'adds a new record' do
30
+ expect {
31
+ subject.add(:people, person_attributes)
32
+ }.to change { subject.count(:people) }.by(1)
33
+ end
34
+
35
+ it 'returns id of the created record' do
36
+ expect(subject.add(:people, person_attributes)).to eq(1)
37
+ end
38
+
39
+ it 'adds the id attribute to the created record' do
40
+ id = subject.add(:people, person_attributes)
41
+ expect(subject.get(:people, id)[:id]).to eq(id)
42
+ end
43
+
44
+ it 'increments the id with each record' do
45
+ 5.times { subject.add(:people, person_attributes) }
46
+
47
+ expect(subject.count(:people)).to eq(5)
48
+ expect(subject.get(:people, 5)).to eq(person_attributes.merge(id: 5))
49
+ end
50
+ end
51
+
52
+ describe '#change' do
53
+ before(:each) do
54
+ subject.add(:people, person_attributes)
55
+ end
56
+
57
+ it 'changes an existing record' do
58
+ expect {
59
+ subject.change(:people, 1, name: 'Jack')
60
+ }.to change { subject.get(:people, 1)[:name] }.to('Jack')
61
+ end
62
+ end
63
+
64
+ describe '#remove' do
65
+ before(:each) do
66
+ subject.add(:people, person_attributes)
67
+ end
68
+
69
+ it 'removes the record with the specified id' do
70
+ expect {
71
+ subject.remove(:people, 1)
72
+ }.to change { subject.count(:people) }.by(-1)
73
+ end
74
+ end
75
+
76
+ describe '#migrate' do
77
+ it 'does nothing' do
78
+ expect {
79
+ subject.migrate(:block)
80
+ }.not_to raise_error
81
+ end
82
+ end
83
+
84
+ describe YADM::Adapters::Memory::Collection do
85
+ describe '#send_query' do
86
+ let(:criteria) do
87
+ YADM::Criteria.new(
88
+ condition: :some_condition,
89
+ order: :some_order,
90
+ limit: :some_limit
91
+ )
92
+ end
93
+
94
+ let(:query) { double('Query', criteria: criteria, arguments: {}) }
95
+
96
+ let(:records) { %i(first second third fourth) }
97
+ let(:filtered_records) { %i(first third fourth) }
98
+ let(:ordered_records) { %i(third first fourth) }
99
+ let(:limited_records) { %i(third first) }
100
+
101
+ before(:each) do
102
+ allow(subject).to receive(:all).and_return(records)
103
+ end
104
+
105
+ it 'returns the transformed records collection' do
106
+ expect(subject).to receive(:filter).with(records, :some_condition, {}).and_return(filtered_records)
107
+ expect(subject).to receive(:order).with(filtered_records, :some_order, {}).and_return(ordered_records)
108
+ expect(subject).to receive(:limit).with(ordered_records, :some_limit, {}).and_return(limited_records)
109
+
110
+ expect(subject.send_query(query)).to eq(limited_records)
111
+ end
112
+ end
113
+
114
+ describe '#filter' do
115
+ before(:each) do
116
+ subject.add(title: 'First post', comments_count: 14)
117
+ subject.add(title: 'Second post', comments_count: 7)
118
+ subject.add(title: 'Third post', comments_count: 17)
119
+ end
120
+
121
+ let(:condition) do
122
+ attribute = build_attribute(:comments_count)
123
+ expression = build_expression(attribute, :>, 10)
124
+
125
+ build_condition(expression)
126
+ end
127
+
128
+ it 'returns all records matching the condition' do
129
+ result = subject.filter(subject.all, condition, {})
130
+
131
+ expect(result.count).to eq(2)
132
+ expect(result.first[:title]).to eq('First post')
133
+ expect(result.last[:title]).to eq('Third post')
134
+ end
135
+
136
+ context 'with arguments' do
137
+ let(:condition) do
138
+ attribute = build_attribute(:comments_count)
139
+ argument = build_argument(:first, 0)
140
+ expression = build_expression(attribute, :>, argument)
141
+
142
+ build_condition(expression)
143
+ end
144
+
145
+ it 'returns all records matching the condition' do
146
+ result = subject.filter(subject.all, condition, first: [15])
147
+
148
+ expect(result.count).to eq(1)
149
+ expect(result.first[:title]).to eq('Third post')
150
+ end
151
+ end
152
+ end
153
+
154
+ describe '#order' do
155
+ before(:each) do
156
+ now = Time.now
157
+
158
+ subject.add(name: 'Past', created_at: now - 5)
159
+ subject.add(name: 'Future', created_at: now + 5)
160
+ subject.add(name: 'Present', created_at: now)
161
+ subject.add(name: 'Pre-past', created_at: now - 5)
162
+ end
163
+
164
+ let(:order) do
165
+ created_at = build_attribute(:created_at)
166
+ timestamp_clause = build_order_clause(:asc, created_at)
167
+
168
+ id = build_attribute(:id)
169
+ id_clause = build_order_clause(:desc, id)
170
+
171
+ build_order([timestamp_clause, id_clause])
172
+ end
173
+
174
+ it 'returns records in the specified order' do
175
+ result = subject.order(subject.all, order, {})
176
+ names = result.map { |record| record[:name] }
177
+
178
+ expect(names).to eq(%w(Pre-past Past Present Future))
179
+ end
180
+
181
+ context 'with arguments' do
182
+ let(:order) do
183
+ name = build_attribute(:name)
184
+ argument = build_argument(:first, 0)
185
+ expression = build_expression(name, :[], argument)
186
+
187
+ clause = build_order_clause(:asc, expression)
188
+ build_order([clause])
189
+ end
190
+
191
+ it 'returns records in the specified order' do
192
+ result = subject.order(subject.all, order, first: [1])
193
+ names = result.map { |record| record[:name] }
194
+
195
+ expect(names).to eq(%w(Past Present Pre-past Future))
196
+ end
197
+ end
198
+ end
199
+
200
+ describe '#limit' do
201
+ before(:each) do
202
+ subject.add(name: 'First')
203
+ subject.add(name: 'Second')
204
+ subject.add(name: 'Third')
205
+ end
206
+
207
+ let(:limit) { build_limit(2) }
208
+
209
+ it 'returns first N records' do
210
+ result = subject.limit(subject.all, limit, {})
211
+
212
+ expect(result.count).to eq(2)
213
+ expect(result.first[:name]).to eq('First')
214
+ expect(result.last[:name]).to eq('Second')
215
+ end
216
+
217
+ context 'with arguments' do
218
+ let(:limit) do
219
+ argument = build_argument(:first, 0)
220
+ build_limit(argument)
221
+ end
222
+
223
+ it 'returns first N records' do
224
+ result = subject.limit(subject.all, limit, first: [1])
225
+ expect(result.count).to eq(1)
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,9 @@
1
+ require 'yadm/adapters/mysql'
2
+
3
+ RSpec.describe YADM::Adapters::MySQL do
4
+ subject do
5
+ described_class.new(database: 'yadm_test', user: 'yadm', password: 'yadm')
6
+ end
7
+
8
+ it_behaves_like 'a sequel adapter'
9
+ end
@@ -0,0 +1,9 @@
1
+ require 'yadm/adapters/postgresql'
2
+
3
+ RSpec.describe YADM::Adapters::PostgreSQL do
4
+ subject do
5
+ described_class.new(database: 'yadm_test', user: 'yadm', password: 'yadm')
6
+ end
7
+
8
+ it_behaves_like 'a sequel adapter'
9
+ end
@@ -0,0 +1,5 @@
1
+ require 'yadm/adapters/sqlite'
2
+
3
+ RSpec.describe YADM::Adapters::Sqlite do
4
+ it_behaves_like 'a sequel adapter'
5
+ end
@@ -0,0 +1,32 @@
1
+ RSpec.describe YADM::Adapters do
2
+ describe '.register' do
3
+ let(:adapter) { double('Adapter') }
4
+
5
+ it 'stores a new adapter in the registry' do
6
+ subject.register(:some_adapter, adapter)
7
+ expect(subject.fetch(:some_adapter)).to eq(adapter)
8
+ end
9
+ end
10
+
11
+ describe '.fetch' do
12
+ context 'with a registered adapter name' do
13
+ let(:adapter) { double('Adapter') }
14
+
15
+ before(:each) do
16
+ subject.register(:some_adapter, adapter)
17
+ end
18
+
19
+ it 'returns the corresponding adapter' do
20
+ expect(subject.fetch(:some_adapter)).to eq(adapter)
21
+ end
22
+ end
23
+
24
+ context 'with an unknown adapter_name' do
25
+ it 'raises a NotImplementedError' do
26
+ expect {
27
+ subject.fetch(:unknown)
28
+ }.to raise_error(NotImplementedError, "Adapter `:unknown` isn't registered.")
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,50 @@
1
+ RSpec.describe YADM::Criteria::Condition do
2
+ describe '.merge' do
3
+ let(:first_condition) do
4
+ attribute = build_attribute(:age)
5
+ expression = build_expression(attribute, :>, 20)
6
+
7
+ described_class.new(expression)
8
+ end
9
+
10
+ let(:second_condition) do
11
+ attribute = build_attribute(:age)
12
+ expression = build_expression(attribute, :<, 35)
13
+
14
+ described_class.new(expression)
15
+ end
16
+
17
+ subject { described_class.merge(first_condition, second_condition) }
18
+
19
+ it 'concatenates the conditions with a logical AND' do
20
+ expect(subject.expression.receiver).to eq(first_condition.expression)
21
+ expect(subject.expression.method_name).to eq(:&)
22
+ expect(subject.expression.arguments).to eq([second_condition.expression])
23
+ end
24
+
25
+ context 'with first argument being nil' do
26
+ let(:first_condition) { nil }
27
+
28
+ it 'returns the second argument' do
29
+ expect(subject).to eq(second_condition)
30
+ end
31
+ end
32
+
33
+ context 'with second argument being nil' do
34
+ let(:second_condition) { nil }
35
+
36
+ it 'returns the first argument' do
37
+ expect(subject).to eq(first_condition)
38
+ end
39
+ end
40
+
41
+ context 'with both arguments being nil' do
42
+ let(:first_condition) { nil }
43
+ let(:second_condition) { nil }
44
+
45
+ it 'returns nil' do
46
+ expect(subject).to be_nil
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,45 @@
1
+ RSpec.describe YADM::Criteria::Limit do
2
+ describe '.merge' do
3
+ let(:first_limit) { described_class.new(10) }
4
+ let(:second_limit) { described_class.new(15) }
5
+
6
+ subject { described_class.merge(first_limit, second_limit) }
7
+
8
+ it 'respects the last value' do
9
+ expect(subject.limit).to eq(15)
10
+ end
11
+
12
+ context 'with second argument having a nil limit' do
13
+ let(:second_limit) { described_class.new(nil) }
14
+
15
+ it 'returns nil' do
16
+ expect(subject).to be_nil
17
+ end
18
+ end
19
+
20
+ context 'with first argument being nil' do
21
+ let(:first_limit) { nil }
22
+
23
+ it 'returns the second argument' do
24
+ expect(subject).to eq(second_limit)
25
+ end
26
+ end
27
+
28
+ context 'with second argument being nil' do
29
+ let(:second_limit) { nil }
30
+
31
+ it 'returns the first argument' do
32
+ expect(subject).to eq(first_limit)
33
+ end
34
+ end
35
+
36
+ context 'with both arguments being nil' do
37
+ let(:first_limit) { nil }
38
+ let(:second_limit) { nil }
39
+
40
+ it 'returns nil' do
41
+ expect(subject).to be_nil
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,50 @@
1
+ RSpec.describe YADM::Criteria::Order do
2
+ describe '.merge' do
3
+ let(:first_order) do
4
+ attribute = build_attribute(:last_name)
5
+ clause = build_order_clause(:asc, attribute)
6
+
7
+ described_class.new([clause])
8
+ end
9
+
10
+ let(:second_order) do
11
+ attribute = build_attribute(:first_name)
12
+ clause = build_order_clause(:asc, attribute)
13
+
14
+ described_class.new([clause])
15
+ end
16
+
17
+ subject { described_class.merge(first_order, second_order) }
18
+
19
+ it 'sums up the clauses' do
20
+ expect(subject.clauses.count).to eq(2)
21
+ expect(subject.clauses.first).to eq(first_order.clauses.first)
22
+ expect(subject.clauses.last).to eq(second_order.clauses.first)
23
+ end
24
+
25
+ context 'with first argument being nil' do
26
+ let(:first_order) { nil }
27
+
28
+ it 'returns the second argument' do
29
+ expect(subject).to eq(second_order)
30
+ end
31
+ end
32
+
33
+ context 'with second argument being nil' do
34
+ let(:second_order) { nil }
35
+
36
+ it 'returns the first argument' do
37
+ expect(subject).to eq(first_order)
38
+ end
39
+ end
40
+
41
+ context 'with both arguments being nil' do
42
+ let(:first_order) { nil }
43
+ let(:second_order) { nil }
44
+
45
+ it 'returns nil' do
46
+ expect(subject).to be_nil
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,47 @@
1
+ RSpec.describe YADM::CriteriaParser::ExpressionParser do
2
+ subject { described_class.new(block) }
3
+
4
+ describe '#result' do
5
+ let(:result) { subject.result }
6
+
7
+ context 'with a simple block' do
8
+ let(:block) do
9
+ proc do
10
+ age > 12
11
+ end
12
+ end
13
+
14
+ let(:expected_expression) do
15
+ build_expression(build_attribute(:age), :>, 12)
16
+ end
17
+
18
+ it 'returns a Criteria::Expression' do
19
+ expect(result).to eq(expected_expression)
20
+ end
21
+ end
22
+
23
+ context 'with a nested expression in the block' do
24
+ let(:block) do
25
+ proc do
26
+ (age >= 18) & (age <= 27) & (sex == 'male')
27
+ end
28
+ end
29
+
30
+ let(:expected_expression) do
31
+ build_expression(
32
+ build_expression(
33
+ build_expression(build_attribute(:age), :>=, 18),
34
+ :&,
35
+ build_expression(build_attribute(:age), :<=, 27)
36
+ ),
37
+ :&,
38
+ build_expression(build_attribute(:sex), :==, 'male')
39
+ )
40
+ end
41
+
42
+ it 'returns a nested Criteria::Expression' do
43
+ expect(result).to eq(expected_expression)
44
+ end
45
+ end
46
+ end
47
+ end