enumerize 0.3.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -1
  3. data/.rspec +2 -0
  4. data/.travis.yml +38 -11
  5. data/CHANGELOG.md +258 -0
  6. data/Gemfile +5 -11
  7. data/Gemfile.global +20 -0
  8. data/Gemfile.mongo_mapper +7 -0
  9. data/Gemfile.rails42 +7 -0
  10. data/Gemfile.rails50 +7 -0
  11. data/Gemfile.rails52 +7 -0
  12. data/README.md +405 -17
  13. data/Rakefile +7 -1
  14. data/enumerize.gemspec +6 -3
  15. data/lib/enumerize/activemodel.rb +47 -0
  16. data/lib/enumerize/activerecord.rb +127 -2
  17. data/lib/enumerize/attribute.rb +167 -7
  18. data/lib/enumerize/attribute_map.rb +4 -0
  19. data/lib/enumerize/base.rb +60 -61
  20. data/lib/enumerize/hooks/formtastic.rb +11 -12
  21. data/lib/enumerize/hooks/sequel_dataset.rb +17 -0
  22. data/lib/enumerize/hooks/simple_form.rb +21 -8
  23. data/lib/enumerize/hooks/uniqueness.rb +22 -0
  24. data/lib/enumerize/integrations/rails_admin.rb +18 -0
  25. data/lib/enumerize/integrations/rspec/matcher.rb +161 -0
  26. data/lib/enumerize/integrations/rspec.rb +19 -0
  27. data/lib/enumerize/module.rb +33 -0
  28. data/lib/enumerize/module_attributes.rb +3 -2
  29. data/lib/enumerize/mongoid.rb +29 -0
  30. data/lib/enumerize/predicatable.rb +23 -0
  31. data/lib/enumerize/predicates.rb +76 -0
  32. data/lib/enumerize/scope/activerecord.rb +49 -0
  33. data/lib/enumerize/scope/mongoid.rb +46 -0
  34. data/lib/enumerize/scope/sequel.rb +52 -0
  35. data/lib/enumerize/sequel.rb +62 -0
  36. data/lib/enumerize/set.rb +81 -0
  37. data/lib/enumerize/utils.rb +12 -0
  38. data/lib/enumerize/value.rb +19 -46
  39. data/lib/enumerize/version.rb +3 -1
  40. data/lib/enumerize.rb +56 -4
  41. data/lib/sequel/plugins/enumerize.rb +18 -0
  42. data/spec/enumerize/integrations/rspec/matcher_spec.rb +260 -0
  43. data/spec/spec_helper.rb +30 -0
  44. data/test/activemodel_test.rb +114 -0
  45. data/test/activerecord_test.rb +542 -8
  46. data/test/attribute_map_test.rb +2 -0
  47. data/test/attribute_test.rb +102 -4
  48. data/test/base_test.rb +61 -39
  49. data/test/formtastic_test.rb +102 -17
  50. data/test/module_attributes_test.rb +25 -2
  51. data/test/mongo_mapper_test.rb +84 -0
  52. data/test/mongoid_test.rb +98 -7
  53. data/test/multiple_test.rb +59 -0
  54. data/test/predicates_test.rb +65 -0
  55. data/test/rails_admin_test.rb +27 -0
  56. data/test/sequel_test.rb +341 -0
  57. data/test/set_test.rb +166 -0
  58. data/test/simple_form_test.rb +110 -4
  59. data/test/support/mock_controller.rb +11 -1
  60. data/test/support/shared_enums.rb +43 -0
  61. data/test/support/view_test_helper.rb +6 -0
  62. data/test/test_helper.rb +25 -6
  63. data/test/value_test.rb +102 -13
  64. metadata +62 -28
  65. data/gemfiles/Gemfile-ar-3.1.x +0 -13
@@ -1,34 +1,141 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
  require 'active_record'
3
5
  require 'logger'
4
6
 
7
+ db = (ENV['DB'] || 'sqlite3').to_sym
8
+
5
9
  silence_warnings do
6
10
  ActiveRecord::Migration.verbose = false
7
11
  ActiveRecord::Base.logger = Logger.new(nil)
