在 Parent 的验证中使用 Child 的常量
Using a Child's constant within a Parent's Validation
使用下面的代码我可以访问 child 的常量 (ADDRESS_FIELDS
) 在 initialize
方法中没有问题(通过使用 self.class::ADDRESS_FIELDS
)但是我无法在验证中访问它(得到 NameError: uninitialized constant Class::ADDRESS_FIELDS
)。关于如何在 parent 验证中使用 child 的常量有什么想法吗?还有 PaymentType
的其他 children,他们自己的值为 ADDRESS_FIELDS
。
class PaymentType < ActiveRecord::Base
attr_accessible :address
validates :address, hash_key: { presence: self.class::ADDRESS_FIELDS }
def initialize(attributes = {}, options = {})
super
return self if address.present?
address = {}
self.class::ADDRESS_FIELDS.each do |field|
address[field] = nil
end
self.address = address
end
end
class WireTransfer < PaymentType
ADDRESS_FIELDS = %i(first_name last_name bank_name routing_number account_number)
end
到处都用全名来指代它:
WireTransfer::ADDRESS_FIELDS
或者在子模型中,您可以简单地使用:
ADDRESS_FIELDS
无需预先添加 self.class
昨天很高兴 chatting 和你在一起。回顾一下,您将 validates
调用放入 PaymentType
的动机是让您的代码变干(因为它在 PaymentType
的所有 children 中都是相同的)。
问题是 Ruby 在加载 WireTransfer
之前加载 PaymentType
(我相信是由于继承)所以 validates
找不到 ADDRESS_FIELDS
(因为它是在尚未加载的 WireTransfer
上定义的)。这是下面 RSpec 测试中的第一个测试。
rspec 'spec/stack_overflow/child_constant_parent_validation_spec.rb' -fd
Using a Child's Constant within a Parent's Validation
when 'validates' in parent
raises error
现在,您可以在每个 child 中放入 validates
。但是,这有点糟糕,因为你必须在每个 child 中定义它——但它在所有 children 中都是相同的。所以,你并不像你想的那样干。这是下面的第二个测试。
rspec 'spec/stack_overflow/child_constant_parent_validation_spec.rb' -fd
Using a Child's Constant within a Parent's Validation
when 'validates' in parent
raises error
when 'validates' in child
doesn't raise an error
has the correct class methods
has the correct instance methods
kinda sucks because 'validates' has to be defined in every child.
那么,你注定要湿漉漉的吗?不必要。你可以把你的 validates
放在一个模块中,这样你就可以定义一次并在任何地方使用它。然后,您可以将该模块包含在您的 children 类 中。诀窍是 (1) 使用 included
挂钩并访问 base::ADDRESS_FIELDS
,以及 (2) 在 [=] 中设置 ADDRESS_FIELDS
之后确保 include
模块45=]。这是下面的第三个测试。
rspec 'spec/stack_overflow/child_constant_parent_validation_spec.rb' -fd
Using a Child's Constant within a Parent's Validation
when 'validates' in parent
raises error
when 'validates' in child
doesn't raise an error
has the correct class methods
has the correct instance methods
kinda sucks because 'validates' has to be defined in every child.
when 'validates' in module
doesn't raise an error
has the correct class methods
has the correct instance methods
is a little better because you can define 'validates' once and use in all children
Finished in 0.00811 seconds (files took 0.1319 seconds to load)
9 examples, 0 failures
当然,您仍然需要记住在每个 child 中包含该模块,但这应该不会太糟糕。比到处定义 validates
更好。
完成所有操作后,您的 类 可能看起来像:
class PaymentType
class << self
def a_useful_class_method_from_payment_base; end
end
def a_useful_instance_method_from_payment_base; end
end
module PaymentTypeValidations
def self.included(base)
validates :address, hash_key: { presence: base::ADDRESS_FIELDS }
end
end
class WireTransfer < PaymentType
ADDRESS_FIELDS = %i(first_name last_name bank_name routing_number account_number)
include PaymentTypeValidations
end
class Bitcoin < PaymentType
ADDRESS_FIELDS = %i(wallet_address)
include PaymentTypeValidations
end
我把整个 RSpec 测试放在下面,以防你想 运行 自己测试。
RSpec.describe "Using a Child's Constant within a Parent's Validation " do
before(:all) do
module Validations
def validates(field, options={})
define_method("valid?") do
end
define_method("valid_#{field}?") do
end
end
end
module PaymentType
class Base
extend Validations
class << self
def a_useful_class_method_from_payment_base; end
end
def a_useful_instance_method_from_payment_base; end
end
end
module WireTransfer
end
end
context "when 'validates' in parent" do
it "raises error" do
expect{
class PaymentType::WithValidates < PaymentType::Base
validates :address, hash_key: { presence: self::ADDRESS_FIELDS }
end
class WireTransfer::Base < PaymentType::WithValidation
ADDRESS_FIELDS = %i(first_name last_name bank_name routing_number account_number)
end
}.to raise_error(NameError)
end
end
context "when 'validates' in child" do
it "doesn't raise an error" do
expect{
class PaymentType::WithoutValidates < PaymentType::Base
end
class WireTransfer::WithValidates < PaymentType::WithoutValidates
ADDRESS_FIELDS = %i(first_name last_name bank_name routing_number account_number)
validates :address, hash_key: { presence: self::ADDRESS_FIELDS }
end
}.to_not raise_error
end
it "has the correct class methods" do
expect(WireTransfer::WithValidates).to respond_to("a_useful_class_method_from_payment_base")
end
it "has the correct instance methods" do
wire_transfer = WireTransfer::WithValidates.new
["valid?","valid_address?","a_useful_instance_method_from_payment_base"].each do |method|
expect(wire_transfer).to respond_to(method)
end
end
it "kinda sucks because 'validates' has to be defined in every child." do
module Bitcoin
class Base < PaymentType::WithoutValidates
end
end
bitcoin = Bitcoin::Base.new
["valid?","valid_address?"].each do |method|
expect(bitcoin).to_not respond_to(method)
end
end
end
context "when 'validates' in module" do
it "doesn't raise an error" do
expect{
module PaymentTypeValidations
extend Validations
def self.included(base)
validates :address, hash_key: { presence: base::ADDRESS_FIELDS }
end
end
class WireTransfer::IncludingValidationsModule < PaymentType::WithoutValidates
ADDRESS_FIELDS = %i(first_name last_name bank_name routing_number account_number)
include PaymentTypeValidations
end
}.to_not raise_error
end
it "has the correct class methods" do
expect(WireTransfer::IncludingValidationsModule).to respond_to("a_useful_class_method_from_payment_base")
end
it "has the correct instance methods" do
wire_transfer = WireTransfer::IncludingValidationsModule.new
["valid?","valid_address?","a_useful_instance_method_from_payment_base"].each do |method|
expect(wire_transfer).to respond_to(method)
end
end
it "is a little better because you can define 'validates' once and use in all children" do
class Bitcoin::IncludingValidationsModule < PaymentType::WithoutValidates
ADDRESS_FIELDS = %i(wallet_address)
include PaymentTypeValidations
end
bitcoin = Bitcoin::IncludingValidationsModule.new
["valid?","valid_address?","a_useful_instance_method_from_payment_base"].each do |method|
expect(bitcoin).to respond_to(method)
end
end
end
end
使用下面的代码我可以访问 child 的常量 (ADDRESS_FIELDS
) 在 initialize
方法中没有问题(通过使用 self.class::ADDRESS_FIELDS
)但是我无法在验证中访问它(得到 NameError: uninitialized constant Class::ADDRESS_FIELDS
)。关于如何在 parent 验证中使用 child 的常量有什么想法吗?还有 PaymentType
的其他 children,他们自己的值为 ADDRESS_FIELDS
。
class PaymentType < ActiveRecord::Base
attr_accessible :address
validates :address, hash_key: { presence: self.class::ADDRESS_FIELDS }
def initialize(attributes = {}, options = {})
super
return self if address.present?
address = {}
self.class::ADDRESS_FIELDS.each do |field|
address[field] = nil
end
self.address = address
end
end
class WireTransfer < PaymentType
ADDRESS_FIELDS = %i(first_name last_name bank_name routing_number account_number)
end
到处都用全名来指代它:
WireTransfer::ADDRESS_FIELDS
或者在子模型中,您可以简单地使用:
ADDRESS_FIELDS
无需预先添加 self.class
昨天很高兴 chatting 和你在一起。回顾一下,您将 validates
调用放入 PaymentType
的动机是让您的代码变干(因为它在 PaymentType
的所有 children 中都是相同的)。
问题是 Ruby 在加载 WireTransfer
之前加载 PaymentType
(我相信是由于继承)所以 validates
找不到 ADDRESS_FIELDS
(因为它是在尚未加载的 WireTransfer
上定义的)。这是下面 RSpec 测试中的第一个测试。
rspec 'spec/stack_overflow/child_constant_parent_validation_spec.rb' -fd
Using a Child's Constant within a Parent's Validation
when 'validates' in parent
raises error
现在,您可以在每个 child 中放入 validates
。但是,这有点糟糕,因为你必须在每个 child 中定义它——但它在所有 children 中都是相同的。所以,你并不像你想的那样干。这是下面的第二个测试。
rspec 'spec/stack_overflow/child_constant_parent_validation_spec.rb' -fd
Using a Child's Constant within a Parent's Validation
when 'validates' in parent
raises error
when 'validates' in child
doesn't raise an error
has the correct class methods
has the correct instance methods
kinda sucks because 'validates' has to be defined in every child.
那么,你注定要湿漉漉的吗?不必要。你可以把你的 validates
放在一个模块中,这样你就可以定义一次并在任何地方使用它。然后,您可以将该模块包含在您的 children 类 中。诀窍是 (1) 使用 included
挂钩并访问 base::ADDRESS_FIELDS
,以及 (2) 在 [=] 中设置 ADDRESS_FIELDS
之后确保 include
模块45=]。这是下面的第三个测试。
rspec 'spec/stack_overflow/child_constant_parent_validation_spec.rb' -fd
Using a Child's Constant within a Parent's Validation
when 'validates' in parent
raises error
when 'validates' in child
doesn't raise an error
has the correct class methods
has the correct instance methods
kinda sucks because 'validates' has to be defined in every child.
when 'validates' in module
doesn't raise an error
has the correct class methods
has the correct instance methods
is a little better because you can define 'validates' once and use in all children
Finished in 0.00811 seconds (files took 0.1319 seconds to load)
9 examples, 0 failures
当然,您仍然需要记住在每个 child 中包含该模块,但这应该不会太糟糕。比到处定义 validates
更好。
完成所有操作后,您的 类 可能看起来像:
class PaymentType
class << self
def a_useful_class_method_from_payment_base; end
end
def a_useful_instance_method_from_payment_base; end
end
module PaymentTypeValidations
def self.included(base)
validates :address, hash_key: { presence: base::ADDRESS_FIELDS }
end
end
class WireTransfer < PaymentType
ADDRESS_FIELDS = %i(first_name last_name bank_name routing_number account_number)
include PaymentTypeValidations
end
class Bitcoin < PaymentType
ADDRESS_FIELDS = %i(wallet_address)
include PaymentTypeValidations
end
我把整个 RSpec 测试放在下面,以防你想 运行 自己测试。
RSpec.describe "Using a Child's Constant within a Parent's Validation " do
before(:all) do
module Validations
def validates(field, options={})
define_method("valid?") do
end
define_method("valid_#{field}?") do
end
end
end
module PaymentType
class Base
extend Validations
class << self
def a_useful_class_method_from_payment_base; end
end
def a_useful_instance_method_from_payment_base; end
end
end
module WireTransfer
end
end
context "when 'validates' in parent" do
it "raises error" do
expect{
class PaymentType::WithValidates < PaymentType::Base
validates :address, hash_key: { presence: self::ADDRESS_FIELDS }
end
class WireTransfer::Base < PaymentType::WithValidation
ADDRESS_FIELDS = %i(first_name last_name bank_name routing_number account_number)
end
}.to raise_error(NameError)
end
end
context "when 'validates' in child" do
it "doesn't raise an error" do
expect{
class PaymentType::WithoutValidates < PaymentType::Base
end
class WireTransfer::WithValidates < PaymentType::WithoutValidates
ADDRESS_FIELDS = %i(first_name last_name bank_name routing_number account_number)
validates :address, hash_key: { presence: self::ADDRESS_FIELDS }
end
}.to_not raise_error
end
it "has the correct class methods" do
expect(WireTransfer::WithValidates).to respond_to("a_useful_class_method_from_payment_base")
end
it "has the correct instance methods" do
wire_transfer = WireTransfer::WithValidates.new
["valid?","valid_address?","a_useful_instance_method_from_payment_base"].each do |method|
expect(wire_transfer).to respond_to(method)
end
end
it "kinda sucks because 'validates' has to be defined in every child." do
module Bitcoin
class Base < PaymentType::WithoutValidates
end
end
bitcoin = Bitcoin::Base.new
["valid?","valid_address?"].each do |method|
expect(bitcoin).to_not respond_to(method)
end
end
end
context "when 'validates' in module" do
it "doesn't raise an error" do
expect{
module PaymentTypeValidations
extend Validations
def self.included(base)
validates :address, hash_key: { presence: base::ADDRESS_FIELDS }
end
end
class WireTransfer::IncludingValidationsModule < PaymentType::WithoutValidates
ADDRESS_FIELDS = %i(first_name last_name bank_name routing_number account_number)
include PaymentTypeValidations
end
}.to_not raise_error
end
it "has the correct class methods" do
expect(WireTransfer::IncludingValidationsModule).to respond_to("a_useful_class_method_from_payment_base")
end
it "has the correct instance methods" do
wire_transfer = WireTransfer::IncludingValidationsModule.new
["valid?","valid_address?","a_useful_instance_method_from_payment_base"].each do |method|
expect(wire_transfer).to respond_to(method)
end
end
it "is a little better because you can define 'validates' once and use in all children" do
class Bitcoin::IncludingValidationsModule < PaymentType::WithoutValidates
ADDRESS_FIELDS = %i(wallet_address)
include PaymentTypeValidations
end
bitcoin = Bitcoin::IncludingValidationsModule.new
["valid?","valid_address?","a_useful_instance_method_from_payment_base"].each do |method|
expect(bitcoin).to respond_to(method)
end
end
end
end