como permitir uma matriz com parâmetros fortes

269

Eu tenho um aplicativo Rails 3 em funcionamento que usa has_many: por meio de associações que não são, pois eu o refizo como um aplicativo Rails 4, permitindo salvar IDs do modelo associado na versão Rails 4.

Estes são os três modelos relevantes são os mesmos para as duas versões.

Categorization.rb

class Categorization < ActiveRecord::Base

  belongs_to :question
  belongs_to :category
end

Question.rb

has_many :categorizations
has_many :categories, through: :categorizations

Category.rb

has_many :categorizations
has_many :questions, through: :categorizations

Nos dois aplicativos, os IDs da categoria são passados ​​para a ação de criação como esta

  "question"=>{"question_content"=>"How do you spell car?", "question_details"=>"blah ", "category_ids"=>["", "2"],

No aplicativo Rails 3, quando crio uma nova pergunta, ele é inserido na tabela de perguntas e depois na tabela de categorizações

 SQL (82.1ms)  INSERT INTO "questions" ("accepted_answer_id", "city", "created_at", "details", "province", "province_id", "question", "updated_at", "user_id") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)  [["accepted_answer_id", nil], ["city", "dd"], ["created_at", Tue, 14 May 2013 17:10:25 UTC +00:00], ["details", "greyound?"], ["province", nil], ["province_id", 2], ["question", "Whos' the biggest dog in the world"], ["updated_at", Tue, 14 May 2013 17:10:25 UTC +00:00], ["user_id", 53]]
  SQL (0.4ms)  INSERT INTO "categorizations" ("category_id", "created_at", "question_id", "updated_at") VALUES (?, ?, ?, ?)  [["category_id", 2], ["created_at", Tue, 14 May 2013 17:10:25 UTC +00:00], ["question_id", 66], ["updated_at", Tue, 14 May 2013 17:10:25 UTC +00:00]]

No aplicativo rails 4, depois de processar os parâmetros no QuestionController # create, estou recebendo esse erro nos logs do servidor

Unpermitted parameters: category_ids

e a pergunta está sendo inserida apenas na tabela de perguntas

 (0.2ms)  BEGIN
  SQL (67.6ms)  INSERT INTO "questions" ("city", "created_at", "province_id", "question_content", "question_details", "updated_at", "user_id") VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING "id"  [["city", "dd"], ["created_at", Tue, 14 May 2013 17:17:53 UTC +00:00], ["province_id", 3], ["question_content", "How's your car?"], ["question_details", "is it runnign"], ["updated_at", Tue, 14 May 2013 17:17:53 UTC +00:00], ["user_id", 12]]
   (31.9ms)  COMMIT

Embora eu não esteja armazenando os category_ids no modelo de Perguntas, defino category_ids como um parâmetro permitido no questions_controller

   def question_params

      params.require(:question).permit(:question_details, :question_content, :user_id, :accepted_answer_id, :province_id, :city, :category_ids)
    end

Alguém pode explicar como eu devo salvar os category_ids? Observe que não há ação de criação no categories_controller.rb de qualquer aplicativo.

Estas são as três tabelas iguais nos dois aplicativos

 create_table "questions", force: true do |t|
    t.text     "question_details"
    t.string   "question_content"
    t.integer  "user_id"
    t.integer  "accepted_answer_id"
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer  "province_id"
    t.string   "city"
  end

 create_table "categories", force: true do |t|
    t.string   "name"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

  create_table "categorizations", force: true do |t|
    t.integer  "category_id"
    t.integer  "question_id"
    t.datetime "created_at"
    t.datetime "updated_at"
  end

Atualizar

Esta é a ação de criação do aplicativo Rails 3

  def create
      @question = Question.new(params[:question])
      respond_to do |format|
      if @question.save
        format.html { redirect_to @question, notice: 'Question was successfully created.' }
        format.json { render json: @question, status: :created, location: @question }
      else
        format.html { render action: "new" }
        format.json { render json: @question.errors, status: :unprocessable_entity }
      end
    end
end

Esta é a ação de criação do aplicativo Rails 4

   def create
      @question = Question.new(question_params)

       respond_to do |format|
      if @question.save
        format.html { redirect_to @question, notice: 'Question was successfully created.' }
        format.json { render json: @question, status: :created, location: @question }
      else
        format.html { render action: "new" }
        format.json { render json: @question.errors, status: :unprocessable_entity }
      end
    end
    end

Este é o método question_params

 private
    def question_params 
      params.require(:question).permit(:question_details, :question_content, :user_id, :accepted_answer_id, :province_id, :city, :category_ids)
    end
