enumerize 0.3.0 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
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