Programing

STI, 하나의 컨트롤러

lottogame 2020. 12. 10. 08:26
반응형

STI, 하나의 컨트롤러


저는 Rails를 처음 사용하고이 디자인 문제에 갇혀 있습니다. 쉽게 해결할 수 있지만 아무데도 얻지 못합니다. 하이라이트와 할인이라는 두 가지 유형의 광고가 있습니다. 둘 다 제목, 설명 및 하나의 이미지 (종이 클립 포함)와 같은 동일한 속성을 갖습니다. 또한 색인, 새로 작성, 편집, 작성, 업데이트 및 삭제와 같은 동일한 종류의 작업을 적용 할 수 있습니다.

다음과 같이 STI를 설정했습니다.

광고 모델 : ad.rb

class Ad < ActiveRecord::Base
end

바겐 세일 모델 : bargain.rb

class Bargain < Ad
end

하이라이트 모델 : highlight.rb

class Highlight < Ad
end

문제는 AdsControllerwww.foo.com/bargains [/ ...] 또는 www.foo.com과 같이 URL에 따라 할인 또는 하이라이트에 대해 말한 작업을 실행하는 컨트롤러 ( )를 하나만 갖고 싶다는 것입니다. /하이라이트[/...].

예를 들면 :

  • GET www.foo.com/highlights => 하이라이트 인 모든 광고 목록.
  • GET www.foo.com/highlights/new => 새로운 하이라이트 등을 만들기위한 양식 ...

어떻게 할 수 있습니까?

감사!


먼저. 새로운 경로 추가 :

resources :highlights, :controller => "ads", :type => "Highlight"
resources :bargains, :controller => "ads", :type => "Bargain"

그리고 AdsController. 예를 들면 :

def new
  @ad = Ad.new()
  @ad.type = params[:type]
end

이 모든 컨트롤러 작업에 대한 최상의 접근 방식을 보려면 이 주석을 보십시오 .

그게 다야. 이제로 이동할 수 localhost:3000/highlights/new있으며 new Highlight가 초기화됩니다.

인덱스 작업은 다음과 같습니다.

def index
  @ads = Ad.where(:type => params[:type])
end

로 이동하면 localhost:3000/highlights하이라이트 목록이 나타납니다.
할인에 대한 동일한 방법 :localhost:3000/bargains

기타

URL

<%= link_to 'index', :highlights %>
<%= link_to 'new', [:new, :highlight] %>
<%= link_to 'edit', [:edit, @ad] %>
<%= link_to 'destroy', @ad, :method => :delete %>

다형성을 위해 :)

<%= link_to 'index', @ad.class %>

fl00r에는 좋은 해결책이 있지만 한 가지 조정을합니다.

이것은 귀하의 경우에 필요할 수도 있고 필요하지 않을 수도 있습니다. STI 모델, 특히 유효성 검사 및 수명주기 후크에서 변경되는 동작에 따라 다릅니다.

컨트롤러에 private 메서드를 추가하여 유형 매개 변수를 사용하려는 실제 클래스 상수로 변환합니다.

def ad_type
  params[:type].constantize
end

그러나 위의 내용은 안전하지 않습니다. 유형의 허용 목록을 추가합니다.

def ad_types
  [MyType, MyType2]
end

def ad_type
  params[:type].constantize if params[:type].in? ad_types
end

레일 상수 화 방법에 대한 자세한 내용은 http://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-constantize를 참조 하십시오.

그런 다음 컨트롤러 작업에서 다음을 수행 할 수 있습니다.

def new
  ad_type.new
end

def create
  ad_type.new(params)
  # ...
end

def index
  ad_type.all
end

이제 속성 유형이 설정된 상위 클래스 대신 올바른 동작으로 실제 클래스를 사용하고 있습니다.


이 주제와 관련된 흥미로운 트릭이 많이 있기 때문에이 링크를 포함하고 싶었습니다.

Alex Reisner-Rails의 단일 테이블 상속


나는 이것이 @flOOr 및 @Alan_Peabody의 답변을 포함하는 내가 좋아하는 패턴이라는 오래된 질문이라는 것을 알고 있습니다. (Rails 4.2에서 테스트되었으며 아마도 Rails 5에서 작동 할 것입니다)

