zig*_*ism 169 ruby ruby-on-rails single-table-inheritance
我的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
); 当rails执行时link_to @person
,它会出错undefined method employee_path' for #<#<Class:0x000001022bcd40>:0x0000010226d038>
.Rails正在寻找由对象的类名定义的路由,即雇员.这些员工路线未定义,并且没有员工控制器,因此也未定义操作.
之前已经问过这个问题:
routes.rb
将子类资源映射到父类(map.resources :employees, :controller => 'people'
).在同一个SO问题中的最佳答案建议使用代码库中的每个实例对象进行类型转换.becomes
routes.rb
,因为只有抓住从路由断裂link_to
和redirect_to
,但不从form_for
.所以他建议在父类中添加一个方法,让子类对它们的类撒谎.听起来不错,但他的方法给了我错误undefined local variable or method `child' for #
.所以这似乎是最优雅,拥有最共识(但不是所有的答案是优雅的,也不是太大的共识),是资源添加到您的routes.rb
.除此之外不起作用form_for
.我需要一些清晰度!为了提炼上述选择,我的选择是
routes.rb
(并希望我不需要在任何子类上调用form_for)有了所有这些相互矛盾的答案,我需要一个裁决.在我看来,似乎没有好的答案.这是rails设计中的失败吗?如果是这样,它是否可以修复?或者如果没有,那么我希望有人可以让我直截了当,让我了解每个选项的优点和缺点(或解释为什么不是一个选项),哪一个是正确的答案,以及为什么.或者是否有一个我在网上找不到的正确答案?
Pra*_*art 135
这是我能够提出的最简单的解决方案,副作用最小.
class Person < Contact
def self.model_name
Contact.model_name
end
end
Run Code Online (Sandbox Code Playgroud)
现在url_for @person
将按contact_path
预期映射.
工作原理: URL助手依赖于YourModel.model_name
反映模型并生成(在许多方面)单数/复数路由键.这Person
基本上是说我就像个Contact
老兄,问他.
Jam*_*mes 46
我有同样的问题.使用STI后,该form_for
方法发布到错误的子URL.
NoMethodError (undefined method `building_url' for
Run Code Online (Sandbox Code Playgroud)
我最后添加了子类的额外路由并将它们指向相同的控制器
resources :structures
resources :buildings, :controller => 'structures'
resources :bridges, :controller => 'structures'
Run Code Online (Sandbox Code Playgroud)
另外:
<% form_for(@structure, :as => :structure) do |f| %>
Run Code Online (Sandbox Code Playgroud)
在这种情况下,结构实际上是一个建筑物(子类)
在提交后似乎对我有用form_for
.
Siw*_*申思维 32
我建议你看一下:https://stackoverflow.com/a/605172/445908,使用这种方法可以让你使用"form_for".
ActiveRecord::Base#becomes
Run Code Online (Sandbox Code Playgroud)
job*_*wat 17
在路线中使用类型:
resources :employee, controller: 'person', type: 'Employee'
Run Code Online (Sandbox Code Playgroud)
http://samurails.com/tutorial/single-table-inheritance-with-rails-4-part-2/
elo*_*esp 13
遵循@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
Run Code Online (Sandbox Code Playgroud)
现在url_for @person将按预期映射到contact_path.
小智 11
我也遇到了这个问题的麻烦,并且在类似于我们的问题上得到了这个答案.它对我有用.
form_for @list.becomes(List)
Run Code Online (Sandbox Code Playgroud)
此处显示的答案:在同一控制器上使用STI路径
该.becomes
方法被定义为主要用于解决像您这样的STI问题form_for
.
.becomes
info:http://apidock.com/rails/ActiveRecord/Base/becomes
超级迟到的回应,但这是我能找到的最佳答案,对我来说效果很好.希望这对某人有所帮助.干杯!
好吧,我在Rails的这个领域遇到了很多挫折,并且已经达到了以下方法,也许这将有助于其他人.
首先要注意的是,网络上方和周围的许多解决方案都建议在客户端提供的参数上使用constantize.这是一个已知的DoS攻击向量,因为Ruby不会垃圾收集符号,从而允许攻击者创建任意符号并消耗可用内存.
我已经实现了下面的方法,它支持模型子类的实例化,并且从上面的contantize问题是SAFE.它与rails 4的功能非常相似,但也允许不止一个级别的子类(与Rails 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)
Run Code Online (Sandbox Code Playgroud)
在为开发问题中的"子类加载"尝试了各种方法之后,我发现唯一可行的方法就是在我的模型类中使用'require_dependency'.这可确保类加载在开发中正常工作,并且不会导致生产中出现问题.在开发中,没有'require_dependency'AR不会知道所有子类,这会影响为类型列匹配而发出的SQL.此外,如果没有'require_dependency',您最终也会遇到具有多个版本的模型类的情况!(例如,当您更改基类或中间类时,可能会发生这种情况,子类似乎并不总是重新加载,而是从旧类继承子类)
# contact.rb
class Contact < ActiveRecord::Base
acts_as_castable
end
require_dependency 'person'
require_dependency 'organisation'
Run Code Online (Sandbox Code Playgroud)
我也没有按照上面的建议覆盖model_name,因为我使用I18n并且需要不同的字符串用于不同子类的属性,例如:tax_identifier变为组织的'ABN',以及Person的'TFN'(在澳大利亚).
我也使用路由映射,如上所述,设置类型:
resources :person, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Person.sti_name } }
resources :organisation, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Organisation.sti_name } }
Run Code Online (Sandbox Code Playgroud)
除了路由映射,我正在使用InheritedResources和SimpleForm,我使用以下通用表单包装器来执行新操作:
simple_form_for resource, as: resource_request_name, url: collection_url,
html: { class: controller_name, multipart: true }
Run Code Online (Sandbox Code Playgroud)
...以及编辑操作:
simple_form_for resource, as: resource_request_name, url: resource_url,
html: { class: controller_name, multipart: true }
Run Code Online (Sandbox Code Playgroud)
为了使这项工作,在我的基础ResourceContoller中,我将InheritedResource的resource_request_name公开为视图的辅助方法:
helper_method :resource_request_name
Run Code Online (Sandbox Code Playgroud)
如果您没有使用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
Run Code Online (Sandbox Code Playgroud)
总是乐于听到别人的经历和改进.
归档时间: |
|
查看次数: |
27217 次 |
最近记录: |