enum_table 0.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.
- data/.gitignore +2 -0
- data/CHANGELOG +3 -0
- data/Gemfile +2 -0
- data/LICENSE +20 -0
- data/README.markdown +136 -0
- data/Rakefile +1 -0
- data/enum_table.gemspec +19 -0
- data/lib/enum_table.rb +11 -0
- data/lib/enum_table/railtie.rb +13 -0
- data/lib/enum_table/record.rb +214 -0
- data/lib/enum_table/reflection.rb +52 -0
- data/lib/enum_table/schema_dumper.rb +51 -0
- data/lib/enum_table/schema_statements.rb +86 -0
- data/lib/enum_table/version.rb +11 -0
- data/test/database.yml +19 -0
- data/test/enum_table/test_record.rb +578 -0
- data/test/enum_table/test_reflection.rb +114 -0
- data/test/enum_table/test_schema_dumper.rb +75 -0
- data/test/enum_table/test_schema_statements.rb +131 -0
- data/test/test_helper.rb +53 -0
- metadata +109 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
module EnumTable
|
2
|
+
module SchemaDumper
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
alias_method_chain :tables, :enum_table
|
7
|
+
alias_method_chain :ignore_tables, :enum_table
|
8
|
+
end
|
9
|
+
|
10
|
+
def tables_with_enum_table(stream)
|
11
|
+
tables_without_enum_table(stream)
|
12
|
+
table_names = @connection.enum_tables
|
13
|
+
table_names.each do |table_name|
|
14
|
+
stream.puts " create_enum_table #{table_name.inspect}, force: true do |t|"
|
15
|
+
enum_table_column(stream, table_name, 'id', SchemaStatements::DEFAULT_ID_ATTRIBUTES)
|
16
|
+
enum_table_column(stream, table_name, 'value', SchemaStatements::DEFAULT_VALUE_ATTRIBUTES)
|
17
|
+
@connection.execute("SELECT id, value FROM #{table_name} ORDER BY id").each do |row|
|
18
|
+
stream.puts " t.add #{row[1].to_s.inspect}, #{row[0]}"
|
19
|
+
end
|
20
|
+
stream.puts " end"
|
21
|
+
stream.puts
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def ignore_tables_with_enum_table
|
26
|
+
ignore_tables_without_enum_table + @connection.enum_tables << 'enum_tables'
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def enum_table_column(stream, table_name, column_name, defaults)
|
32
|
+
column = @connection.columns(table_name).find { |c| c.name == column_name }
|
33
|
+
custom_attributes = {}
|
34
|
+
COLUMN_ATTRIBUTES.each do |attribute|
|
35
|
+
value = column.send(attribute)
|
36
|
+
value == defaults[attribute] or
|
37
|
+
custom_attributes[attribute] = value
|
38
|
+
end
|
39
|
+
if custom_attributes.present?
|
40
|
+
formatted = custom_attributes.map { |k, v| "#{k}: #{v.inspect}" }.join(', ')
|
41
|
+
stream.puts " t.#{column_name} #{formatted}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
COLUMN_ATTRIBUTES = [:default, :type, :limit, :null, :precision, :scale]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# This is not loaded in enum_table.rb as it's only needed when dumping the
|
50
|
+
# schema. Instead, we make rake tasks load this file explicitly.
|
51
|
+
ActiveRecord::SchemaDumper.send :include, EnumTable::SchemaDumper
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module EnumTable
|
2
|
+
module SchemaStatements
|
3
|
+
def create_enum_table(table_name, options={})
|
4
|
+
table = NewTable.new(self, table_name, options)
|
5
|
+
yield table if block_given?
|
6
|
+
table._create
|
7
|
+
end
|
8
|
+
|
9
|
+
def change_enum_table(table_name)
|
10
|
+
yield Table.new(self, table_name)
|
11
|
+
end
|
12
|
+
|
13
|
+
def drop_enum_table(table_name)
|
14
|
+
drop_table table_name
|
15
|
+
execute "DELETE FROM enum_tables WHERE table_name = '#{table_name}'"
|
16
|
+
end
|
17
|
+
|
18
|
+
def enum_tables
|
19
|
+
return [] if !table_exists?('enum_tables')
|
20
|
+
execute("SELECT table_name FROM enum_tables").map do |row|
|
21
|
+
row[0]
|
22
|
+
end.sort
|
23
|
+
end
|
24
|
+
|
25
|
+
DEFAULT_ID_ATTRIBUTES = {type: :integer, limit: 1, null: false}.freeze
|
26
|
+
DEFAULT_VALUE_ATTRIBUTES = {type: :string, limit: 255, null: false}.freeze
|
27
|
+
|
28
|
+
class NewTable
|
29
|
+
def initialize(connection, name, options)
|
30
|
+
@connection = connection
|
31
|
+
@name = name
|
32
|
+
@options = options
|
33
|
+
@id = DEFAULT_ID_ATTRIBUTES.dup
|
34
|
+
@value = DEFAULT_VALUE_ATTRIBUTES.dup
|
35
|
+
@adds = []
|
36
|
+
values = options.delete(:values) and
|
37
|
+
values.each { |args| add(*args) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def _create
|
41
|
+
@connection.create_table @name, @options.merge(id: false) do |t|
|
42
|
+
t.column :id, @id.delete(:type), @id
|
43
|
+
t.column :value, @value.delete(:type), @value
|
44
|
+
end
|
45
|
+
unless @connection.table_exists?(:enum_tables)
|
46
|
+
@connection.create_table :enum_tables, id: false, force: true do |t|
|
47
|
+
t.string :table_name, null: false
|
48
|
+
end
|
49
|
+
end
|
50
|
+
@connection.execute "INSERT INTO enum_tables(table_name) VALUES('#{@name}')"
|
51
|
+
table = Table.new(@connection, @name, 0)
|
52
|
+
@adds.each { |args| table.add(*args) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def id(options)
|
56
|
+
@id.update(options)
|
57
|
+
end
|
58
|
+
|
59
|
+
def value(options)
|
60
|
+
@value.update(options)
|
61
|
+
end
|
62
|
+
|
63
|
+
def add(*args)
|
64
|
+
@adds << args
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
class Table
|
69
|
+
def initialize(connection, name, max_id=nil)
|
70
|
+
@connection = connection
|
71
|
+
@name = name
|
72
|
+
@max_id = @connection.execute("SELECT max(id) FROM #{@name}").to_a[0][0] || 0
|
73
|
+
end
|
74
|
+
|
75
|
+
def add(value, id=nil)
|
76
|
+
id ||= @max_id + 1
|
77
|
+
@max_id = id if id > @max_id
|
78
|
+
@connection.execute "INSERT INTO #{@name}(id, value) VALUES(#{id}, '#{value}')"
|
79
|
+
end
|
80
|
+
|
81
|
+
def remove(value)
|
82
|
+
@connection.execute "DELETE FROM #{@name} WHERE value = '#{value}'"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/test/database.yml
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
sqlite3:
|
2
|
+
database: ':memory:'
|
3
|
+
mysql:
|
4
|
+
username: root
|
5
|
+
password:
|
6
|
+
database: db_nazi_test
|
7
|
+
mysql2:
|
8
|
+
username: root
|
9
|
+
password:
|
10
|
+
database: db_nazi_test
|
11
|
+
postgresql:
|
12
|
+
username: postgres
|
13
|
+
password:
|
14
|
+
database: db_nazi_test
|
15
|
+
min_messages: warning
|
16
|
+
jdbcmysql:
|
17
|
+
username: root
|
18
|
+
password:
|
19
|
+
database: db_nazi_test
|
@@ -0,0 +1,578 @@
|
|
1
|
+
require_relative '../test_helper'
|
2
|
+
|
3
|
+
describe EnumTable do
|
4
|
+
use_database
|
5
|
+
|
6
|
+
before do
|
7
|
+
connection.create_table :users do |t|
|
8
|
+
t.integer :gender_id
|
9
|
+
t.integer :status_id
|
10
|
+
t.string :user_type
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
before { Object.const_set :User, Class.new(ActiveRecord::Base) }
|
15
|
+
after { Object.send :remove_const, :User }
|
16
|
+
|
17
|
+
describe '.enum' do
|
18
|
+
before do
|
19
|
+
connection.create_table(:user_genders) { |t| t.string :value }
|
20
|
+
connection.execute "INSERT INTO user_genders(id, value) VALUES (1, 'female'), (2, 'male')"
|
21
|
+
end
|
22
|
+
|
23
|
+
it "defines an enum by a conventionally named table by default" do
|
24
|
+
User.enum :gender
|
25
|
+
User.enums[:gender].value(1).must_equal(:female)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "returns the reflection" do
|
29
|
+
reflection = User.enum :gender
|
30
|
+
reflection.must_be_kind_of EnumTable::Reflection
|
31
|
+
reflection.name.must_equal :gender
|
32
|
+
end
|
33
|
+
|
34
|
+
it "accepts the :table name as a string" do
|
35
|
+
connection.create_table(:custom_table) { |t| t.string :value }
|
36
|
+
connection.execute "INSERT INTO custom_table(id, value) VALUES (1, 'male'), (2, 'female')"
|
37
|
+
User.enum :gender, table: 'custom_table'
|
38
|
+
User.enums[:gender].value(1).must_equal(:male)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "accepts the :table name as a symbol" do
|
42
|
+
connection.create_table(:custom_table) { |t| t.string :value }
|
43
|
+
connection.execute "INSERT INTO custom_table(id, value) VALUES (1, 'male'), (2, 'female')"
|
44
|
+
User.enum :gender, table: :custom_table
|
45
|
+
User.enums[:gender].value(1).must_equal(:male)
|
46
|
+
end
|
47
|
+
|
48
|
+
it "accepts the :table directly as a hash" do
|
49
|
+
User.enum :gender, table: {male: 1, female: 2}
|
50
|
+
User.enums[:gender].value(1).must_equal :male
|
51
|
+
end
|
52
|
+
|
53
|
+
it "accepts the :table as an array" do
|
54
|
+
User.enum :gender, table: [:male, :female]
|
55
|
+
User.enums[:gender].value(1).must_equal :male
|
56
|
+
end
|
57
|
+
|
58
|
+
it "raises an ArgumentError if :table is something else" do
|
59
|
+
->{ User.enum :gender, table: Object.new }.must_raise ArgumentError, /invalid :table specifier/
|
60
|
+
end
|
61
|
+
|
62
|
+
it "passes other options to the Reflection" do
|
63
|
+
User.enum :gender, id_name: :gender_number
|
64
|
+
enum = User.enums[:gender]
|
65
|
+
enum.id_name.must_equal :gender_number
|
66
|
+
end
|
67
|
+
|
68
|
+
describe "on a subclass" do
|
69
|
+
before do
|
70
|
+
User.inheritance_column = :user_type
|
71
|
+
Object.const_set :Subuser, Class.new(User)
|
72
|
+
end
|
73
|
+
|
74
|
+
after do
|
75
|
+
Object.send :remove_const, :Subuser
|
76
|
+
end
|
77
|
+
|
78
|
+
it "makes any values in the superclass available in the subclass" do
|
79
|
+
User.enum :gender, table: {female: 1}
|
80
|
+
Subuser.enum :gender, table: {male: 2}
|
81
|
+
Subuser.reflect_on_enum(:gender).id(:female).must_equal 1
|
82
|
+
end
|
83
|
+
|
84
|
+
it "makes any values added in the subclass not available to the superclass" do
|
85
|
+
User.enum :gender, table: {female: 1}
|
86
|
+
Subuser.enum :gender, table: {male: 2}
|
87
|
+
User.reflect_on_enum(:gender).id(:male).must_be_nil
|
88
|
+
end
|
89
|
+
|
90
|
+
it "inherits options from the superclass that aren't given for the subclass" do
|
91
|
+
User.enum :gender, table: {female: 1, male: 2}, type: :string, id_name: :status_id
|
92
|
+
Subuser.enum :gender
|
93
|
+
reflection = Subuser.reflect_on_enum(:gender)
|
94
|
+
reflection.id(:female).must_equal 1
|
95
|
+
reflection.type.must_equal :string
|
96
|
+
reflection.id_name.must_equal :status_id
|
97
|
+
end
|
98
|
+
|
99
|
+
it "inherits options from the superclass if no enum call is made in the subclass" do
|
100
|
+
User.enum :gender, table: {female: 1, male: 2}
|
101
|
+
Subuser.reflect_on_enum(:gender).id(:female).must_equal 1
|
102
|
+
end
|
103
|
+
|
104
|
+
it "allows overriding the :type option in the subclass" do
|
105
|
+
User.enum :gender, type: :symbol
|
106
|
+
Subuser.enum :gender, type: :string
|
107
|
+
User.reflect_on_enum(:gender).type.must_equal :symbol
|
108
|
+
Subuser.reflect_on_enum(:gender).type.must_equal :string
|
109
|
+
end
|
110
|
+
|
111
|
+
it "allows overriding the :id_name option in the subclass" do
|
112
|
+
User.enum :gender, id_name: :status_id, type: :symbol
|
113
|
+
Subuser.enum :gender, id_name: :status_id, type: :string
|
114
|
+
User.reflect_on_enum(:gender).type.must_equal :symbol
|
115
|
+
Subuser.reflect_on_enum(:gender).type.must_equal :string
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
describe ".reflect_on_enum" do
|
121
|
+
before { User.enum :gender, table: {} }
|
122
|
+
|
123
|
+
it "returns the reflection for the named enum" do
|
124
|
+
reflection = User.reflect_on_enum(:gender)
|
125
|
+
reflection.name.must_equal :gender
|
126
|
+
end
|
127
|
+
|
128
|
+
it "returns nil if there is no such enum" do
|
129
|
+
User.reflect_on_enum(:invalid).must_be_nil
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
describe ".enum_id" do
|
134
|
+
before { User.enum :gender, table: {female: 1} }
|
135
|
+
|
136
|
+
it "raises an ArgumentError if the enum name is invalid" do
|
137
|
+
->{ User.enum_id(:bad) }.must_raise ArgumentError
|
138
|
+
end
|
139
|
+
|
140
|
+
it "returns the id for the given enum value" do
|
141
|
+
User.enum_id(:gender, :female).must_equal 1
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe ".initialize_attributes" do
|
146
|
+
before { User.enum :gender, table: {female: 1, male: 2} }
|
147
|
+
|
148
|
+
it "converts enums to their underlying IDs" do
|
149
|
+
attributes = User.initialize_attributes('gender' => 'female')
|
150
|
+
attributes.must_equal('gender_id' => 1)
|
151
|
+
end
|
152
|
+
|
153
|
+
it "does not prevent optimistic locking from working, which also uses this internal method" do
|
154
|
+
attributes = User.initialize_attributes('lock_version' => nil)
|
155
|
+
attributes.must_equal('lock_version' => 0)
|
156
|
+
end
|
157
|
+
|
158
|
+
it "should favor the ID if both the id and value are present in the attributes hash (so enums override columns)" do
|
159
|
+
attributes = User.initialize_attributes('gender_id' => 1, 'gender' => :male)
|
160
|
+
attributes['gender_id'].must_equal 1
|
161
|
+
attributes.key?('gender').must_equal false
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "#enum_id" do
|
166
|
+
before { User.enum :gender, table: {female: 1} }
|
167
|
+
|
168
|
+
it "raises an ArgumentError if the enum name is invalid" do
|
169
|
+
user = User.new
|
170
|
+
->{ user.enum_id(:bad) }.must_raise ArgumentError
|
171
|
+
end
|
172
|
+
|
173
|
+
it "returns the id for the given enum value" do
|
174
|
+
user = User.new
|
175
|
+
user.enum_id(:gender, :female).must_equal 1
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "#read_enum" do
|
180
|
+
before { User.enum :gender, table: {female: 1, male: 2} }
|
181
|
+
|
182
|
+
it "raises an ArgumentError if the enum name is invalid" do
|
183
|
+
user = User.new
|
184
|
+
->{ user.read_enum(:bad) }.must_raise ArgumentError
|
185
|
+
end
|
186
|
+
|
187
|
+
it "returns the value mapped to the id" do
|
188
|
+
user = User.new(gender_id: 1)
|
189
|
+
user.read_enum(:gender).must_equal :female
|
190
|
+
end
|
191
|
+
|
192
|
+
it "returns nil if the id is not mapped" do
|
193
|
+
user = User.new(gender_id: 3)
|
194
|
+
user.read_enum(:gender).must_be_nil
|
195
|
+
end
|
196
|
+
|
197
|
+
describe "when loading an existing record" do
|
198
|
+
before do
|
199
|
+
connection.execute "INSERT INTO users(id, gender_id) VALUES(1, 1)"
|
200
|
+
end
|
201
|
+
|
202
|
+
it "returns the correct value" do
|
203
|
+
user = User.find(1)
|
204
|
+
user.read_enum(:gender).must_equal :female
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe "#write_enum" do
|
210
|
+
before { User.enum :gender, table: {female: 1, male: 2} }
|
211
|
+
|
212
|
+
it "raises an ArgumentError if the enum name is invalid" do
|
213
|
+
user = User.new
|
214
|
+
->{ user.write_enum(:bad, :female) }.must_raise ArgumentError
|
215
|
+
end
|
216
|
+
|
217
|
+
it "sets the id to the id mapped to the given value" do
|
218
|
+
user = User.new
|
219
|
+
user.write_enum(:gender, :female)
|
220
|
+
user.gender_id.must_equal 1
|
221
|
+
end
|
222
|
+
|
223
|
+
it "sets the id to nil if the given value is not mapped to an id" do
|
224
|
+
user = User.new
|
225
|
+
user.write_enum(:gender, :other)
|
226
|
+
user.gender_id.must_be_nil
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
describe "#read_attribute" do
|
231
|
+
before { User.enum :gender, table: {female: 1, male: 2} }
|
232
|
+
|
233
|
+
it "reads enums" do
|
234
|
+
user = User.new(gender_id: 1)
|
235
|
+
user.read_attribute(:gender).must_equal :female
|
236
|
+
end
|
237
|
+
|
238
|
+
it "allows string names for enums" do
|
239
|
+
user = User.new(gender_id: 1)
|
240
|
+
user.read_attribute('gender').must_equal :female
|
241
|
+
end
|
242
|
+
|
243
|
+
it "still reads attributes" do
|
244
|
+
user = User.new(gender_id: 1)
|
245
|
+
user.read_attribute(:gender_id).must_equal 1
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
describe "#write_attribute" do
|
250
|
+
before { User.enum :gender, table: {female: 1, male: 2} }
|
251
|
+
|
252
|
+
it "writes enums" do
|
253
|
+
user = User.new
|
254
|
+
user.write_attribute(:gender, :female)
|
255
|
+
user.gender_id.must_equal 1
|
256
|
+
end
|
257
|
+
|
258
|
+
it "allows string names for enums" do
|
259
|
+
user = User.new
|
260
|
+
user.write_attribute('gender', :female)
|
261
|
+
user.gender_id.must_equal 1
|
262
|
+
end
|
263
|
+
|
264
|
+
it "still writes attributes" do
|
265
|
+
user = User.new
|
266
|
+
user.write_attribute(:gender_id, 1)
|
267
|
+
user.gender_id.must_equal 1
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
describe "#query_enum" do
|
272
|
+
before { User.enum :gender, table: {female: 1, male: 2} }
|
273
|
+
|
274
|
+
it "raises an ArgumentError if the enum name is invalid" do
|
275
|
+
user = User.new
|
276
|
+
->{ user.query_enum(:bad) }.must_raise ArgumentError
|
277
|
+
end
|
278
|
+
|
279
|
+
it "returns true if the is mapped to a value" do
|
280
|
+
user = User.new(gender_id: 1)
|
281
|
+
user.query_enum(:gender).must_equal true
|
282
|
+
end
|
283
|
+
|
284
|
+
it "returns false if the given value is not mapped to an id" do
|
285
|
+
user = User.new(gender_id: 3)
|
286
|
+
user.query_enum(:gender).must_equal false
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
describe "#enum_changed?" do
|
291
|
+
before do
|
292
|
+
User.enum :gender, table: {female: 1, male: 2}
|
293
|
+
User.create(gender_id: 1)
|
294
|
+
end
|
295
|
+
|
296
|
+
it "raises an ArgumentError if the enum name is invalid" do
|
297
|
+
user = User.first
|
298
|
+
->{ user.enum_changed?(:bad) }.must_raise ArgumentError
|
299
|
+
end
|
300
|
+
|
301
|
+
it "returns true if the enum value changed" do
|
302
|
+
user = User.first
|
303
|
+
user.gender = :male
|
304
|
+
user.enum_changed?(:gender).must_equal true
|
305
|
+
end
|
306
|
+
|
307
|
+
it "returns true if the id attribute changed" do
|
308
|
+
user = User.first
|
309
|
+
user.gender_id = 2
|
310
|
+
user.enum_changed?(:gender).must_equal true
|
311
|
+
end
|
312
|
+
|
313
|
+
it "returns false if the attribute value has not changed" do
|
314
|
+
user = User.first
|
315
|
+
user.enum_changed?(:gender).must_equal false
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
describe "#enum_was" do
|
320
|
+
before do
|
321
|
+
User.enum :gender, table: {female: 1, male: 2}
|
322
|
+
User.create(gender_id: 1)
|
323
|
+
end
|
324
|
+
|
325
|
+
it "raises an ArgumentError if the enum name is invalid" do
|
326
|
+
user = User.first
|
327
|
+
->{ user.enum_was(:bad) }.must_raise ArgumentError
|
328
|
+
end
|
329
|
+
|
330
|
+
it "returns the old value if the enum value changed" do
|
331
|
+
user = User.first
|
332
|
+
user.gender = :male
|
333
|
+
user.enum_was(:gender).must_equal :female
|
334
|
+
end
|
335
|
+
|
336
|
+
it "returns the old value if the id attribute changed" do
|
337
|
+
user = User.first
|
338
|
+
user.gender_id = 2
|
339
|
+
user.enum_was(:gender).must_equal :female
|
340
|
+
end
|
341
|
+
|
342
|
+
it "returns the current value if the enum has not changed" do
|
343
|
+
user = User.first
|
344
|
+
user.enum_was(:gender).must_equal :female
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
describe "#enum_change" do
|
349
|
+
before do
|
350
|
+
User.enum :gender, table: {female: 1, male: 2}
|
351
|
+
User.create(gender_id: 1)
|
352
|
+
end
|
353
|
+
|
354
|
+
it "raises an ArgumentError if the enum name is invalid" do
|
355
|
+
user = User.first
|
356
|
+
->{ user.enum_change(:bad) }.must_raise ArgumentError
|
357
|
+
end
|
358
|
+
|
359
|
+
it "returns the old and new values if the enum value changed" do
|
360
|
+
user = User.first
|
361
|
+
user.gender = :male
|
362
|
+
user.enum_change(:gender).must_equal [:female, :male]
|
363
|
+
end
|
364
|
+
|
365
|
+
it "returns the old and new values if the enum id changed" do
|
366
|
+
user = User.first
|
367
|
+
user.gender_id = 2
|
368
|
+
user.enum_change(:gender).must_equal [:female, :male]
|
369
|
+
end
|
370
|
+
|
371
|
+
it "returns nil if the enum has not changed" do
|
372
|
+
user = User.first
|
373
|
+
user.enum_change(:gender).must_be_nil
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
describe "#ENUM" do
|
378
|
+
before { User.enum :gender, table: {female: 1, male: 2} }
|
379
|
+
|
380
|
+
it "returns the enum value" do
|
381
|
+
user = User.new(gender_id: 1)
|
382
|
+
user.gender.must_equal :female
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
describe "#ENUM=" do
|
387
|
+
before { User.enum :gender, table: {female: 1, male: 2} }
|
388
|
+
|
389
|
+
it "sets the enum value" do
|
390
|
+
user = User.new
|
391
|
+
user.gender = :female
|
392
|
+
user.gender_id.must_equal 1
|
393
|
+
end
|
394
|
+
end
|
395
|
+
|
396
|
+
describe "#ENUM?" do
|
397
|
+
before { User.enum :gender, table: {female: 1, male: 2} }
|
398
|
+
|
399
|
+
it "returns true if the enum value is present" do
|
400
|
+
user = User.new(gender_id: nil)
|
401
|
+
user.gender?.must_equal false
|
402
|
+
|
403
|
+
user = User.new(gender_id: 1)
|
404
|
+
user.gender?.must_equal true
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
describe "#ENUM_changed?" do
|
409
|
+
before do
|
410
|
+
User.enum :gender, table: {female: 1, male: 2}
|
411
|
+
User.create(gender_id: 1)
|
412
|
+
end
|
413
|
+
|
414
|
+
it "returns the changed flag for the enum" do
|
415
|
+
user = User.first
|
416
|
+
user.gender_changed?.must_equal false
|
417
|
+
|
418
|
+
user.gender_id = 2
|
419
|
+
user.gender_changed?.must_equal true
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
describe "#ENUM_was" do
|
424
|
+
before do
|
425
|
+
User.enum :gender, table: {female: 1, male: 2}
|
426
|
+
User.create(gender_id: 1)
|
427
|
+
end
|
428
|
+
|
429
|
+
it "returns the changed flag for the enum" do
|
430
|
+
user = User.first
|
431
|
+
user.gender_was.must_equal :female
|
432
|
+
end
|
433
|
+
end
|
434
|
+
|
435
|
+
describe "#ENUM_change" do
|
436
|
+
before do
|
437
|
+
User.enum :gender, table: {female: 1, male: 2}
|
438
|
+
User.create(gender_id: 1)
|
439
|
+
end
|
440
|
+
|
441
|
+
it "returns the old and new values for the enum" do
|
442
|
+
user = User.first
|
443
|
+
user.gender_change.must_be_nil
|
444
|
+
|
445
|
+
user.gender_id = 2
|
446
|
+
user.gender_change.must_equal [:female, :male]
|
447
|
+
end
|
448
|
+
end
|
449
|
+
|
450
|
+
describe "roundtripping" do
|
451
|
+
it "roundtrips a value through write and read" do
|
452
|
+
User.enum :gender, table: {female: 1, male: 2}
|
453
|
+
user = User.new
|
454
|
+
user.gender = :female
|
455
|
+
user.gender.must_equal :female
|
456
|
+
end
|
457
|
+
|
458
|
+
it "roundtrips a value through persistence" do
|
459
|
+
User.enum :gender, table: {female: 1, male: 2}
|
460
|
+
User.create(gender: :female)
|
461
|
+
User.first.gender.must_equal :female
|
462
|
+
end
|
463
|
+
|
464
|
+
it "supports strings values" do
|
465
|
+
User.enum :gender, table: {female: 1, male: 2}, type: :string
|
466
|
+
User.create(gender: 'female')
|
467
|
+
User.first.gender.must_equal 'female'
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
describe ".where" do
|
472
|
+
it "supports filtering by enums with symbol keys" do
|
473
|
+
User.enum :gender, table: {female: 1, male: 2}
|
474
|
+
female = User.create(gender_id: 1)
|
475
|
+
male = User.create(gender_id: 2)
|
476
|
+
User.where(gender: :female).all.must_equal [female]
|
477
|
+
end
|
478
|
+
|
479
|
+
it "supports filtering by enums with string keys" do
|
480
|
+
User.enum :gender, table: {female: 1, male: 2}
|
481
|
+
female = User.create(gender_id: 1)
|
482
|
+
male = User.create(gender_id: 2)
|
483
|
+
User.where('gender' => :female).all.must_equal [female]
|
484
|
+
end
|
485
|
+
|
486
|
+
it "supports filtering by multiple values" do
|
487
|
+
User.enum :gender, table: {female: 1, male: 2, other: 3}
|
488
|
+
female = User.create(gender_id: 1)
|
489
|
+
male = User.create(gender_id: 2)
|
490
|
+
other = User.create(gender_id: 3)
|
491
|
+
User.where(gender: [:female, :male]).all.must_equal [female, male]
|
492
|
+
end
|
493
|
+
|
494
|
+
it "still supports filtering by other attributes" do
|
495
|
+
User.enum :gender, table: {female: 1, male: 2}
|
496
|
+
female1 = User.create(gender_id: 1, status_id: 1)
|
497
|
+
male1 = User.create(gender_id: 2, status_id: 1)
|
498
|
+
male2 = User.create(gender_id: 2, status_id: 2)
|
499
|
+
User.where(gender: :male, status_id: 1).all.must_equal [male1]
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
describe "dynamic finders" do
|
504
|
+
it "support retrieval by enums" do
|
505
|
+
User.enum :gender, table: {female: 1, male: 2}
|
506
|
+
female = User.create(gender_id: 1)
|
507
|
+
male = User.create(gender_id: 2)
|
508
|
+
|
509
|
+
User.find_by_gender(:female).must_equal female
|
510
|
+
User.find_all_by_gender(:female).must_equal [female]
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
describe "when the inheritance column is an enum" do
|
515
|
+
before do
|
516
|
+
connection.add_column :users, :type_id, :integer
|
517
|
+
User.enum :type, table: {Admin: 1, Member: 2}
|
518
|
+
Object.const_set :Admin, Class.new(User)
|
519
|
+
Object.const_set :Member, Class.new(User)
|
520
|
+
end
|
521
|
+
|
522
|
+
after do
|
523
|
+
Object.send :remove_const, :Admin
|
524
|
+
Object.send :remove_const, :Member
|
525
|
+
|
526
|
+
# Instantiating STI classes goes through ActiveSupport::Dependencies.
|
527
|
+
ActiveSupport::Dependencies.clear
|
528
|
+
end
|
529
|
+
|
530
|
+
it "sets the type ID when instantiating" do
|
531
|
+
admin = Admin.create
|
532
|
+
admin.type_id.must_equal 1
|
533
|
+
end
|
534
|
+
|
535
|
+
it "instantiates the correct class through the superclass" do
|
536
|
+
admin = Admin.create
|
537
|
+
user = User.find(admin.id)
|
538
|
+
user.class.must_equal Admin
|
539
|
+
user.id.must_equal admin.id
|
540
|
+
end
|
541
|
+
|
542
|
+
it "finds and instantiates the correct class through the subclass" do
|
543
|
+
admin = Admin.create
|
544
|
+
found = Admin.find(admin.id)
|
545
|
+
found.class.must_equal Admin
|
546
|
+
found.id.must_equal admin.id
|
547
|
+
end
|
548
|
+
|
549
|
+
it "filters correctly when finding through the subclass" do
|
550
|
+
admin = Admin.create
|
551
|
+
Admin.find(admin.id).must_equal admin
|
552
|
+
Member.find_by_id(admin.id).must_be_nil
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
describe "when an enum has the same name as a column" do
|
557
|
+
before do
|
558
|
+
connection.add_column :users, :gender, :string
|
559
|
+
User.enum :gender, table: {female: 1, male: 2}
|
560
|
+
end
|
561
|
+
|
562
|
+
# TODO: It's probably desirable to write the column if it's present, for
|
563
|
+
# transitioning to an enum.
|
564
|
+
it "only writes the enum id" do
|
565
|
+
User.create(gender_id: 1)
|
566
|
+
results = connection.execute("SELECT gender_id, gender FROM users").to_a
|
567
|
+
results.size.must_equal 1
|
568
|
+
results[0][0].must_equal 1
|
569
|
+
results[0][1].must_equal nil
|
570
|
+
end
|
571
|
+
|
572
|
+
it "favors the enum when loading" do
|
573
|
+
connection.execute "INSERT INTO users(gender_id, gender) VALUES(1, 'male')"
|
574
|
+
User.first.gender_id.must_equal 1
|
575
|
+
User.first.gender.must_equal :female
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|