cómo permitir una matriz con parámetros fuertes

269

Tengo una aplicación Rails 3 en funcionamiento que utiliza has_many: a través de asociaciones que no lo son, ya que la rehago como una aplicación Rails 4, lo que me permite guardar identificadores del modelo asociado en la versión Rails 4.

Estos son los tres modelos relevantes que son iguales para las dos versiones.

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

En ambas aplicaciones, los identificadores de categoría se pasan a la acción de creación como esta

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

En la aplicación Rails 3, cuando creo una nueva pregunta, se inserta en la tabla de preguntas y luego en la tabla de categorizaciones

 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]]

En la aplicación rails 4, después de que procesa los parámetros en QuestionController # create, recibo este error en los registros del servidor

Unpermitted parameters: category_ids

y la pregunta solo se inserta en la tabla de preguntas

 (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

Aunque no estoy almacenando los category_ids en el modelo de Preguntas, configuro category_ids como un parámetro permitido en el question_controller

   def question_params

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

¿Alguien puede explicar cómo se supone que debo guardar los category_ids? Tenga en cuenta que no hay ninguna acción de creación en el category_controller.rb de ninguna de las aplicaciones.

Estas son las tres tablas que son iguales en ambas aplicaciones.

 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

Actualizar

Esta es la acción de crear desde la aplicación 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 es la acción de crear desde la aplicación 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 es el 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
fuente
¿Cómo se ve la acción de crear en ambas aplicaciones?
bennick
@bennick Agregué las dos acciones de creación. Gracias
Leahcim

Respuestas:

527

Este https://github.com/rails/strong_parameters parece ser la sección relevante de los documentos:

Los tipos escalares permitidos son String, Symbol, NilClass, Numeric, TrueClass, FalseClass, Date, Time, DateTime, StringIO, IO, ActionDispatch :: Http :: UploadedFile y Rack :: Test :: UploadedFile.

Para declarar que el valor en los parámetros debe ser una matriz de valores escalares permitidos, asigne la clave a una matriz vacía:

params.permit(:id => [])

En mi aplicación, los category_ids se pasan a la acción de crear en una matriz

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

Por lo tanto, al declarar parámetros fuertes, establezco explícitamente category_ids para ser una matriz

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

¡Funciona perfectamente ahora!

( IMPORTANTE: como @Lenart señala en los comentarios, las declaraciones de la matriz deben estar al final de la lista de atributos, de lo contrario obtendrá un error de sintaxis).

Leahcim
fuente
105
También he notado que las declaraciones para las matrices deben estar al final de la lista de atributos. De lo contrario, aparece un error de sintaxissyntax error, unexpected ')', expecting =>
Lenart
45
La razón por la que las declaraciones de matriz (parámetros anidados) están al final es que ActionController :: Parameters.permit espera que cada argumento sea un Hash o un Símbolo. La magia de Ruby convertirá todos los pares de valores clave s al final de una llamada al método en un hash, pero Ruby no sabrá qué hacer si combina símbolos con pares clave / valor en su llamada al método.
Sameers
77
si category_idsno es una matriz (p. ej., se envió una cadena), los rieles se vuelven totalmente locos y generan un ERROR TypeError: expected Array (got String) for param `category_ids'¿Es un error en los rieles? Editar: sí, es: github.com/rails/rails/issues/11502
Dominik Goltermann
44
@Lenart Gracias, pensé que me estaba volviendo loco. Esto al menos debería aparecer en el archivo Léame strong_params
Sebastialonso
3
@Lenart puede agregarlo como hash si desea evitar el error de sintaxis. params.permit(:foo, { bar: [] }, :zoo).
Foo Bar Zoo
99

Si desea permitir una matriz de hashes (o an array of objectsdesde la perspectiva de JSON)

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

2 puntos para notar aquí:

  1. arraydebería ser el último argumento del permitmétodo.
  2. debe especificar las claves del hash en la matriz, de lo contrario obtendrá un error Unpermitted parameter: array, que es muy difícil de depurar en este caso.
Brian
fuente
y las matrices de matrices no son compatibles (todavía): github.com/rails/rails/issues/23640
Andrei Dziahel
11
arrayno necesita estar al final, pero debe asegurarse de tener un Ruby válido. params.permit(:foo, array: [:key1, :key2], :bar)no sería válido ruby ​​ya que el intérprete espera claves hash: pares de valores después del primer conjunto. Para tener Ruby válido necesitasparams.permit(:foo, {array: [:key1, :key2]}, :bar)
mastaBlasta
Eres un campeón!
Lollypop
22

Debería ser como

params.permit(:id => [])

También desde rails versión 4+ puedes usar:

params.permit(id: [])
Matias Seguel
fuente
12
por favor no que la matriz sea el último argumento del método de permiso
Matias Elorriaga
3
Esa sintaxis hash que dijiste que aplica para Rails v4 + es en realidad una sintaxis disponible en Ruby 1.9 y posteriores, no en el marco de Rails (aunque Rails 4 requiere Ruby 1.9.3 o posterior)
MegaTux
11

Si tiene una estructura hash como esta:

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

Entonces así es como lo hice funcionar:

params.require(:link).permit(:title, time_span: [[:start, :end]])
Extraño compañero
fuente
6

No puedo comentar todavía, pero siguiendo la solución Fellow Stranger también puede seguir anidando en caso de que tenga claves cuyos valores son una matriz. Me gusta esto:

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

Esto funciona:

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