torque-postgresql 1.1.7 → 2.0.4

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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/lib/torque/postgresql.rb +0 -2
  3. data/lib/torque/postgresql/adapter.rb +7 -1
  4. data/lib/torque/postgresql/adapter/database_statements.rb +6 -15
  5. data/lib/torque/postgresql/adapter/oid.rb +3 -1
  6. data/lib/torque/postgresql/adapter/oid/box.rb +2 -0
  7. data/lib/torque/postgresql/adapter/oid/circle.rb +2 -0
  8. data/lib/torque/postgresql/adapter/oid/enum.rb +2 -0
  9. data/lib/torque/postgresql/adapter/oid/enum_set.rb +2 -0
  10. data/lib/torque/postgresql/adapter/oid/interval.rb +2 -0
  11. data/lib/torque/postgresql/adapter/oid/line.rb +2 -0
  12. data/lib/torque/postgresql/adapter/oid/range.rb +2 -0
  13. data/lib/torque/postgresql/adapter/oid/segment.rb +2 -0
  14. data/lib/torque/postgresql/adapter/quoting.rb +2 -0
  15. data/lib/torque/postgresql/adapter/schema_creation.rb +20 -23
  16. data/lib/torque/postgresql/adapter/schema_definitions.rb +9 -21
  17. data/lib/torque/postgresql/adapter/schema_dumper.rb +76 -11
  18. data/lib/torque/postgresql/adapter/schema_statements.rb +4 -12
  19. data/lib/torque/postgresql/arel/infix_operation.rb +5 -1
  20. data/lib/torque/postgresql/arel/join_source.rb +2 -0
  21. data/lib/torque/postgresql/arel/nodes.rb +2 -0
  22. data/lib/torque/postgresql/arel/operations.rb +2 -0
  23. data/lib/torque/postgresql/arel/select_manager.rb +2 -0
  24. data/lib/torque/postgresql/arel/visitors.rb +6 -3
  25. data/lib/torque/postgresql/associations.rb +0 -3
  26. data/lib/torque/postgresql/associations/association.rb +5 -1
  27. data/lib/torque/postgresql/associations/association_scope.rb +20 -60
  28. data/lib/torque/postgresql/associations/belongs_to_many_association.rb +5 -1
  29. data/lib/torque/postgresql/associations/builder/belongs_to_many.rb +2 -0
  30. data/lib/torque/postgresql/associations/builder/has_many.rb +2 -0
  31. data/lib/torque/postgresql/associations/preloader.rb +0 -32
  32. data/lib/torque/postgresql/associations/preloader/association.rb +42 -10
  33. data/lib/torque/postgresql/attributes/builder.rb +2 -0
  34. data/lib/torque/postgresql/attributes/builder/enum.rb +5 -3
  35. data/lib/torque/postgresql/attributes/builder/period.rb +6 -4
  36. data/lib/torque/postgresql/attributes/enum.rb +5 -10
  37. data/lib/torque/postgresql/attributes/enum_set.rb +2 -0
  38. data/lib/torque/postgresql/attributes/lazy.rb +3 -1
  39. data/lib/torque/postgresql/attributes/period.rb +2 -0
  40. data/lib/torque/postgresql/autosave_association.rb +9 -3
  41. data/lib/torque/postgresql/auxiliary_statement.rb +3 -13
  42. data/lib/torque/postgresql/auxiliary_statement/settings.rb +2 -0
  43. data/lib/torque/postgresql/base.rb +2 -0
  44. data/lib/torque/postgresql/coder.rb +6 -5
  45. data/lib/torque/postgresql/collector.rb +2 -0
  46. data/lib/torque/postgresql/config.rb +3 -4
  47. data/lib/torque/postgresql/geometry_builder.rb +2 -0
  48. data/lib/torque/postgresql/i18n.rb +2 -0
  49. data/lib/torque/postgresql/inheritance.rb +15 -17
  50. data/lib/torque/postgresql/migration/command_recorder.rb +2 -0
  51. data/lib/torque/postgresql/railtie.rb +2 -0
  52. data/lib/torque/postgresql/reflection.rb +2 -0
  53. data/lib/torque/postgresql/reflection/abstract_reflection.rb +28 -26
  54. data/lib/torque/postgresql/reflection/association_reflection.rb +2 -0
  55. data/lib/torque/postgresql/reflection/belongs_to_many_reflection.rb +6 -26
  56. data/lib/torque/postgresql/reflection/has_many_reflection.rb +2 -0
  57. data/lib/torque/postgresql/reflection/runtime_reflection.rb +2 -0
  58. data/lib/torque/postgresql/reflection/through_reflection.rb +2 -0
  59. data/lib/torque/postgresql/relation.rb +10 -11
  60. data/lib/torque/postgresql/relation/auxiliary_statement.rb +7 -8
  61. data/lib/torque/postgresql/relation/distinct_on.rb +3 -1
  62. data/lib/torque/postgresql/relation/inheritance.rb +2 -0
  63. data/lib/torque/postgresql/relation/merger.rb +2 -0
  64. data/lib/torque/postgresql/schema_cache.rb +2 -0
  65. data/lib/torque/postgresql/version.rb +3 -1
  66. data/spec/en.yml +19 -0
  67. data/spec/factories/authors.rb +6 -0
  68. data/spec/factories/comments.rb +13 -0
  69. data/spec/factories/posts.rb +6 -0
  70. data/spec/factories/tags.rb +5 -0
  71. data/spec/factories/texts.rb +5 -0
  72. data/spec/factories/users.rb +6 -0
  73. data/spec/factories/videos.rb +5 -0
  74. data/spec/mocks/cache_query.rb +16 -0
  75. data/spec/mocks/create_table.rb +35 -0
  76. data/spec/models/activity.rb +3 -0
  77. data/spec/models/activity_book.rb +4 -0
  78. data/spec/models/activity_post.rb +7 -0
  79. data/spec/models/activity_post/sample.rb +4 -0
  80. data/spec/models/author.rb +4 -0
  81. data/spec/models/author_journalist.rb +4 -0
  82. data/spec/models/comment.rb +3 -0
  83. data/spec/models/course.rb +2 -0
  84. data/spec/models/geometry.rb +2 -0
  85. data/spec/models/guest_comment.rb +4 -0
  86. data/spec/models/post.rb +6 -0
  87. data/spec/models/tag.rb +2 -0
  88. data/spec/models/text.rb +2 -0
  89. data/spec/models/time_keeper.rb +2 -0
  90. data/spec/models/user.rb +8 -0
  91. data/spec/models/video.rb +2 -0
  92. data/spec/schema.rb +141 -0
  93. data/spec/spec_helper.rb +59 -0
  94. data/spec/tests/arel_spec.rb +74 -0
  95. data/spec/tests/auxiliary_statement_spec.rb +593 -0
  96. data/spec/tests/belongs_to_many_spec.rb +246 -0
  97. data/spec/tests/coder_spec.rb +367 -0
  98. data/spec/tests/collector_spec.rb +59 -0
  99. data/spec/tests/distinct_on_spec.rb +65 -0
  100. data/spec/tests/enum_set_spec.rb +306 -0
  101. data/spec/tests/enum_spec.rb +628 -0
  102. data/spec/tests/geometric_builder_spec.rb +221 -0
  103. data/spec/tests/has_many_spec.rb +400 -0
  104. data/spec/tests/interval_spec.rb +167 -0
  105. data/spec/tests/lazy_spec.rb +24 -0
  106. data/spec/tests/period_spec.rb +954 -0
  107. data/spec/tests/quoting_spec.rb +24 -0
  108. data/spec/tests/range_spec.rb +36 -0
  109. data/spec/tests/relation_spec.rb +57 -0
  110. data/spec/tests/table_inheritance_spec.rb +416 -0
  111. metadata +102 -14
  112. data/lib/torque/postgresql/associations/join_dependency/join_association.rb +0 -15
  113. data/lib/torque/postgresql/schema_dumper.rb +0 -91
@@ -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