torque-postgresql 1.1.8 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql.rb +0 -2
  3. data/lib/torque/postgresql/adapter.rb +0 -1
  4. data/lib/torque/postgresql/adapter/database_statements.rb +4 -15
  5. data/lib/torque/postgresql/adapter/schema_creation.rb +13 -23
  6. data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -21
  7. data/lib/torque/postgresql/adapter/schema_dumper.rb +71 -11
  8. data/lib/torque/postgresql/adapter/schema_statements.rb +2 -12
  9. data/lib/torque/postgresql/associations.rb +0 -3
  10. data/lib/torque/postgresql/associations/association.rb +0 -4
  11. data/lib/torque/postgresql/associations/association_scope.rb +18 -60
  12. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +12 -15
  13. data/lib/torque/postgresql/associations/preloader.rb +0 -32
  14. data/lib/torque/postgresql/associations/preloader/association.rb +13 -10
  15. data/lib/torque/postgresql/autosave_association.rb +4 -4
  16. data/lib/torque/postgresql/auxiliary_statement.rb +1 -13
  17. data/lib/torque/postgresql/coder.rb +1 -2
  18. data/lib/torque/postgresql/config.rb +0 -6
  19. data/lib/torque/postgresql/inheritance.rb +13 -17
  20. data/lib/torque/postgresql/reflection/abstract_reflection.rb +19 -25
  21. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +4 -38
  22. data/lib/torque/postgresql/relation.rb +11 -16
  23. data/lib/torque/postgresql/relation/auxiliary_statement.rb +2 -8
  24. data/lib/torque/postgresql/relation/distinct_on.rb +1 -1
  25. data/lib/torque/postgresql/version.rb +1 -1
  26. data/spec/en.yml +19 -0
  27. data/spec/factories/authors.rb +6 -0
  28. data/spec/factories/comments.rb +13 -0
  29. data/spec/factories/posts.rb +6 -0
  30. data/spec/factories/tags.rb +5 -0
  31. data/spec/factories/texts.rb +5 -0
  32. data/spec/factories/users.rb +6 -0
  33. data/spec/factories/videos.rb +5 -0
  34. data/spec/mocks/cache_query.rb +16 -0
  35. data/spec/mocks/create_table.rb +35 -0
  36. data/spec/models/activity.rb +3 -0
  37. data/spec/models/activity_book.rb +4 -0
  38. data/spec/models/activity_post.rb +7 -0
  39. data/spec/models/activity_post/sample.rb +4 -0
  40. data/spec/models/author.rb +4 -0
  41. data/spec/models/author_journalist.rb +4 -0
  42. data/spec/models/comment.rb +3 -0
  43. data/spec/models/course.rb +2 -0
  44. data/spec/models/geometry.rb +2 -0
  45. data/spec/models/guest_comment.rb +4 -0
  46. data/spec/models/post.rb +6 -0
  47. data/spec/models/tag.rb +2 -0
  48. data/spec/models/text.rb +2 -0
  49. data/spec/models/time_keeper.rb +2 -0
  50. data/spec/models/user.rb +8 -0
  51. data/spec/models/video.rb +2 -0
  52. data/spec/schema.rb +141 -0
  53. data/spec/spec_helper.rb +59 -0
  54. data/spec/tests/arel_spec.rb +72 -0
  55. data/spec/tests/auxiliary_statement_spec.rb +593 -0
  56. data/spec/tests/belongs_to_many_spec.rb +240 -0
  57. data/spec/tests/coder_spec.rb +367 -0
  58. data/spec/tests/collector_spec.rb +59 -0
  59. data/spec/tests/distinct_on_spec.rb +65 -0
  60. data/spec/tests/enum_set_spec.rb +306 -0
  61. data/spec/tests/enum_spec.rb +621 -0
  62. data/spec/tests/geometric_builder_spec.rb +221 -0
  63. data/spec/tests/has_many_spec.rb +390 -0
  64. data/spec/tests/interval_spec.rb +167 -0
  65. data/spec/tests/lazy_spec.rb +24 -0
  66. data/spec/tests/period_spec.rb +954 -0
  67. data/spec/tests/quoting_spec.rb +24 -0
  68. data/spec/tests/range_spec.rb +36 -0
  69. data/spec/tests/relation_spec.rb +57 -0
  70. data/spec/tests/table_inheritance_spec.rb +403 -0
  71. metadata +103 -15
  72. data/lib/torque/postgresql/associations/join_dependency/join_association.rb +0 -15
  73. data/lib/torque/postgresql/schema_dumper.rb +0 -101