모델에서 시작시 화이트리스트를 만듭니다. dev에서 이것은 열심히로드되어야합니다.

class Ad < ActiveRecord::Base
    Rails.application.eager_load! if Rails.env.development?
    TYPE_NAMES = self.subclasses.map(&:name)
    #You can add validation like the answer by @dankohn
end

이제 모든 컨트롤러에서이 화이트리스트를 참조하여 올바른 범위를 빌드 할 수있을뿐만 아니라 양식에서 : type 선택을위한 컬렉션 등을 만들 수 있습니다.

class AdsController < ApplicationController
    before_action :set_ad, :only => [:show, :compare, :edit, :update, :destroy]

    def new
        @ad = ad_scope.new
    end

    def create
        @ad = ad_scope.new(ad_params)
        #the usual stuff comes next...
    end

    private
    def set_ad
        #works as normal but we use our scope to ensure subclass
        @ad = ad_scope.find(params[:id])
    end

    #return the scope of a Ad STI subclass based on params[:type] or default to Ad
    def ad_scope
        #This could also be done in some kind of syntax that makes it more like a const.
        @ad_scope ||= params[:type].try(:in?, Ad::TYPE_NAMES) ? params[:type].constantize : Ad
    end

    #strong params check works as expected
    def ad_params
        params.require(:ad).permit({:foo})
    end
end

객체의 실제 : type에도 불구하고 라우팅이 기본 클래스 컨트롤러로 전송되어야하므로 양식을 처리해야합니다. 이를 위해 "becomes"를 사용하여 양식 작성기를 올바른 라우팅으로 속이고 : as 지시문을 사용하여 입력 이름도 기본 클래스가되도록합니다. 이 조합을 사용하면 수정되지 않은 경로 (리소스 : ads)를 사용할 수있을뿐만 아니라 양식에서 돌아 오는 params [: ad]에 대한 강력한 매개 변수 검사도 사용할 수 있습니다.

#/views/ads/_form.html.erb
<%= form_for(@ad.becomes(Ad), :as => :ad) do |f| %>

[완벽하게 작동하는 더 간단한 솔루션으로 다시 작성 :]

Iterating on the other answers, I have come up with the following solution for a single controller with Single Table Inheritance that works well with Strong Parameters in Rails 4.1. Just including :type as a permitted parameter caused an ActiveRecord::SubclassNotFound error if an invalid type is entered. Moreover, type is not updated because the SQL query explicitly looks for the old type. Instead, :type needs to be updated separately with update_column if it is different than what is current set and is a valid type. Note also that I've succeeded in DRYing up all lists of types.

# app/models/company.rb
class Company < ActiveRecord::Base
  COMPANY_TYPES = %w[Publisher Buyer Printer Agent]
  validates :type, inclusion: { in: COMPANY_TYPES,
    :message => "must be one of: #{COMPANY_TYPES.join(', ')}" }
end

Company::COMPANY_TYPES.each do |company_type|
  string_to_eval = <<-heredoc
    class #{company_type} < Company
      def self.model_name  # http://stackoverflow.com/a/12762230/1935918
        Company.model_name
      end
    end
  heredoc
  eval(string_to_eval, TOPLEVEL_BINDING)
end

And in the controller:

  # app/controllers/companies_controller.rb
  def update
    @company = Company.find(params[:id])

    # This separate step is required to change Single Table Inheritance types
    new_type = params[:company][:type]
    if new_type != @company.type && Company::COMPANY_TYPES.include?(new_type)
      @company.update_column :type, new_type
    end

    @company.update(company_params)
    respond_with(@company)
  end

And routes:

# config/routes.rb
Rails.application.routes.draw do
  resources :companies
  Company::COMPANY_TYPES.each do |company_type|
    resources company_type.underscore.to_sym, type: company_type, controller: 'companies', path: 'companies'
  end
  root 'companies#index'

Finally, I recommend using the responders gem and setting scaffolding to use a responders_controller, which is compatible with STI. Config for scaffolding is:

# config/application.rb
    config.generators do |g|
      g.scaffold_controller "responders_controller"
    end

참고URL : https://stackoverflow.com/questions/5246767/sti-one-controller

반응형