Rails STI和强大的参数

Bog*_*opa 6 ruby-on-rails sti

我有一个客户端资源有两种类型:人员和公司.

routes.rb中:

resources :clients
resources :people, :controller => "clients", :type => "Person"
resources :companies, :controller => "clients", :type => "Company"
Run Code Online (Sandbox Code Playgroud)

clients_controller:

def new
  @client = Client.new()
  @client.type = params[:type]
end

def create
  @client = current_partner.clients.new(client_params)
  if @client.save
    redirect_to clients_path
  ...
end

...
private
def client_params
  params.require(:client).permit(:type, :partner_id, :name, :email, :phone, :cui,   :registration_id, :address)
end

def find_client
  @client ||= Client.find(params[:id])
end
Run Code Online (Sandbox Code Playgroud)

client.rb

class Client < ActiveRecord::Base
  validates_presence_of :type
  CLIENT_TYPES = ['Person', 'Company']
end
Run Code Online (Sandbox Code Playgroud)

person.rb

class Person < Client
  validates_presence_of :name, :email, :phone
end
Run Code Online (Sandbox Code Playgroud)

compay.rb

class Company < Client
  validates_presence_of :name, :email, :cui, :registration_id, :phone
  validates_uniqueness_of :cui, :registration_id, uniqueness: {scope: :partner_id}
end
Run Code Online (Sandbox Code Playgroud)

问题是,当我尝试编辑客户端的详细信息并提交更改时,我发现param丢失或值为空:客户端.我收到此错误的路线是....../companies/3.

对这个noobie问题的任何帮助?谢谢!

Ran*_*ili 7

您可以在允许操作之前将继承的模型伪装成它的父级

def client_params

  if params.has_key? :person
    params[:client] = params.delete :person
  elsif params.has_key? :company
    params[:client] = params.delete :company
  end

  params.require(:client).permit(...)

end
Run Code Online (Sandbox Code Playgroud)

它只是在params散列中重命名模型的键,并且可以解决问题.


Ric*_*eck 5

楷模

我认为您没有正确使用STI

STI 是针对Models 的,而不是 针对controllers. 根据MVC 编程模式,您Models处理所有数据构造方法。您的控制器用作您的user输入和您的views:

在此处输入图片说明

这意味着如果你想使用或创建 STI 驱动的功能,你最好只使用后端classes(而不是手动传递type等):

#app/models/person.rb
Class Person < Client
   ...
end

#app/models/company.rb
Class Company < Client
end

#app/models/client.rb
Class Client < ActiveRecord::Base
end
Run Code Online (Sandbox Code Playgroud)

这将使您能够做到这一点:

#config/routes.rb
resources :clients
resources :people,    controller: "clients", type: "Person"
resources :companies, controller: "clients", type: "Company"

#app/controllers/clients_controller.rb
Class ClientsController < ApplicationController
    def create
       model = get_model(params[:type])

       @model = model.new(model_params)
       @model.save
    end

    private

    def get_model type
       return type.singularize.titleize.camelize.constantize
    end

    def model_params
        params.require(params[:type].to_sym).permit(:client, :model, :attributes)
    end
end
Run Code Online (Sandbox Code Playgroud)

这将Model是您通过访问/people或设置的各种 STI ,/companies然后让您能够将数据保存到其中(当然,这将保存到Client模型type中)

  • 警告:您基本上允许任何有权访问此控制器操作的人能够在您的服务器上创建他们喜欢的任何 Ruby 类的实例。更糟糕的是,您允许他们通过 `initialize` 方法将参数传递给该类实例。你需要在调用 `constantize` 之前将 `params[:type]` 的值列入白名单。此外,请查看用于将字符串转换为驼峰式类名称字符串的 `classify` 方法。 (3认同)
  • 跟进:最安全的方法是在您的班级中使用 `TYPES = { 'person' =&gt; Person, 'company' =&gt; Company }`。然后 `Client::TYPES[params[:type]].new(params[:client])` 将安全地将你的参数 `type` 一步转换为一个实际的类,或者返回 `nil`。在 `nil` 上调用 `new` 会引发错误,但这比替代方法更好,并且无论如何都会发出某种干扰信号。 (2认同)