处理铁路中 STI 子类路线的最佳实践

发布于 2024-10-08 15:06:20 字数 2662 浏览 0 评论 0 原文

我的 Rails 视图和控制器中充斥着 redirect_tolink_toform_for 方法调用。有时 link_toredirect_to 在它们链接的路径中是明确的(例如 link_to 'New Person', new_person_path),但很多时候这些路径是隐式的(例如link_to 'Show', person)。

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

这个问题之前已经被问过:

  1. StackOverflow,答案是编辑整个代码库中 link_to 等的每个实例,并
  2. StackOverflow 再次,有两个人建议使用 routes.rb 将子类资源映射到父类 (map.resources :employees, :controller = >“人”)。同一个 SO 问题中的最佳答案建议使用 .becomes 对代码库中的每个实例对象进行类型转换,
  3. 而另一个位于 StackOverflow,最上面的答案是 Do Repeat Yourself 阵营的方式,并建议为每个子类创建重复的脚手架。
  4. 这里再次出现同样的问题,其中最重要的答案似乎是错误的(Rails magic有效!)
  5. 在网络的其他地方,我找到了这篇博文 其中F2Andy建议在代码中各处的路径中进行编辑。
  6. 在博客文章单表继承和 RESTful 路由 在逻辑现实设计中,建议将子类的资源映射到超类控制器,如上面的答案 2 所示。
  7. Alex Reisner 有一篇文章 Rails 中的单表继承,其中他主张不要在 routes.rb 中将子类的资源映射到父类,因为这只能捕获来自 link_toredirect_to 的路由中断,但不是来自 form_for。因此他建议向父类添加一个方法,让子类对它们的类​​撒谎。听起来不错,但他的方法给了我错误未定义的局部变量或方法`child' for #

因此,看起来最优雅且最具共识的答案(但并不完全那么优雅,也太多共识),是将资源添加到您的路线.rb。但这对于 form_for 不起作用。我需要一些澄清!为了提炼上面的选择,我的选择是

  1. routes.rb 中超类的控制器(并希望我不需要在任何子类上调用 form_for)
  2. 将子类的资源映射到 使类相互欺骗的方法
  3. 编辑代码中隐式或显式调用对象操作路径的每个实例,更改路径或对对象进行类型转换。

面对所有这些相互矛盾的答案,我需要做出裁决。在我看来,似乎没有好的答案。这是 Rails 设计的失败吗?如果是这样,这是一个可以修复的错误吗?或者如果没有,那么我希望有人可以直接告诉我这个问题,引导我了解每个选项的优点和缺点(或解释为什么这不是一个选项),以及哪个是正确的答案,以及为什么。或者我在网上找不到正确的答案?

My Rails views and controllers are littered with redirect_to, link_to, and form_for method calls. Sometimes link_to and redirect_to are explicit in the paths they're linking (e.g. link_to 'New Person', new_person_path), but many times the paths are implicit (e.g. link_to 'Show', person).

I add some single table inheritance (STI) to my model (say Employee < Person), and all of these methods break for an instance of the subclass (say Employee); when rails executes link_to @person, it errors with undefined method employee_path' for #<#<Class:0x000001022bcd40>:0x0000010226d038>. Rails is looking for a route defined by the class name of the object, which is employee. These employee routes are not defined, and there is no employee controller so the actions aren't defined either.

This question has been asked before:

  1. At StackOverflow, the answer is to edit every instance of link_to etc in your entire codebase, and state the path explicitly
  2. On StackOverflow again, two people suggest using routes.rb to map the subclass resources to the parent class (map.resources :employees, :controller => 'people'). The top answer in that same SO question suggests type-casting every instance object in the codebase using .becomes
  3. Yet another one at StackOverflow, the top answer is way in the Do Repeat Yourself camp, and suggests creating duplicate scaffolding for every subclass.
  4. Here's the same question again at SO, where the top answer seems to just be wrong (Rails magic Just Works!)
  5. Elsewhere on the web, I found this blog post where F2Andy recommends editing in the path everywhere in the code.
  6. On the blog post Single Table Inheritance and RESTful Routes at Logical Reality Design, it is recommended to map the resources for the subclass to the superclass controller, as in SO answer number 2 above.
  7. Alex Reisner has a post Single Table Inheritance in Rails, in which he advocates against mapping the resources of the child classes to the parent class in routes.rb, since that only catches routing breakages from link_to and redirect_to, but not from form_for. So he recommends instead adding a method to the parent class to get the subclasses to lie about their class. Sounds good, but his method gave me the error undefined local variable or method `child' for #.

