Home > OS >  Rails 6 nested attributes not deleting existing records when updating
Rails 6 nested attributes not deleting existing records when updating

Time:11-16

I'm building an application where I have used nested attributes to store different option records under a question record. There is a form where the user can dynamically add and remove options. Everything works fine in my create action, but in the update action, if I remove an existing option and submit the form, it is not deleted from the database.

When updating the question record, is there any way to completely overwrite the existing nested parameters and replace it with the ones we pass in the update request?

I understand that adding _destroy to the attributes and passing it as a parameter would satisfy my requirement here. Since I'm deleting the option information from my frontend state on press of a "remove" button in the UI, I'm not sending it along with the params. Is there any other method in Rails to completely overwrite nested attributes and delete those nested records which are not passed in the update request, from the update action in the controller itself?

question.rb

class Question < ApplicationRecord
  belongs_to :quiz
  has_many :options

  validates :body, presence: true
  accepts_nested_attributes_for :options
end

option.rb

class Option < ApplicationRecord
  belongs_to :question

  validates :body, presence: true
  validates :is_correct, inclusion: { in: [ true, false ], message: "must be true or false" }
end

questions_controller.rb

class QuestionsController < ApplicationController
 ...

 def update
   @question = Question.find_by(id: params[:id])
   if @question.update(question_params)
     render status: :ok, json: { notice: t("question.successfully_updated") }
   else
     render status: :unprocessable_entity, json: { error: @question.errors.full_messages.to_sentence }
   end
 end

...

private

  def question_params
    params.require(:question).permit(:body, :quiz_id, options_attributes: [:id, :body, :is_correct])
  end

Relevant question

CodePudding user response:

If I understand you correctly you're deleting the options one by one by clicking a button next to the option. Thats not actually something you need or want to use nested attributes for. Nested attributes is only relevant when you're creating/editing multiple records at once.

While you can destroy a single nested record by updating the parent:

patch '/questions/1', params: { 
  question: { options_attributes: [{ id: 1, _destroy: true }] }
}

Its very clunky and not really a good RESTful design.

Instead you can just setup a standard destroy action:

# config/routes.rb
resources :options, only: :destroy
<%= button_to 'Destroy option', option, method: :delete %>
class OptionsController < ApplicationController
  # @todo authenticate the user and 
  # authorize that they should be allowed to destroy the option
  # DELETE /options/1
  def destroy
    @option = Option.find(params[:id])
    @option.destroy
    respond_to do |format|
      format.html { redirect_to @option.question, notice: 'Option destroyed' }
      format.json { head :no_content }
    end
  end 
end

This uses the correct HTTP verb (DELETE instead of PATCH) and clearly conveys what you're doing.

CodePudding user response:

I can share my recent project work which is a bit similar to your where I am using shrine gem for upload images and I can update/destroy images which is associated with a Product model product.rb

.
.
has_many :product_images, dependent: :destroy
accepts_nested_attributes_for :product_images, allow_destroy: true

product_image.rb

.
  belongs_to :product
.

_form.html.erb for update

<%= f.hidden_field(:id, value: f.object.id) %>
<%= image_tag f.object.image_url unless f.object.image_url.nil? %>
<%= f.check_box :_destroy %>

and in products controller,I have whitelisted this

product_images_attributes: [:_destroy,:image, :id]

Hope this helps you to solve on your case

  • Related