@@ -0,0 +1,221 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'Geometries' do
4
+ context 'on build' do
5
+ let(:klass) do
6
+ klass = Class.new(Torque::PostgreSQL::GeometryBuilder)
7
+ klass.define_singleton_method(:name) { 'TestSample' }
8
+ klass.const_set('PIECES', %i[a b c d].freeze)
9
+ klass.const_set('FORMATION', '(%s, %s, <%s, {%s}>)'.freeze)
10
+ klass
11
+ end
12
+
13
+ let(:instance) { klass.new }
14
+
15
+ context '#type' do
16
+ it 'originally does not have the constant defined' do
17
+ expect(klass.constants).not_to include('TYPE')
18
+ end
19
+
20
+ it 'creates the type constant based on the name' do
21
+ expect(instance.type).to be_eql(:test_sample)
22
+ expect(klass.constants).to include(:TYPE)
23
+ expect(klass::TYPE).to be_eql(:test_sample)
24
+ end
25
+
26
+ it 'returns the constant value' do
27
+ klass.const_set('TYPE', 'another_type')
28
+ expect(instance.type).to be_eql('another_type')
29
+ end
30
+ end
31
+
32
+ context '#pieces' do
33
+ it 'returns the definition pieces' do
34
+ expect(instance.pieces).to be_eql([:a, :b, :c, :d])
35
+ end
36
+
37
+ it 'returns whatever is in the constant' do
38
+ klass.send(:remove_const, 'PIECES')
39
+ klass.const_set('PIECES', %i[a].freeze)
40
+ expect(instance.pieces).to be_eql([:a])
41
+ end
42
+ end
43
+
44
+ context '#formation' do
45
+ it 'returns the definition set' do
46
+ expect(instance.formation).to be_eql("(%s, %s, <%s, {%s}>)")
47
+ end
48
+
49
+ it 'returns whatever is in the constant' do
50
+ klass.send(:remove_const, 'FORMATION')
51
+ klass.const_set('FORMATION', '(<%s>)'.freeze)
52
+ expect(instance.formation).to be_eql("(<%s>)")
53
+ end
54
+ end
55
+
56
+ context '#cast' do
57
+ let(:config_class) { double }
58
+
59
+ before { allow(instance).to receive(:config_class).and_return(config_class) }
60
+
61
+ it 'accepts string values' do
62
+ expect(instance.cast('')).to be_nil
63
+
64
+ expect(config_class).to receive(:new).with(1, 2, 3, 4).and_return(4)
65
+ expect(instance.cast('1, 2, 3, 4')).to be_eql(4)
66
+
67
+ expect(config_class).to receive(:new).with(1, 2, 3, 4).and_return(8)
68
+ expect(instance.cast('(1, {2}, <3>, 4)')).to be_eql(8)
69
+
70
+ expect(config_class).to receive(:new).with(1, 2, 3, 4).and_return(7)
71
+ expect(instance.cast('1, 2, 3, 4, 5, 6')).to be_eql(7)
72
+
73
+ expect(config_class).to receive(:new).with(1.0, 2.0, 3.0, 4.0).and_return(1)
74
+ expect(instance.cast('1.0, 2.0, 3.0, 4.0')).to be_eql(1)
75
+
76
+ expect { instance.cast(['6 6 6']) }.to raise_error(RuntimeError, 'Invalid format')
77
+ end
78
+
79
+ it 'accepts hash values' do
80
+ expect(instance.cast({})).to be_nil
81
+
82
+ expect { instance.cast({ 'a' => 1, 'b' => 2 }) }.to raise_error(RuntimeError, 'Invalid format')
83
+
84
+ expect(config_class).to receive(:new).with(1, 2, 3, 4).and_return(4)
85
+ expect(instance.cast({ 'a' => 1, 'b' => 2 , 'c' => 3, 'd' => 4})).to be_eql(4)
86
+
87
+ expect(config_class).to receive(:new).with(1.0, 2.0, 3.0, 4.0).and_return(5)
88
+ expect(instance.cast({ 'a' => 1.0, 'b' => 2.0, 'c' => 3.0, 'd' => 4.0})).to be_eql(5)
89
+
90
+ expect(config_class).to receive(:new).with(1, 2, 3, 4).and_return(2)
91
+ expect(instance.cast({ a: 1, b: 2 , c: 3, d: 4, e: 5, f: 6})).to be_eql(2)
92
+ end
93
+
94
+ it 'accepts array values' do
95
+ expect(config_class).to receive(:new).with(1, 2, 3, 4).and_return(4)
96
+ expect(instance.cast([1, 2, 3, 4])).to be_eql(4)
97
+
98
+ expect(config_class).to receive(:new).with(1.1, 1.2, 1.3, 1.4).and_return(9)
99
+ expect(instance.cast(['1.1', '1.2', '1.3', '1.4'])).to be_eql(9)
100
+
101
+ expect(config_class).to receive(:new).with(6, 5, 4, 3).and_return(2)
102
+ expect(instance.cast([6, 5, 4, 3, 2, 1])).to be_eql(2)
103
+
104
+ expect(instance.cast([])).to be_nil
105
+
106
+ expect { instance.cast([6, 5, 4]) }.to raise_error(RuntimeError, 'Invalid format')
107
+ end
108
+ end
109
+
110
+ context '#serialize' do
111
+ before { allow(instance).to receive(:config_class).and_return(OpenStruct) }
112
+
113
+ it 'return value nil' do
114
+ expect(instance.serialize(nil)).to be_nil
115
+ end
116
+
117
+ it 'accepts config class' do
118
+ expect(instance.serialize(OpenStruct.new)).to be_nil
119
+ expect(instance.serialize(OpenStruct.new(a: 1, b: 2, c: 3, d: 4))).to be_eql('(1, 2, <3, {4}>)')
120
+ expect(instance.serialize(OpenStruct.new(a: 1, b: 2, c: 3, d: 4, e: 5))).to be_eql('(1, 2, <3, {4}>)')
121
+ end
122
+
123
+ it 'accepts hash value' do
124
+ expect { instance.cast({a: 1, b: 2, c: 3}) }.to raise_error(RuntimeError, 'Invalid format')
125
+ expect(instance.serialize({a: 1, b: 2, c: 3, d: 4})).to be_eql('(1, 2, <3, {4}>)')
126
+ expect(instance.serialize({a: 1, b: 2, c: 3, d: 4, e: 5, f: 6})).to be_eql('(1, 2, <3, {4}>)')
127
+ end
128
+
129
+ it 'accepts array value' do
130
+ expect { instance.serialize([6, 5, 4]) }.to raise_error(RuntimeError, 'Invalid format')
131
+ expect(instance.serialize([1, 2, 3, 4])).to be_eql('(1, 2, <3, {4}>)')
132
+ expect(instance.serialize([5, 4, 3, 2, 1, 0])).to be_eql('(5, 4, <3, {2}>)')
133
+ end
134
+
135
+ end
136
+
137
+ context '#deserialize' do
138
+ let(:config_class) { double }
139
+
140
+ before { allow(instance).to receive(:config_class).and_return(config_class) }
141
+
142
+ it 'return value nil' do
143
+ expect(instance.deserialize(nil)).to be_nil
144
+ end
145
+
146
+ it 'accept correct format' do
147
+ expect(config_class).to receive(:new).with(1, 2, 3, 4).and_return(6)
148
+ expect(instance.deserialize('(1, 2, <3, {4}>)')).to be_eql(6)
149
+ end
150
+ end
151
+
152
+ context '#type_cast_for_schema' do
153
+ before { allow(instance).to receive(:config_class).and_return(OpenStruct) }
154
+
155
+ it 'returns the array for schema' do
156
+ result = instance.type_cast_for_schema(OpenStruct.new(a: 1, b: 2, c: 3, d: 4))
157
+ expect(result).to be_eql([1, 2, 3, 4])
158
+ end
159
+ end
160
+ end
161
+
162
+ context 'on box' do
163
+ let(:klass) { Torque::PostgreSQL::Adapter::OID::Box }
164
+ let(:value_klass) { Torque::PostgreSQL::Box }
165
+ let(:instance) { klass.new }
166
+ let(:value_instance) { instance.cast([1, 2, 3, 4]) }
167
+
168
+ before { allow(instance).to receive(:config_class).and_return(value_klass) }
169
+
170
+ it '#points' do
171
+ mock_klass = Struct.new(:a, :b)
172
+ Torque::PostgreSQL.config.geometry.point_class = mock_klass
173
+
174
+ result = value_instance.points
175
+ expect(result).to be_a(Array)
176
+ expect(result.size).to be_eql(4)
177
+ expect(result).to all(be_a(mock_klass))
178
+
179
+ expect(result[0].a).to be_eql(1.0)
180
+ expect(result[0].b).to be_eql(2.0)
181
+ expect(result[1].a).to be_eql(1.0)
182
+ expect(result[1].b).to be_eql(4.0)
183
+ expect(result[2].a).to be_eql(3.0)
184
+ expect(result[2].b).to be_eql(2.0)
185
+ expect(result[3].a).to be_eql(3.0)
186
+ expect(result[3].b).to be_eql(4.0)
187
+ end
188
+ end
189
+
190
+ context 'on circle' do
191
+ let(:klass) { Torque::PostgreSQL::Adapter::OID::Circle }
192
+ let(:value_klass) { Torque::PostgreSQL::Circle }
193
+ let(:instance) { klass.new }
194
+ let(:value_instance) { instance.cast([1, 2, 3]) }
195
+
196
+ before { allow(instance).to receive(:config_class).and_return(value_klass) }
197
+
198
+ it '#center' do
199
+ mock_klass = Struct.new(:a, :b)
200
+ Torque::PostgreSQL.config.geometry.point_class = mock_klass
201
+
202
+ result = value_instance.center
203
+ expect(result).to be_a(mock_klass)
204
+ expect(result.a).to be_eql(1.0)
205
+ expect(result.b).to be_eql(2.0)
206
+ end
207
+
208
+ it '#center=' do
209
+ mock_klass = Struct.new(:x, :y)
210
+ Torque::PostgreSQL.config.geometry.point_class = mock_klass
211
+
212
+ value_instance.center = [1, 2]
213
+ expect(value_instance.x).to be_eql(1)
214
+ expect(value_instance.y).to be_eql(2)
215
+
216
+ value_instance.center = mock_klass.new(3, 4)
217
+ expect(value_instance.x).to be_eql(3)
218
+ expect(value_instance.y).to be_eql(4)
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,390 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe 'HasMany' do
4
+ context 'on builder' do
5
+ let(:builder) { ActiveRecord::Associations::Builder::HasMany }
6
+
7
+ it 'adds the array option' do
8
+ expect(builder.send(:valid_options, [])).to include(:array)
9
+ end
10
+ end
11
+
12
+ context 'on original' do
13
+ let(:other) { Text }
14
+
15
+ before { User.has_many :texts }
16
+ subject { User.create(name: 'User 1') }
17
+ after { User._reflections = {} }
18
+
19
+ it 'has the method' do
20
+ expect(subject).to respond_to(:texts)
21
+ expect(subject._reflections).to include('texts')
22
+ end
23
+
24
+ it 'loads associated records' do
25
+ expect(subject.texts.to_sql).to match(Regexp.new(<<-SQL.squish))
26
+ SELECT "texts"\\.\\* FROM "texts" WHERE \\(?"texts"\\."user_id" = #{subject.id}\\)?
27
+ SQL
28
+
29
+ expect(subject.texts.load).to be_a(ActiveRecord::Associations::CollectionProxy)
30
+ expect(subject.texts.to_a).to be_eql([])
31
+ end
32
+
33
+ it 'can be marked as loaded' do
34
+ expect(subject.texts.loaded?).to be_eql(false)
35
+ expect(subject.texts).to respond_to(:load_target)
36
+ expect(subject.texts.load_target).to be_eql([])
37
+ expect(subject.texts.loaded?).to be_eql(true)
38
+ end
39
+
40
+ it 'can find specific records' do
41
+ records = FactoryBot.create_list(:text, 10, user_id: subject.id)
42
+ ids = records.map(&:id).sample(5)
43
+
44
+ expect(subject.texts).to respond_to(:find)
45
+ records = subject.texts.find(*ids)
46
+
47
+ expect(records.size).to be_eql(5)
48
+ expect(records.map(&:id).sort).to be_eql(ids.sort)
49
+ end
50
+
51
+ it 'can return last n records' do
52
+ records = FactoryBot.create_list(:text, 10, user_id: subject.id)
53
+ ids = records.map(&:id).last(5)
54
+
55
+ expect(subject.texts).to respond_to(:last)
56
+ records = subject.texts.last(5)
57
+
58
+ expect(records.size).to be_eql(5)
59
+ expect(records.map(&:id).sort).to be_eql(ids.sort)
60
+ end
61
+
62
+ it 'can return first n records' do
63
+ records = FactoryBot.create_list(:text, 10, user_id: subject.id)
64
+ ids = records.map(&:id).first(5)
65
+
66
+ expect(subject.texts).to respond_to(:take)
67
+ records = subject.texts.take(5)
68
+
69
+ expect(records.size).to be_eql(5)
70
+ expect(records.map(&:id).sort).to be_eql(ids.sort)
71
+ end
72
+
73
+ it 'can build an associated record' do
74
+ record = subject.texts.build(content: 'Test')
75
+ expect(record).to be_a(other)
76
+ expect(record).not_to be_persisted
77
+ expect(record.content).to be_eql('Test')
78
+ expect(record.user_id).to be_eql(subject.id)
79
+
80
+ expect(subject.save).to be_truthy
81
+ expect(subject.texts.size).to be_eql(1)
82
+ end
83
+
84
+ it 'can create an associated record' do
85
+ record = subject.texts.create(content: 'Test')
86
+ expect(subject.texts).to respond_to(:create!)
87
+
88
+ expect(record).to be_a(other)
89
+ expect(record).to be_persisted
90
+ expect(record.content).to be_eql('Test')
91
+ expect(record.user_id).to be_eql(subject.id)
92
+ end
93
+
94
+ it 'can concat records' do
95
+ FactoryBot.create(:text, user_id: subject.id)
96
+ expect(subject.texts.size).to be_eql(1)
97
+
98
+ subject.texts.concat(other.new(content: 'Test'))
99
+ expect(subject.texts.size).to be_eql(2)
100
+ expect(subject.texts.last.content).to be_eql('Test')
101
+ end
102
+
103
+ it 'can replace records' do
104
+ FactoryBot.create(:text, user_id: subject.id)
105
+ expect(subject.texts.size).to be_eql(1)
106
+
107
+ subject.texts.replace([other.new(content: 'Test 1'), other.new(content: 'Test 2')])
108
+ expect(subject.texts.size).to be_eql(2)
109
+ expect(subject.texts[0].content).to be_eql('Test 1')
110
+ expect(subject.texts[1].content).to be_eql('Test 2')
111
+ end
112
+
113
+ it 'can delete all records' do
114
+ FactoryBot.create_list(:text, 5, user_id: subject.id)
115
+ expect(subject.texts.size).to be_eql(5)
116
+
117
+ subject.texts.delete_all
118
+ expect(subject.texts.size).to be_eql(0)
119
+ end
120
+
121
+ it 'can destroy all records' do
122
+ FactoryBot.create_list(:text, 5, user_id: subject.id)
123
+ expect(subject.texts.size).to be_eql(5)
124
+
125
+ subject.texts.destroy_all
126
+ expect(subject.texts.size).to be_eql(0)
127
+ end
128
+
129
+ it 'can have sum operations' do
130
+ result = FactoryBot.create_list(:text, 5, user_id: subject.id).map(&:id).reduce(:+)
131
+ expect(subject.texts).to respond_to(:sum)
132
+ expect(subject.texts.sum(:id)).to be_eql(result)
133
+ end
134
+
135
+ it 'can have a pluck operation' do
136
+ result = FactoryBot.create_list(:text, 5, user_id: subject.id).map(&:content).sort
137
+ expect(subject.texts).to respond_to(:pluck)
138
+ expect(subject.texts.pluck(:content).sort).to be_eql(result)
139
+ end
140
+
141
+ it 'can be markes as empty' do
142
+ expect(subject.texts).to respond_to(:empty?)
143
+ expect(subject.texts.empty?).to be_truthy
144
+
145
+ FactoryBot.create(:text, user_id: subject.id)
146
+ expect(subject.texts.empty?).to be_falsey
147
+ end
148
+
149
+ it 'can check if a record is included on the list' do
150
+ inside = FactoryBot.create(:text, user_id: subject.id)
151
+ outside = FactoryBot.create(:text)
152
+
153
+ expect(subject.texts).to respond_to(:include?)
154
+ expect(subject.texts.include?(inside)).to be_truthy
155
+ expect(subject.texts.include?(outside)).to be_falsey
156
+ end
157
+
158
+ it 'can append records' do
159
+ FactoryBot.create(:text, user_id: subject.id)
160
+ expect(subject.texts.size).to be_eql(1)
161
+
162
+ subject.texts << other.new(content: 'Test')
163
+ expect(subject.texts.size).to be_eql(2)
164
+ expect(subject.texts.last.content).to be_eql('Test')
165
+ end
166
+
167
+ it 'can clear records' do
168
+ FactoryBot.create(:text, user_id: subject.id)
169
+ expect(subject.texts.size).to be_eql(1)
170
+
171
+ subject.texts.clear
172
+ expect(subject.texts.size).to be_eql(0)
173
+ end
174
+
175
+ it 'can reload records' do
176
+ expect(subject.texts.size).to be_eql(0)
177
+ FactoryBot.create(:text, user_id: subject.id)
178
+
179
+ expect(subject.texts.size).to be_eql(0)
180
+
181
+ subject.texts.reload
182
+ expect(subject.texts.size).to be_eql(1)
183
+ end
184
+
185
+ it 'can preload records' do
186
+ FactoryBot.create_list(:text, 5, user_id: subject.id)
187
+ entries = User.all.includes(:texts).load
188
+
189
+ expect(entries.size).to be_eql(1)
190
+ expect(entries.first.texts).to be_loaded
191
+ expect(entries.first.texts.size).to be_eql(5)
192
+ end
193
+
194
+ it 'can joins records' do
195
+ query = User.all.joins(:texts)
196
+ expect(query.to_sql).to match(/INNER JOIN "texts"/)
197
+ expect { query.load }.not_to raise_error
198
+ end
199
+ end
200
+
201
+ context 'on array' do
202
+ let(:other) { Video }
203
+
204
+ before { Tag.has_many :videos, array: true }
205
+ subject { Tag.create(name: 'A') }
206
+ after { Tag._reflections = {} }
207
+
208
+ it 'has the method' do
209
+ expect(subject).to respond_to(:videos)
210
+ expect(subject._reflections).to include('videos')
211
+ end
212
+
213
+ it 'loads associated records' do
214
+ expect(subject.videos.to_sql).to match(Regexp.new(<<-SQL.squish))
215
+ SELECT "videos"\\.\\* FROM "videos"
216
+ WHERE \\(?"videos"\\."tag_ids" && ARRAY\\[#{subject.id}\\]::bigint\\[\\]\\)?
217
+ SQL
218
+
219
+ expect(subject.videos.load).to be_a(ActiveRecord::Associations::CollectionProxy)
220
+ expect(subject.videos.to_a).to be_eql([])
221
+ end
222
+
223
+ it 'can be marked as loaded' do
224
+ expect(subject.videos.loaded?).to be_eql(false)
225
+ expect(subject.videos).to respond_to(:load_target)
226
+ expect(subject.videos.load_target).to be_eql([])
227
+ expect(subject.videos.loaded?).to be_eql(true)
228
+ end
229
+
230
+ it 'can find specific records' do
231
+ records = FactoryBot.create_list(:video, 10, tag_ids: [subject.id])
232
+ ids = records.map(&:id).sample(5)
233
+
234
+ expect(subject.videos).to respond_to(:find)
235
+ records = subject.videos.find(*ids)
236
+
237
+ expect(records.size).to be_eql(5)
238
+ expect(records.map(&:id).sort).to be_eql(ids.sort)
239
+ end
240
+
241
+ it 'can return last n records' do
242
+ records = FactoryBot.create_list(:video, 10, tag_ids: [subject.id])
243
+ ids = records.map(&:id).last(5)
244
+
245
+ expect(subject.videos).to respond_to(:last)
246
+ records = subject.videos.last(5)
247
+
248
+ expect(records.size).to be_eql(5)
249
+ expect(records.map(&:id).sort).to be_eql(ids.sort)
250
+ end
251
+
252
+ it 'can return first n records' do
253
+ records = FactoryBot.create_list(:video, 10, tag_ids: [subject.id])
254
+ ids = records.map(&:id).first(5)
255
+
256
+ expect(subject.videos).to respond_to(:take)
257
+ records = subject.videos.take(5)
258
+
259
+ expect(records.size).to be_eql(5)
260
+ expect(records.map(&:id).sort).to be_eql(ids.sort)
261
+ end
262
+
263
+ it 'can build an associated record' do
264
+ record = subject.videos.build(title: 'Test')
265
+ expect(record).to be_a(other)
266
+ expect(record).not_to be_persisted
267
+ expect(record.title).to be_eql('Test')
268
+
269
+ expect(subject.save).to be_truthy
270
+ expect(record.tag_ids).to be_eql([subject.id])
271
+ expect(subject.videos.size).to be_eql(1)
272
+ end
273
+
274
+ it 'can create an associated record' do
275
+ record = subject.videos.create(title: 'Test')
276
+ expect(subject.videos).to respond_to(:create!)
277
+
278
+ expect(record).to be_a(other)
279
+ expect(record).to be_persisted
280
+ expect(record.title).to be_eql('Test')
281
+ expect(record.tag_ids).to be_eql([subject.id])
282
+ end
283
+
284
+ it 'can concat records' do
285
+ FactoryBot.create(:video, tag_ids: [subject.id])
286
+ expect(subject.videos.size).to be_eql(1)
287
+
288
+ subject.videos.concat(other.new(title: 'Test'))
289
+ expect(subject.videos.size).to be_eql(2)
290
+ expect(subject.videos.last.title).to be_eql('Test')
291
+ end
292
+
293
+ it 'can replace records' do
294
+ FactoryBot.create(:video, tag_ids: [subject.id])
295
+ expect(subject.videos.size).to be_eql(1)
296
+
297
+ subject.videos.replace([other.new(title: 'Test 1'), other.new(title: 'Test 2')])
298
+ expect(subject.videos.size).to be_eql(2)
299
+ expect(subject.videos[0].title).to be_eql('Test 1')
300
+ expect(subject.videos[1].title).to be_eql('Test 2')
301
+ end
302
+
303
+ it 'can delete all records' do
304
+ FactoryBot.create_list(:video, 5, tag_ids: [subject.id])
305
+ expect(subject.videos.size).to be_eql(5)
306
+
307
+ subject.videos.delete_all
308
+ expect(subject.videos.size).to be_eql(0)
309
+ end
310
+
311
+ it 'can destroy all records' do
312
+ FactoryBot.create_list(:video, 5, tag_ids: [subject.id])
313
+ expect(subject.videos.size).to be_eql(5)
314
+
315
+ subject.videos.destroy_all
316
+ expect(subject.videos.size).to be_eql(0)
317
+ end
318
+
319
+ it 'can have sum operations' do
320
+ result = FactoryBot.create_list(:video, 5, tag_ids: [subject.id]).map(&:id).reduce(:+)
321
+ expect(subject.videos).to respond_to(:sum)
322
+ expect(subject.videos.sum(:id)).to be_eql(result)
323
+ end
324
+
325
+ it 'can have a pluck operation' do
326
+ result = FactoryBot.create_list(:video, 5, tag_ids: [subject.id]).map(&:title).sort
327
+ expect(subject.videos).to respond_to(:pluck)
328
+ expect(subject.videos.pluck(:title).sort).to be_eql(result)
329
+ end
330
+
331
+ it 'can be markes as empty' do
332
+ expect(subject.videos).to respond_to(:empty?)
333
+ expect(subject.videos.empty?).to be_truthy
334
+
335
+ FactoryBot.create(:video, tag_ids: [subject.id])
336
+ expect(subject.videos.empty?).to be_falsey
337
+ end
338
+
339
+ it 'can check if a record is included on the list' do
340
+ inside = FactoryBot.create(:video, tag_ids: [subject.id])
341
+ outside = FactoryBot.create(:video)
342
+
343
+ expect(subject.videos).to respond_to(:include?)
344
+ expect(subject.videos.include?(inside)).to be_truthy
345
+ expect(subject.videos.include?(outside)).to be_falsey
346
+ end
347
+
348
+ it 'can append records' do
349
+ FactoryBot.create(:video, tag_ids: [subject.id])
350
+ expect(subject.videos.size).to be_eql(1)
351
+
352
+ subject.videos << other.new(title: 'Test')
353
+ expect(subject.videos.size).to be_eql(2)
354
+ expect(subject.videos.last.title).to be_eql('Test')
355
+ end
356
+
357
+ it 'can clear records' do
358
+ FactoryBot.create(:video, tag_ids: [subject.id])
359
+ expect(subject.videos.size).to be_eql(1)
360
+
361
+ subject.videos.clear
362
+ expect(subject.videos.size).to be_eql(0)
363
+ end
364
+
365
+ it 'can reload records' do
366
+ expect(subject.videos.size).to be_eql(0)
367
+ FactoryBot.create(:video, tag_ids: [subject.id])
368
+
369
+ expect(subject.videos.size).to be_eql(0)
370
+
371
+ subject.videos.reload
372
+ expect(subject.videos.size).to be_eql(1)
373
+ end
374
+
375
+ it 'can preload records' do
376
+ FactoryBot.create_list(:video, 5, tag_ids: [subject.id])
377
+ entries = Tag.all.includes(:videos).load
378
+
379
+ expect(entries.size).to be_eql(1)
380
+ expect(entries.first.videos).to be_loaded
381
+ expect(entries.first.videos.size).to be_eql(5)
382
+ end
383
+
384
+ it 'can joins records' do
385
+ query = Tag.all.joins(:videos)
386
+ expect(query.to_sql).to match(/INNER JOIN "videos"/)
387
+ expect { query.load }.not_to raise_error
388
+ end
389
+ end
390
+ end