Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/ruby/21.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Ruby on rails rails中处理STI子类路由的最佳实践_Ruby On Rails_Ruby_Single Table Inheritance - Fatal编程技术网

Ruby on rails rails中处理STI子类路由的最佳实践

Ruby on rails rails中处理STI子类路由的最佳实践,ruby-on-rails,ruby,single-table-inheritance,Ruby On Rails,Ruby,Single Table Inheritance,我的Rails视图和控制器中充斥着用于方法调用的重定向到,链接到,以及表单。有时link\u to和redirect\u to在它们链接的路径中是显式的(例如link\u to'New Person',New Person\u path),但很多时候路径是隐式的(例如link\u to'Show',Person) 我在模型中添加了一些单表继承(STI)(比如Employee 在本例中,结构实际上是一个建筑(子类) 在使用表单提交hackish之后,它似乎对我有效,但只是解决方案列表中的另一个

我的Rails视图和控制器中充斥着用于方法调用的
重定向到
链接到
,以及
表单。有时
link\u to
redirect\u to
在它们链接的路径中是显式的(例如
link\u to'New Person',New Person\u path
),但很多时候路径是隐式的(例如
link\u to'Show',Person

我在模型中添加了一些单表继承(STI)(比如
Employee
),所有这些方法都会中断子类的实例(比如
Employee
);当rails执行
link_to@person
时,它会在#
未定义的方法employee_path'中出错。Rails正在查找由对象的类名(employee)定义的路由。这些员工路线没有定义,并且没有员工控制器,因此操作也没有定义

这个问题以前有人问过:

  • 首先,答案是在整个代码库中编辑link_to etc的每个实例,并显式地声明路径
  • 再次,两个人建议使用
    routes.rb
    将子类资源映射到父类(
    map.resources:employees,:controller=>“people”
    )。同一个SO问题中的顶部答案建议使用
    转换代码库中的每个实例对象
  • 在另一个例子中,最重要的答案是DoRepeatYourself阵营,建议为每个子类创建重复的脚手架
  • 同样的问题再次出现在SO,最上面的答案似乎是错误的(Rails magic正好有效!)
  • 在web上的其他地方,我找到了F2Andy建议在代码中任何地方的路径中进行编辑的地方
  • 在Logical Reality Design的博客文章中,建议将子类的资源映射到超类控制器,如上面答案2所示
  • Alex Reisner有一篇文章,他在文章中反对将子类的资源映射到
    routes.rb
    中的父类,因为这只捕获从
    链接到
    重定向到
    ,而不是从
    表单到
    的路由中断。因此,他建议改为向父类添加一个方法,以使子类对其类撒谎。听起来不错,但是他的方法给了我一个错误
    未定义的局部变量或方法'child'。
  • 因此,看起来最优雅、最有共识的答案(但不是那么优雅,也不是那么有共识)是将资源添加到您的
    routes.rb
    。除了这不适用于
    表单_for
    。我需要澄清!要提取上述选项,我的选项如下

  • 将子类的资源映射到
    routes.rb
    中的超类控制器(希望我不需要调用任何子类的表单)
  • 重写rails内部方法以使类彼此独立
  • 编辑代码中隐式或显式调用对象操作路径的每个实例,更改路径或类型转换对象

  • 所有这些相互矛盾的答案,我需要一个裁决。在我看来,似乎没有好的答案。这是rails设计中的一个失败吗?如果是这样的话,这是一个可以修复的bug吗?如果不是,我希望有人能让我明白这一点,让我了解每个选项的优缺点(或者解释为什么这不是一个选项),哪一个是正确的答案,为什么。还是有我在网上找不到的正确答案?

    我也有同样的问题。使用STI后,
    方法的
    表单_发布到错误的子url

    NoMethodError (undefined method `building_url' for
    
    最后,我为子类添加了额外的路由,并将它们指向相同的控制器

     resources :structures
     resources :buildings, :controller => 'structures'
     resources :bridges, :controller => 'structures'
    
    此外:

    <% form_for(@structure, :as => :structure) do |f| %>
    
    :结构)do | f |%>
    
    在本例中,结构实际上是一个建筑(子类)


    在使用
    表单提交

    hackish之后,它似乎对我有效,但只是解决方案列表中的另一个

    class Parent < ActiveRecord::Base; end
    
    Class Child < Parent
      def class
        Parent
      end
    end
    
    class父类
    在rails 2.x和3.x上工作最近,我尝试在rails 3.0应用程序中使用稳定的STI模式。这是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
    
    #app/controllers/kase_controller.rb
    类KaseController<应用程序控制器
    def新
    设置\u sti\u模型
    # ...
    结束
    def创建
    设置\u sti\u模型
    # ...
    结束
    私有的
    def设置_sti_模型
    #这使我们可以从表单和查询字符串中设置“type”属性
    模型=零
    如果!参数[:kase]。空白?还有!参数[:kase][:type]。是否为空?
    model=params[:kase].delete(:type).constantize.tos
    结束
    @kase=kase.new(参数[:kase])
    @kase.type=模型
    结束
    结束
    #app/models/kase.rb
    类Kase

    这种方法绕过了您列出的问题以及其他人在STI方法中遇到的许多其他问题。

    这是我能够想到的最简单的解决方案,副作用最小

    class Person < Contact
      def self.model_name
        Contact.model_name
      end
    end
    
    class-Person
    现在@person的
    url\u将映射到
    contact\u路径
    ActiveRecord::Base#becomes
    
    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
    
    class AModel < ActiveRecord::Base ; end
    class BModel < AModel ; end
    class CModel < AModel ; end
    class DModel < AModel ; end
    class EModel < AModel ; end
    
    module ManagedAtAModelLevel
      def model_name
        AModel.model_name
      end
    end
    
    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
    
    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
    
    # 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)
    
    # contact.rb
    class Contact < ActiveRecord::Base
      acts_as_castable
    end
    
    require_dependency 'person'
    require_dependency 'organisation'
    
    resources :person, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Person.sti_name } }
    resources :organisation, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Organisation.sti_name } }
    
    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 }
    
    helper_method :resource_request_name 
    
    # 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
    
    resources :employee, path: :person, controller: :person
    
    resources :employee, controller: 'person', type: 'Employee' 
    
    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|
    
    form_for @list.becomes(List)
    
    class Contact < ActiveRecord::Base
      def self.model_name
        ActiveModel::Name.new(self, nil, 'Contact')
      end
    end
    
    def self.inherited(subclass)
      super
    
      def subclass.model_name
        super.tap do |name|
          route_key = base_class.name.underscore
          name.instance_variable_set(:@singular_route_key, route_key)
          name.instance_variable_set(:@route_key, route_key.pluralize)
        end
      end
    end
    
    polymorphic_url([:admin, @article, @comment])
    # => admin_article_comment_url(@article, @comment)
    
    edit_polymorphic_path(@post) 
    # => "/posts/1/edit"
    
    url_for([:admin, Role])
    # => "admin/roles" # index
    
    url_for([:admin, Role, action: :new])
    # => "admin/roles/new" # new
    
    url_for([:admin, @role])
    # => "admin/roles/1" # show; for destroy, use link "method: :delete"
    
    url_for([:edit, :admin, @role])
    # => "admin/roles/1/edit" # edit
    
    foo.becomes(foo.class.base_class)
    
    def becomes_base
      becomes(self.class.base_class)
    end