8
- ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
12
+ ActiveRecord::Base.configurations = {
13
+ 'sqlite3' => {
14
+ 'adapter' => 'sqlite3',
15
+ 'database' => ':memory:'
16
+ },
17
+ 'postgresql' => {
18
+ 'adapter' => 'postgresql',
19
+ 'username' => ENV['DB_USER'],
20
+ 'password' => ENV['DB_PASSD'],
21
+ 'database' => 'enumerize_test'
22
+ },
23
+ 'postgresql_master' => {
24
+ 'adapter' => 'postgresql',
25
+ 'username' => ENV['DB_USER'],
26
+ 'password' => ENV['DB_PASS'],
27
+ 'database' => 'template1',
28
+ 'schema_search_path' => 'public'
29
+ }
30
+ }
31
+ if db == :postgresql
32
+ ActiveRecord::Base.establish_connection(:postgresql_master)
33
+ ActiveRecord::Base.connection.recreate_database('enumerize_test')
34
+ end
35
+
36
+ ActiveRecord::Base.establish_connection(db)
9
37
  end
10
38
 
11
39
  ActiveRecord::Base.connection.instance_eval do
12
40
  create_table :users do |t|
13
41
  t.string :sex
14
42
  t.string :role
43
+ t.string :lambda_role
15
44
  t.string :name
45
+ t.string :interests
46
+ t.integer :status
47
+ t.text :settings
48
+ t.integer :skill
49
+ t.string :account_type, :default => :basic
50
+ t.string :foo
16
51
  end
52
+
53
+ create_table :documents do |t|
54
+ t.integer :user_id
55
+ t.string :visibility
56
+ t.integer :status
57
+ t.timestamps null: true
58
+ end
59
+ end
60
+
61
+ class BaseEntity < ActiveRecord::Base
62
+ self.abstract_class = true
63
+
64
+ extend Enumerize
65
+ enumerize :visibility, :in => [:public, :private, :protected], :scope => true, :default => :public
66
+ end
67
+
68
+ class Document < BaseEntity
69
+ belongs_to :user
70
+
71
+ enumerize :status, in: {draft: 1, release: 2}
72
+ end
73
+
74
+ module RoleEnum
75
+ extend Enumerize
76
+ enumerize :role, :in => [:user, :admin], :default => :user, scope: :having_role
77
+ enumerize :lambda_role, :in => [:user, :admin], :default => lambda { :admin }
17
78
  end
18
79
 
19
80
  class User < ActiveRecord::Base
20
- include Enumerize
81
+ extend Enumerize
82
+ include RoleEnum
21
83
 
22
- enumerize :sex, :in => [:male, :female]
84
+ store :settings, accessors: [:language]
85
+
86
+ enumerize :sex, :in => [:male, :female], scope: :shallow
87
+ enumerize :language, :in => [:en, :jp]
88
+
89
+ serialize :interests, Array
90
+ enumerize :interests, :in => [:music, :sports, :dancing, :programming], :multiple => true
91
+
92
+ enumerize :status, :in => { active: 1, blocked: 2 }, scope: true
93
+
94
+ enumerize :skill, :in => { noob: 0, casual: 1, pro: 2 }, scope: :shallow
95
+
96
+ enumerize :account_type, :in => [:basic, :premium]
97
+
98
+ # There is no column for relationship enumeration for testing purposes: model
99
+ # should not be broken even if the associated column does not exist yet.
100
+ enumerize :relationship, :in => [:single, :married]
101
+
102
+ has_many :documents
103
+ end
104
+
105
+ class UniqStatusUser < User
106
+ validates :status, uniqueness: true
107
+ validates :sex, presence: true
108
+ end
109
+
110
+ class InterestsRequiredUser < User
111
+ validates :interests, presence: true
112
+ end
113
+
114
+ class SkipValidationsUser < ActiveRecord::Base
115
+ self.table_name = "users"
116
+ include SkipValidationsEnum
117
+ end
23
118
 
24
- enumerize :role, :in => [:user, :admin], :default => :user
119
+ class DoNotSkipValidationsUser < ActiveRecord::Base
120
+ self.table_name = "users"
121
+ include DoNotSkipValidationsEnum
25
122
  end
26
123
 