So the answer that seems most elegant and has the most consensus (but it's not all that elegant, nor that much consensus), is the add the resources to your routes.rb. Except this doesn't work for form_for. I need some clarity! To distill the choices above, my options are

  1. map the resources of the subclass to the controller of the superclass in routes.rb (and hope I don't need to call form_for on any subclasses)
  2. Override rails internal methods to make the classes lie to each other
  3. Edit every instance in the code where the path to an object's action is invoked implicitly or explicitly, either changing the path or type-casting the object.

With all these conflicting answers, I need a ruling. It seems to me like there is no good answer. Is this a failing in rails' design? If so, is it a bug that may get fixed? Or if not, then I'm hoping someone can set me straight on this, walk me through the pros and cons of each option (or explain why that's not an option), and which one is the right answer, and why. Or is there a right answer that I'm not finding on the web?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(18

拧巴小姐 2024-10-15 15:06:20

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

class Person < Contact
  def self.model_name
    Contact.model_name
  end
end

现在,url_for @person 将按预期映射到 contact_path

工作原理:URL 帮助程序依赖 YourModel.model_name 来反映模型并生成(其中包括)单数/复数路由键。这里Person基本上是在说我就像Contact老兄,问他

This is the simplest solution I was able to come up with with minimal side effect.

class Person < Contact
  def self.model_name
    Contact.model_name
  end
end

Now url_for @person will map to contact_path as expected.

How it works: URL helpers rely on YourModel.model_name to reflect upon the model and generate (amongst many things) singular/plural route keys. Here Person is basically saying I'm just like Contact dude, ask him.

花心好男孩 2024-10-15 15:06:20

我也有同样的问题。使用 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.

I had the same problem. After using STI, the form_for method was posting to the wrong child url.

NoMethodError (undefined method `building_url' for

I ended up adding in the extra routes for the child classes and pointing them to the same controllers

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

Additionally:

<% form_for(@structure, :as => :structure) do |f| %>

in this case structure is actually a building (child class)

It seems to work for me after doing a submit with form_for.

毁虫ゝ 2024-10-15 15:06:20

我建议你看一下:https://stackoverflow.com/a/605172/445908,使用此方法将使您能够使用“form_for”。

ActiveRecord::Base#becomes

I suggest you take a look at : https://stackoverflow.com/a/605172/445908, using this method will enable you to use "form_for".

ActiveRecord::Base#becomes
下壹個目標 2024-10-15 15:06:20

我也遇到了这个问题,并在与我们类似的问题上得到了这个答案。这对我有用。

form_for @list.becomes(List)

此处显示的答案:使用具有相同控制器的 STI 路径

.becomes 方法被定义为主要用于解决 STI 问题,例如您的 form_for 问题。

.becomes 信息位于:http://apidock.com/rails/ActiveRecord /Base/becomes

回复超级晚,但这是我能找到的最好答案,而且对我来说效果很好。希望这对某人有所帮助。干杯!

I was having trouble with this problem too and came by this answer on a question similar to ours. It worked for me.

form_for @list.becomes(List)

Answer shown here: Using STI path with same controller

The .becomes method is defined as mainly used for solving STI problems like your form_for one.

.becomes info here: http://apidock.com/rails/ActiveRecord/Base/becomes

Super late response, but this is the best answer I could find and it worked well for me. Hope this helps some one. Cheers!

我喜欢麦丽素 2024-10-15 15:06:20

在路线中使用类型

resources :employee, controller: 'person', type: 'Employee' 

http://samurails.com/tutorial/single-table-inheritance-with-rails-4-part-2/

Use type in the routes:

resources :employee, controller: 'person', type: 'Employee' 

http://samurails.com/tutorial/single-table-inheritance-with-rails-4-part-2/

故事灯 2024-10-15 15:06:20

遵循@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。

Following the idea of @Prathan Thananart but trying to not destroy nothing. (since there is so much magic involved)

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

Now url_for @person will map to contact_path as expected.

叹梦 2024-10-15 15:06:20

好吧,我在 Rails 的这个领域遇到了很多挫折,并得出了以下方法,也许这会对其他人有所帮助。

首先请注意,网上和网上的许多解决方案建议对客户端提供的参数使用常量化。这是一种已知的 DoS 攻击媒介,因为 Ruby 不会对符号进行垃圾收集,从而允许攻击者创建任意符号并消耗可用内存。

我已经实现了下面的方法,该方法支持模型子类的实例化,并且对于上面的 contantize 问题是安全的。它与 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)

在尝试了各种与上面建议的类似的“开发中的子类加载问题”方法之后,我发现唯一可靠的方法是在我的模型类中使用“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

总是很高兴听到其他人的经验和改进。

Ok, Ive had a ton of frustration in this area of Rails, and have arrived at the following approach, perhaps this will help others.

Firstly be aware that a number of solutions above and around the net suggest using constantize on client provided parameters. This is a known DoS attack vector as Ruby does not garbage collect symbols, thus allowing an attacker to create arbitrary symbols and consume available memory.

I've implemented the approach below which supports instantiation of model subclasses, and is SAFE from the contantize problem above. It is very similar to what rails 4 does, but also allows more than one level of subclassing (unlike Rails 4) and works in 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)

After trying various approaches for the 'sublclass loading in devlopment issue' many similar to whats suggested above, I found the only thing that worked reliably was to use 'require_dependency' in my model classes. This ensures that class loading works properly in development and causes no issues in production. In development, without 'require_dependency' AR wont know about all subclasses, which impacts the SQL emitted for matching on the type column. In addition without 'require_dependency' you can also end up in a situation with multiple versions of the model classes at the same time! (eg. this can happen when you change a base or intermediate class, the sub-classes don't always seem to reload and are left subclassing from the old class)

# contact.rb
class Contact < ActiveRecord::Base
  acts_as_castable
end

require_dependency 'person'
require_dependency 'organisation'

I also don't override model_name as suggested above because I use I18n and need different strings for the attributes of different subclasses, eg :tax_identifier becomes 'ABN' for Organisation, and 'TFN' for Person (in Australia).

I also use route mapping, as suggested above, setting the type:

resources :person, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Person.sti_name } }
resources :organisation, :controller => 'contacts', :defaults => { 'contact' => { 'type' => Organisation.sti_name } }

In addition to the route mapping, I'm using InheritedResources and SimpleForm and I use the following generic form wrapper for new actions:

simple_form_for resource, as: resource_request_name, url: collection_url,
      html: { class: controller_name, multipart: true }

... and for edit actions:

simple_form_for resource, as: resource_request_name, url: resource_url,
      html: { class: controller_name, multipart: true }

And to make this work, in my base ResourceContoller I expose InheritedResource's resource_request_name as a helper method for the view:

helper_method :resource_request_name 

If you're not using InheritedResources, then use something like the following in your '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

Always happy to hear others experiences and improvements.

盗心人 2024-10-15 15:06:20

我最近记录了我的尝试在 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

此方法解决了您列出的问题以及其他人在使用 STI 方法时遇到的许多其他问题。

I recently documented my attempts to get a stable STI pattern working in a Rails 3.0 app. Here's the TL;DR version:

# 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

This approach gets around the problems that you list as well as a number of other issues that others have had with STI approaches.

℡Ms空城旧梦 2024-10-15 15:06:20

我发现的最干净的解决方案是将以下内容添加到基类中:

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

它适用于所有子类,并且比覆盖整个模型名称对象安全得多。通过仅针对路由键,我们可以解决路由问题,而不会破坏 I18n 或因覆盖 Rails 定义的模型名称而导致任何潜在副作用的风险。

The cleanest solution I found is to add the following to the base class:

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

It works for all subclasses and is much safer than overriding the entire model name object. By targeting only the route keys, we solve the routing problems without breaking I18n or risking any potential side effects caused by overriding the model name as defined by Rails.

秋日私语 2024-10-15 15:06:20

如果你没有嵌套路由,你可以尝试这个:

resources :employee, path: :person, controller: :person

或者你可以采取另一种方式并使用一些 OOP 魔法,如下所述: https://coderwall.com/p/yijmuq

在第二种方式中,您可以为所有嵌套模型制作类似的助手。

You can try this, if you have no nested routes:

resources :employee, path: :person, controller: :person

Or you can go another way and use some OOP-magic like described here: https://coderwall.com/p/yijmuq

In second way you can make similar helpers for all your nested models.

枕梦 2024-10-15 15:06:20

这是一种安全、干净的方法,可以让它在我们使用的表单和整个应用程序中工作。

resources :districts
resources :district_counties, controller: 'districts', type: 'County'
resources :district_cities, controller: 'districts', type: 'City'

然后我就有了我的形式。为此添加的部分是 as: :district。

= form_for(@district, as: :district, html: { class: "form-horizontal",         role: "form" }) do |f|

希望这有帮助。

Here is a safe clean way to have it work in forms and throughout your application that we use.

resources :districts
resources :district_counties, controller: 'districts', type: 'County'
resources :district_cities, controller: 'districts', type: 'City'

Then I have in my form. The added piece for this is the as: :district.

= form_for(@district, as: :district, html: { class: "form-horizontal",         role: "form" }) do |f|

Hope this helps.

可爱咩 2024-10-15 15:06:20

覆盖 model_name 似乎很危险。使用 .becomes 似乎是更安全的选择。

一个问题是您不知道正在处理的模型(以及基本模型)的情况。

我只是想分享一下,在这种情况下,可以使用:

foo.becomes(foo.class.base_class)

为了便于使用,我已将此方法添加到我的 ApplicationRecord 中:

def becomes_base
  becomes(self.class.base_class)
end

.becomes_base 添加到一些路由辅助方法对我来说似乎没什么大不了的。

Overriding model_name seems dangerous. Using .becomes seems like the safer option.

One issue is in cases where you don't know what model you are dealing with (and thus the base model).

I just wanted to share that in such cases, one can use:

foo.becomes(foo.class.base_class)

For ease of use, I've added this method to my ApplicationRecord:

def becomes_base
  becomes(self.class.base_class)
end

Adding .becomes_base to a few route helper methods doesn't seem like too big of a deal to me.

┈┾☆殇 2024-10-15 15:06:20

按照 @prathan-thananart answer,对于多个 STI 类,您可以将以下内容添加到父模型 ->

class Contact < ActiveRecord::Base
  def self.model_name
    ActiveModel::Name.new(self, nil, 'Contact')
  end
end

这将使包含联系人数据的每个表单以 params[:contact] 形式发送参数,而不是 params[:contact_person]params[:contact_whatever]

Following @prathan-thananart answer, and for the multiple STI classes, you can add the following to the parent model ->

class Contact < ActiveRecord::Base
  def self.model_name
    ActiveModel::Name.new(self, nil, 'Contact')
  end
end

That will make each form with Contact data to send params as params[:contact] instead of params[:contact_person], params[:contact_whatever].

╰つ倒转 2024-10-15 15:06:20

如果我考虑像这样的 STI 继承:

class AModel < ActiveRecord::Base ; end
class BModel < AModel ; end
class CModel < AModel ; end
class DModel < AModel ; end
class EModel < AModel ; end

在“app/models/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

因此我什至可以轻松选择我想要使用默认模型的模型,而无需触摸子类定义。非常干燥。

If I consider an STI inheritance like this:

class AModel < ActiveRecord::Base ; end
class BModel < AModel ; end
class CModel < AModel ; end
class DModel < AModel ; end
class EModel < AModel ; end

in 'app/models/a_model.rb' I add:

module ManagedAtAModelLevel
  def model_name
    AModel.model_name
  end
end

And then in the AModel class:

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

Therefore I can even easily choose which model I want to use the default one, and this without even touching the sub class definition. Very dry.

烏雲後面有陽光 2024-10-15 15:06:20

这种方法对我来说没问题(在基类中定义这个方法):

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

This way works for me ok (define this method in the base class):

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
九命猫 2024-10-15 15:06:20

您可以创建返回虚拟父对象的方法以用于路由目的

class Person < ActiveRecord::Base      
  def routing_object
    Person.new(id: id)
  end
end

,然后只需调用 form_for @employee.routing_object
如果没有类型,将返回 Person 类对象

You can create method that returns dummy Parent object for routing purpouse

class Person < ActiveRecord::Base      
  def routing_object
    Person.new(id: id)
  end
end

and then simply call form_for @employee.routing_object
which without type will return Person class object

睫毛溺水了 2024-10-15 15:06:20

我赞成使用 PolymorphicRoutesurl_for 根据资源、任何命名空间等动态生成路由。

https://api.rubyonrails.org/classes/ActionDispatch/Routing/PolymorphicRoutes.html

https://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html

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

I'm in favor of using PolymorphicRoutes or url_for to dynamically generate routes based on the resource, any namespace, etc.

https://api.rubyonrails.org/classes/ActionDispatch/Routing/PolymorphicRoutes.html

https://api.rubyonrails.org/classes/ActionDispatch/Routing/UrlFor.html

polymorphic_url([:admin, @article, @comment])
# => admin_article_comment_url(@article, @comment)

edit_polymorphic_path(@post) 
# => "/posts/1/edit"

With admin namespace

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
北城半夏 2024-10-15 15:06:20

hackish,但这只是解决方案列表中的另一个。

class Parent < ActiveRecord::Base; end

Class Child < Parent
  def class
    Parent
  end
end

适用于 Rails 2.x 和 3.x

hackish,but just another one to the list of solutions.

class Parent < ActiveRecord::Base; end

Class Child < Parent
  def class
    Parent
  end
end

works on rails 2.x and 3.x

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文