ruby on rails - Single Table Inheritance or Type Table -
i facing design decision cannot solve. in application user have ability create campaign set of different campaign types available them.
originally, implemented creating campaign , campaigntype model campaign has campaign_type_id attribute know type of campaign was.
i seeded database possible campaigntype models. allows me fetch campaigntype's , display them options users when creating campaign.
i looking refactor because in solution stuck using switch or if/else blocks check type campaign before performing logic (no subclasses).
the alternative rid of campaigntype table , use simple type attribute on campaign model. allows me create subclasses of campaign , rid of switch , if/else blocks.
the problem approach still need able list available campaign types users. means need iterate campaign.subclasses classes. works except means need add bunch of attributes each subclass methods displaying in ui.
original
campaigntype.create! :fa_icon => "fa-line-chart", :avatar=> "spend.png", :name => "spend based", :short_description => "spend x y"
in sti
class spendbasedcampaign < campaign def name "spend based" end def fa_icon "fa-line-chart" end def avatar "spend.png" end end
neither way feels right me. best approach problem?
a not performant solution using phantom methods. technique works ruby >= 2.0, because since 2.0, unbound methods modules can bound object, while in earlier versions, unbound method can bound objects kind_of?
class defining method.
# 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
update
use class macros improve performance, , keep models clean possible hiding nasty things concerns , builder.
this model class:
# app/models/campaign.rb class campaign < activerecord::base include campaignattributes enum :campaign_type => [:spend_based, ...] campaign_attr :name, :fa_icon, :avatar, ... end
and campaign type definition:
# app/models/campaigns/spend_based.rb campaigns.build 'spendbased' name 'spend based' fa_icon 'fa-line-chart' avatar 'spend.png' end
a concern providing campaign_attr
model class:
# app/models/concerns/campaign_attributes.rb module campaignattributes extend activesupport::concern module classmethods private def campaign_attr(*names) names.each |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
and finally, module builder:
# 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
Comments
Post a Comment