enumerize 2.8.1 → 3.0.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e8451ec4f75c5a22b6a9c6e337bece407942da5cb12f504b689582e56e1db55
4
- data.tar.gz: cfd3f27f2fe811d1c23cf47e875da412b76efd0bae55d18f44d551eb56447507
3
+ metadata.gz: aa3e9016f87e07bed5c034618ffb19c15104b334adace5eb5cd7903a2a82cf47
4
+ data.tar.gz: 779be0d6f9644f0ad8019d03bf0c2e1d5638b62c531bd2aef69518f05bf7ae76
5
5
  SHA512:
6
- metadata.gz: ccc0ed4ca53517171dcac492ae9fd618c17fb08b9b39f4f42b6d951cdc46defb3acc92f66ce345fbe13cb0a654db8d7800b4dc72c19cb0c4e3c4ab1c3088780c
7
- data.tar.gz: bd8521f6e55b9af3283073e36197ea5fc5107d4114f297713b6a27ac221bcd98f2376a4803351eeb8c1a7b518c032b6bcc5d16a6c7750294753d0f64f76a4352
6
+ metadata.gz: 81aea0e826349ad121cfed3a44ee14566fcb7dbba7f78cca5ca5ad007f166edea65b8fdc79e64c441f4249c9f760bafae4918226a07eac54f8ab17a1c80e8f11
7
+ data.tar.gz: 719f1f33563b31f1f8c73d256c55a16d371ebf581ebddcdae60cb7b98ac0de5960ebcc44c55c836749662b73f1733aaeb437bbe4fc3ed0e4cc103bcdd4431493
@@ -8,7 +8,7 @@ on:
8
8
 
9
9
  jobs:
10
10
  test:
11
- runs-on: ubuntu-20.04
11
+ runs-on: ubuntu-24.04
12
12
  services:
13
13
  mongodb:
14
14
  image: mongo:latest
@@ -32,12 +32,12 @@ jobs:
32
32
  strategy:
33
33
  fail-fast: false
34
34
  matrix:
35
- ruby-version: ['3.0', '3.1', '3.2', '3.3']
35
+ ruby-version: ['3.2', '3.3', '3.4', '4.0']
36
36
  test: ['minitest', 'rspec']
37
37
  gemfile:
38
- - Gemfile.rails61
39
- - Gemfile.rails70
40
- - Gemfile.rails71
38
+ - Gemfile.rails72
39
+ - Gemfile.rails80
40
+ - Gemfile.rails81
41
41
  - Gemfile.railsmaster
42
42
  - Gemfile.mongo_mapper
43
43
  db:
@@ -47,18 +47,14 @@ jobs:
47
47
  exclude:
48
48
  - gemfile: Gemfile.mongo_mapper
49
49
  db: postgresql
50
- - ruby-version: '3.0'
51
- gemfile: Gemfile.mongo_mapper
52
- - ruby-version: '3.1'
53
- gemfile: Gemfile.mongo_mapper
54
- - ruby-version: '3.0'
50
+ - ruby-version: '3.2'
55
51
  gemfile: Gemfile.railsmaster
56
52
  env:
57
53
  BUNDLE_GEMFILE: "${{ matrix.gemfile }}"
58
54
  DB: "${{ matrix.db }}"
59
55
  TEST_FRAMEWORK: "${{ matrix.test }}"
60
56
  steps:
61
- - uses: actions/checkout@v4
57
+ - uses: actions/checkout@v7
62
58
  - name: Set up Ruby
63
59
  uses: ruby/setup-ruby@v1
64
60
  with:
data/.gitignore CHANGED
@@ -3,6 +3,7 @@
3
3
  .bundle
4
4
  .config
5
5
  .ruby-version
6
+ .tool-versions
6
7
  .yardoc
7
8
  .idea/
8
9
  enumerize.iml
