철도에서 STI 서브 클래스의 경로를 처리하는 모범 사례
내 레일 뷰와 컨트롤러는 뒤덮 redirect_to
, link_to
및 form_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는 객체의 클래스 이름 (직원)으로 정의 된 경로를 찾고 있습니다. 이 직원 경로는 정의되어 있지 않으며 직원 컨트롤러가 없으므로 작업도 정의되지 않습니다.
이 질문은 전에 요청되었습니다 :
- 에서 StackOverflow의 대답은 전체 코드베이스에 LINK_TO 등의 모든 인스턴스를 편집하고 명시 적 경로를 명시하는 것입니다
- 에 StackOverflow에 다시 두 사람이 사용하는 것이 좋습니다
routes.rb
부모 클래스의 서브 클래스 리소스 (매핑map.resources :employees, :controller => 'people'
). 같은 SO 질문에서 가장 좋은 대답은 코드베이스의 모든 인스턴스 객체를 사용하여 유형 캐스팅을 제안합니다..becomes
- StackOverflow의 또 다른 하나 는 Do Repeat Yourself 캠프의 방법이며 모든 하위 클래스에 대해 중복 스캐 폴딩을 만드는 것을 제안합니다.
- 여기에 다시 같은 질문이 있습니다. 여기서 가장 좋은 대답은 틀린 것 같습니다 (Rails magic Just Works!)
- 웹의 다른 곳 에서 F2Andy가 코드의 모든 경로에서 편집을 권장하는 이 블로그 게시물을 발견 했습니다 .
- Logical Reality Design 의 블로그 게시물 Single Table Inheritance 및 RESTful Routes 에서 위의 SO 답변 2와 같이 서브 클래스의 리소스를 수퍼 클래스 컨트롤러에 매핑하는 것이 좋습니다.
- 알렉스 Reisner는 게시물이 레일에서 단일 테이블 상속 그가에서 상위 클래스에 하위 클래스의 자원을 매핑에 대해 옹호하는,
routes.rb
만 어획량에서 파손을 라우팅하기 때문에,link_to
그리고redirect_to
있지만에서을form_for
. 따라서 대신 서브 클래스가 자신의 클래스에 대해 거짓말하도록 부모 클래스에 메소드를 추가하는 것이 좋습니다. 잘 들리지만 그의 방법으로 오류가 발생했습니다undefined local variable or method `child' for #
.
가장 우아한 보인다 가장 공감대를 가지고 (그러나 그것은 모두가 아니다 대답 그래서 그 우아하고도 그 많은 합의)는 당신에 리소스를 추가입니다 routes.rb
. 이 기능은 작동하지 않습니다 form_for
. 명확성이 필요합니다! 위의 선택을 증류하기 위해 내 옵션은
- 서브 클래스의 리소스를 수퍼 클래스의 컨트롤러에 매핑하십시오 (서브 클래스에서
routes.rb
form_for를 호출 할 필요가 없기를 바랍니다) - 클래스가 서로에게 있도록 레일 내부 메소드를 대체
- 경로를 변경하거나 객체를 타입 캐스팅하여 객체의 동작 경로가 암시 적 또는 명시 적으로 호출되는 코드의 모든 인스턴스를 편집하십시오.
이 모든 상충되는 답변으로 판결이 필요합니다. 좋은 대답이없는 것 같습니다. 이것이 레일 디자인에서 실패한 것입니까? 그렇다면 수정 될 수있는 버그입니까? 또는 그렇지 않은 경우 누군가가 나를 바로 설정하고 각 옵션의 장단점을 안내하거나 옵션이 아닌 이유를 설명하고 올바른 답변과 이유를 설명하기를 바랍니다. 아니면 웹에서 찾지 못하는 정답이 있습니까?
이것은 부작용을 최소화 할 수있는 가장 간단한 솔루션입니다.
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
'Programing' 카테고리의 다른 글
HTML5 History API (Pushstate?) 사용을위한 유용한 자습서 (0) | 2020.05.27 |
---|---|
자바에서 클래스를 언로드? (0) | 2020.05.27 |
Emacs에서 * scratch * 버퍼를 다시 열겠습니까? (0) | 2020.05.27 |
Java 8에서 String.chars ()가 int 스트림 인 이유는 무엇입니까? (0) | 2020.05.27 |
전 처리기 지시문으로 OS를 어떻게 확인합니까? (0) | 2020.05.26 |