Skip to content

Commit ee7cf8c

Browse files
authored
Merge pull request rails#41328 from kamipo/avoid-leading-_-from-reserved-options
Allow new syntax for `enum` to avoid leading `_` from reserved options
2 parents bc9a4c5 + 0618d2d commit ee7cf8c

File tree

4 files changed

+100
-23
lines changed

4 files changed

+100
-23
lines changed

activerecord/CHANGELOG.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
* Allow new syntax for `enum` to avoid leading `_` from reserved options.
2+
3+
Before:
4+
5+
```ruby
6+
class Book < ActiveRecord::Base
7+
enum status: [ :proposed, :written ], _prefix: true, _scopes: false
8+
enum cover: [ :hard, :soft ], _suffix: true, _default: :hard
9+
end
10+
```
11+
12+
After:
13+
14+
```ruby
15+
class Book < ActiveRecord::Base
16+
enum :status, [ :proposed, :written ], prefix: true, scopes: false
17+
enum :cover, [ :hard, :soft ], suffix: true, default: :hard
18+
end
19+
```
20+
21+
*Ryuta Kamizono*
22+
123
* Add `ActiveRecord::Relation#load_async`.
224

325
This method schedules the query to be performed asynchronously from a thread pool.

activerecord/lib/active_record/enum.rb

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
# frozen_string_literal: true
22

3+
require "active_support/core_ext/hash/slice"
34
require "active_support/core_ext/object/deep_dup"
45

56
module ActiveRecord
67
# Declare an enum attribute where the values map to integers in the database,
78
# but can be queried by name. Example:
89
#
910
# class Conversation < ActiveRecord::Base
10-
# enum status: [ :active, :archived ]
11+
# enum :status, [ :active, :archived ]
1112
# end
1213
#
1314
# # conversation.update! status: 0
@@ -41,16 +42,16 @@ module ActiveRecord
4142
# Conversation.where(status: [:active, :archived])
4243
# Conversation.where.not(status: :active)
4344
#
44-
# Defining scopes can be disabled by setting +:_scopes+ to +false+.
45+
# Defining scopes can be disabled by setting +:scopes+ to +false+.
4546
#
4647
# class Conversation < ActiveRecord::Base
47-
# enum status: [ :active, :archived ], _scopes: false
48+
# enum :status, [ :active, :archived ], scopes: false
4849
# end
4950
#
50-
# You can set the default enum value by setting +:_default+, like:
51+
# You can set the default enum value by setting +:default+, like:
5152
#
5253
# class Conversation < ActiveRecord::Base
53-
# enum status: [ :active, :archived ], _default: "active"
54+
# enum :status, [ :active, :archived ], default: :active
5455
# end
5556
#
5657
# conversation = Conversation.new
@@ -60,7 +61,7 @@ module ActiveRecord
6061
# database integer with a hash:
6162
#
6263
# class Conversation < ActiveRecord::Base
63-
# enum status: { active: 0, archived: 1 }
64+
# enum :status, active: 0, archived: 1
6465
# end
6566
#
6667
# Note that when an array is used, the implicit mapping from the values to database
@@ -85,14 +86,14 @@ module ActiveRecord
8586
#
8687
# Conversation.where("status <> ?", Conversation.statuses[:archived])
8788
#
88-
# You can use the +:_prefix+ or +:_suffix+ options when you need to define
89+
# You can use the +:prefix+ or +:suffix+ options when you need to define
8990
# multiple enums with same values. If the passed value is +true+, the methods
9091
# are prefixed/suffixed with the name of the enum. It is also possible to
9192
# supply a custom value:
9293
#
9394
# class Conversation < ActiveRecord::Base
94-
# enum status: [:active, :archived], _suffix: true
95-
# enum comments_status: [:active, :inactive], _prefix: :comments
95+
# enum :status, [ :active, :archived ], suffix: true
96+
# enum :comments_status, [ :active, :inactive ], prefix: :comments
9697
# end
9798
#
9899
# With the above example, the bang and predicate methods along with the
@@ -158,17 +159,16 @@ def assert_valid_value(value)
158159
attr_reader :name, :mapping
159160
end
160161

161-
def enum(definitions)
162-
prefix = definitions.delete(:_prefix)
163-
suffix = definitions.delete(:_suffix)
164-
scopes = definitions.delete(:_scopes) != false
162+
def enum(name = nil, values = nil, **options)
163+
if name
164+
values, options = options, {} unless values
165+
return _enum(name, values, **options)
166+
end
165167

