我正在尝试为我的rails应用程序编写更多的模块化代码,因此已经开始在类中包含模块。我对它们的功能有基本的了解,但我发现在保持DRY的同时很难让它们保持灵活性。
这是一个当前的例子。
我有一个名为Contactable的模块。它有两个非常基本的功能。
这是
module Contactable
extend ActiveSupport::Concern
ERROR = 'please ensure necessary fields are in place'
included do
REQUIRED_DATABASE_FIELDS.map { |rdf| raise "#{rdf} not included. #{ERROR}" unless column_names.include?(rdf)}
REQUIRED_INPUT_FIELDS.map { |rif| validates rif.to_sym, presence: true}
end
end
我希望可以联系其他三个模块(可电话,可电子邮件和可寻址),其中包含要求的列数组和要验证的字段。我现在正在研究的是“可寻址的”
module Addressable
extend ActiveSupport::Concern
ERROR = 'please ensure necessary fields are in place'
REQUIRED_DATABASE_FIELDS = %w{address1
address2
address3
town
county
country
postcode}
REQUIRED_INPUT_FIELDS = %w{address1 postcode}
included do
REQUIRED_DATABASE_FIELDS.map { |rdf| raise "#{rdf} not included. #{ERROR}" unless column_names.include?(rdf)}
REQUIRED_INPUT_FIELDS.map { |rif| validates rif.to_sym, presence: true}
end
end
显然这里有重复。但是,如果我将此模块包含在可联系中,我可以避免重复某些操作,但这意味着Contactable将始终包含Phoneable和Emailable。在某些情况下,我可能不想验证或要求这些特征。有没有办法可以实现这种灵活性?
答案 0 :(得分:1)
您可以这样做:
添加 /app/models/concerns/fields_validator.rb
module FieldsValidator
extend ActiveSupport::Concern
class_methods do
def validate_required_attributes
required_attributes.each do |a|
puts "adds validation for #{a}"
validates(a.to_sym, presence: true)
end
end
def load_required_attributes(*_attrs)
puts "loading attrs: #{_attrs.to_s}"
@required_attributes ||=[]
@required_attributes += _attrs
@required_attributes.uniq!
end
def required_attributes
@required_attributes
end
end
end
添加 /app/models/concerns/contact.rb
module Contact
extend ActiveSupport::Concern
include FieldsValidator
included do
puts "include contact..."
load_required_attributes(:product_details, :observations, :offer_details)
end
end
添加 /app/models/concerns/address.rb
module Address
extend ActiveSupport::Concern
include FieldsValidator
included do
puts "include address..."
load_required_attributes(:sku, :amount, :observations)
end
end
在模型中......
class Promotion < ActiveRecord::Base
include Address
include Contact
validate_required_attributes
end
输出:
include address...
loading attrs: [:sku, :amount, :observations]
include contact...
loading attrs: [:product_details, :observations, :offer_details]
adds validation for sku
adds validation for amount
adds validation for observations
adds validation for product_details
adds validation for offer_details
要检查这是否有效......
Promotion.new.save!
"ActiveRecord::RecordInvalid: Validation failed: Sku can't be blank, Amount can't be blank, Observations can't be blank, Product details can't be blank, Offer details can't be blank"
<强>考虑:强>
将您的模块保存在自定义命名空间中。您将遇到现有Addressable
模块的问题。例如:
module MyApp
module Addressable
# code...
end
end
class Promotion < ActiveRecord::Base
include MyApp::Addressable
validate_required_attributes
end
您需要先加载所有属性,然后应用验证。如果你不这样做,你可以在模块共享属性时重复验证。
FieldsValidator
模块答案 1 :(得分:0)
你应该在这里使用单元测试。通过从模型(或它们包含的模块)检查数据库模式,您并没有真正实现任何目标。如果列不存在,则应用程序将抛出NoMethodError或数据库驱动程序错误。
实际上有更好的单元测试可以覆盖你的模型并确保它们按预期工作。
require 'rails_helper'
describe User
# Tests the presence of the database column indirectly.
it { should respond_to :email }
# Explicit test - there a very few good reasons to actually do this.
it "should have the email column" do
expect(User.column_names).to have_key :email
end
end
如果您使用的是RSpec,则可以使用共享示例来减少规格中的重复次数。
# support/example_groups/addressable.rb
require 'spec_helper'
RSpec.shared_examples_for "an addressable" do
it { should respond_to :address1 }
it { should respond_to :address2 }
it { should respond_to :address3 }
it { should respond_to :county }
it { should respond_to :postcode }
# ...
end
require 'rails_helper'
require 'support/example_groups/addressable'
describe User
it_should_behave_like "an addressable"
end
有关如何使用test_unit / minitest实现相同功能的示例,请参阅How do i get RSpec's shared examples like behavior in Ruby Test::Unit?。