torque-postgresql 1.1.1 → 2.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 (72) hide show
  1. checksums.yaml +5 -5
  2. data/Rakefile +5 -2
  3. data/lib/torque/postgresql.rb +0 -2
  4. data/lib/torque/postgresql/adapter.rb +0 -1
  5. data/lib/torque/postgresql/adapter/database_statements.rb +4 -15
  6. data/lib/torque/postgresql/adapter/schema_creation.rb +13 -23
  7. data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -21
  8. data/lib/torque/postgresql/adapter/schema_dumper.rb +71 -11
  9. data/lib/torque/postgresql/adapter/schema_statements.rb +2 -12
  10. data/lib/torque/postgresql/associations.rb +0 -3
  11. data/lib/torque/postgresql/associations/association_scope.rb +18 -61
  12. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +2 -1
  13. data/lib/torque/postgresql/associations/preloader.rb +0 -24
  14. data/lib/torque/postgresql/associations/preloader/association.rb +13 -9
  15. data/lib/torque/postgresql/auxiliary_statement.rb +12 -17
  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/relation.rb +11 -16
  21. data/lib/torque/postgresql/relation/auxiliary_statement.rb +9 -15
  22. data/lib/torque/postgresql/relation/distinct_on.rb +1 -1
  23. data/lib/torque/postgresql/schema_cache.rb +19 -11
  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 +240 -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 +390 -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 +103 -16
  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,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