166-
default = {}
167-
default[:default] = definitions.delete(:_default) if definitions.key?(:_default)
168+
definitions = options.slice!(:_prefix, :_suffix, :_scopes, :_default)
169+
options.transform_keys! { |key| :"#{key[1..-1]}" }
168170

169-
definitions.each do |name, values|
170-
_enum(name, values, prefix: prefix, suffix: suffix, scopes: scopes, **default)
171-
end
171+
definitions.each { |name, values| _enum(name, values, **options) }
172172
end
173173

174174
private

activerecord/test/cases/enum_test.rb

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -639,7 +639,7 @@ def self.name; "Book"; end
639639
assert_equal "published", klass.new.status
640640
end
641641

642-
test "overloaded default" do
642+
test "overloaded default by :_default" do
643643
klass = Class.new(ActiveRecord::Base) do
644644
self.table_name = "books"
645645
enum status: [:proposed, :written, :published], _default: :published
@@ -648,7 +648,7 @@ def self.name; "Book"; end
648648
assert_equal "published", klass.new.status
649649
end
650650

651-
test "scopes can be disabled" do
651+
test "scopes can be disabled by :_scopes" do
652652
klass = Class.new(ActiveRecord::Base) do
653653
self.table_name = "books"
654654
enum status: [:proposed, :written], _scopes: false
@@ -657,6 +657,61 @@ def self.name; "Book"; end
657657
assert_raises(NoMethodError) { klass.proposed }
658658
end
659659

660+
test "overloaded default by :default" do
661+
klass = Class.new(ActiveRecord::Base) do
662+
self.table_name = "books"
663+
enum :status, [:proposed, :written, :published], default: :published
664+
end
665+
666+
assert_equal "published", klass.new.status
667+
end
668+
669+
test "scopes can be disabled by :scopes" do
670+
klass = Class.new(ActiveRecord::Base) do
671+
self.table_name = "books"
672+
enum :status, [:proposed, :written], scopes: false
673+
end
674+
675+
assert_raises(NoMethodError) { klass.proposed }
676+
end
677+
678+
test "query state by predicate with :prefix" do
679+
klass = Class.new(ActiveRecord::Base) do
680+
self.table_name = "books"
681+
enum :status, { proposed: 0, written: 1 }, prefix: true
682+
enum :last_read, { unread: 0, reading: 1, read: 2 }, prefix: :being
683+
end
684+
685+
book = klass.new
686+
assert_respond_to book, :status_proposed?
687+
assert_respond_to book, :being_unread?
688+
end
689+
690+
test "query state by predicate with :suffix" do
691+
klass = Class.new(ActiveRecord::Base) do
692+
self.table_name = "books"
693+
enum :cover, { hard: 0, soft: 1 }, suffix: true
694+
enum :difficulty, { easy: 0, medium: 1, hard: 2 }, suffix: :to_read
695+
end
696+
697+
book = klass.new
698+
assert_respond_to book, :hard_cover?
699+
assert_respond_to book, :easy_to_read?
700+
end
701+
702+
test "option names can be used as label" do
703+
klass = Class.new(ActiveRecord::Base) do
704+
self.table_name = "books"
705+
enum :status, default: 0, scopes: 1, prefix: 2, suffix: 3
706+
end
707+
708+
book = klass.new
709+
assert_predicate book, :default?
710+
assert_not_predicate book, :scopes?
711+
assert_not_predicate book, :prefix?
712+
assert_not_predicate book, :suffix?
713+
end
714+
660715
test "scopes are named like methods" do
661716
klass = Class.new(ActiveRecord::Base) do
662717
self.table_name = "cats"

guides/source/active_record_querying.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class Order < ApplicationRecord
6262
belongs_to :customer
6363
has_and_belongs_to_many :books, join_table: 'books_orders'
6464

65-
enum status: [:shipped, :being_packed, :complete, :cancelled]
65+
enum :status, [:shipped, :being_packed, :complete, :cancelled]
6666

6767
scope :created_before, ->(time) { where('created_at < ?', time) }
6868
end
@@ -73,7 +73,7 @@ class Review < ApplicationRecord
7373
belongs_to :customer
7474
belongs_to :book
7575

76-
enum state: [:not_reviewed, :published, :hidden]
76+
enum :state, [:not_reviewed, :published, :hidden]
7777
end
7878
```
7979

@@ -1769,7 +1769,7 @@ For example, given this [`enum`][] declaration:
17691769

17701770
```ruby
17711771
class Order < ApplicationRecord
1772-
enum status: [:shipped, :being_packaged, :complete, :cancelled]
1772+
enum :status, [:shipped, :being_packaged, :complete, :cancelled]
17731773
end
17741774
```
17751775

0 commit comments

Comments
 (0)