torque-postgresql 1.1.4 → 2.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/lib/torque/postgresql.rb +0 -2
- data/lib/torque/postgresql/adapter.rb +0 -1
- data/lib/torque/postgresql/adapter/database_statements.rb +4 -15
- data/lib/torque/postgresql/adapter/schema_creation.rb +13 -23
- data/lib/torque/postgresql/adapter/schema_definitions.rb +7 -21
- data/lib/torque/postgresql/adapter/schema_dumper.rb +74 -11
- data/lib/torque/postgresql/adapter/schema_statements.rb +2 -12
- data/lib/torque/postgresql/associations.rb +0 -3
- data/lib/torque/postgresql/associations/association_scope.rb +18 -60
- data/lib/torque/postgresql/associations/belongs_to_many_association.rb +2 -1
- data/lib/torque/postgresql/associations/preloader.rb +0 -24
- data/lib/torque/postgresql/associations/preloader/association.rb +13 -9
- data/lib/torque/postgresql/auxiliary_statement.rb +1 -13
- data/lib/torque/postgresql/coder.rb +1 -2
- data/lib/torque/postgresql/config.rb +0 -4
- data/lib/torque/postgresql/inheritance.rb +13 -17
- data/lib/torque/postgresql/reflection/abstract_reflection.rb +19 -25
- data/lib/torque/postgresql/relation.rb +11 -16
- data/lib/torque/postgresql/relation/auxiliary_statement.rb +2 -8
- data/lib/torque/postgresql/relation/distinct_on.rb +1 -1
- data/lib/torque/postgresql/version.rb +1 -1
- data/spec/en.yml +19 -0
- data/spec/factories/authors.rb +6 -0
- data/spec/factories/comments.rb +13 -0
- data/spec/factories/posts.rb +6 -0
- data/spec/factories/tags.rb +5 -0
- data/spec/factories/texts.rb +5 -0
- data/spec/factories/users.rb +6 -0
- data/spec/factories/videos.rb +5 -0
- data/spec/mocks/cache_query.rb +16 -0
- data/spec/mocks/create_table.rb +35 -0
- data/spec/models/activity.rb +3 -0
- data/spec/models/activity_book.rb +4 -0
- data/spec/models/activity_post.rb +7 -0
- data/spec/models/activity_post/sample.rb +4 -0
- data/spec/models/author.rb +4 -0
- data/spec/models/author_journalist.rb +4 -0
- data/spec/models/comment.rb +3 -0
- data/spec/models/course.rb +2 -0
- data/spec/models/geometry.rb +2 -0
- data/spec/models/guest_comment.rb +4 -0
- data/spec/models/post.rb +6 -0
- data/spec/models/tag.rb +2 -0
- data/spec/models/text.rb +2 -0
- data/spec/models/time_keeper.rb +2 -0
- data/spec/models/user.rb +8 -0
- data/spec/models/video.rb +2 -0
- data/spec/schema.rb +141 -0
- data/spec/spec_helper.rb +59 -0
- data/spec/tests/arel_spec.rb +72 -0
- data/spec/tests/auxiliary_statement_spec.rb +593 -0
- data/spec/tests/belongs_to_many_spec.rb +240 -0
- data/spec/tests/coder_spec.rb +367 -0
- data/spec/tests/collector_spec.rb +59 -0
- data/spec/tests/distinct_on_spec.rb +65 -0
- data/spec/tests/enum_set_spec.rb +306 -0
- data/spec/tests/enum_spec.rb +628 -0
- data/spec/tests/geometric_builder_spec.rb +221 -0
- data/spec/tests/has_many_spec.rb +390 -0
- data/spec/tests/interval_spec.rb +167 -0
- data/spec/tests/lazy_spec.rb +24 -0
- data/spec/tests/period_spec.rb +954 -0
- data/spec/tests/quoting_spec.rb +24 -0
- data/spec/tests/range_spec.rb +36 -0
- data/spec/tests/relation_spec.rb +57 -0
- data/spec/tests/table_inheritance_spec.rb +416 -0
- metadata +103 -16
- data/lib/torque/postgresql/associations/join_dependency/join_association.rb +0 -15
- data/lib/torque/postgresql/schema_dumper.rb +0 -88
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Data collector', type: :helper do
|
4
|
+
let(:methods_list) { [:foo, :bar] }
|
5
|
+
subject { Torque::PostgreSQL::Collector.new(*methods_list) }
|
6
|
+
|
7
|
+
it 'is a class creator' do
|
8
|
+
expect(subject).to be_a(Class)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'has the requested methods' do
|
12
|
+
instance = subject.new
|
13
|
+
methods_list.each do |name|
|
14
|
+
expect(instance).to respond_to(name)
|
15
|
+
expect(instance).to respond_to("#{name}=")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'instace values starts as nil' do
|
20
|
+
instance = subject.new
|
21
|
+
methods_list.each do |name|
|
22
|
+
expect(instance.send(name)).to be_nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'set values on the same method' do
|
27
|
+
instance = subject.new
|
28
|
+
methods_list.each do |name|
|
29
|
+
expect(instance.send(name, name)).to eql(name)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'get value on the same method' do
|
34
|
+
instance = subject.new
|
35
|
+
methods_list.each do |name|
|
36
|
+
instance.send(name, name)
|
37
|
+
expect(instance.send(name)).to eql(name)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'accepts any kind of value' do
|
42
|
+
instance = subject.new
|
43
|
+
|
44
|
+
instance.foo 123
|
45
|
+
expect(instance.foo).to eql(123)
|
46
|
+
|
47
|
+
instance.foo 'chars'
|
48
|
+
expect(instance.foo).to eql('chars')
|
49
|
+
|
50
|
+
instance.foo :test, :test
|
51
|
+
expect(instance.foo).to eql([:test, :test])
|
52
|
+
|
53
|
+
instance.foo test: :test
|
54
|
+
expect(instance.foo).to eql({test: :test})
|
55
|
+
|
56
|
+
instance.foo nil
|
57
|
+
expect(instance.foo).to be_nil
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'DistinctOn' do
|
4
|
+
|
5
|
+
context 'on relation' do
|
6
|
+
subject { Post.unscoped }
|
7
|
+
|
8
|
+
it 'has its method' do
|
9
|
+
expect(subject).to respond_to(:distinct_on)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'does not mess with original distinct form without select' do
|
13
|
+
expect(subject.distinct.to_sql).to \
|
14
|
+
eql('SELECT DISTINCT "posts".* FROM "posts"')
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'does not mess with original distinct form with select' do
|
18
|
+
expect(subject.select(:name).distinct.to_sql).to \
|
19
|
+
eql('SELECT DISTINCT "name" FROM "posts"')
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'is able to do the basic form' do
|
23
|
+
expect(subject.distinct_on(:title).to_sql).to \
|
24
|
+
eql('SELECT DISTINCT ON ( "posts"."title" ) "posts".* FROM "posts"')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'is able to do with multiple attributes' do
|
28
|
+
expect(subject.distinct_on(:title, :content).to_sql).to \
|
29
|
+
eql('SELECT DISTINCT ON ( "posts"."title", "posts"."content" ) "posts".* FROM "posts"')
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'is able to do with relation' do
|
33
|
+
expect(subject.distinct_on(author: :name).to_sql).to \
|
34
|
+
eql('SELECT DISTINCT ON ( "authors"."name" ) "posts".* FROM "posts"')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'is able to do with relation and multiple attributes' do
|
38
|
+
expect(subject.distinct_on(author: [:name, :age]).to_sql).to \
|
39
|
+
eql('SELECT DISTINCT ON ( "authors"."name", "authors"."age" ) "posts".* FROM "posts"')
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'raises with invalid relation' do
|
43
|
+
expect { subject.distinct_on(tags: :name).to_sql }.to \
|
44
|
+
raise_error(ArgumentError, /Relation for/)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'raises with third level hash' do
|
48
|
+
expect { subject.distinct_on(author: [comments: :body]).to_sql }.to \
|
49
|
+
raise_error(ArgumentError, /on third level/)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'on model' do
|
54
|
+
subject { Post }
|
55
|
+
|
56
|
+
it 'has its method' do
|
57
|
+
expect(subject).to respond_to(:distinct_on)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'returns a relation when using the method' do
|
61
|
+
expect(subject.distinct_on(:title)).to be_a(ActiveRecord::Relation)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,306 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Enum' do
|
4
|
+
let(:connection) { ActiveRecord::Base.connection }
|
5
|
+
let(:attribute_klass) { Torque::PostgreSQL::Attributes::EnumSet }
|
6
|
+
|
7
|
+
def decorate(model, field, options = {})
|
8
|
+
attribute_klass.include_on(model, :enum_set)
|
9
|
+
model.enum_set(field, **options)
|
10
|
+
end
|
11
|
+
|
12
|
+
before :each do
|
13
|
+
Torque::PostgreSQL.config.enum.set_method = :pg_set_enum
|
14
|
+
Torque::PostgreSQL::Attributes::EnumSet.include_on(ActiveRecord::Base)
|
15
|
+
|
16
|
+
# Define a method to find yet to define constants
|
17
|
+
Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:const_missing) do |name|
|
18
|
+
Torque::PostgreSQL::Attributes::EnumSet.lookup(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Define a helper method to get a sample value
|
22
|
+
Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:sample) do |name|
|
23
|
+
Torque::PostgreSQL::Attributes::EnumSet.lookup(name).sample
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'on table definition' do
|
28
|
+
subject { ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition.new('articles') }
|
29
|
+
|
30
|
+
it 'can be defined as an array' do
|
31
|
+
subject.enum(:content_status, array: true)
|
32
|
+
expect(subject['content_status'].name).to be_eql('content_status')
|
33
|
+
expect(subject['content_status'].type).to be_eql(:content_status)
|
34
|
+
|
35
|
+
array = subject['content_status'].respond_to?(:options) \
|
36
|
+
? subject['content_status'].options[:array] \
|
37
|
+
: subject['content_status'].array
|
38
|
+
|
39
|
+
expect(array).to be_eql(true)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'on schema' do
|
44
|
+
it 'can be used on tables' do
|
45
|
+
dump_io = StringIO.new
|
46
|
+
checker = /t\.enum +"conflicts", +array: true, +subtype: :conflicts/
|
47
|
+
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
48
|
+
expect(dump_io.string).to match checker
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'can have a default value as an array of symbols' do
|
52
|
+
dump_io = StringIO.new
|
53
|
+
checker = /t\.enum +"types", +default: \[:A, :B\], +array: true, +subtype: :types/
|
54
|
+
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
55
|
+
expect(dump_io.string).to match checker
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'on value' do
|
60
|
+
subject { Enum::TypesSet }
|
61
|
+
let(:values) { %w(A B C D) }
|
62
|
+
let(:error) { Torque::PostgreSQL::Attributes::EnumSet::EnumSetError }
|
63
|
+
let(:mock_enum) do
|
64
|
+
enum_klass = Class.new(subject::EnumSource.superclass)
|
65
|
+
enum_klass.instance_variable_set(:@values, values << '15')
|
66
|
+
|
67
|
+
klass = Class.new(subject.superclass)
|
68
|
+
klass.const_set('EnumSource', enum_klass)
|
69
|
+
klass
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'class exists' do
|
73
|
+
namespace = Torque::PostgreSQL.config.enum.namespace
|
74
|
+
expect(namespace.const_defined?('TypesSet')).to be_truthy
|
75
|
+
expect(subject.const_defined?('EnumSource')).to be_truthy
|
76
|
+
expect(subject < Torque::PostgreSQL::Attributes::EnumSet).to be_truthy
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'returns the db type name' do
|
80
|
+
expect(subject.type_name).to be_eql('types[]')
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'values match database values' do
|
84
|
+
expect(subject.values).to be_eql(values)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'values can be reach using fetch, as in hash enums' do
|
88
|
+
expect(subject).to respond_to(:fetch)
|
89
|
+
|
90
|
+
value = subject.fetch('A', 'A')
|
91
|
+
expect(value).to be_a(subject)
|
92
|
+
expect(value).to be_eql(subject.A)
|
93
|
+
|
94
|
+
value = subject.fetch('other', 'other')
|
95
|
+
expect(value).to be_nil
|
96
|
+
end
|
97
|
+
|
98
|
+
it 'values can be reach using [], as in hash enums' do
|
99
|
+
expect(subject).to respond_to(:[])
|
100
|
+
|
101
|
+
value = subject['A']
|
102
|
+
expect(value).to be_a(subject)
|
103
|
+
expect(value).to be_eql(subject.A)
|
104
|
+
|
105
|
+
value = subject['other']
|
106
|
+
expect(value).to be_nil
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'accepts respond_to against value' do
|
110
|
+
expect(subject).to respond_to(:A)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'allows fast creation of values' do
|
114
|
+
value = subject.A
|
115
|
+
expect(value).to be_a(subject)
|
116
|
+
end
|
117
|
+
|
118
|
+
it 'keeps blank values as Lazy' do
|
119
|
+
expect(subject.new(nil)).to be_nil
|
120
|
+
expect(subject.new([])).to be_blank
|
121
|
+
end
|
122
|
+
|
123
|
+
it 'can start from nil value using lazy' do
|
124
|
+
lazy = Torque::PostgreSQL::Attributes::Lazy
|
125
|
+
value = subject.new(nil)
|
126
|
+
|
127
|
+
expect(value.__class__).to be_eql(lazy)
|
128
|
+
expect(value.to_s).to be_eql('')
|
129
|
+
expect(value.to_i).to be_nil
|
130
|
+
|
131
|
+
expect(value.A?).to be_falsey
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'accepts values to come from numeric as power' do
|
135
|
+
expect(subject.new(0)).to be_blank
|
136
|
+
expect(subject.new(1)).to be_eql(subject.A)
|
137
|
+
expect(subject.new(3)).to be_eql(subject.A | subject.B)
|
138
|
+
expect { subject.new(16) }.to raise_error(error, /out of bounds/)
|
139
|
+
end
|
140
|
+
|
141
|
+
it 'accepts values to come from numeric list' do
|
142
|
+
expect(subject.new([0])).to be_eql(subject.A)
|
143
|
+
expect(subject.new([0, 1])).to be_eql(subject.A | subject.B)
|
144
|
+
expect { subject.new([4]) }.to raise_error(error.superclass, /out of bounds/)
|
145
|
+
end
|
146
|
+
|
147
|
+
it 'accepts string initialization' do
|
148
|
+
expect(subject.new('A')).to be_eql(subject.A)
|
149
|
+
expect { subject.new('E') }.to raise_error(error.superclass, /not valid for/)
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'allows values bitwise operations' do
|
153
|
+
expect((subject.A | subject.B).to_i).to be_eql(3)
|
154
|
+
expect((subject.A & subject.B).to_i).to be_nil
|
155
|
+
expect(((subject.A | subject.B) & subject.B).to_i).to be_eql(2)
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'allows values comparison' do
|
159
|
+
value = subject.B | subject.C
|
160
|
+
expect(value).to be > subject.A
|
161
|
+
expect(value).to be < subject.D
|
162
|
+
expect(value).to be_eql(6)
|
163
|
+
expect(value).to_not be_eql(1)
|
164
|
+
expect(subject.A == mock_enum.A).to be_falsey
|
165
|
+
end
|
166
|
+
|
167
|
+
it 'accepts value checking' do
|
168
|
+
value = subject.B | subject.C
|
169
|
+
expect(value).to respond_to(:B?)
|
170
|
+
expect(value.B?).to be_truthy
|
171
|
+
expect(value.C?).to be_truthy
|
172
|
+
expect(value.A?).to be_falsey
|
173
|
+
expect(value.D?).to be_falsey
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'accepts replace and bang value' do
|
177
|
+
value = subject.B | subject.C
|
178
|
+
expect(value).to respond_to(:B!)
|
179
|
+
expect(value.A!).to be_eql(7)
|
180
|
+
expect(value.replace(:D)).to be_eql(subject.D)
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'accepts values turn into integer by its power' do
|
184
|
+
expect(subject.B.to_i).to be_eql(2)
|
185
|
+
expect(subject.C.to_i).to be_eql(4)
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'accepts values turn into an array of integer by index' do
|
189
|
+
expect((subject.B | subject.C).map(&:to_i)).to be_eql([1, 2])
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'can return a sample for resting purposes' do
|
193
|
+
expect(subject).to receive(:new).with(Numeric)
|
194
|
+
subject.sample
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
context 'on OID' do
|
199
|
+
let(:enum) { Enum::TypesSet }
|
200
|
+
let(:enum_source) { enum::EnumSource }
|
201
|
+
subject { Torque::PostgreSQL::Adapter::OID::EnumSet.new('types', enum_source) }
|
202
|
+
|
203
|
+
context 'on deserialize' do
|
204
|
+
it 'returns nil' do
|
205
|
+
expect(subject.deserialize(nil)).to be_nil
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'returns enum' do
|
209
|
+
value = subject.deserialize('{B,C}')
|
210
|
+
expect(value).to be_a(enum)
|
211
|
+
expect(value).to be_eql(enum.B | enum.C)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
context 'on serialize' do
|
216
|
+
it 'returns nil' do
|
217
|
+
expect(subject.serialize(nil)).to be_nil
|
218
|
+
expect(subject.serialize(0)).to be_nil
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'returns as string' do
|
222
|
+
expect(subject.serialize(enum.B | enum.C)).to be_eql('{B,C}')
|
223
|
+
expect(subject.serialize(3)).to be_eql('{A,B}')
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
context 'on cast' do
|
228
|
+
it 'accepts nil' do
|
229
|
+
expect(subject.cast(nil)).to be_nil
|
230
|
+
end
|
231
|
+
|
232
|
+
it 'accepts invalid values as nil' do
|
233
|
+
expect(subject.cast([])).to be_nil
|
234
|
+
end
|
235
|
+
|
236
|
+
it 'accepts array of strings' do
|
237
|
+
value = subject.cast(['A'])
|
238
|
+
expect(value).to be_a(enum)
|
239
|
+
expect(value).to be_eql(enum.A)
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'accepts array of numbers' do
|
243
|
+
value = subject.cast([1])
|
244
|
+
expect(value).to be_a(enum)
|
245
|
+
expect(value).to be_eql(enum.B)
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context 'on I18n' do
|
251
|
+
subject { Enum::TypesSet }
|
252
|
+
|
253
|
+
it 'has the text method' do
|
254
|
+
expect(subject.new(0)).to respond_to(:text)
|
255
|
+
end
|
256
|
+
|
257
|
+
it 'brings the correct values' do
|
258
|
+
expect(subject.new(0).text).to be_eql('')
|
259
|
+
expect(subject.new(1).text).to be_eql('A')
|
260
|
+
expect(subject.new(2).text).to be_eql('B')
|
261
|
+
expect(subject.new(3).text).to be_eql('A and B')
|
262
|
+
expect(subject.new(7).text).to be_eql('A, B, and C')
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context 'on model' do
|
267
|
+
before(:each) { decorate(Course, :types) }
|
268
|
+
|
269
|
+
subject { Course }
|
270
|
+
let(:instance) { Course.new }
|
271
|
+
|
272
|
+
it 'has all enum set methods' do
|
273
|
+
expect(subject).to respond_to(:types)
|
274
|
+
expect(subject).to respond_to(:types_keys)
|
275
|
+
expect(subject).to respond_to(:types_texts)
|
276
|
+
expect(subject).to respond_to(:types_options)
|
277
|
+
|
278
|
+
expect(subject).to respond_to(:has_types)
|
279
|
+
expect(subject).to respond_to(:has_any_types)
|
280
|
+
|
281
|
+
expect(instance).to respond_to(:types_text)
|
282
|
+
|
283
|
+
subject.types.each do |value|
|
284
|
+
value = value.underscore
|
285
|
+
expect(subject).to respond_to(value)
|
286
|
+
expect(instance).to respond_to(value + '?')
|
287
|
+
expect(instance).to respond_to(value + '!')
|
288
|
+
end
|
289
|
+
end
|
290
|
+
|
291
|
+
it 'scope the model correctly' do
|
292
|
+
query = subject.a.to_sql
|
293
|
+
expect(query).to match(/"courses"."types" @> ARRAY\['A'\]::types\[\]/)
|
294
|
+
end
|
295
|
+
|
296
|
+
it 'has a match all scope' do
|
297
|
+
query = subject.has_types('B', 'A').to_sql
|
298
|
+
expect(query).to match(/"courses"."types" @> ARRAY\['B', 'A'\]::types\[\]/)
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'has a match any scope' do
|
302
|
+
query = subject.has_any_types('B', 'A').to_sql
|
303
|
+
expect(query).to match(/"courses"."types" && ARRAY\['B', 'A'\]::types\[\]/)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
@@ -0,0 +1,628 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe 'Enum' do
|
4
|
+
let(:connection) { ActiveRecord::Base.connection }
|
5
|
+
let(:attribute_klass) { Torque::PostgreSQL::Attributes::Enum }
|
6
|
+
|
7
|
+
def decorate(model, field, options = {})
|
8
|
+
attribute_klass.include_on(model, :pg_enum)
|
9
|
+
model.pg_enum(field, **options)
|
10
|
+
end
|
11
|
+
|
12
|
+
before :each do
|
13
|
+
Torque::PostgreSQL.config.enum.base_method = :pg_enum
|
14
|
+
Torque::PostgreSQL::Attributes::Enum.include_on(ActiveRecord::Base)
|
15
|
+
|
16
|
+
# Define a method to find yet to define constants
|
17
|
+
Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:const_missing) do |name|
|
18
|
+
Torque::PostgreSQL::Attributes::Enum.lookup(name)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Define a helper method to get a sample value
|
22
|
+
Torque::PostgreSQL.config.enum.namespace.define_singleton_method(:sample) do |name|
|
23
|
+
Torque::PostgreSQL::Attributes::Enum.lookup(name).sample
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'on migration' do
|
28
|
+
it 'can be created' do
|
29
|
+
connection.create_enum(:status, %i(foo bar))
|
30
|
+
expect(connection.type_exists?(:status)).to be_truthy
|
31
|
+
expect(connection.enum_values(:status)).to be_eql(['foo', 'bar'])
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'can be deleted' do
|
35
|
+
connection.create_enum(:status, %i(foo bar))
|
36
|
+
expect(connection.type_exists?(:status)).to be_truthy
|
37
|
+
|
38
|
+
connection.drop_type(:status)
|
39
|
+
expect(connection.type_exists?(:status)).to be_falsey
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'can be renamed' do
|
43
|
+
connection.rename_type(:content_status, :status)
|
44
|
+
expect(connection.type_exists?(:content_status)).to be_falsey
|
45
|
+
expect(connection.type_exists?(:status)).to be_truthy
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'can have prefix' do
|
49
|
+
connection.create_enum(:status, %i(foo bar), prefix: true)
|
50
|
+
expect(connection.enum_values(:status)).to be_eql(['status_foo', 'status_bar'])
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'can have suffix' do
|
54
|
+
connection.create_enum(:status, %i(foo bar), suffix: 'tst')
|
55
|
+
expect(connection.enum_values(:status)).to be_eql(['foo_tst', 'bar_tst'])
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'inserts values at the end' do
|
59
|
+
connection.create_enum(:status, %i(foo bar))
|
60
|
+
connection.add_enum_values(:status, %i(baz qux))
|
61
|
+
expect(connection.enum_values(:status)).to be_eql(['foo', 'bar', 'baz', 'qux'])
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'inserts values in the beginning' do
|
65
|
+
connection.create_enum(:status, %i(foo bar))
|
66
|
+
connection.add_enum_values(:status, %i(baz qux), prepend: true)
|
67
|
+
expect(connection.enum_values(:status)).to be_eql(['baz', 'qux', 'foo', 'bar'])
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'inserts values in the middle' do
|
71
|
+
connection.create_enum(:status, %i(foo bar))
|
72
|
+
connection.add_enum_values(:status, %i(baz), after: 'foo')
|
73
|
+
expect(connection.enum_values(:status)).to be_eql(['foo', 'baz', 'bar'])
|
74
|
+
|
75
|
+
connection.add_enum_values(:status, %i(qux), before: 'bar')
|
76
|
+
expect(connection.enum_values(:status)).to be_eql(['foo', 'baz', 'qux', 'bar'])
|
77
|
+
end
|
78
|
+
|
79
|
+
it 'inserts values with prefix or suffix' do
|
80
|
+
connection.create_enum(:status, %i(foo bar))
|
81
|
+
connection.add_enum_values(:status, %i(baz), prefix: true)
|
82
|
+
connection.add_enum_values(:status, %i(qux), suffix: 'tst')
|
83
|
+
expect(connection.enum_values(:status)).to be_eql(['foo', 'bar', 'status_baz', 'qux_tst'])
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'on table definition' do
|
88
|
+
subject { ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition.new('articles') }
|
89
|
+
|
90
|
+
it 'has the enum method' do
|
91
|
+
expect(subject).to respond_to(:enum)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'can be used in a single form' do
|
95
|
+
subject.enum('content_status')
|
96
|
+
expect(subject['content_status'].name).to be_eql('content_status')
|
97
|
+
expect(subject['content_status'].type).to be_eql(:content_status)
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'can be used in a multiple form' do
|
101
|
+
subject.enum('foo', 'bar', 'baz', subtype: :content_status)
|
102
|
+
expect(subject['foo'].type).to be_eql(:content_status)
|
103
|
+
expect(subject['bar'].type).to be_eql(:content_status)
|
104
|
+
expect(subject['baz'].type).to be_eql(:content_status)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'can have custom type' do
|
108
|
+
subject.enum('foo', subtype: :content_status)
|
109
|
+
expect(subject['foo'].name).to be_eql('foo')
|
110
|
+
expect(subject['foo'].type).to be_eql(:content_status)
|
111
|
+
end
|
112
|
+
|
113
|
+
it 'raises StatementInvalid when type isn\'t defined' do
|
114
|
+
subject.enum('foo')
|
115
|
+
creation = connection.send(:schema_creation).accept subject
|
116
|
+
expect{ connection.execute creation }.to raise_error(ActiveRecord::StatementInvalid)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context 'on schema' do
|
121
|
+
it 'dumps when has it' do
|
122
|
+
dump_io = StringIO.new
|
123
|
+
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
124
|
+
expect(dump_io.string).to match /create_enum \"content_status\", \[/
|
125
|
+
end
|
126
|
+
|
127
|
+
it 'sorts the enum entries to better consistency' do
|
128
|
+
dump_io = StringIO.new
|
129
|
+
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
130
|
+
items = dump_io.string.scan(/create_enum "(\w+)"/).flatten
|
131
|
+
expect(items).to be_eql(items.sort)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'do not dump when has none' do
|
135
|
+
connection.drop_type(:content_status, force: :cascade)
|
136
|
+
|
137
|
+
dump_io = StringIO.new
|
138
|
+
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
139
|
+
expect(dump_io.string).not_to match /create_enum \"content_status\", \[/
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'can be used on tables too' do
|
143
|
+
dump_io = StringIO.new
|
144
|
+
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
145
|
+
expect(dump_io.string).to match /t\.enum +"status", +subtype: :content_status/
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'can have a default value as symbol' do
|
149
|
+
dump_io = StringIO.new
|
150
|
+
ActiveRecord::SchemaDumper.dump(connection, dump_io)
|
151
|
+
expect(dump_io.string).to match /t\.enum +"role", +default: :visitor, +subtype: :roles/
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
context 'on value' do
|
156
|
+
subject { Enum::ContentStatus }
|
157
|
+
let(:values) { %w(created draft published archived) }
|
158
|
+
let(:error) { Torque::PostgreSQL::Attributes::Enum::EnumError }
|
159
|
+
let(:mock_enum) do
|
160
|
+
klass = Class.new(subject.superclass)
|
161
|
+
klass.instance_variable_set(:@values, values << '15')
|
162
|
+
klass
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'class exists' do
|
166
|
+
namespace = Torque::PostgreSQL.config.enum.namespace
|
167
|
+
expect(namespace.const_defined?('ContentStatus')).to be_truthy
|
168
|
+
expect(subject < Torque::PostgreSQL::Attributes::Enum).to be_truthy
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'lazy loads values' do
|
172
|
+
expect(subject.instance_variable_defined?(:@values)).to be_falsey
|
173
|
+
end
|
174
|
+
|
175
|
+
it 'returns the db type name' do
|
176
|
+
expect(subject.type_name).to be_eql('content_status')
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'values match database values' do
|
180
|
+
expect(subject.values).to be_eql(values)
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'can return a sample value' do
|
184
|
+
expect(Enum).to respond_to(:sample)
|
185
|
+
expect(Enum::ContentStatus).to respond_to(:sample)
|
186
|
+
expect(Enum::ContentStatus.sample).to satisfy { |v| values.include?(v) }
|
187
|
+
expect(Enum.sample(:content_status)).to satisfy { |v| values.include?(v) }
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'values can be iterated by using each direct on class' do
|
191
|
+
expect(subject).to respond_to(:each)
|
192
|
+
expect(subject.each).to be_a(Enumerator)
|
193
|
+
expect(subject.each.entries).to be_eql(values)
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'values can be reach using fetch, as in hash enums' do
|
197
|
+
expect(subject).to respond_to(:fetch)
|
198
|
+
|
199
|
+
value = subject.fetch('archived', 'archived')
|
200
|
+
expect(value).to be_a(subject)
|
201
|
+
expect(value).to be_eql(subject.archived)
|
202
|
+
|
203
|
+
value = subject.fetch('other', 'other')
|
204
|
+
expect(value).to be_nil
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'values can be reach using [], as in hash enums' do
|
208
|
+
expect(subject).to respond_to(:[])
|
209
|
+
|
210
|
+
value = subject['archived']
|
211
|
+
expect(value).to be_a(subject)
|
212
|
+
expect(value).to be_eql(subject.archived)
|
213
|
+
|
214
|
+
value = subject['other']
|
215
|
+
expect(value).to be_nil
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'accepts respond_to against value' do
|
219
|
+
expect(subject).to respond_to(:archived)
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'allows fast creation of values' do
|
223
|
+
value = subject.draft
|
224
|
+
expect(value).to be_a(subject)
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'keeps blank values as Lazy' do
|
228
|
+
expect(subject.new(nil)).to be_nil
|
229
|
+
expect(subject.new([])).to be_nil
|
230
|
+
expect(subject.new('')).to be_nil
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'can start from nil value using lazy' do
|
234
|
+
lazy = Torque::PostgreSQL::Attributes::Lazy
|
235
|
+
value = subject.new(nil)
|
236
|
+
|
237
|
+
expect(value.__class__).to be_eql(lazy)
|
238
|
+
expect(value.to_s).to be_eql('')
|
239
|
+
expect(value.to_i).to be_nil
|
240
|
+
|
241
|
+
expect(value.draft?).to be_falsey
|
242
|
+
end
|
243
|
+
|
244
|
+
it 'accepts values to come from numeric' do
|
245
|
+
expect(subject.new(0)).to be_eql(subject.created)
|
246
|
+
expect { subject.new(5) }.to raise_error(error, /out of bounds/)
|
247
|
+
end
|
248
|
+
|
249
|
+
it 'accepts string initialization' do
|
250
|
+
expect(subject.new('created')).to be_eql(subject.created)
|
251
|
+
expect { subject.new('updated') }.to raise_error(error, /not valid for/)
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'allows values comparison' do
|
255
|
+
value = subject.draft
|
256
|
+
expect(value).to be > subject.created
|
257
|
+
expect(value).to be < subject.archived
|
258
|
+
expect(value).to be_eql(subject.draft)
|
259
|
+
expect(value).to_not be_eql(subject.published)
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'allows values comparison with string' do
|
263
|
+
value = subject.draft
|
264
|
+
expect(value).to be > :created
|
265
|
+
expect(value).to be < :archived
|
266
|
+
expect(value).to be_eql(:draft)
|
267
|
+
expect(value).to_not be_eql(:published)
|
268
|
+
end
|
269
|
+
|
270
|
+
it 'allows values comparison with symbol' do
|
271
|
+
value = subject.draft
|
272
|
+
expect(value).to be > 'created'
|
273
|
+
expect(value).to be < 'archived'
|
274
|
+
expect(value).to be_eql('draft')
|
275
|
+
expect(value).to_not be_eql('published')
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'allows values comparison with number' do
|
279
|
+
value = subject.draft
|
280
|
+
expect(value).to be > 0
|
281
|
+
expect(value).to be < 3
|
282
|
+
expect(value).to be_eql(1)
|
283
|
+
expect(value).to_not be_eql(2.5)
|
284
|
+
end
|
285
|
+
|
286
|
+
it 'does not allow cross-enum comparison' do
|
287
|
+
expect { subject.draft < mock_enum.published }.to raise_error(error, /^Comparison/)
|
288
|
+
expect { subject.draft > mock_enum.created }.to raise_error(error, /^Comparison/)
|
289
|
+
end
|
290
|
+
|
291
|
+
it 'does not allow other types comparison' do
|
292
|
+
expect { subject.draft > true }.to raise_error(error, /^Comparison/)
|
293
|
+
expect { subject.draft < [] }.to raise_error(error, /^Comparison/)
|
294
|
+
end
|
295
|
+
|
296
|
+
it 'accepts value checking' do
|
297
|
+
value = subject.draft
|
298
|
+
expect(value).to respond_to(:archived?)
|
299
|
+
expect(value.draft?).to be_truthy
|
300
|
+
expect(value.published?).to be_falsey
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'accepts replace and bang value' do
|
304
|
+
value = subject.draft
|
305
|
+
expect(value).to respond_to(:archived!)
|
306
|
+
expect(value.archived!).to be_eql(subject.archived)
|
307
|
+
expect(value.replace('created')).to be_eql(subject.created)
|
308
|
+
end
|
309
|
+
|
310
|
+
it 'accepts values turn into integer by its index' do
|
311
|
+
mock_value = mock_enum.new('15')
|
312
|
+
expect(subject.created.to_i).to be_eql(0)
|
313
|
+
expect(subject.archived.to_i).to be_eql(3)
|
314
|
+
expect(mock_value.to_i).to_not be_eql(15)
|
315
|
+
expect(mock_value.to_i).to be_eql(4)
|
316
|
+
end
|
317
|
+
|
318
|
+
context 'on members' do
|
319
|
+
it 'has enumerable operations' do
|
320
|
+
expect(subject).to respond_to(:all?)
|
321
|
+
expect(subject).to respond_to(:any?)
|
322
|
+
expect(subject).to respond_to(:collect)
|
323
|
+
expect(subject).to respond_to(:count)
|
324
|
+
expect(subject).to respond_to(:cycle)
|
325
|
+
expect(subject).to respond_to(:detect)
|
326
|
+
expect(subject).to respond_to(:drop)
|
327
|
+
expect(subject).to respond_to(:drop_while)
|
328
|
+
expect(subject).to respond_to(:each)
|
329
|
+
expect(subject).to respond_to(:each_with_index)
|
330
|
+
expect(subject).to respond_to(:entries)
|
331
|
+
expect(subject).to respond_to(:find)
|
332
|
+
expect(subject).to respond_to(:find_all)
|
333
|
+
expect(subject).to respond_to(:find_index)
|
334
|
+
expect(subject).to respond_to(:first)
|
335
|
+
expect(subject).to respond_to(:flat_map)
|
336
|
+
expect(subject).to respond_to(:include?)
|
337
|
+
expect(subject).to respond_to(:inject)
|
338
|
+
expect(subject).to respond_to(:lazy)
|
339
|
+
expect(subject).to respond_to(:map)
|
340
|
+
expect(subject).to respond_to(:member?)
|
341
|
+
expect(subject).to respond_to(:one?)
|
342
|
+
expect(subject).to respond_to(:reduce)
|
343
|
+
expect(subject).to respond_to(:reject)
|
344
|
+
expect(subject).to respond_to(:reverse_each)
|
345
|
+
expect(subject).to respond_to(:select)
|
346
|
+
expect(subject).to respond_to(:sort)
|
347
|
+
expect(subject).to respond_to(:zip)
|
348
|
+
end
|
349
|
+
|
350
|
+
it 'works with map' do
|
351
|
+
result = subject.map(&:to_i)
|
352
|
+
expect(result).to be_eql([0, 1, 2, 3])
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
context 'on OID' do
|
358
|
+
let(:enum) { Enum::ContentStatus }
|
359
|
+
subject { Torque::PostgreSQL::Adapter::OID::Enum.new('content_status') }
|
360
|
+
|
361
|
+
context 'on deserialize' do
|
362
|
+
it 'returns nil' do
|
363
|
+
expect(subject.deserialize(nil)).to be_nil
|
364
|
+
end
|
365
|
+
|
366
|
+
it 'returns enum' do
|
367
|
+
value = subject.deserialize('created')
|
368
|
+
expect(value).to be_a(enum)
|
369
|
+
expect(value).to be_eql(enum.created)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
context 'on serialize' do
|
374
|
+
it 'returns nil' do
|
375
|
+
expect(subject.serialize(nil)).to be_nil
|
376
|
+
expect(subject.serialize('test')).to be_nil
|
377
|
+
expect(subject.serialize(15)).to be_nil
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'returns as string' do
|
381
|
+
expect(subject.serialize(enum.created)).to be_eql('created')
|
382
|
+
expect(subject.serialize(1)).to be_eql('draft')
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
context 'on cast' do
|
387
|
+
it 'accepts nil' do
|
388
|
+
expect(subject.cast(nil)).to be_nil
|
389
|
+
end
|
390
|
+
|
391
|
+
it 'accepts invalid values as nil' do
|
392
|
+
expect(subject.cast(false)).to be_nil
|
393
|
+
expect(subject.cast(true)).to be_nil
|
394
|
+
expect(subject.cast([])).to be_nil
|
395
|
+
end
|
396
|
+
|
397
|
+
it 'accepts string' do
|
398
|
+
value = subject.cast('created')
|
399
|
+
expect(value).to be_a(enum)
|
400
|
+
expect(value).to be_eql(enum.created)
|
401
|
+
end
|
402
|
+
|
403
|
+
it 'accepts numeric' do
|
404
|
+
value = subject.cast(1)
|
405
|
+
expect(value).to be_a(enum)
|
406
|
+
expect(value).to be_eql(enum.draft)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
context 'on I18n' do
|
412
|
+
subject { Enum::ContentStatus }
|
413
|
+
|
414
|
+
it 'has the text method' do
|
415
|
+
expect(subject.new(0)).to respond_to(:text)
|
416
|
+
end
|
417
|
+
|
418
|
+
it 'brings the correct values' do
|
419
|
+
expect(subject.new(0).text).to be_eql('1 - Created')
|
420
|
+
expect(subject.new(1).text).to be_eql('Draft (2)')
|
421
|
+
expect(subject.new(2).text).to be_eql('Finally published')
|
422
|
+
expect(subject.new(3).text).to be_eql('Archived')
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
context 'on model' do
|
427
|
+
before(:each) { decorate(User, :role) }
|
428
|
+
|
429
|
+
subject { User }
|
430
|
+
let(:instance) { FactoryBot.build(:user) }
|
431
|
+
|
432
|
+
it 'has all enum methods' do
|
433
|
+
expect(subject).to respond_to(:roles)
|
434
|
+
expect(subject).to respond_to(:roles_keys)
|
435
|
+
expect(subject).to respond_to(:roles_texts)
|
436
|
+
expect(subject).to respond_to(:roles_options)
|
437
|
+
expect(instance).to respond_to(:role_text)
|
438
|
+
|
439
|
+
subject.roles.each do |value|
|
440
|
+
expect(subject).to respond_to(value)
|
441
|
+
expect(instance).to respond_to(value + '?')
|
442
|
+
expect(instance).to respond_to(value + '!')
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
it 'plural method brings the list of values' do
|
447
|
+
result = subject.roles
|
448
|
+
expect(result).to be_a(Array)
|
449
|
+
expect(result).to be_eql(Enum::Roles.values)
|
450
|
+
end
|
451
|
+
|
452
|
+
it 'text value now uses model and attribute references' do
|
453
|
+
instance.role = :visitor
|
454
|
+
expect(instance.role_text).to be_eql('A simple Visitor')
|
455
|
+
|
456
|
+
instance.role = :assistant
|
457
|
+
expect(instance.role_text).to be_eql('An Assistant')
|
458
|
+
|
459
|
+
instance.role = :manager
|
460
|
+
expect(instance.role_text).to be_eql('The Manager')
|
461
|
+
|
462
|
+
instance.role = :admin
|
463
|
+
expect(instance.role_text).to be_eql('Super Duper Admin')
|
464
|
+
end
|
465
|
+
|
466
|
+
it 'has scopes correctly applied' do
|
467
|
+
subject.roles.each do |value|
|
468
|
+
expect(subject.send(value).to_sql).to match(/WHERE "users"."role" = '#{value}'/)
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
it 'has scopes available on associations' do
|
473
|
+
author = FactoryBot.create(:author)
|
474
|
+
FactoryBot.create(:post, author: author)
|
475
|
+
|
476
|
+
decorate(Post, :status)
|
477
|
+
expect(author.posts).to respond_to(:test_scope)
|
478
|
+
|
479
|
+
Enum::ContentStatus.each do |value|
|
480
|
+
expect(author.posts).to be_a(ActiveRecord::Associations::CollectionProxy)
|
481
|
+
expect(author.posts).to respond_to(value.to_sym)
|
482
|
+
expect(author.posts.send(value).to_sql).to match(/AND "posts"."status" = '#{value}'/)
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
it 'ask methods work' do
|
487
|
+
instance.role = :assistant
|
488
|
+
expect(instance.manager?).to be_falsey
|
489
|
+
expect(instance.assistant?).to be_truthy
|
490
|
+
end
|
491
|
+
|
492
|
+
it 'bang methods work' do
|
493
|
+
instance.admin!
|
494
|
+
expect(instance.persisted?).to be_truthy
|
495
|
+
|
496
|
+
updated_at = instance.updated_at
|
497
|
+
Torque::PostgreSQL.config.enum.save_on_bang = false
|
498
|
+
instance.visitor!
|
499
|
+
Torque::PostgreSQL.config.enum.save_on_bang = true
|
500
|
+
|
501
|
+
expect(instance.role).to be_eql(:visitor)
|
502
|
+
expect(instance.updated_at).to be_eql(updated_at)
|
503
|
+
|
504
|
+
instance.reload
|
505
|
+
expect(instance.role).to be_eql(:admin)
|
506
|
+
end
|
507
|
+
|
508
|
+
it 'raises when starting an enum with conflicting methods' do
|
509
|
+
Torque::PostgreSQL.config.enum.raise_conflicting = true
|
510
|
+
AText = Class.new(ActiveRecord::Base)
|
511
|
+
AText.table_name = 'texts'
|
512
|
+
|
513
|
+
expect { decorate(AText, :conflict) }.to raise_error(ArgumentError, /already exists in/)
|
514
|
+
Torque::PostgreSQL.config.enum.raise_conflicting = false
|
515
|
+
end
|
516
|
+
|
517
|
+
it 'scope the model correctly' do
|
518
|
+
query = subject.manager.to_sql
|
519
|
+
expect(query).to match(/"users"."role" = 'manager'/)
|
520
|
+
end
|
521
|
+
|
522
|
+
context 'on inherited classes' do
|
523
|
+
it 'has all enum methods' do
|
524
|
+
klass = Class.new(User)
|
525
|
+
instance = klass.new
|
526
|
+
|
527
|
+
expect(klass).to respond_to(:roles)
|
528
|
+
expect(klass).to respond_to(:roles_keys)
|
529
|
+
expect(klass).to respond_to(:roles_texts)
|
530
|
+
expect(klass).to respond_to(:roles_options)
|
531
|
+
expect(instance).to respond_to(:role_text)
|
532
|
+
|
533
|
+
klass.roles.each do |value|
|
534
|
+
expect(klass).to respond_to(value)
|
535
|
+
expect(instance).to respond_to(value + '?')
|
536
|
+
expect(instance).to respond_to(value + '!')
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
context 'without autoload' do
|
542
|
+
subject { Author }
|
543
|
+
let(:instance) { FactoryBot.build(:author) }
|
544
|
+
|
545
|
+
it 'has both rails original enum and the new pg_enum' do
|
546
|
+
expect(subject).to respond_to(:enum)
|
547
|
+
expect(subject).to respond_to(:pg_enum)
|
548
|
+
expect(subject.method(:pg_enum).arity).to eql(-1)
|
549
|
+
end
|
550
|
+
|
551
|
+
it 'does not create all methods' do
|
552
|
+
AAuthor = Class.new(ActiveRecord::Base)
|
553
|
+
AAuthor.table_name = 'authors'
|
554
|
+
|
555
|
+
expect(AAuthor).to_not respond_to(:specialties)
|
556
|
+
expect(AAuthor).to_not respond_to(:specialties_keys)
|
557
|
+
expect(AAuthor).to_not respond_to(:specialties_texts)
|
558
|
+
expect(AAuthor).to_not respond_to(:specialties_options)
|
559
|
+
expect(AAuthor.instance_methods).to_not include(:specialty_text)
|
560
|
+
|
561
|
+
Enum::Specialties.values.each do |value|
|
562
|
+
expect(AAuthor).to_not respond_to(value)
|
563
|
+
expect(AAuthor.instance_methods).to_not include(value + '?')
|
564
|
+
expect(AAuthor.instance_methods).to_not include(value + '!')
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
it 'can be manually initiated' do
|
569
|
+
decorate(Author, :specialty)
|
570
|
+
expect(subject).to respond_to(:specialties)
|
571
|
+
expect(subject).to respond_to(:specialties_keys)
|
572
|
+
expect(subject).to respond_to(:specialties_texts)
|
573
|
+
expect(subject).to respond_to(:specialties_options)
|
574
|
+
expect(instance).to respond_to(:specialty_text)
|
575
|
+
|
576
|
+
Enum::Specialties.values.each do |value|
|
577
|
+
expect(subject).to respond_to(value)
|
578
|
+
expect(instance).to respond_to(value + '?')
|
579
|
+
expect(instance).to respond_to(value + '!')
|
580
|
+
end
|
581
|
+
end
|
582
|
+
end
|
583
|
+
|
584
|
+
context 'with prefix' do
|
585
|
+
before(:each) { decorate(Author, :specialty, prefix: 'in') }
|
586
|
+
subject { Author }
|
587
|
+
let(:instance) { FactoryBot.build(:author) }
|
588
|
+
|
589
|
+
it 'creates all methods correctly' do
|
590
|
+
expect(subject).to respond_to(:specialties)
|
591
|
+
expect(subject).to respond_to(:specialties_keys)
|
592
|
+
expect(subject).to respond_to(:specialties_texts)
|
593
|
+
expect(subject).to respond_to(:specialties_options)
|
594
|
+
expect(instance).to respond_to(:specialty_text)
|
595
|
+
|
596
|
+
subject.specialties.each do |value|
|
597
|
+
expect(subject).to respond_to('in_' + value)
|
598
|
+
expect(instance).to respond_to('in_' + value + '?')
|
599
|
+
expect(instance).to respond_to('in_' + value + '!')
|
600
|
+
end
|
601
|
+
end
|
602
|
+
end
|
603
|
+
|
604
|
+
context 'with suffix, only, and except' do
|
605
|
+
before(:each) do
|
606
|
+
decorate(Author, :specialty, suffix: 'expert', only: %w(books movies), except: 'books')
|
607
|
+
end
|
608
|
+
|
609
|
+
subject { Author }
|
610
|
+
let(:instance) { FactoryBot.build(:author) }
|
611
|
+
|
612
|
+
it 'creates only the requested methods' do
|
613
|
+
expect(subject).to respond_to('movies_expert')
|
614
|
+
expect(instance).to respond_to('movies_expert?')
|
615
|
+
expect(instance).to respond_to('movies_expert!')
|
616
|
+
|
617
|
+
expect(subject).to_not respond_to('books_expert')
|
618
|
+
expect(instance).to_not respond_to('books_expert?')
|
619
|
+
expect(instance).to_not respond_to('books_expert!')
|
620
|
+
|
621
|
+
expect(subject).to_not respond_to('plays_expert')
|
622
|
+
expect(instance).to_not respond_to('plays_expert?')
|
623
|
+
expect(instance).to_not respond_to('plays_expert!')
|
624
|
+
|
625
|
+
end
|
626
|
+
end
|
627
|
+
end
|
628
|
+
end
|