如何在 Rails 6 个模型问题中构建动态枚举?
How to build dynamic enums in Rails 6 model concerns?
我正在尝试构建一个 Bookable
模型关注点,将枚举添加到包含模型,用于跟踪预订阶段:
module Bookable
extend ActiveSupport::Concern
STAGES = {
confirmed: 0,
completed: 1,
cancelled: 2,
issue_raised: 3
}.freeze
included do
enum stage: STAGES.merge(self.extra_stages)
belongs_to :customer
belongs_to :provider
validates :stage, presence: true
def self.extra_stages
{}
end
end
end
STAGES
常量定义了可用的阶段,但是我希望包括模型能够通过覆盖 self.extra_stages
方法来添加特定于它们的阶段。例如,在 Mission
模型中:
class Mission < ApplicationRecord
include Bookable
def self.extra_stages
{
awaiting_estimate: 4,
awaiting_payment: 5,
awaiting_report: 6,
report_sent: 7
}
end
end
然而,这段代码失败了:
$ bundle exec rails c
Loading development environment (Rails 6.0.2.2)
[1] pry(main)> Mission.stages
NoMethodError: undefined method `extra_stages' for Mission (call 'Mission.connection' to establish a connection):Class
Did you mean? extract_associated
from /home/gueorgui/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activerecord-6.0.2.2/lib/active_record/dynamic_matchers.rb:22:in `method_missing'
关于我可能做错了什么的任何线索?提前致谢!
感谢 yzalavin 给我指明了正确的方向(事实上 included
是一个在 include Bookable
之后求值的钩子),我找到了这个解决方案,它的工作方式与我很满意:
module Bookable
extend ActiveSupport::Concern
STAGES = {
confirmed: 0,
completed: 1,
cancelled: 2,
issue_raised: 3
}.freeze
included do |base|
enum stage: STAGES.merge(base.extra_stages)
belongs_to :customer
belongs_to :provider
validates :stage, presence: true
end
class_methods do
def extra_stages
return {} unless defined? self::EXTRA_STAGES
self::EXTRA_STAGES
end
end
end
并且在包含的模型中:
class Mission < ApplicationRecord
EXTRA_STAGES = {
awaiting_estimate: 4,
awaiting_payment: 5,
awaiting_report: 6,
report_sent: 7
}.freeze
include Bookable
# (...)
end
我欢迎对此解决方案进行任何改进,因为它仍然感觉可以简化。
有更简洁的方法来完成我即将介绍的内容,但它会完成您想要做的事情。注意简洁(我删除了您的关联以在我的机器上进行快速抽查):
module Bookable
extend ActiveSupport::Concern
included do
STAGES = {
confirmed: 0,
completed: 1,
cancelled: 2,
issue_raised: 3
}.freeze
end
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.acts_as_bookable_with(extra_stages = {})
include Bookable
enum stage: self::STAGES.merge(extra_stages)
end
end
class Mission < ApplicationRecord
acts_as_bookable_with({
awaiting_estimate: 4,
awaiting_payment: 5,
awaiting_report: 6,
report_sent: 7
})
end
如果你想在 class 上定义这些,它看起来像这样:
module Bookable
extend ActiveSupport::Concern
included do
STAGES = {
confirmed: 0,
completed: 1,
cancelled: 2,
issue_raised: 3
}.freeze
end
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.acts_as_bookable_with(extra_stages)
include Bookable
if extra_stages.is_a?(Symbol)
extra_stages = self.send(extra_stages)
elsif extra_stages.is_a?(Hash)
# do nothing
else
raise TypeError, "can't find extra_stages from #{extra_stages.inspect}"
end
stages = self::STAGES.merge(extra_stages)
enum stage: stages
end
end
class Comment < ApplicationRecord
def self.extra_stages
{
awaiting_estimate: 4,
awaiting_payment: 5,
awaiting_report: 6,
report_sent: 7
}
end
acts_as_bookable_with(:extra_stages)
end
请注意,我们在定义 class 方法后调用 acts_as_bookable_with
。否则,我们将得到未定义的方法错误。
在 ApplicationRecord 中没有很多 "bad"。这不是最理想的方式,但这些 acts_as_*
模块中的大多数无论如何都遵循这个确切的模式并注入 ActiveRecord::Base
.
我正在尝试构建一个 Bookable
模型关注点,将枚举添加到包含模型,用于跟踪预订阶段:
module Bookable
extend ActiveSupport::Concern
STAGES = {
confirmed: 0,
completed: 1,
cancelled: 2,
issue_raised: 3
}.freeze
included do
enum stage: STAGES.merge(self.extra_stages)
belongs_to :customer
belongs_to :provider
validates :stage, presence: true
def self.extra_stages
{}
end
end
end
STAGES
常量定义了可用的阶段,但是我希望包括模型能够通过覆盖 self.extra_stages
方法来添加特定于它们的阶段。例如,在 Mission
模型中:
class Mission < ApplicationRecord
include Bookable
def self.extra_stages
{
awaiting_estimate: 4,
awaiting_payment: 5,
awaiting_report: 6,
report_sent: 7
}
end
end
然而,这段代码失败了:
$ bundle exec rails c
Loading development environment (Rails 6.0.2.2)
[1] pry(main)> Mission.stages
NoMethodError: undefined method `extra_stages' for Mission (call 'Mission.connection' to establish a connection):Class
Did you mean? extract_associated
from /home/gueorgui/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/activerecord-6.0.2.2/lib/active_record/dynamic_matchers.rb:22:in `method_missing'
关于我可能做错了什么的任何线索?提前致谢!
感谢 yzalavin 给我指明了正确的方向(事实上 included
是一个在 include Bookable
之后求值的钩子),我找到了这个解决方案,它的工作方式与我很满意:
module Bookable
extend ActiveSupport::Concern
STAGES = {
confirmed: 0,
completed: 1,
cancelled: 2,
issue_raised: 3
}.freeze
included do |base|
enum stage: STAGES.merge(base.extra_stages)
belongs_to :customer
belongs_to :provider
validates :stage, presence: true
end
class_methods do
def extra_stages
return {} unless defined? self::EXTRA_STAGES
self::EXTRA_STAGES
end
end
end
并且在包含的模型中:
class Mission < ApplicationRecord
EXTRA_STAGES = {
awaiting_estimate: 4,
awaiting_payment: 5,
awaiting_report: 6,
report_sent: 7
}.freeze
include Bookable
# (...)
end
我欢迎对此解决方案进行任何改进,因为它仍然感觉可以简化。
有更简洁的方法来完成我即将介绍的内容,但它会完成您想要做的事情。注意简洁(我删除了您的关联以在我的机器上进行快速抽查):
module Bookable
extend ActiveSupport::Concern
included do
STAGES = {
confirmed: 0,
completed: 1,
cancelled: 2,
issue_raised: 3
}.freeze
end
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.acts_as_bookable_with(extra_stages = {})
include Bookable
enum stage: self::STAGES.merge(extra_stages)
end
end
class Mission < ApplicationRecord
acts_as_bookable_with({
awaiting_estimate: 4,
awaiting_payment: 5,
awaiting_report: 6,
report_sent: 7
})
end
如果你想在 class 上定义这些,它看起来像这样:
module Bookable
extend ActiveSupport::Concern
included do
STAGES = {
confirmed: 0,
completed: 1,
cancelled: 2,
issue_raised: 3
}.freeze
end
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
def self.acts_as_bookable_with(extra_stages)
include Bookable
if extra_stages.is_a?(Symbol)
extra_stages = self.send(extra_stages)
elsif extra_stages.is_a?(Hash)
# do nothing
else
raise TypeError, "can't find extra_stages from #{extra_stages.inspect}"
end
stages = self::STAGES.merge(extra_stages)
enum stage: stages
end
end
class Comment < ApplicationRecord
def self.extra_stages
{
awaiting_estimate: 4,
awaiting_payment: 5,
awaiting_report: 6,
report_sent: 7
}
end
acts_as_bookable_with(:extra_stages)
end
请注意,我们在定义 class 方法后调用 acts_as_bookable_with
。否则,我们将得到未定义的方法错误。
在 ApplicationRecord 中没有很多 "bad"。这不是最理想的方式,但这些 acts_as_*
模块中的大多数无论如何都遵循这个确切的模式并注入 ActiveRecord::Base
.