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 +4 -4
- data/.github/workflows/ruby.yml +7 -11
- data/.gitignore +1 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile.global +1 -1
- data/Gemfile.mongo_mapper +5 -2
- data/Gemfile.rails72 +8 -0
- data/Gemfile.rails80 +8 -0
- data/Gemfile.rails81 +8 -0
- data/Gemfile.railsmaster +1 -1
- data/README.md +9 -9
- data/enumerize.gemspec +8 -2
- data/lib/enumerize/activemodel.rb +23 -1
- data/lib/enumerize/activerecord.rb +13 -23
- data/lib/enumerize/version.rb +1 -1
- data/spec/enumerize/integrations/rspec/matcher_spec.rb +2 -2
- data/spec/spec_helper.rb +1 -0
- data/test/activemodel_test.rb +189 -0
- data/test/activerecord_test.rb +149 -20
- data/test/test_helper.rb +1 -0
- metadata +13 -12
- data/.tool-versions +0 -1
- data/Gemfile.rails61 +0 -6
- data/Gemfile.rails70 +0 -8
- data/Gemfile.rails71 +0 -8
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aa3e9016f87e07bed5c034618ffb19c15104b334adace5eb5cd7903a2a82cf47
|
|
4
|
+
data.tar.gz: 779be0d6f9644f0ad8019d03bf0c2e1d5638b62c531bd2aef69518f05bf7ae76
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81aea0e826349ad121cfed3a44ee14566fcb7dbba7f78cca5ca5ad007f166edea65b8fdc79e64c441f4249c9f760bafae4918226a07eac54f8ab17a1c80e8f11
|
|
7
|
+
data.tar.gz: 719f1f33563b31f1f8c73d256c55a16d371ebf581ebddcdae60cb7b98ac0de5960ebcc44c55c836749662b73f1733aaeb437bbe4fc3ed0e4cc103bcdd4431493
|
data/.github/workflows/ruby.yml
CHANGED
|
@@ -8,7 +8,7 @@ on:
|
|
|
8
8
|
|
|
9
9
|
jobs:
|
|
10
10
|
test:
|
|
11
|
-
runs-on: ubuntu-
|
|
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.
|
|
35
|
+
ruby-version: ['3.2', '3.3', '3.4', '4.0']
|
|
36
36
|
test: ['minitest', 'rspec']
|
|
37
37
|
gemfile:
|
|
38
|
-
- Gemfile.
|
|
39
|
-
- Gemfile.
|
|
40
|
-
- Gemfile.
|
|
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.
|
|
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@
|
|
57
|
+
- uses: actions/checkout@v7
|
|
62
58
|
- name: Set up Ruby
|
|
63
59
|
uses: ruby/setup-ruby@v1
|
|
64
60
|
with:
|
data/.gitignore
CHANGED
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
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-
|
|
4
|
+
gem 'rails', github: 'rails/rails', branch: '7-2-stable', require: false
|
|
5
5
|
gem 'mongo_mapper'
|
|
6
|
-
|
|
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
data/Gemfile.rails80
ADDED
data/Gemfile.rails81
ADDED
data/Gemfile.railsmaster
CHANGED
data/README.md
CHANGED
|
@@ -46,8 +46,8 @@ Or install it yourself as:
|
|
|
46
46
|
|
|
47
47
|
## Supported Versions
|
|
48
48
|
|
|
49
|
-
- Ruby 3.
|
|
50
|
-
- Rails
|
|
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: {:
|
|
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, :
|
|
329
|
-
enumerize :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, :
|
|
350
|
-
enumerize :role, :
|
|
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, :
|
|
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, :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
25
|
-
attribute(name)
|
|
24
|
+
attribute(name)
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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)
|
data/lib/enumerize/version.rb
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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
data/test/activemodel_test.rb
CHANGED
|
@@ -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
|
data/test/activerecord_test.rb
CHANGED
|
@@ -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
|
-
|
|
706
|
-
|
|
707
|
-
User.delete_all
|
|
734
|
+
it 'supports AR#insert_all' do
|
|
735
|
+
User.delete_all
|
|
708
736
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
737
|
+
User.insert_all([{ sex: :male }])
|
|
738
|
+
User.insert_all([{ status: :active }])
|
|
739
|
+
User.insert_all([{ interests: [:music, :sports] }])
|
|
712
740
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
719
|
-
|
|
746
|
+
it 'supports AR#upsert_all' do
|
|
747
|
+
User.delete_all
|
|
720
748
|
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
749
|
+
User.upsert_all([{ sex: :male }])
|
|
750
|
+
User.upsert_all([{ status: :active }])
|
|
751
|
+
User.upsert_all([{ interests: [:music, :sports] }])
|
|
724
752
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
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
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:
|
|
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:
|
|
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.
|
|
43
|
-
- Gemfile.
|
|
44
|
-
- Gemfile.
|
|
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
|
-
|
|
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
|
|
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:
|
|
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
data/Gemfile.rails70
DELETED