Ruby on rails STI,一个控制器

Ruby on rails STI,一个控制器,ruby-on-rails,ruby,ruby-on-rails-3,Ruby On Rails,Ruby,Ruby On Rails 3,我是rails的新手,我被这个设计问题困住了,这个问题可能很容易解决,但我什么都没有得到: 我有两种不同的广告:亮点广告和特价广告。它们都有相同的属性:标题、描述和一个图像(带回形针)。它们也有相同的操作可应用于它们:索引、新建、编辑、创建、更新和销毁 我设置了一个STI,如下所示: def index @ads = Ad.where(:type => params[:type]) end 广告型号:Ad.rb class Ad < ActiveRecord::Base end

我是rails的新手,我被这个设计问题困住了,这个问题可能很容易解决,但我什么都没有得到: 我有两种不同的广告:亮点广告和特价广告。它们都有相同的属性:标题、描述和一个图像(带回形针)。它们也有相同的操作可应用于它们:索引、新建、编辑、创建、更新和销毁

我设置了一个STI,如下所示:

def index
  @ads = Ad.where(:type => params[:type])
end
广告型号:Ad.rb

class Ad < ActiveRecord::Base
end
class-Ad
讨价还价模型:countable.rb

class Bargain < Ad
end
class特价
突出显示模型:Highlight.rb

class Highlight < Ad
end
类突出显示
问题是,我只希望有一个控制器(
AdsController
)根据URL执行我在特价商品或亮点商品上所说的操作,比如www.foo.com/bargains[/…]或www.foo.com/highlights[/…]

例如:

def new
  @ad = Ad.new()
  @ad.type = params[:type]
end
  • 获取www.foo.com/highlights=>所有精彩广告的列表
  • 获取www.foo.com/highlights/new=>表单以创建新的突出显示 等等
我该怎么做


谢谢

首先。添加一些新路线:

resources :highlights, :controller => "ads", :type => "Highlight"
resources :bargains, :controller => "ads", :type => "Bargain"
并修复
AdsController
中的一些操作。例如:

def new
  @ad = Ad.new()
  @ad.type = params[:type]
end
为所有控制器作业寻找最佳方法

就这些。现在您可以转到
localhost:3000/highlights/new
,新的
Highlight
将被初始化

索引操作可以如下所示:

def index
  @ads = Ad.where(:type => params[:type])
end
转到
localhost:3000/highlights
将显示突出显示列表。
交易方式相同:
localhost:3000/交易

URL

<%= link_to 'index', :highlights %>
<%= link_to 'new', [:new, :highlight] %>
<%= link_to 'edit', [:edit, @ad] %>
<%= link_to 'destroy', @ad, :method => :delete %>

:删除%>
对于多态性:)



fl00r有一个很好的解决方案,不过我会做一个调整

这在您的情况下可能需要,也可能不需要。这取决于STI模型中正在发生的行为变化,尤其是验证和生命周期挂钩

将私有方法添加到控制器,以将类型参数转换为要使用的实际类常量:

def ad_type
  params[:type].constantize
end
然而,上述情况是不安全的。添加类型的白名单:

def ad_types
  [MyType, MyType2]
end

def ad_type
  params[:type].constantize if params[:type].in? ad_types
end
有关rails constantize方法的更多信息,请参见:

然后在控制器操作中,您可以执行以下操作:

def new
  ad_type.new
end

def create
  ad_type.new(params)
  # ...
end

def index
  ad_type.all
end

现在您使用的是具有正确行为的实际类,而不是具有属性类型集的父类。

我只想包含此链接,因为有许多有趣的技巧都与此主题相关


[使用完全有效的更简单解决方案重写:]

在重复其他答案的基础上,我为具有单表继承的单控制器提出了以下解决方案,该解决方案在Rails 4.1中适用于强参数。如果输入了无效的类型,仅将:type作为允许的参数,会导致
ActiveRecord::Subclass NotFound
错误。此外,不会更新类型,因为SQL查询显式查找旧类型。相反,
:type
需要使用update\u列单独更新,如果它与当前设置的内容不同并且是有效类型。还要注意的是,我已经成功地将所有类型列表晾干了

