STI,一个控制器

我是新来的铁轨,我很困扰这个devise问题,这可能很容易解决,但我没有得到任何地方:我有两种不同的广告:亮点和讨价还价。 他们都有相同的属性:标题,描述和一个图像(用回形针)。 他们也有相同的行为来应用他们:索引,新build,编辑,创build,更新和销毁。

我像这样设置了一个STI:

广告模型:ad.rb

class Ad < ActiveRecord::Base end 

讨价还价模型:bargain.rb

 class Bargain < Ad end 

突出显示模型:highlight.rb

 class Highlight < Ad end 

问题是,我想只有一个控制器( AdsController )执行我说的讨价还价或高亮取决于URL的行动,说www.foo.com/bargains [/ …]或www.foo。 COM /亮点[/ …]。

例如:

  • GET www.foo.com/highlights =>所有重点广告的列表。
  • GET www.foo.com/highlights/new =>表单创build一个新的亮点等…

我怎样才能做到这一点?

谢谢!

第一。 添加一些新的路线:

 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/highlightslocalhost:3000/highlights列表将出现。
同类的便宜货: localhost:3000/bargains

等等

URLS

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

为了多态:)

 <%= link_to 'index', @ad.class %> 

fl00r有一个很好的解决scheme,但是我会做一个调整。

你的情况可能会或可能不需要。 这取决于STI模型中的行为正在发生变化,特别是validation和生命周期钩子。

添加一个私有方法到你的控制器来转换你的types参数到你想要使用的实际类常量:

 def ad_type params[:type].constantize end 

以上是不安全的,但是。 添加types的白名单:

 def ad_types [MyType, MyType2] end def ad_type params[:type].constantize if params[:type].in? ad_types end 

更多的轨道constantize方法在这里: http : //api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize

然后在控制器的操作你可以做:

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

现在,您正在使用具有正确行为的实际类,而不是使用设置了属性types的父类。

我只是想包括这个链接,因为有一些有趣的技巧都与这个主题有关。

Alex Reisner – Rails中的单表inheritance

[用更简单的解决scheme重写:]

迭代其他的答案,我已经拿出单一控制器的单一表inheritance与以下解决scheme,良好的Rails 4.1强参数。 只要包括:键入作为允许参数导致ActiveRecord::SubclassNotFound错误,如果input无效types。 此外,types不会更新,因为SQL查询显式查找旧types。 相反,如果:type不同于当前设置并且是有效types,则需要使用update_column单独更新。 还要注意,我已经成功地干掉了所有types的列表。

 # 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 

而在控制器中:

  # app/controllers/companies_controller.rb def update @company = Company.find(params[:id]) # This separate step is required to change Single Table Inheritance types new_type = params[:company][:type] if new_type != @company.type && Company::COMPANY_TYPES.include?(new_type) @company.update_column :type, new_type end @company.update(company_params) respond_with(@company) 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' 

最后,我build议使用响应者 gem并设置脚手架来使用与STI兼容的responders_controller。 configuration脚手架是:

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

我知道这是一个老问题,这里是一个我喜欢的模式,其中包括@flOOr和@Alan_Peabody的答案。 (在Rails 4.2中testing,可能在Rails 5中有效)

在您的模型中,在启动时创build白名单。 在开发中,这一定是急切的加载。

 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 

现在我们可以在任何一个控制器中引用这个白名单来构build正确的范围,也可以在一个表单中select一个types:

 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 

我们需要处理我们的表单,因为路由应该被发送到基类控制器,尽pipe实际的:types的对象。 要做到这一点,我们使用“变成”欺骗表单生成器到正确的路由,而作为指令强制input名称也是基类。 这种组合使我们能够使用未经修改的路线(资源:广告)以及强大的参数检查从表单返回的参数[:ad]。

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