Programing

철도에서 STI 서브 클래스의 경로를 처리하는 모범 사례

lottogame 2020. 5. 27. 07:30
반응형

철도에서 STI 서브 클래스의 경로를 처리하는 모범 사례


내 레일 뷰와 컨트롤러는 뒤덮 redirect_to, link_toform_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>. Rails는 객체의 클래스 이름 (직원)으로 정의 된 경로를 찾고 있습니다. 이 직원 경로는 정의되어 있지 않으며 직원 컨트롤러가 없으므로 작업도 정의되지 않습니다.

이 질문은 전에 요청되었습니다 :

  1. 에서 StackOverflow의 대답은 전체 코드베이스에 LINK_TO 등의 모든 인스턴스를 편집하고 명시 적 경로를 명시하는 것입니다
  2. StackOverflow에 다시 두 사람이 사용하는 것이 좋습니다 routes.rb부모 클래스의 서브 클래스 리소스 (매핑 map.resources :employees, :controller => 'people'). 같은 SO 질문에서 가장 좋은 대답은 코드베이스의 모든 인스턴스 객체를 사용하여 유형 캐스팅을 제안합니다..becomes
  3. StackOverflow의 또 다른 하나 는 Do Repeat Yourself 캠프의 방법이며 모든 하위 클래스에 대해 중복 스캐 폴딩을 만드는 것을 제안합니다.
  4. 여기에 다시 같은 질문이 있습니다. 여기서 가장 좋은 대답은 틀린 것 같습니다 (Rails magic Just Works!)
  5. 웹의 다른 곳 에서 F2Andy가 코드의 모든 경로에서 편집을 권장하는 이 블로그 게시물을 발견 했습니다 .
  6. Logical Reality Design 의 블로그 게시물 Single Table Inheritance 및 RESTful Routes 에서 위의 SO 답변 2와 같이 서브 클래스의 리소스를 수퍼 클래스 컨트롤러에 매핑하는 것이 좋습니다.
  7. 알렉스 Reisner는 게시물이 레일에서 단일 테이블 상속 그가에서 상위 클래스에 하위 클래스의 자원을 매핑에 대해 옹호하는, routes.rb만 어획량에서 파손을 라우팅하기 때문에, link_to그리고 redirect_to있지만에서을 form_for. 따라서 대신 서브 클래스가 자신의 클래스에 대해 거짓말하도록 부모 클래스에 메소드를 추가하는 것이 좋습니다. 잘 들리지만 그의 방법으로 오류가 발생했습니다 undefined local variable or method `child' for #.

가장 우아한 보인다 가장 공감대를 가지고 (그러나 그것은 모두가 아니다 대답 그래서 우아하고도 많은 합의)는 당신에 리소스를 추가입니다 routes.rb. 이 기능은 작동하지 않습니다 form_for. 명확성이 필요합니다! 위의 선택을 증류하기 위해 내 옵션은

  1. 서브 클래스의 리소스를 수퍼 클래스의 컨트롤러에 매핑하십시오 (서브 클래스에서 routes.rbform_for를 호출 할 필요가 없기를 바랍니다)
  2. 클래스가 서로에게 있도록 레일 내부 메소드를 대체
  3. 경로를 변경하거나 객체를 타입 캐스팅하여 객체의 동작 경로가 암시 적 또는 명시 적으로 호출되는 코드의 모든 인스턴스를 편집하십시오.

이 모든 상충되는 답변으로 판결이 필요합니다. 좋은 대답이없는 것 같습니다. 이것이 레일 디자인에서 실패한 것입니까? 그렇다면 수정 될 수있는 버그입니까? 또는 그렇지 않은 경우 누군가가 나를 바로 설정하고 각 옵션의 장단점을 안내하거나 옵션이 아닌 이유를 설명하고 올바른 답변과 이유를 설명하기를 바랍니다. 아니면 웹에서 찾지 못하는 정답이 있습니까?


이것은 부작용을 최소화 할 수있는 가장 간단한 솔루션입니다.

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

이제 예상대로 url_for @person매핑됩니다 contact_path.

작동 방식 : URL 헬퍼 YourModel.model_name는 모델을 반영하고 단수 / 복수 경로 키를 생성하기 위해 의존 합니다. 여기는 Person기본적으로 내가 Contact친구 와 같다고 말합니다 .


나는 같은 문제가 있었다. 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.


: 나는 당신이 살펴 보시기 바랍니다 https://stackoverflow.com/a/605172/445908을 "form_for"사용할 수 있도록 할 것이다이 방법을 사용.

ActiveRecord::Base#becomes

경로에서 유형사용하십시오 .

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

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


@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에 매핑됩니다.


나도이 문제에 어려움을 겪고 있었고 우리와 비슷한 질문에 대한 대답이 나왔습니다. 그것은 나를 위해 일했다.

form_for @list.becomes(List)

여기에 표시된 답변 : 동일한 컨트롤러에서 STI 경로 사용

.becomes방법은 주로 STI 문제를 해결하는 데 사용되는 것으로 정의됩니다 form_for.

.becomes여기 정보 : http://apidock.com/rails/ActiveRecord/Base/becomes

매우 늦은 답변이지만 이것이 내가 찾을 수있는 가장 좋은 답변이며 그것은 나에게 잘 작동했습니다. 이것이 도움이되기를 바랍니다. 건배!


좋아, Ive는 Rails의이 지역에서 많은 좌절을 겪었고 다음과 같은 접근 방식에 도달했을 것입니다. 아마도 이것이 다른 사람들을 도울 것입니다.

먼저, 위와 주변의 많은 솔루션에서 클라이언트가 제공 한 매개 변수를 상수로 사용하는 것이 좋습니다. Ruby는 심볼을 가비지 수집하지 않으므로 공격자가 임의의 심볼을 만들고 사용 가능한 메모리를 사용할 수 있으므로 DoS 공격 경로로 알려져 있습니다.

나는 모델 서브 클래스의 인스턴스화를 지원하는 위의 접근 방식을 구현했으며 위의 지속적인 문제로부터 안전합니다. 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'

또한 I18n을 사용하고 다른 하위 클래스의 속성에 대해 다른 문자열이 필요하기 때문에 위에서 제안한 것처럼 model_name을 재정의하지 않습니다.

위에서 제안한대로 경로 매핑을 사용하여 유형을 설정합니다.

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

항상 다른 사람의 경험과 개선 사항을 듣고 기뻐하십시오.


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.


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.


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.


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.


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

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


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].


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

참고URL : https://stackoverflow.com/questions/4507149/best-practices-to-handle-routes-for-sti-subclasses-in-rails

반응형