data/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## 3.0.0 (August 4, 2026)
2
+
3
+ ### bug fix
4
+
5
+ * Fixed `Enumerize::ActiveModelAttributesSupport::Type` to wrap values in `#cast` so enumerized readers on plain `ActiveModel::Attributes` objects return an `Enumerize::Value` (and value predicates keep working) on Rails 8.1. Rails 8.1 reordered attribute-method module inclusion so the generated reader can outrank enumerize's module; assignment reads go through `#cast`, which previously returned the raw value. See #482.
6
+ * Fixed `Enumerize::ActiveModelAttributesSupport::Type#deserialize` to properly handle arrays for `multiple: true` attributes. Previously, deserializing an array would return `nil` instead of the enumerated values. This bug only affected ActiveModel::Attributes usage (not ActiveRecord) and was exposed when used with gems like store_model v2.0.0+ that call `deserialize` during load.
7
+ * Allow an object to `#becomes` a non-Enumerize model.
8
+ * Fix `Type#cast` to support Rails 8.0 bulk operations. (by [@snaka](https://github.com/snaka))
9
+
10
+ ### enchancements
11
+
12
+ * Support only Ruby 3.1+ and Rails 7.0+. (by [@nashby](https://github.com/nashby))
13
+ * Ensure that `enumerize` is agnostic to whether the key to a store accessor is a `String` or `Symbol`. (by [@4ndypanda](https://github.com/4ndypanda))
14
+
1
15
  ## 2.8.1 (March 25, 2024)
2
16
 
3
17
  ### bug fix
data/Gemfile.global CHANGED
@@ -8,7 +8,7 @@ if ENV['TEST_FRAMEWORK'] == 'rspec'
8
8
  gem 'rspec', :require => false
9
9
  end
10
10
 
11
- gem 'pg', '~> 1.5.3', :platform => [:ruby, :mswin, :mingw]
11
+ gem 'pg', '~> 1.5.3', platforms: [:ruby, :mswin, :mingw]
12
12
  gem 'sequel'
13
13
  gem 'simple_form'
14
14
  gem 'formtastic'
data/Gemfile.mongo_mapper CHANGED
@@ -1,6 +1,9 @@
1
1
  eval_gemfile('Gemfile.global')
2
2
 
3
3
  gem 'minitest', '~> 5.8'
4
- gem 'rails', github: 'rails/rails', branch: '7-0-stable', require: false
4
+ gem 'rails', github: 'rails/rails', branch: '7-2-stable', require: false
5
5
  gem 'mongo_mapper'
6
- gem 'sqlite3', :platform => [:ruby, :mswin, :mingw]
6
+
7
+ # This Rails version has a pessimistic version constraint on sqlite3:
8
+ # https://github.com/rails/rails/blob/7-2-stable/Gemfile#L160
9
+ gem 'sqlite3', '~> 1.6', '>= 1.6.6', platforms: [:ruby, :mswin, :mingw]
data/Gemfile.rails72 ADDED
@@ -0,0 +1,8 @@
1
+ eval_gemfile('Gemfile.global')
2
+
3
+ gem 'minitest', '~> 5.8'
4
+ gem 'rails', github: 'rails/rails', branch: '7-2-stable', require: false
5
+
6
+ gem 'mongoid', github: 'mongodb/mongoid'
7
+
8
+ gem 'sqlite3', platforms: [:ruby, :mswin, :mingw]
data/Gemfile.rails80 ADDED
@@ -0,0 +1,8 @@
1
+ eval_gemfile('Gemfile.global')
2
+
3
+ gem 'minitest', '~> 5.8'
4
+ gem 'rails', github: 'rails/rails', branch: '8-0-stable', require: false
5
+
6
+ gem 'mongoid', github: 'mongodb/mongoid'
7
+
8
+ gem 'sqlite3', platforms: [:ruby, :mswin, :mingw]
data/Gemfile.rails81 ADDED
@@ -0,0 +1,8 @@
1
+ eval_gemfile('Gemfile.global')
2
+
3
+ gem 'minitest', '~> 5.8'
4
+ gem 'rails', github: 'rails/rails', branch: '8-1-stable', require: false
5
+
6
+ gem 'mongoid', github: 'mongodb/mongoid'
7
+
8
+ gem 'sqlite3', platforms: [:ruby, :mswin, :mingw]
data/Gemfile.railsmaster CHANGED
@@ -2,4 +2,4 @@ eval_gemfile('Gemfile.global')
2
2
 
3
3
  gem 'minitest', '~> 5.8'
4
4
  gem 'rails', github: 'rails/rails', branch: 'main', require: false
5
- gem 'sqlite3', :platform => [:ruby, :mswin, :mingw]
5
+ gem 'sqlite3', platforms: [:ruby, :mswin, :mingw]
data/README.md CHANGED
@@ -46,8 +46,8 @@ Or install it yourself as:
46
46
 
47
47
  ## Supported Versions
48
48
 
49
- - Ruby 3.0+
50
- - Rails 6.1+
49
+ - Ruby 3.1+
50
+ - Rails 7.0+
51
51
 
52
52
  ## Usage
53
53
 
@@ -306,7 +306,7 @@ It's also possible to store enumerized attribute value using custom values (e.g.
306
306
  class User < ActiveRecord::Base
307
307
  extend Enumerize
308
308
 
309
- enumerize :role, in: {:user => 1, :admin => 2}
309
+ enumerize :role, in: { user: 1, admin: 2 }
310
310
  end
311
311
 
312
312
  user = User.new
@@ -325,8 +325,8 @@ User.role.find_value(:admin).value #=> 2
325
325
  ```ruby
326
326
  class User < ActiveRecord::Base
327
327
  extend Enumerize
328
- enumerize :role, :in => [:user, :admin], scope: true
329
- enumerize :status, :in => { student: 1, employed: 2, retired: 3 }, scope: :having_status
328
+ enumerize :role, in: [:user, :admin], scope: true
329
+ enumerize :status, in: { student: 1, employed: 2, retired: 3 }, scope: :having_status
330
330
  end
331
331
 
332
332
  User.with_role(:admin)
@@ -346,8 +346,8 @@ Adds named scopes to the class directly.
346
346
  ```ruby
347
347
  class User < ActiveRecord::Base
348
348
  extend Enumerize
349
- enumerize :status, :in => [:student, :employed, :retired], scope: :shallow
350
- enumerize :role, :in => { user: 1, admin: 2 }, scope: :shallow
349
+ enumerize :status, in: [:student, :employed, :retired], scope: :shallow
350
+ enumerize :role, in: { user: 1, admin: 2 }, scope: :shallow
351
351
  end
352
352
 
353
353
  User.student
@@ -422,7 +422,7 @@ and if you want it as radio buttons:
422
422
 
423
423
  ```erb
424
424
  <%= simple_form_for @user do |f| %>
425
- <%= f.input :status, :as => :radio_buttons %>
425
+ <%= f.input :status, as: :radio_buttons %>
426
426
  <% end %>
427
427
  ```
428
428
 
@@ -442,7 +442,7 @@ and if you want it as radio buttons:
442
442
 
443
443
  ```erb
444
444
  <%= semantic_form_for @user do |f| %>
445
- <%= f.input :status, :as => :radio %>
445
+ <%= f.input :status, as: :radio %>
446
446
  <% end %>
447
447
  ```
448
448
 
data/enumerize.gemspec CHANGED
@@ -15,8 +15,14 @@ Gem::Specification.new do |gem|
15
15
  gem.name = "enumerize"
16
16
  gem.require_paths = ["lib"]
17
17
  gem.version = Enumerize::VERSION
18
-
19
- gem.required_ruby_version = '>= 2.7'
18
+ gem.metadata = {
19
+ "homepage_uri" => "https://github.com/brainspec/enumerize",
20
+ "changelog_uri" => "https://github.com/brainspec/enumerize/blob/main/CHANGELOG.md",
21
+ "source_code_uri" => "https://github.com/brainspec/enumerize",
22
+ "bug_tracker_uri" => "https://github.com/brainspec/enumerize/issues",
23
+ "wiki_uri" => "https://github.com/brainspec/enumerize/wiki"
24
+ }
25
+ gem.required_ruby_version = '>= 3.2'
20
26
 
21
27
  gem.add_dependency('activesupport', '>= 3.2')
22
28
  end
@@ -40,7 +40,29 @@ module Enumerize
40
40
  end
41
41
 
42
42
  def deserialize(value)
43
- @attr.find_value(value)
43
+ return nil if value.nil?
44
+
45
+ # Use find_values for arrays on multiple: true attributes
46
+ # Otherwise use find_value for single values
47
+ if value.is_a?(Array) && @attr.arguments[:multiple]
48
+ @attr.find_values(*value)
49
+ else
50
+ @attr.find_value(value)
51
+ end
52
+ end
53
+
54
+ def cast(value)
55
+ case value
56
+ when nil
57
+ return super
58
+ when Array
59
+ if @attr.arguments[:multiple]
60
+ found = @attr.find_values(*value)
61
+ return found.presence || super
62
+ end
63
+ end
64
+
65
+ @attr.find_value(value) || value
44
66
  end
45
67
  end
46
68
  end
@@ -21,24 +21,10 @@ module Enumerize
21
21
  require 'enumerize/hooks/uniqueness'
22
22
 
23
23
  unless options[:multiple]
24
- if ::ActiveRecord.version >= ::Gem::Version.new("7.2.0.alpha")
25
- attribute(name)
24
+ attribute(name)
26
25
 
27
- decorate_attributes([name]) do |_, subtype|
28
- Type.new(enumerized_attributes[name], subtype)
29
- end
30
- elsif ::ActiveRecord.version >= ::Gem::Version.new("7.0.0.alpha")
31
- attribute(name) do |subtype|
32
- Type.new(enumerized_attributes[name], subtype)
33
- end
34
- elsif ::ActiveRecord.version >= ::Gem::Version.new("6.1.0.alpha")
35
- decorate_attribute_type(name.to_s) do |subtype|
36
- Type.new(enumerized_attributes[name], subtype)
37
- end
38
- else
39
- decorate_attribute_type(name, :enumerize) do |subtype|
40
- Type.new(enumerized_attributes[name], subtype)
41
- end
26
+ decorate_attributes([name]) do |_, subtype|
27
+ Type.new(enumerized_attributes[name], subtype)
42
28
  end
43
29
  end
44
30
  end
@@ -58,6 +44,8 @@ module Enumerize
58
44
  # Support multiple enumerized attributes
59
45
  def becomes(klass)
60
46
  became = super
47
+ return became unless became.respond_to?(:enumerized_attributes)
48
+
61
49
  klass.enumerized_attributes.each do |attr|
62
50
  # Rescue when column associated to the enum does not exist.
63
51
  begin
@@ -76,7 +64,7 @@ module Enumerize
76
64
  begin
77
65
  # Checks first if the enumerized attribute is in ActiveRecord::Store
78
66
  store_attr, _ = reloaded.class.stored_attributes.detect do |_store_attr, keys|
79
- keys.include?(attr.name)
67
+ keys.map(&:to_sym).include?(attr.name)
80
68
  end
81
69
 
82
70
  if store_attr.present?
@@ -125,12 +113,14 @@ module Enumerize
125
113
 
126
114
  def cast(value)
127
115
  return value if @subtype.is_a?(Type)
116
+ return value if value.is_a?(::Enumerize::Value)
128
117
 
129
- if value.is_a?(::Enumerize::Value)
130
- value
131
- else
132
- @attr.find_value(@subtype.cast(value))
133
- end
118
+ # First try to find the enumerize value directly
119
+ enumerize_value = @attr.find_value(value)
120
+ return enumerize_value if enumerize_value
121
+
122
+ # If not found, delegate to subtype then try to find value
123
+ @attr.find_value(@subtype.cast(value))
134
124
  end
135
125
 
136
126
  def as_json(options = nil)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Enumerize
4
- VERSION = '2.8.1'
4
+ VERSION = '3.0.0'
5
5
  end
@@ -107,14 +107,14 @@ RSpec.describe Enumerize::Integrations::RSpec::Matcher do
107
107
  end
108
108
 
109
109
  it 'rejects wrong keys' do
110
- message = 'Expected Model to define enumerize :sex in: "{:boy=>0, :girl=>1}"'
110
+ message = "Expected Model to define enumerize :sex in: \"#{{boy: 0, girl: 1}.inspect}\""
111
111
  expect do
112
112
  expect(subject).to enumerize(:sex).in(boy: 0, girl: 1)
113
113
  end.to fail_with(message)
114
114
  end
115
115
 
116
116
  it 'rejects wrong values' do
117
- message = 'Expected Model to define enumerize :sex in: "{:male=>2, :female=>3}"'
117
+ message = "Expected Model to define enumerize :sex in: \"#{{male: 2, female: 3}.inspect}\""
118
118
  expect do
119
119
  expect(subject).to enumerize(:sex).in(male: 2, female: 3)
120
120
  end.to fail_with(message)
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logger'
3
4
  require 'rails'
4
5
  require 'enumerize'
5
6
  require 'rspec/matchers/fail_matchers'
@@ -20,6 +20,19 @@ class ActiveModelTest < Minitest::Spec
20
20
  validates :interests, presence: true
21
21
  end
22
22
 
23
+ # No plain `attribute` is declared before `enumerize` here. On Rails 8.1 the
24
+ # ActiveModel-generated attribute-method module then outranks enumerize's own
25
+ # reader module in the ancestor chain, so reads resolve through the type's
26
+ # #cast. This model reproduces the raw-value regression that #cast fixes.
27
+ class CastActiveModelUser
28
+ include ActiveModel::Model
29
+ include ActiveModel::Attributes
30
+ extend Enumerize
31
+
32
+ enumerize :sex, :in => %w[male female]
33
+ enumerize :role, :in => %w[admin user], :default => 'user'
34
+ end
35
+
23
36
  let(:model) { ActiveModelUser }
24
37
 
25
38
  it 'initialize value' do
@@ -107,6 +120,182 @@ class ActiveModelTest < Minitest::Spec
107
120
 
108
121
  expect(user.errors[:interests]).must_be_empty
109
122
  end
123
+
124
+ describe 'Type#deserialize' do
125
+ it 'deserializes single value' do
126
+ type = model.attribute_types['sex']
127
+ result = type.deserialize('male')
128
+ expect(result).must_be_instance_of Enumerize::Value
129
+ expect(result.to_s).must_equal 'male'
130
+ end
131
+
132
+ it 'returns nil for nil single value' do
133
+ type = model.attribute_types['sex']
134
+ result = type.deserialize(nil)
135
+ expect(result).must_be_nil
136
+ end
137
+
138
+ it 'returns nil for invalid single value' do
139
+ type = model.attribute_types['sex']
140
+ result = type.deserialize('invalid')
141
+ expect(result).must_be_nil
142
+ end
143
+
144
+ it 'treats array as invalid for non-multiple attribute' do
145
+ type = model.attribute_types['sex']
146
+ result = type.deserialize(['male', 'female'])
147
+ expect(result).must_be_nil
148
+ end
149
+
150
+ it 'deserializes array of valid values for multiple attribute' do
151
+ type = model.attribute_types['interests']
152
+ result = type.deserialize(['music', 'sports'])
153
+ expect(result).must_be_instance_of Array
154
+ expect(result.map(&:to_s)).must_equal ['music', 'sports']
155
+ end
156
+
157
+ it 'deserializes empty array for multiple attribute' do
158
+ type = model.attribute_types['interests']
159
+ result = type.deserialize([])
160
+ expect(result).must_equal []
161
+ end
162
+
163
+ it 'filters out invalid values from array' do
164
+ type = model.attribute_types['interests']
165
+ result = type.deserialize(['music', 'invalid', 'sports'])
166
+ expect(result.map(&:to_s)).must_equal ['music', 'sports']
167
+ end
168
+
169
+ it 'returns nil for nil array value' do
170
+ type = model.attribute_types['interests']
171
+ result = type.deserialize(nil)
172
+ expect(result).must_be_nil
173
+ end
174
+
175
+ it 'preserves values through serialize/deserialize cycle for single value' do
176
+ type = model.attribute_types['sex']
177
+ user = model.new(sex: 'female')
178
+
179
+ serialized = type.serialize(user.sex)
180
+ expect(serialized).must_equal 'female'
181
+
182
+ deserialized = type.deserialize(serialized)
183
+ expect(deserialized.to_s).must_equal 'female'
184
+ end
185
+
186
+ it 'preserves values through serialize/deserialize cycle for multiple values' do
187
+ type = model.attribute_types['interests']
188
+ user = model.new(interests: ['music', 'programming'])
189
+
190
+ # Serialize the entire set
191
+ serialized = user.interests.map { |v| type.serialize(v) }
192
+ expect(serialized).must_equal ['music', 'programming']
193
+
194
+ # Deserialize back
195
+ deserialized = type.deserialize(serialized)
196
+ expect(deserialized.map(&:to_s)).must_equal ['music', 'programming']
197
+ end
198
+ end
199
+
200
+ describe 'Type#cast' do
201
+ it 'casts single valid value to Enumerize::Value' do
202
+ type = model.attribute_types['sex']
203
+ result = type.cast('male')
204
+ expect(result).must_be_instance_of Enumerize::Value
205
+ expect(result.to_s).must_equal 'male'
206
+ end
207
+
208
+ it 'returns nil for nil single value' do
209
+ type = model.attribute_types['sex']
210
+ result = type.cast(nil)
211
+ expect(result).must_be_nil
212
+ end
213
+
214
+ it 'returns original value for invalid single value' do
215
+ type = model.attribute_types['sex']
216
+ result = type.cast('invalid')
217
+ expect(result).must_equal 'invalid'
218
+ end
219
+
220
+ it 'casts array of valid values for multiple attribute' do
221
+ type = model.attribute_types['interests']
222
+ result = type.cast(['music', 'sports'])
223
+ expect(result).must_be_instance_of Array
224
+ expect(result.map(&:to_s)).must_equal ['music', 'sports']
225
+ end
226
+
227
+ it 'returns empty array as-is for multiple attribute' do
228
+ type = model.attribute_types['interests']
229
+ result = type.cast([])
230
+ expect(result).must_equal []
231
+ end
232
+
233
+ it 'filters out invalid values when at least one is valid' do
234
+ type = model.attribute_types['interests']
235
+ result = type.cast(['music', 'invalid', 'sports'])
236
+ expect(result.map(&:to_s)).must_equal ['music', 'sports']
237
+ end
238
+
239
+ it 'returns array as-is when all values are invalid for multiple attribute' do
240
+ type = model.attribute_types['interests']
241
+ result = type.cast(['invalid1', 'invalid2'])
242
+ expect(result).must_equal ['invalid1', 'invalid2']
243
+ end
244
+
245
+ it 'returns nil for nil value on multiple attribute' do
246
+ type = model.attribute_types['interests']
247
+ result = type.cast(nil)
248
+ expect(result).must_be_nil
249
+ end
250
+ end
251
+
252
+ describe 'Type#cast (model reader)' do
253
+ # Regression coverage for the Rails 8.1 attribute-method module ordering
254
+ # change: when no plain `attribute` precedes `enumerize`, the generated
255
+ # reader outranks enumerize's module, so reads go through #cast and must
256
+ # still return an Enumerize::Value. Without the #cast override the first
257
+ # two examples fail on Rails 8.1 (raw String/Symbol instead of a value).
258
+ it 'wraps a user-assigned value so predicate methods work' do
259
+ user = CastActiveModelUser.new(sex: 'male')
260
+ expect(user.sex).must_be_instance_of Enumerize::Value
261
+ expect(user.sex.male?).must_equal true
262
+ expect(user.sex.female?).must_equal false
263
+ end
264
+
265
+ it 'wraps a value assigned after initialization' do
266
+ user = CastActiveModelUser.new
267
+ user.sex = :female
268
+ expect(user.sex).must_be_instance_of Enumerize::Value
269
+ expect(user.sex.female?).must_equal true
270
+ end
271
+
272
+ it 'wraps the default value' do
273
+ user = CastActiveModelUser.new
274
+ expect(user.role).must_be_instance_of Enumerize::Value
275
+ expect(user.role.user?).must_equal true
276
+ end
277
+
278
+ it 'returns nil when nil is assigned' do
279
+ user = CastActiveModelUser.new(sex: 'male')
280
+ user.sex = nil
281
+ expect(user.sex).must_be_nil
282
+ end
283
+
284
+ it 'keeps an invalid value assignable so inclusion validation still rejects it' do
285
+ # The reader's return for an invalid value is ordering-dependent (nil when
286
+ # enumerize's writer wins on Rails <= 8.0, the raw value when the generated
287
+ # reader wins on Rails 8.1); the portable guarantee is that validation fails.
288
+ user = CastActiveModelUser.new(sex: 'invalid')
289
+ expect(user).wont_be :valid?
290
+ expect(user.errors[:sex]).wont_be :empty?
291
+ end
292
+
293
+ it 'leaves multiple: true attributes as an unmangled Enumerize::Set' do
294
+ user = ActiveModelUser.new(interests: [:music, :programming])
295
+ expect(user.interests).must_be_instance_of Enumerize::Set
296
+ expect(user.interests.to_a).must_equal %w[music programming]
297
+ end
298
+ end
110
299
  end
111
300
 
112
301
  else
@@ -108,10 +108,11 @@ class User < ActiveRecord::Base
108
108
  extend Enumerize
109
109
  include RoleEnum
110
110
 
111
- store :settings, accessors: [:language]
111
+ store :settings, accessors: [:language, 'country_code']
112
112
 
113
113
  enumerize :sex, :in => [:male, :female], scope: :shallow
114
114
  enumerize :language, :in => [:en, :jp]
115
+ enumerize :country_code, :in => [:us, :ca]
115
116
 
116
117
  serialize :interests, type: Array
117
118
  enumerize :interests, :in => [:music, :sports, :dancing, :programming], :multiple => true
@@ -211,6 +212,15 @@ class ActiveRecordTest < Minitest::Spec
211
212
  expect(user.origin).must_be_nil
212
213
  end
213
214
 
215
+ it 'saves stored attribute value for store accessor with string key' do
216
+ User.delete_all
217
+ user = User.new
218
+ user.country_code = :us
219
+ user.save!
220
+ user.reload
221
+ expect(user.country_code).must_equal 'us'
222
+ end
223
+
214
224
  it 'has default value' do
215
225
  expect(User.new.role).must_equal 'user'
216
226
  expect(User.new.attributes['role']).must_equal 'user'
@@ -521,6 +531,25 @@ class ActiveRecordTest < Minitest::Spec
521
531
  expect(user.interests).must_equal uniq_user.interests
522
532
  end
523
533
 
534
+ it 'allows an object to #becomes a non-Enumerize model' do
535
+ class NonEnumerizeUser < ActiveRecord::Base
536
+ self.table_name = "users"
537
+ end
538
+
539
+ class EnumerizeUser < NonEnumerizeUser
540
+ extend Enumerize
541
+
542
+ enumerize :role, in: [:admin, :ghost]
543
+ end
544
+ EnumerizeUser.delete_all
545
+
546
+ user = EnumerizeUser.create(role: :admin)
547
+ expect(user.role).must_equal 'admin'
548
+
549
+ non_enumerize_user = user.becomes(NonEnumerizeUser)
550
+ expect(non_enumerize_user).must_be :valid?
551
+ end
552
+
524
553
  it "doesn't update record" do
525
554
  Document.delete_all
526
555
 
@@ -702,29 +731,129 @@ class ActiveRecordTest < Minitest::Spec
702
731
  expect(admin.account_type).must_equal 'pro'
703
732
  end
704
733
 
705
- if Rails::VERSION::MAJOR >= 6
706
- it 'supports AR#insert_all' do
707
- User.delete_all
734
+ it 'supports AR#insert_all' do
735
+ User.delete_all
708
736
 
709
- User.insert_all([{ sex: :male }])
710
- User.insert_all([{ status: :active }])
711
- User.insert_all([{ interests: [:music, :sports] }])
737
+ User.insert_all([{ sex: :male }])
738
+ User.insert_all([{ status: :active }])
739
+ User.insert_all([{ interests: [:music, :sports] }])
712
740
 
713
- expect(User.exists?(sex: :male)).must_equal true
714
- expect(User.exists?(status: :active)).must_equal true
715
- expect(User.exists?(interests: [:music, :sports])).must_equal true
716
- end
741
+ expect(User.exists?(sex: :male)).must_equal true
742
+ expect(User.exists?(status: :active)).must_equal true
743
+ expect(User.exists?(interests: [:music, :sports])).must_equal true
744
+ end
717
745
 
718
- it 'supports AR#upsert_all' do
719
- User.delete_all
746
+ it 'supports AR#upsert_all' do
747
+ User.delete_all
720
748
 
721
- User.upsert_all([{ sex: :male }])
722
- User.upsert_all([{ status: :active }])
723
- User.upsert_all([{ interests: [:music, :sports] }])
749
+ User.upsert_all([{ sex: :male }])
750
+ User.upsert_all([{ status: :active }])
751
+ User.upsert_all([{ interests: [:music, :sports] }])
724
752
 
725
- expect(User.exists?(sex: :male)).must_equal true
726
- expect(User.exists?(status: :active)).must_equal true
727
- expect(User.exists?(interests: [:music, :sports])).must_equal true
728
- end
753
+ expect(User.exists?(sex: :male)).must_equal true
754
+ expect(User.exists?(status: :active)).must_equal true
755
+ expect(User.exists?(interests: [:music, :sports])).must_equal true
756
+ end
757
+
758
+ it 'Type#cast returns Enumerize::Value for valid symbols' do
759
+ type = User.type_for_attribute('status')
760
+ result = type.cast(:active)
761
+
762
+ expect(result).must_be_instance_of Enumerize::Value
763
+ expect(result.to_s).must_equal 'active'
764
+ expect(result.value).must_equal 1
765
+ end
766
+
767
+ it 'Type#cast returns Enumerize::Value for integers' do
768
+ type = User.type_for_attribute('status')
769
+ result = type.cast(1)
770
+
771
+ expect(result).must_be_instance_of Enumerize::Value
772
+ expect(result.to_s).must_equal 'active'
773
+ expect(result.value).must_equal 1
774
+ end
775
+
776
+ it 'Type#cast returns Enumerize::Value for valid string keys' do
777
+ type = User.type_for_attribute('status')
778
+ result = type.cast('active')
779
+
780
+ expect(result).must_be_instance_of Enumerize::Value
781
+ expect(result.to_s).must_equal 'active'
782
+ expect(result.value).must_equal 1
783
+ end
784
+
785
+ it 'Type#cast returns nil for invalid values' do
786
+ type = User.type_for_attribute('status')
787
+ result = type.cast(:invalid)
788
+
789
+ expect(result).must_be_nil
790
+ end
791
+
792
+ it 'Type#cast preserves existing Enumerize::Value objects' do
793
+ type = User.type_for_attribute('status')
794
+ original_value = User.enumerized_attributes[:status].find_value(:active)
795
+ result = type.cast(original_value)
796
+
797
+ expect(result).must_equal original_value
798
+ end
799
+
800
+ it 'insert_all with mixed value types works correctly' do
801
+ User.delete_all
802
+
803
+ User.insert_all([
804
+ { sex: :male, status: :active },
805
+ { sex: 'female', status: 2 },
806
+ { sex: :male, status: 'blocked' }
807
+ ])
808
+
809
+ users = User.all.to_a
810
+ expect(users.size).must_equal 3
811
+
812
+ expect(users[0].sex).must_equal 'male'
813
+ expect(users[0].status).must_equal 'active'
814
+
815
+ expect(users[1].sex).must_equal 'female'
816
+ expect(users[1].status).must_equal 'blocked'
817
+
818
+ expect(users[2].sex).must_equal 'male'
819
+ expect(users[2].status).must_equal 'blocked'
820
+ end
821
+
822
+ it 'insert_all handles invalid enum values gracefully' do
823
+ User.delete_all
824
+
825
+ User.insert_all([
826
+ { sex: :invalid_sex, status: 999 } # Use invalid integer for status
827
+ ])
828
+
829
+ user = User.first
830
+ expect(user.sex).must_be_nil
831
+ expect(user.status).must_be_nil
832
+ end
833
+
834
+ it 'upsert_all with different value formats works correctly' do
835
+ User.delete_all
836
+
837
+ User.upsert_all([
838
+ { id: 1, sex: :male, status: 1 },
839
+ { id: 2, sex: 'female', status: :blocked }
840
+ ])
841
+
842
+ User.upsert_all([
843
+ { id: 1, sex: 'male', status: :active },
844
+ { id: 3, sex: :female, status: 2 }
845
+ ])
846
+
847
+ users = User.order(:id).to_a
848
+ expect(users.size).must_equal 3
849
+
850
+ expect(users[0].sex).must_equal 'male'
851
+ expect(users[0].status).must_equal 'active'
852
+
853
+ expect(users[1].sex).must_equal 'female'
854
+ expect(users[1].status).must_equal 'blocked'
855
+
856
+ expect(users[2].sex).must_equal 'female'
857
+ expect(users[2].status).must_equal 'blocked'
729
858
  end
730
859
  end
data/test/test_helper.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logger'
3
4
  require 'minitest/autorun'
4
5
  require 'minitest/spec'
5
6
  require 'active_support/core_ext/kernel/reporting'
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: enumerize
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.1
4
+ version: 3.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sergey Nartimov
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-03-25 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: activesupport
@@ -35,13 +34,12 @@ files:
35
34
  - ".github/workflows/ruby.yml"
36
35
  - ".gitignore"
37
36
  - ".rspec"
38
- - ".tool-versions"
39
37
  - CHANGELOG.md
40
38
  - Gemfile.global
41
39
  - Gemfile.mongo_mapper
42
- - Gemfile.rails61
43
- - Gemfile.rails70
44
- - Gemfile.rails71
40
+ - Gemfile.rails72
41
+ - Gemfile.rails80
42
+ - Gemfile.rails81
45
43
  - Gemfile.railsmaster
46
44
  - MIT-LICENSE
47
45
  - README.md
@@ -99,8 +97,12 @@ files:
99
97
  homepage: https://github.com/brainspec/enumerize
100
98
  licenses:
101
99
  - MIT
102
- metadata: {}
103
- post_install_message:
100
+ metadata:
101
+ homepage_uri: https://github.com/brainspec/enumerize
102
+ changelog_uri: https://github.com/brainspec/enumerize/blob/main/CHANGELOG.md
103
+ source_code_uri: https://github.com/brainspec/enumerize
104
+ bug_tracker_uri: https://github.com/brainspec/enumerize/issues
105
+ wiki_uri: https://github.com/brainspec/enumerize/wiki
104
106
  rdoc_options: []
105
107
  require_paths:
106
108
  - lib
@@ -108,15 +110,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
108
110
  requirements:
109
111
  - - ">="
110
112
  - !ruby/object:Gem::Version
111
- version: '2.7'
113
+ version: '3.2'
112
114
  required_rubygems_version: !ruby/object:Gem::Requirement
113
115
  requirements:
114
116
  - - ">="
115
117
  - !ruby/object:Gem::Version
116
118
  version: '0'
117
119
  requirements: []
118
- rubygems_version: 3.5.3
119
- signing_key:
120
+ rubygems_version: 4.0.6
120
121
  specification_version: 4
121
122
  summary: Enumerated attributes with I18n and ActiveRecord/Mongoid/MongoMapper support
122
123
  test_files:
data/.tool-versions DELETED
@@ -1 +0,0 @@
1
- ruby 2.7.8
data/Gemfile.rails61 DELETED
@@ -1,6 +0,0 @@
1
- eval_gemfile('Gemfile.global')
2
-
3
- gem 'minitest', '~> 5.8'
4
- gem 'rails', github: 'rails/rails', branch: '6-1-stable', require: false
5
- gem 'mongoid', github: 'mongodb/mongoid'
6
- gem 'sqlite3', :platform => [:ruby, :mswin, :mingw]
data/Gemfile.rails70 DELETED
@@ -1,8 +0,0 @@
1
- eval_gemfile('Gemfile.global')
2
-
3
- gem 'minitest', '~> 5.8'
4
- gem 'rails', github: 'rails/rails', branch: '7-0-stable', require: false
5
-
6
- gem 'mongoid', github: 'mongodb/mongoid'
7
-
8
- gem 'sqlite3', :platform => [:ruby, :mswin, :mingw]
data/Gemfile.rails71 DELETED
@@ -1,8 +0,0 @@
1
- eval_gemfile('Gemfile.global')
2
-
3
- gem 'minitest', '~> 5.8'
4
- gem 'rails', github: 'rails/rails', branch: '7-1-stable', require: false
5
-
6
- gem 'mongoid', github: 'mongodb/mongoid'
7
-
8
- gem 'sqlite3', :platform => [:ruby, :mswin, :mingw]