programing

레일에서 STI 하위 클래스의 경로를 처리하는 모범 사례

newsource 2023. 6. 14. 21:53

레일에서 STI 하위 클래스의 경로를 처리하는 모범 사례

My Rails 뷰 및 컨트롤러에 다음과 같은 정보가 표시됩니다.redirect_to,link_to,그리고.form_for메서드 호출.가끔씩link_to그리고.redirect_to 연는경명예표로시니다됩으적시에로결하예(다:▁are니▁explicit됩▁they).link_to 'New Person', new_person_path의 경우 암시적입니다( ).link_to 'Show', person).

STI를합니다(예: STI(Sti(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'). 에 대해 동한SO 질답다사음용코드스의이모다를 사용하여 .becomes
  3. StackOverflow의 또 다른 방법은 Do Repeat Yourself 캠프에서 모든 하위 클래스에 대해 중복 비계를 만들 것을 제안하는 것입니다.
  4. SO에서 다시 같은 질문이 나왔는데, 여기서 최고의 답변은 그저 잘못된 것 같습니다(레일즈 매직 저스트 효과!).
  5. 웹의 다른 곳에서, 저는 F2Andy가 코드의 모든 경로에서 편집할 것을 권장하는 이 블로그 게시물을 발견했습니다.
  6. 블로그 게시물 Logical Reality Design의 단일 테이블 상속 RESTful Routes에서 위의 SO 답변 번호 2에서와 같이 하위 클래스에 대한 리소스를 슈퍼 클래스 컨트롤러에 매핑하는 것이 좋습니다.
  7. Alex Reisner는 Rails에서 하위 클래스의 리소스를 상위 클래스에 매핑하는 것을 옹호하는 포스트 Single Table Inheritance를 가지고 있습니다.routes.rb그것은 라우팅 중단만 잡아내기 때문입니다.link_to그리고.redirect_to하지만 로부터는 아닙니다.form_for그래서 그는 대신 하위 클래스가 자신의 클래스에 대해 거짓말을 하도록 하는 방법을 부모 클래스에 추가할 것을 권장합니다.것 , .undefined local variable or method `child' for #.

따라서 가장 우아해 보이고 가장 많은 공감대를 가지고 있는 것처럼 보이는 답변(그러나 그렇게 우아하지도 않고 그렇게 많은 공감대를 가지고 있지도 않음)은 리소스를 귀사에 추가하는 것입니다.routes.rb하지만 이것은 효과가 없습니다.form_for명확한 설명이 필요해요!, 의 선택사항은 , 나선은택의입니다.

  1. .routes.rb가 어떤 가 없기를 바랍니다 (_for_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기본적으로 내가 그냥 친구 같다고 말하는 거야, 그에게 물어봐.

저도 같은 문제가 있었습니다.한 후 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/

저도 이 문제로 어려움을 겪다가 우리와 비슷한 질문에 이 답을 얻었습니다.그것은 나에게 효과가 있었다.

form_for @list.becomes(List)

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

.becomes의 STI 됩니다.form_for 둘, 셋

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

반응이 너무 늦었지만, 이것이 제가 찾을 수 있는 가장 좋은 답이고 저에게 잘 먹혔습니다.이것이 누군가에게 도움이 되기를 바랍니다.건배!

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

좋아요, 저는 레일즈의 이 분야에서 많은 좌절을 겪었고, 다음과 같은 접근법에 도달했습니다. 아마도 이것은 다른 사람들에게 도움이 될 것입니다.

먼저, 인터넷 상 및 주변의 많은 솔루션이 클라이언트가 제공한 매개 변수에 대해 상수화를 사용할 것을 제안한다는 점에 유의하십시오.Ruby가 기호를 가비지 않기 때문에 공격자가 임의 기호를 만들고 사용 가능한 메모리를 사용할 수 있기 때문에 알려진 DoS 공격 벡터입니다.

저는 모델 하위 클래스의 인스턴스화를 지원하고 위의 Contantize 문제로부터 안전한 아래의 접근 방식을 구현했습니다.이것은 레일 4와 매우 유사하지만 (레일 4와 달리) 둘 이상의 하위 분류를 허용하고 레일 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을(를) 재정의하지 않습니다. 예를 들어, tax_identifier는 조직의 경우 'ABN'이 되고, 호주의 경우 'TFN'이 됩니다.

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

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 }

그리고 이 작업을 수행하기 위해 기본 ResourceController에서 InheritedResource의 resource_request_name을 다음 보기에 대한 도우미 메서드로 표시합니다.

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

다른 사람들의 경험과 발전을 듣는 것은 항상 행복합니다.

최근 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 접근 방식과 관련하여 겪은 여러 가지 다른 문제를 해결합니다.

내가 찾은 가장 깨끗한 해결책은 다음을 기본 클래스에 추가하는 것입니다.

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에 의해 정의된 모델 이름을 재정의함으로써 발생하는 잠재적인 부작용 없이 라우팅 문제를 해결합니다.

중첩된 경로가 없는 경우 다음을 시도할 수 있습니다.

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

또는 여기에 설명된 것처럼 다른 방법으로 OOP 마법을 사용할 수도 있습니다. https://coderwall.com/p/yijmuq

두 번째 방법으로 모든 내포된 모형에 대해 유사한 도우미를 만들 수 있습니다.

여기 우리가 사용하는 양식과 응용프로그램 전체에서 작동할 수 있는 안전하고 깨끗한 방법이 있습니다.

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|

이게 도움이 되길 바랍니다.

@prathan-than-art 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].

다음과 같은 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

다음은 A 모델 클래스입니다.

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

따라서 하위 클래스 정의를 건드리지 않고 기본 모델과 이 모델을 사용할 모델을 쉽게 선택할 수 있습니다.매우 건조합니다.

이 방법은 Mook에 적용됩니다(기본 클래스에서 이 방법 정의).

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

라우팅 목적으로 더미 상위 개체를 반환하는 메서드를 만들 수 있습니다.

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

그런 다음 유형 없이 사용자 클래스 개체를 반환하는 form_for @employee.ro uting_object를 호출합니다.

을 사용하는 것에 찬성합니다.PolymorphicRoutes또는url_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"

와 함께admin

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

재정의 중model_name위험해 보입니다.용사를 합니다..becomes더 안전한 선택인 것 같습니다.

한 가지 문제는 어떤 모델과 기본 모델을 다루고 있는지 모르는 경우입니다.

이러한 경우 다음과 같은 기능을 사용할 수 있습니다.

foo.becomes(foo.class.base_class)

사용 편의성을 위해 이 방법을 사용자 환경에 추가했습니다.ApplicationRecord:

def becomes_base
  becomes(self.class.base_class)
end

추가하기.becomes_base몇 가지 경로 도우미 방법은 저에게 큰 문제가 아닌 것 같습니다.

해킹을 하지만 해결책 목록에 있는 또 다른 하나일 뿐입니다.

class Parent < ActiveRecord::Base; end

Class Child < Parent
  def class
    Parent
  end
end

레일 2.x 및 3.x에서 작동합니다.

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