레일에서 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>
레일즈는 객체의 클래스 이름(직원)으로 정의된 경로를 찾고 있습니다.이러한 직원 경로는 정의되지 않았으며 직원 컨트롤러도 없으므로 작업도 정의되지 않습니다.
이 질문은 이전에 제기된 적이 있습니다.
- StackOverflow에서 답은 전체 코드베이스에서 link_to 등의 모든 인스턴스를 편집하고 경로를 명시적으로 지정하는 것입니다.
- StackOverflow에서 두 사람이 다음을 사용할 것을 제안합니다.
routes.rb
클래스에 .map.resources :employees, :controller => 'people'
). 에 대해 동한SO 질답다사음용코드스의이모다를 사용하여.becomes
- StackOverflow의 또 다른 방법은 Do Repeat Yourself 캠프에서 모든 하위 클래스에 대해 중복 비계를 만들 것을 제안하는 것입니다.
- SO에서 다시 같은 질문이 나왔는데, 여기서 최고의 답변은 그저 잘못된 것 같습니다(레일즈 매직 저스트 효과!).
- 웹의 다른 곳에서, 저는 F2Andy가 코드의 모든 경로에서 편집할 것을 권장하는 이 블로그 게시물을 발견했습니다.
- 블로그 게시물 Logical Reality Design의 단일 테이블 상속 및 RESTful Routes에서 위의 SO 답변 번호 2에서와 같이 하위 클래스에 대한 리소스를 슈퍼 클래스 컨트롤러에 매핑하는 것이 좋습니다.
- 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
명확한 설명이 필요해요!, 의 선택사항은 , 나선은택의입니다.
- .
routes.rb
가 어떤 가 없기를 바랍니다 (_for_for를 위한 것입니다. - 레일 내부 메서드를 재정의하여 클래스가 서로 연결되도록 합니다.
- 코드에서 객체의 작업 경로가 암시적으로 또는 명시적으로 호출되는 모든 인스턴스를 편집하여 경로를 변경하거나 객체를 유형 캐스트합니다.
이 모든 상반된 답변들과 함께, 저는 판결이 필요합니다.제가 보기에는 좋은 답이 없는 것 같습니다.이것이 레일 설계의 오류입니까?그렇다면 수리될 수 있는 버그인가요?아니면 그렇지 않다면, 저는 누군가가 저에게 이것에 대해 바로 알려주고, 각 옵션의 장단점을 설명해주기를 바랍니다(또는 그것이 옵션이 아닌 이유를 설명해주세요). 그리고 어떤 것이 정답이고, 왜 그런지 설명해주세요.아니면 웹에서 찾을 수 없는 정답이 있습니까?
이것은 부작용을 최소화하면서 제가 생각해 낼 수 있는 가장 간단한 해결책입니다.
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
'programing' 카테고리의 다른 글
왜 클랑은 "||" 안에 "&"이라고 경고합니까? (0) | 2023.06.14 |
---|---|
문서의 전체 텍스트 범위를 가져오기 위한 VS 코드 확장 API? (0) | 2023.06.14 |
변환이 VUEX의 스토어를 업데이트하지 않음 (0) | 2023.06.14 |
텍스트 편집에서 막대 아래를 숨기는 방법 (0) | 2023.06.14 |
UIView에 관점 변환을 적용하려면 어떻게 해야 합니까? (0) | 2023.06.14 |