我的Rails视图和控制器上到处是redirect_to
, link_to
和form_for
方法调用。 有时候link_to
和redirect_to
是在所链接的路径明确的(如link_to 'New Person', new_person_path
),但很多时候的路径是隐式的(如link_to 'Show', person
)。
我补充一些单个表继承(STI),以我的模型(比如Employee < Person
),所有这些方法打破了子类的实例(比如Employee
); 当轨执行link_to @person
,它与误差undefined method employee_path' for #<#<Class:0x000001022bcd40>:0x0000010226d038>
轨道正在寻找由对象,这是雇员的类名称定义的路由。 这些雇员的路由没有被定义,并且存在这样的操作不要么没有定义雇员控制器。
这个问题已经被问过:
- 在StackOverflow上 ,答案是编辑在您的整个代码库中的link_to等每一个实例,并明确规定路径
- 在StackOverflow上再次,两个人建议使用
routes.rb
子类资源映射到父类( map.resources :employees, :controller => 'people'
)。 在相同的,所以问题最多的回答暗示型铸造每一个实例对象使用的代码库.becomes
- 然而,另外一个在StackOverflow上 ,最多的回答是在路DO重复自己阵营,并提出了每个子类中创建重复的脚手架。
- 这里是再次同样的问题在SO,其中顶部的答案似乎只是错误的(Rails的魔法可以工作了)
- 网络上其他地方,我发现这个博客帖子 ,其中F2Andy建议编辑路径无处不在的代码。
- 在博客中单表继承和REST风格的路线 ,在现实的逻辑设计,建议到资源的子类的父类控制器映射,如SO回答数2以上。
- 亚历克斯·赖斯纳有一个帖子在Rails的单表继承中,他主张对映射子类在父类的资源
routes.rb
,因为只有抓住路由从破损link_to
和redirect_to
,而不是从form_for
。 因此,他建议,而不是添加一个方法父类来获取子类谎报类。 听起来不错,但他的方法给我的错误undefined local variable or method `child' for #
。
所以这似乎是最优雅,拥有最共识(但不是所有的优雅,也没有那么多的共识)的答案,是资源添加到您routes.rb
。 除了这不适合工作form_for
。 我需要一些清晰! 提炼上面的选择,我的选择是
- 子类的资源映射到超类的控制器
routes.rb
(希望我不需要调用的form_for的任何子类) - 覆盖导轨内部方法,使类骗对方
- 编辑在路径对象的行为隐含或明确地调用的代码的每个实例,或者更改路径或类型转换的对象。
所有这些相互矛盾的答案,我需要一个裁决。 在我看来,像没有很好的答案。 这是轨道的设计不及格? 如果是这样,难道是可能会修正了一个错误? 或者,如果没有,那么我希望有人可以直接设置在此我,走在我在每个选项的利弊(或解释为什么这不是一个选项),哪一个是正确的答案,以及为什么。 或者是有,我不是在网络上找到正确的答案?
Answer 1:
这是我能够拿出来与最小的副作用,最简单的解决方案。
class Person < Contact
def self.model_name
Contact.model_name
end
end
现在url_for @person
将映射到contact_path
预期。
工作原理:URL佣工依靠YourModel.model_name
反映在模型和生成(其中包括很多东西),单/多条路径键。 这里Person
基本上是说我就像Contact
伙计,问他 。
Answer 2:
我有同样的问题。 使用STI后, form_for
方法发布到不对劲儿的URL。
NoMethodError (undefined method `building_url' for
我结束了在加入了子类的额外路由,并指向同一个控制器
resources :structures
resources :buildings, :controller => 'structures'
resources :bridges, :controller => 'structures'
另外:
<% form_for(@structure, :as => :structure) do |f| %>
在这种情况下,结构实际上是一个建筑(子类)
看来做一个提交后,我工作form_for
。
Answer 3:
我建议你看看: https://stackoverflow.com/a/605172/445908 ,使用这种方法将使你能够用“的form_for”。
ActiveRecord::Base#becomes
Answer 4:
在路线使用类型 :
resources :employee, controller: 'person', type: 'Employee'
http://samurails.com/tutorial/single-table-inheritance-with-rails-4-part-2/
Answer 5:
继@Prathan Thananart的想法,但想不破坏什么。 (因为有如此大的魔力参与)
class Person < Contact
model_name.class_eval do
def route_key
"contacts"
end
def singular_route_key
superclass.model_name.singular_route_key
end
end
end
现在url_for @person将映射到contact_path预期。
Answer 6:
我遇到了麻烦,这个问题也和这个答案就类似于我们这样的问题就来了。 它为我工作。
form_for @list.becomes(List)
这里显示答: 使用STI路径以相同的控制器
该.becomes
方法被定义为主要用于解决像你STI问题form_for
之一。
.becomes
这里信息: http://apidock.com/rails/ActiveRecord/Base/becomes
超级反应迟缓,但是这是我能找到的最好的答案和它的工作很适合我。 希望这有助于一些之一。 干杯!
Answer 7:
好吧,我已经有一吨的挫折在这一领域的Rails,并在下面的方法已经到达,也许这将帮助别人。
首先要知道,上述一系列和周围的网解决方案建议使用在客户端提供的参数constantize。 这是因为Ruby没有垃圾收集的符号,从而允许攻击者创建任意符号和消耗可用存储器中的已知的DoS攻击向量。
我实现了以下支持模型的子类的实例,并从上面的contantize安全问题的办法。 这是非常相似,轨道4,5做,也可以让子类一级以上(与导轨4)和Rails的作品3。
# initializers/acts_as_castable.rb
module ActsAsCastable
extend ActiveSupport::Concern
module ClassMethods
def new_with_cast(*args, &block)
if (attrs = args.first).is_a?(Hash)
if klass = descendant_class_from_attrs(attrs)
return klass.new(*args, &block)
end
end
new_without_cast(*args, &block)
end
def descendant_class_from_attrs(attrs)
subclass_name = attrs.with_indifferent_access[inheritance_column]
return nil if subclass_name.blank? || subclass_name == self.name
unless subclass = descendants.detect { |sub| sub.name == subclass_name }
raise ActiveRecord::SubclassNotFound.new("Invalid single-table inheritance type: #{subclass_name} is not a subclass of #{name}")
end
subclass
end
def acts_as_castable
class << self
alias_method_chain :new, :cast
end
end
end
end
ActiveRecord::Base.send(:include, ActsAsCastable)
“在商发展问题sublclass装载”尝试了各种方法之后,很多类似上面什么建议,我发现只有工作可靠地在我的模型类使用“require_dependency”的东西。 这确保了类加载工作正常发展,并导致生产没有问题。 在发展中,没有“require_dependency” AR不会知道所有的子类,这会影响SQL射出的该类型的列匹配。 此外没有“require_dependency”你也可以结束了与在同一时间模型类的多个版本的情况! (例如,当您更改基或中级班会发生这种情况,子类不似乎总是加载并从老班左子类)
# contact.rb
class Contact < ActiveRecord::Base
acts_as_castable
end
require_dependency 'person'
require_dependency 'organisation'
我也如以上建议不重写MODEL_NAME因为我使用I18N和需要不同的子类,如属性不同的字符串:tax_identifier成为组织“ABN”,和人(澳大利亚)“TFN”。
我也使用路由映射,如上面所建议的,设置类型:
resources :person, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Person.sti_name } }
resources :organisation, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Organisation.sti_name } }
除了路由映射,我使用InheritedResources和SimpleForm我用新的行动以下一般形式的包装:
simple_form_for resource, as: resource_request_name, url: collection_url,
html: { class: controller_name, multipart: true }
...和编辑操作:
simple_form_for resource, as: resource_request_name, url: resource_url,
html: { class: controller_name, multipart: true }
而要完成这项工作,在我的基地ResourceContoller我揭露InheritedResource的resource_request_name作为视图一个辅助方法:
helper_method :resource_request_name
如果你不使用InheritedResources,然后使用像你的“ResourceController”以下内容:
# controllers/resource_controller.rb
class ResourceController < ApplicationController
protected
helper_method :resource
helper_method :resource_url
helper_method :collection_url
helper_method :resource_request_name
def resource
@model
end
def resource_url
polymorphic_path(@model)
end
def collection_url
polymorphic_path(Model)
end
def resource_request_name
ActiveModel::Naming.param_key(Model)
end
end
乐意听到别人的经验和改进。
Answer 8:
最近,我记录我试图得到一个稳定的STI图案在Rails 3.0的应用程序工作。 这里的TL; DR版本:
# app/controllers/kase_controller.rb
class KasesController < ApplicationController
def new
setup_sti_model
# ...
end
def create
setup_sti_model
# ...
end
private
def setup_sti_model
# This lets us set the "type" attribute from forms and querystrings
model = nil
if !params[:kase].blank? and !params[:kase][:type].blank?
model = params[:kase].delete(:type).constantize.to_s
end
@kase = Kase.new(params[:kase])
@kase.type = model
end
end
# app/models/kase.rb
class Kase < ActiveRecord::Base
# This solves the `undefined method alpha_kase_path` errors
def self.inherited(child)
child.instance_eval do
def model_name
Kase.model_name
end
end
super
end
end
# app/models/alpha_kase.rb
# Splitting out the subclasses into separate files solves
# the `uninitialize constant AlphaKase` errors
class AlphaKase < Kase; end
# app/models/beta_kase.rb
class BetaKase < Kase; end
# config/initializers/preload_sti_models.rb
if Rails.env.development?
# This ensures that `Kase.subclasses` is populated correctly
%w[kase alpha_kase beta_kase].each do |c|
require_dependency File.join("app","models","#{c}.rb")
end
end
这种方法得到周围您列出以及许多其他人曾与STI方法等问题的问题。
Answer 9:
你可以试试这个,如果你有没有嵌套的路线:
resources :employee, path: :person, controller: :person
或者你可以走另一条路,并使用一些OOP魔法喜欢这里描述: https://coderwall.com/p/yijmuq
在第二种方式中,你可以为你的所有嵌套模型类似的帮手。
Answer 10:
这里是一个安全干净的办法把它在形式和整个应用程序,我们使用工作。
resources :districts
resources :district_counties, controller: 'districts', type: 'County'
resources :district_cities, controller: 'districts', type: 'City'
然后,我有我的方式。 造成这种情况的增加部分是因为:区。
= form_for(@district, as: :district, html: { class: "form-horizontal", role: "form" }) do |f|
希望这可以帮助。
Answer 11:
如果我认为性病继承这样的:
class AModel < ActiveRecord::Base ; end
class BModel < AModel ; end
class CModel < AModel ; end
class DModel < AModel ; end
class EModel < AModel ; end
在“应用程序/模型/ a_model.rb”我补充一下:
module ManagedAtAModelLevel
def model_name
AModel.model_name
end
end
然后在AMODEL类:
class AModel < ActiveRecord::Base
def self.instanciate_STI
managed_deps = {
:b_model => true,
:c_model => true,
:d_model => true,
:e_model => true
}
managed_deps.each do |dep, managed|
require_dependency dep.to_s
klass = dep.to_s.camelize.constantize
# Inject behavior to be managed at AModel level for classes I chose
klass.send(:extend, ManagedAtAModelLevel) if managed
end
end
instanciate_STI
end
因此,我甚至可以轻松选择我想使用默认之一,这甚至没有触及的子类定义的模式。 非常干燥。
Answer 12:
这种方式适用于我OK(确定在基类中此方法):
def self.inherited(child)
child.instance_eval do
alias :original_model_name :model_name
def model_name
Task::Base.model_name
end
end
super
end
Answer 13:
您可以创建方法,返回假父对象为路由purpouse
class Person < ActiveRecord::Base
def routing_object
Person.new(id: id)
end
end
然后只需拨打的form_for @ employee.routing_object未经类型将返回Person类对象
Answer 14:
继@ prathan-thananart答案,并为多种性病类,你可以添加以下到父模型 - >
class Contact < ActiveRecord::Base
def self.model_name
ActiveModel::Name.new(self, nil, 'Contact')
end
end
这将使每个窗体与联系人数据发送PARAMS为params[:contact]
代替params[:contact_person]
params[:contact_whatever]
Answer 15:
hackish的,但只是一个又一个解决方案的列表。
class Parent < ActiveRecord::Base; end
Class Child < Parent
def class
Parent
end
end
适用于轨道2.x和3.x
文章来源: Best practices to handle routes for STI subclasses in rails