27
- describe Enumerize::ActiveRecord do
124
+ class SkipValidationsLambdaUser < ActiveRecord::Base
125
+ self.table_name = "users"
126
+ include SkipValidationsLambdaEnum
127
+ end
128
+
129
+ class SkipValidationsLambdaWithParamUser < ActiveRecord::Base
130
+ self.table_name = "users"
131
+ include SkipValidationsLambdaWithParamEnum
132
+ end
133
+
134
+ describe Enumerize::ActiveRecordSupport do
28
135
  it 'sets nil if invalid value is passed' do
29
136
  user = User.new
30
137
  user.sex = :invalid
31
- user.sex.must_equal nil
138
+ user.sex.must_be_nil
32
139
  end
33
140
 
34
141
  it 'saves value' do
@@ -49,15 +156,88 @@ describe Enumerize::ActiveRecord do
49
156
  end
50
157
  end
51
158
 
159
+ it 'sets nil if invalid stored attribute value is passed' do
160
+ user = User.new
161
+ user.language = :invalid
162
+ user.language.must_be_nil
163
+ end
164
+
165
+ it 'saves stored attribute value' do
166
+ User.delete_all
167
+ user = User.new
168
+ user.language = :en
169
+ user.save!
170
+ user.reload
171
+ user.language.must_equal 'en'
172
+ end
173
+
52
174
  it 'has default value' do
53
175
  User.new.role.must_equal 'user'
54
176
  User.new.attributes['role'].must_equal 'user'
55
177
  end
56
178
 
179
+ it 'does not set default value for not selected attributes' do
180
+ User.delete_all
181
+ User.create!(:sex => :male)
182
+
183
+ assert_equal ['id'], User.select(:id).first.attributes.keys
184
+ end
185
+
186
+ it 'has default value with lambda' do
187
+ User.new.lambda_role.must_equal 'admin'
188
+ User.new.attributes['lambda_role'].must_equal 'admin'
189
+ end
190
+
191
+ it 'uses after_initialize callback to set default value' do
192
+ User.delete_all
193
+ User.create!(sex: 'male', lambda_role: nil)
194
+
195
+ user = User.where(:sex => 'male').first
196
+ user.lambda_role.must_equal 'admin'
197
+ end
198
+
199
+ it 'uses default value from db column' do
200
+ User.new.account_type.must_equal 'basic'
201
+ end
202
+
203
+ it 'has default value with default scope' do
204
+ UserWithDefaultScope = Class.new(User) do
205
+ default_scope -> { having_role(:user) }
206
+ end
207
+
208
+ UserWithDefaultScope.new.role.must_equal 'user'
209
+ UserWithDefaultScope.new.attributes['role'].must_equal 'user'
210
+ end
211
+
57
212
  it 'validates inclusion' do
58
213
  user = User.new
59
214
  user.role = 'wrong'
60
215
  user.wont_be :valid?
216
+ user.errors[:role].must_include 'is not included in the list'
217
+ end
218
+
219
+ it 'sets value to enumerized field from db when record is reloaded' do
220
+ user = User.create!(interests: [:music])
221
+ User.find(user.id).update(interests: %i[music dancing])
222
+ user.interests.must_equal %w[music]
223
+ user.reload
224
+ user.interests.must_equal %w[music dancing]
225
+ end
226
+
227
+ it 'validates inclusion when using write_attribute with string attribute' do
228
+ user = User.new
229
+ user.send(:write_attribute, 'role', 'wrong')
230
+ user.read_attribute_for_validation(:role).must_equal 'wrong'
231
+ user.wont_be :valid?
232
+ user.errors[:role].must_include 'is not included in the list'
233
+ end
234
+
235
+ it 'validates inclusion when using write_attribute with symbol attribute' do
236
+ user = User.new
237
+ user.send(:write_attribute, :role, 'wrong')
238
+ user.read_attribute_for_validation(:role).must_equal 'wrong'
239
+ user.wont_be :valid?
240
+ user.errors[:role].must_include 'is not included in the list'
61
241
  end
62
242
 
63
243
  it 'validates inclusion on mass assignment' do
@@ -68,7 +248,7 @@ describe Enumerize::ActiveRecord do
68
248
 
