我正面临一个我无法解决的设计决定。在该应用程序中,用户可以从一组可用的不同广告系列类型中创建广告系列。
最初,我通过创建广告系列和CampaignType模型来实现此功能,其中广告系列具有campaign_type_id属性,以了解其所属的广告系列类型。
我使用可能的CampaignType模型播种数据库。这样,我就可以获取所有CampaignType,并在创建广告系列时将其显示为用户选项。
我当时正在寻求重构,因为在这个解决方案中,我在使用switch或if / else块时无法检查广告系列在执行逻辑之前的类型(没有子类)。
另一种方法是删除CampaignType表并在Campaign模型上使用简单的type属性。这允许我创建Campaign的Subclasses并摆脱switch和if / else阻止。
此方法存在的问题是我仍然需要能够向用户列出所有可用的广告系列类型。这意味着我需要迭代Campaign.subclasses来获取类。这有效,除了它还意味着我需要为每个子类添加一堆属性作为在UI中显示的方法。
原始
CampaignType.create! :fa_icon => "fa-line-chart", :avatar=> "spend.png", :name => "Spend Based", :short_description => "Spend X Get Y"
在STI
class SpendBasedCampaign < Campaign
def name
"Spend Based"
end
def fa_icon
"fa-line-chart"
end
def avatar
"spend.png"
end
end
这两种方式都不适合我。解决这个问题的最佳方法是什么?
答案 0 :(得分:0)
使用幻像方法的不太高效的解决方案。此技术仅适用于Ruby&gt; = 2.0,因为从2.0开始,模块中的未绑定方法可以绑定到任何对象,而在早期版本中,任何未绑定方法只能绑定到定义该类的对象kind_of?
。方法
# app/models/campaign.rb
class Campaign < ActiveRecord::Base
enum :campaign_type => [:spend_based, ...]
def method_missing(name, *args, &block)
campaign_type_module.instance_method(name).bind(self).call
rescue NameError
super
end
def respond_to_missing?(name, include_private=false)
super || campaign_type_module.instance_methods(include_private).include?(name)
end
private
def campaign_type_module
Campaigns.const_get(campaign_type.camelize)
end
end
# app/models/campaigns/spend_based.rb
module Campaigns
module SpendBased
def name
"Spend Based"
end
def fa_icon
"fa-line-chart"
end
def avatar
"spend.png"
end
end
end
使用类宏来提高性能,并通过向关注点和构建器隐藏讨厌的内容来尽可能保持模型的清洁。
这是你的模特课:
# app/models/campaign.rb
class Campaign < ActiveRecord::Base
include CampaignAttributes
enum :campaign_type => [:spend_based, ...]
campaign_attr :name, :fa_icon, :avatar, ...
end
这是您的广告系列类型定义:
# app/models/campaigns/spend_based.rb
Campaigns.build 'SpendBased' do
name 'Spend Based'
fa_icon 'fa-line-chart'
avatar 'spend.png'
end
向您的模型类提供campaign_attr
的问题:
# app/models/concerns/campaign_attributes.rb
module CampaignAttributes
extend ActiveSupport::Concern
module ClassMethods
private
def campaign_attr(*names)
names.each do |name|
class_eval <<-EOS, __FILE__, __LINE__ + 1
def #{name}
Campaigns.const_get(campaign_type.camelize).instance_method(:#{name}).bind(self).call
end
EOS
end
end
end
end
最后,模块构建器:
# app/models/campaigns/builder.rb
module Campaigns
class Builder < BasicObject
def initialize
@mod = ::Module.new
end
def method_missing(name, *args)
value = args.shift
@mod.send(:define_method, name) { value }
end
def build(&block)
instance_eval &block
@mod
end
end
def self.build(module_name, &block)
const_set module_name, Builder.new.build(&block)
end
end