最佳做法来处理路线在轨STI子最佳做法来处理路线在轨STI子(Best practices to h

2019-05-12 19:05发布

我的Rails视图和控制器上到处是redirect_tolink_toform_for方法调用。 有时候link_toredirect_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> 轨道正在寻找由对象,这是雇员的类名称定义的路由。 这些雇员的路由没有被定义,并且存在这样的操作不要么没有定义雇员控制器。

这个问题已经被问过:

  1. 在StackOverflow上 ,答案是编辑在您的整个代码库中的link_to等每一个实例,并明确规定路径
  2. 在StackOverflow上再次,两个人建议使用routes.rb子类资源映射到父类( map.resources :employees, :controller => 'people' )。 在相同的,所以问题最多的回答暗示型铸造每一个实例对象使用的代码库.becomes
  3. 然而,另外一个在StackOverflow上 ,最多的回答是在路DO重复自己阵营,并提出了每个子类中创建重复的脚手架。
  4. 这里是再次同样的问题在SO,其中顶部的答案似乎只是错误的(Rails的魔法可以工作了)
  5. 网络上其他地方,我发现这个博客帖子 ,其中F2Andy建议编辑路径无处不在的代码。
  6. 在博客中单表继承和REST风格的路线 ,在现实的逻辑设计,建议到资源的子类的父类控制器映射,如SO回答数2以上。
  7. 亚历克斯·赖斯纳有一个帖子在Rails的单表继承中,他主张对映射子类在父类的资源routes.rb ,因为只有抓住路由从破损link_toredirect_to ,而不是从form_for 。 因此,他建议,而不是添加一个方法父类来获取子类谎报类。 听起来不错,但他的方法给我的错误undefined local variable or method `child' for #

所以这似乎是最优雅,拥有最共识(但不是所有优雅,也没有那么多的共识)的答案,是资源添加到您routes.rb 。 除了这不适合工作form_for 。 我需要一些清晰! 提炼上面的选择,我的选择是

  1. 子类的资源映射到超类的控制器routes.rb (希望我不需要调用的form_for的任何子类)
  2. 覆盖导轨内部方法,使类骗对方
  3. 编辑在路径对象的行为隐含或明确地调用的代码的每个实例,或者更改路径或类型转换的对象。

所有这些相互矛盾的答案,我需要一个裁决。 在我看来,像没有很好的答案。 这是轨道的设计不及格? 如果是这样,难道是可能会修正了一个错误? 或者,如果没有,那么我希望有人可以直接设置在此我,走在我在每个选项的利弊(或解释为什么这不是一个选项),哪一个是正确的答案,以及为什么。 或者是有,我不是在网络上找到正确的答案?

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