# app/models/company.rb
class Company < ActiveRecord::Base
  COMPANY_TYPES = %w[Publisher Buyer Printer Agent]
  validates :type, inclusion: { in: COMPANY_TYPES,
    :message => "must be one of: #{COMPANY_TYPES.join(', ')}" }
end

Company::COMPANY_TYPES.each do |company_type|
  string_to_eval = <<-heredoc
    class #{company_type} < Company
      def self.model_name  # http://stackoverflow.com/a/12762230/1935918
        Company.model_name
      end
    end
  heredoc
  eval(string_to_eval, TOPLEVEL_BINDING)
end
及路线:

# config/routes.rb
Rails.application.routes.draw do
  resources :companies
  Company::COMPANY_TYPES.each do |company_type|
    resources company_type.underscore.to_sym, type: company_type, controller: 'companies', path: 'companies'
  end
  root 'companies#index'
最后,我建议使用gem和设置脚手架来使用与STI兼容的responders_控制器。脚手架的配置为:

# config/application.rb
    config.generators do |g|
      g.scaffold_controller "responders_controller"
    end

我知道这是一个老生常谈的问题,这里有一个我喜欢的模式,其中包括来自@flOOr和@Alan_Peabody的答案。(在Rails 4.2中测试,可能在Rails 5中工作)

在模型中,在启动时创建白名单。在dev中,必须立即加载

class Ad < ActiveRecord::Base
    Rails.application.eager_load! if Rails.env.development?
    TYPE_NAMES = self.subclasses.map(&:name)
    #You can add validation like the answer by @dankohn
end
class-Ad
现在,我们可以在任何控制器中引用此白名单来构建正确的范围,也可以在窗体上的:type select等集合中引用此白名单

class AdsController < ApplicationController
    before_action :set_ad, :only => [:show, :compare, :edit, :update, :destroy]

    def new
        @ad = ad_scope.new
    end

    def create
        @ad = ad_scope.new(ad_params)
        #the usual stuff comes next...
    end

    private
    def set_ad
        #works as normal but we use our scope to ensure subclass
        @ad = ad_scope.find(params[:id])
    end

    #return the scope of a Ad STI subclass based on params[:type] or default to Ad
    def ad_scope
        #This could also be done in some kind of syntax that makes it more like a const.
        @ad_scope ||= params[:type].try(:in?, Ad::TYPE_NAMES) ? params[:type].constantize : Ad
    end

    #strong params check works as expected
    def ad_params
        params.require(:ad).permit({:foo})
    end
end
类AdsController[:显示,:比较,:编辑,:更新,:销毁]
def新
@ad=ad_范围。新建
结束
def创建
@ad=ad_范围。新建(ad_参数)
#接下来是平常的事情。。。
结束
私有的
def set_ad
#正常工作,但我们使用范围来确保子类
@ad=ad_scope.find(参数[:id])
结束
#基于params[:type]或默认值将Ad STI子类的范围返回给Ad
def ad_范围
#这也可以通过某种语法来实现,使其更像一个常量。
@ad|scope | |=参数[:type]。是否尝试(:in?,ad::type|name)?参数[:类型]。常量化:Ad
结束
#强参数检查按预期工作
def ad_参数
参数require(:ad).permit({:foo})
结束
结束
我们需要处理表单,因为不管对象的实际:类型如何,路由都应该发送到基类控制器。为此,我们使用“been”诱使表单生成器进行正确的路由,并使用:as指令强制输入名称也是基类。这种组合允许我们使用未修改的路由(参考资料:ads)以及对从表单返回的参数[:ad]的强参数检查

#/views/ads/_form.html.erb
<%= form_for(@ad.becomes(Ad), :as => :ad) do |f| %>
#/views/ads/_form.html.erb
:ad)do | f |%>

是!!谢谢,效果很好!!我知道有一种方法可以干爽而平静地做这件事。我现在遇到的唯一问题是如何根据广告类型正确设置路径和URL。我想我可以做的只是检查类型值,但我不觉得,非常聪明。。。有什么想法吗?好了,我已经完成了,我已经在视图中设置了路径,以及Alan的想法(就在下面)根据URL使用适当的类(突出显示或特价)。。。我不完全相信,但它是有效的,看起来还不错。嗯。。。我不知道那件事。。。如果有一个控制器使用sames视图,在本例中为ads_控制器,则不应指定:高光,