torque-postgresql 1.1.5 → 2.0.3

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