69
249
  it "uses persisted value for validation if it hasn't been set" do
70
250
  user = User.create! :sex => :male
71
- User.find(user).read_attribute_for_validation(:sex).must_equal 'male'
251
+ User.find(user.id).read_attribute_for_validation(:sex).must_equal 'male'
72
252
  end
73
253
 
74
254
  it 'is valid with empty string assigned' do
@@ -80,6 +260,360 @@ describe Enumerize::ActiveRecord do
80
260
  it 'stores nil when empty string assigned' do
81
261
  user = User.new
82
262
  user.role = ''
83
- user.read_attribute(:role).must_equal nil
263
+ user.read_attribute(:role).must_be_nil
264
+ end
265
+
266
+ it 'validates inclusion when :skip_validations = false' do
267
+ user = DoNotSkipValidationsUser.new
268
+ user.foo = 'wrong'
269
+ user.wont_be :valid?
270
+ user.errors[:foo].must_include 'is not included in the list'
271
+ end
272
+
273
+ it 'does not validate inclusion when :skip_validations = true' do
274
+ user = SkipValidationsUser.new
275
+ user.foo = 'wrong'
276
+ user.must_be :valid?
277
+ end
278
+
279
+ it 'supports :skip_validations option as lambda' do
280
+ user = SkipValidationsLambdaUser.new
281
+ user.foo = 'wrong'
282
+ user.must_be :valid?
283
+ end
284
+
285
+ it 'supports :skip_validations option as lambda with a parameter' do
286
+ user = SkipValidationsLambdaWithParamUser.new
287
+ user.foo = 'wrong'
288
+ user.must_be :valid?
289
+ end
290
+
291
+ it 'supports multiple attributes' do
292
+ user = User.new
293
+ user.interests.must_be_empty
294
+ user.interests << :music
295
+ user.interests.must_equal %w(music)
296
+ user.save!
297
+
298
+ user = User.find(user.id)
299
+ user.interests.must_be_instance_of Enumerize::Set
300
+ user.interests.must_equal %w(music)
301
+ user.interests << :sports
302
+ user.interests.must_equal %w(music sports)
303
+
304
+ user.interests = []
305
+ interests = user.interests
306
+ interests << :music
307
+ interests.must_equal %w(music)
308
+ interests << :dancing
309
+ interests.must_equal %w(music dancing)
310
+ end
311
+
312
+ it 'stores multiple value passed passed to new' do
313
+ user = User.new(interests: [:music, :dancing])
314
+ user.save!
315
+ user.interests.must_equal %w(music dancing)
316
+ User.find(user.id).interests.must_equal %w(music dancing)
317
+ end
318
+
319
+ it 'returns invalid multiple value for validation' do
320
+ user = User.new
321
+ user.interests << :music
322
+ user.interests << :invalid
323
+ values = user.read_attribute_for_validation(:interests)
324
+ values.must_equal %w(music invalid)
325
+ end
326
+
327
+ it 'validates multiple attributes' do
328
+ user = User.new
329
+ user.interests << :invalid
330
+ user.wont_be :valid?
331
+
332
+ user.interests = Object.new
333
+ user.wont_be :valid?
334
+
335
+ user.interests = ['music', '']
336
+ user.must_be :valid?
337
+ end
338
+
339
+ it 'stores custom values for multiple attributes' do
340
+ User.delete_all
341
+
342
+ klass = Class.new(User) do
343
+ def self.name
344
+ 'UserSubclass'
345
+ end
346
+ end
347
+ klass.enumerize :interests, in: { music: 0, sports: 1, dancing: 2, programming: 3}, multiple: true
348
+
349
+ user = klass.new
350
+ user.interests << :music
351
+ user.read_attribute(:interests).must_equal [0]
352
+ user.interests.must_equal %w(music)
353
+ user.save
354
+
355
+ user = klass.find(user.id)
356
+ user.interests.must_equal %w(music)
357
+ end
358
+
359
+ it 'adds scope' do
360
+ User.delete_all
361
+
362
+ user_1 = User.create!(status: :active, role: :admin)
363
+ user_2 = User.create!(status: :blocked)
364
+ user_3 = User.create!(sex: :male, skill: :pro)
365
+
366
+ User.with_status(:active).must_equal [user_1]
367
+ User.with_status(:blocked).must_equal [user_2]
368
+ User.with_status(:active, :blocked).to_set.must_equal [user_1, user_2].to_set
369
+
370
+ User.without_status(:active).must_equal [user_2]
371
+ User.without_status(:active, :blocked).must_equal []
372
+
373
+ User.male.must_equal [user_3]
374
+ User.pro.must_equal [user_3]
375
+ end
376
+
377
+ it 'ignores not enumerized values that passed to the scope method' do
378
+ User.delete_all
379
+
380
+ User.with_status(:foo).must_equal []
381
+ end
382
+
383
+ it 'allows either key or value as valid' do
384
+ user_1 = User.new(status: :active)
385
+ user_2 = User.new(status: 1)
386
+ user_3 = User.new(status: '1')
387
+
388
+ user_1.status.must_equal 'active'
389
+ user_2.status.must_equal 'active'
390
+ user_3.status.must_equal 'active'
391
+
392
+ user_1.must_be :valid?
393
+ user_2.must_be :valid?
394
+ user_3.must_be :valid?
395
+ end
396
+
397
+ it 'supports defining enumerized attributes on abstract class' do
398
+ Document.delete_all
399
+
400
+ document = Document.new
401
+ document.visibility = :protected
402
+ document.visibility.must_equal 'protected'
403
+ end
404
+
405
+ it 'supports defining enumerized scopes on abstract class' do
406
+ Document.delete_all
407
+
408
+ document_1 = Document.create!(visibility: :public)
409
+ document_2 = Document.create!(visibility: :private)
410
+
411
+ Document.with_visibility(:public).must_equal [document_1]
412
+ end
413
+
414
+ it 'validates uniqueness' do
415
+ user = User.new
416
+ user.status = :active
417
+ user.save!
418
+
419
+ user = UniqStatusUser.new
420
+ user.status = :active
421
+ user.valid?
422
+
423
+ user.errors[:status].wont_be :empty?
424
+ end
425
+
426
+ it 'validates presence with multiple attributes' do
427
+ user = InterestsRequiredUser.new
428
+ user.interests = []
429
+ user.valid?
430
+
431
+ user.errors[:interests].wont_be :empty?
432
+
433
+ user.interests = ['']
434
+ user.valid?
435
+
436
+ user.errors[:interests].wont_be :empty?
437
+
438
+ user.interests = [:dancing, :programming]
439
+ user.valid?
440
+
441
+ user.errors[:interests].must_be_empty
442
+ end
443
+
444
+ it 'is valid after #becomes' do
445
+ User.delete_all
446
+ user = User.new
447
+ user.sex = :male
448
+ user.save!
449
+
450
+ uniq_user = User.find(user.id).becomes(UniqStatusUser)
451
+ uniq_user.valid?
452
+
453
+ uniq_user.errors.must_be_empty
454
+ end
455
+
456
+ it 'supports multiple attributes in #becomes' do
457
+ User.delete_all
458
+
459
+ uniq_user = UniqStatusUser.new
460
+ uniq_user.interests = [:sports, :dancing]
461
+ uniq_user.sex = :male
462
+ uniq_user.save!
463
+
464
+ user = uniq_user.becomes(User)
465
+
466
+ user.sex.must_equal uniq_user.sex
467
+ user.interests.must_equal uniq_user.interests
468
+ end
469
+
470
+ it "doesn't update record" do
471
+ Document.delete_all
472
+
473
+ expected = Time.utc(2010, 10, 10)
474
+
475
+ document = Document.new
476
+ document.updated_at = expected
477
+ document.save!
478
+
479
+ document = Document.last
480
+ document.save!
481
+
482
+ assert_equal expected, document.updated_at
483
+ end
484
+
485
+ it 'changes from dirty should be serialized as scalar values' do
486
+ user = User.create(:status => :active)
487
+ user.status = :blocked
488
+
489
+ assert_equal [1, 2], YAML.load(user.changes.to_yaml)[:status]
490
+ end
491
+
492
+ it 'does not change by the practical same value' do
493
+ user = User.create!(status: 'active')
494
+ user.reload
495
+ user.status = 'active'
496
+
497
+ user.changes.must_be_empty
498
+ end
499
+
500
+ it 'allows using update_all' do
501
+ User.delete_all
502
+
503
+ user = User.create(status: :active, account_type: :premium)
504
+
505
+ User.update_all(status: :blocked)
506
+ user.reload
507
+ user.status.must_equal 'blocked'
508
+
509
+ User.update_all(status: :active, account_type: :basic)
510
+ user.reload
511
+ user.status.must_equal 'active'
512
+ user.account_type.must_equal 'basic'
513
+ end
514
+
515
+ it 'allows using update_all for multiple enumerize' do
516
+ User.delete_all
517
+
518
+ klass = Class.new(User) do
519
+ def self.name
520
+ 'UserSubclass'
521
+ end
522
+ end
523
+ klass.enumerize :interests, in: { music: 0, sports: 1, dancing: 2, programming: 3}, multiple: true
524
+
525
+ user = klass.create(status: :active)
526
+ klass.update_all(status: :blocked, interests: [:music, :dancing])
527
+
528
+ user = klass.find(user.id)
529
+ user.status.must_equal 'blocked'
530
+ user.interests.must_equal %w(music dancing)
531
+ end
532
+
533
+ it 'allows using update_all with values' do
534
+ User.delete_all
535
+
536
+ user = User.create(status: :active)
537
+
538
+ User.update_all(status: 2)
539
+ user.reload
540
+ user.status.must_equal 'blocked'
541
+ end
542
+
543
+ it 'allows using update_all on relation objects' do
544
+ User.delete_all
545
+
546
+ user = User.create(status: :active, account_type: :premium)
547
+
548
+ User.all.update_all(status: :blocked)
549
+ user.reload
550
+ user.status.must_equal 'blocked'
551
+ end
552
+
553
+ it 'allows using update_all on association relation objects' do
554
+ User.delete_all
555
+ Document.delete_all
556
+
557
+ user = User.create
558
+ document = Document.create(user: user, status: :draft)
559
+
560
+ user.documents.update_all(status: :release)
561
+ document.reload
562
+ document.status.must_equal 'release'
563
+ end
564
+
565
+ it 'preserves string usage of update_all' do
566
+ User.delete_all
567
+
568
+ user = User.create(name: "Fred")
569
+
570
+ User.update_all("name = 'Frederick'")
571
+ user.reload
572
+ user.name.must_equal 'Frederick'
573
+ end
574
+
575
+ it 'preserves interpolated array usage of update_all' do
576
+ User.delete_all
577
+
578
+ user = User.create(name: "Fred")
579
+
580
+ User.update_all(["name = :name", {name: 'Frederick'}])
581
+ user.reload
582
+ user.name.must_equal 'Frederick'
583
+ end
584
+
585
+ it 'sets attribute to nil if given one is not valid' do
586
+ User.delete_all
587
+
588
+ user = User.create(status: :active)
589
+
590
+ User.update_all(status: :foo)
591
+ user.reload
592
+ user.status.must_be_nil
593
+ end
594
+
595
+ it 'supports AR types serialization' do
596
+ type = User.type_for_attribute('status')
597
+ type.must_be_instance_of Enumerize::ActiveRecordSupport::Type
598
+ serialized = type.serialize('blocked')
599
+ serialized.must_equal 2
600
+ end
601
+
602
+ it 'has AR type itself JSON serializable' do
603
+ type = User.type_for_attribute('status')
604
+ type.as_json['attr'].must_equal 'status'
605
+ end
606
+
607
+ it "doesn't break YAML serialization" do
608
+ user = YAML.load(User.create(status: :blocked).to_yaml)
609
+ user.status.must_equal 'blocked'
610
+ end
611
+
612
+ # https://github.com/brainspec/enumerize/issues/304
613
+ it "fallbacks to a raw passed value if AR type can't find value in the attribute" do
614
+ table = User.arel_table
615
+ sql = User.where(table[:account_type].matches '%foo%').to_sql
616
+
617
+ sql.must_include 'LIKE \'%foo%\''
84
618
  end
85
619
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'test_helper'
2
4
 
3
5
  module Enumerize