单表继承或类型表

时间:2015-06-02 20:43:38

标签: ruby-on-rails ruby inheritance

我正面临一个我无法解决的设计决定。在该应用程序中,用户可以从一组可用的不同广告系列类型中创建广告系列。

最初,我通过创建广告系列和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

这两种方式都不适合我。解决这个问题的最佳方法是什么?

1 个答案:

答案 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