Leahcim
fonte
Como é a ação de criação nos dois aplicativos?
bennick 14/05
@ bennick eu adicionei as duas ações de criação. Obrigado
Leahcim

Respostas:

527

Este https://github.com/rails/strong_parameters parece a seção relevante dos documentos:

Os tipos escalares permitidos são String, Símbolo, NilClass, Numérico, TrueClass, FalseClass, Data, Hora, DateTime, StringIO, IO, ActionDispatch :: Http :: UploadedFile e Rack :: Test :: UploadedFile.

Para declarar que o valor em parâmetros deve ser uma matriz de valores escalares permitidos, mapeie a chave para uma matriz vazia:

params.permit(:id => [])

No meu aplicativo, os category_ids são passados ​​para a ação create em uma matriz

"category_ids"=>["", "2"],

Portanto, ao declarar parâmetros fortes, eu defino explicitamente category_ids como uma matriz

params.require(:question).permit(:question_details, :question_content, :user_id, :accepted_answer_id, :province_id, :city, :category_ids => [])

Funciona perfeitamente agora!

( IMPORTANTE: Como o @Lenart observa nos comentários, as declarações da matriz devem estar no final da lista de atributos; caso contrário, você receberá um erro de sintaxe.)

Leahcim
fonte
105
Também notei que as declarações de matrizes devem estar no final da lista de atributos. Caso contrário, recebo um erro de sintaxesyntax error, unexpected ')', expecting =>
Lenart 13/10
45
O motivo pelo qual as declarações da matriz (parâmetros aninhados) estão no final é que o ActionController :: Parameters.permit espera que cada argumento seja um Hash ou um Símbolo. A magia Ruby transformará todos os pares de valores-chave no final de uma chamada de método em um hash, mas Ruby não saberá o que fazer se você misturar símbolos com pares de chave / valor em sua chamada de método.
Sameers
7
se category_idsnão for um array (por exemplo, uma string foi enviada), os trilhos ficam totalmente loucos e aumentam a ERROR TypeError: expected Array (got String) for param `category_ids'Isso é um bug nos trilhos? Edit: yes is: github.com/rails/rails/issues/11502
Dominik Goltermann
4
@Lenart Obrigado, eu pensei que estava ficando louco. Isso deve pelo menos aparecer no arquivo forte_params README
Sebastialonso
3
@Lenart, você pode adicioná-lo como hash se quiser evitar erros de sintaxe. params.permit(:foo, { bar: [] }, :zoo).
Foo Bar Zoo
99

Se você deseja permitir uma matriz de hashes (ou an array of objectsda perspectiva do JSON)

params.permit(:foo, array: [:key1, :key2])

2 pontos a serem observados aqui:

  1. arraydeve ser o último argumento do permitmétodo
  2. você deve especificar as chaves do hash na matriz, caso contrário, você receberá um erro Unpermitted parameter: array, o que é muito difícil de depurar nesse caso.
Brian
fonte
e matrizes de matrizes não está suportado (ainda): github.com/rails/rails/issues/23640
Andrei Dziahel
11
arraynão precisa estar no final, mas você precisa ter o Ruby válido. params.permit(:foo, array: [:key1, :key2], :bar)não seria válido ruby, pois o intérprete espera pares de chave de hash: valor após o primeiro conjunto. Para ter validade Ruby que você precisaparams.permit(:foo, {array: [:key1, :key2]}, :bar)
mastaBlasta
Você é um campeão!
Lollypop #
22

Deveria ser como

params.permit(:id => [])

Além disso, desde a versão 4+ do Rails, você pode usar:

params.permit(id: [])
Matias Seguel
fonte
12
por favor, não que a matriz deve ser o último argumento do método licença
Matias Elorriaga
3
Essa sintaxe hash que você disse solicitar Rails v4 + é realmente uma sintaxe disponível no Ruby 1.9 e mais recente, não o framework Rails (embora Rails 4 requer o Ruby 1.9.3 ou mais recente)
MegaTux
11

Se você tem uma estrutura de hash como esta:

Parameters: {"link"=>{"title"=>"Something", "time_span"=>[{"start"=>"2017-05-06T16:00:00.000Z", "end"=>"2017-05-06T17:00:00.000Z"}]}}

Então é assim que eu consegui funcionar:

params.require(:link).permit(:title, time_span: [[:start, :end]])
Estrangeiro companheiro
fonte
6

Ainda não posso comentar, mas, seguindo a solução Fellow Stranger, você também pode continuar aninhando caso tenha chaves cujos valores são uma matriz. Como isso:

filters: [{ name: 'test name', values: ['test value 1', 'test value 2'] }]

Isso funciona:

params.require(:model).permit(filters: [[:name, values: []]])
Daniel Duque
fonte