Why the new data are not saved in the nested form? - ruby-on-rails

In ActiveAdmin I have an entity editing form with triple nesting. Now I can edit the data that is present in the database. They are saved.
But if I try to add new data, then I get a ROLLBACK error:
{:"blocks.texts.block"=>["must exist", "can't be blank"]}
I'll clarify again - existing data in this field is successfully updating.
But when creating a new entity in this nested form, some kind of problem arises. I tried to track by logs what is sent in the form, what comes before validation and what remains after validation.
Everything comes to form:
"blocks_attributes"=>{"0"=>{"texts_attributes"=>{"0"=>{"value"=>"first value", "_destroy"=>"0", "id"=>"671518"}}, "label_ids"=>["", "54"], "_destroy"=>"0", "id"=>"18655"}, "1"=>{"texts_attributes"=>{"0"=>{"value"=>"tteesstt"}}}}
# => "1"=>{"texts_attributes"=>{"0"=>{"value"=>"tteesstt"}}}
But before and after validation, this data is no longer available. In texts are present only data previously existed.
In ActiveAdmin have this code:
permit_params :title, :description, :published,
blocks_attributes: [
:id, :_destroy,
texts_attributes: %i[id value _destroy],
label_ids: []
],
category_ids: []
# ...
f.has_many :blocks, allow_destroy: true do |b_f|
b_f.inputs do
b_f.has_many :texts, allow_destroy: true do |b_t_f|
b_t_f.inputs do
b_t_f.input :value
end
end
b_f.input :labels, as: :check_boxes, collection: Label.options_for_select, input_html: { multiple: true }
end
end
The initial Post model has this code:
accepts_nested_attributes_for :blocks,
allow_destroy: true
In Block model:
accepts_nested_attributes_for :texts,
allow_destroy: true
Please tell me why the existing data is updated, and the new ones disappear when saved?
Addition 1
As I understand it, this is connected not with texts, but with block - blocks.texts.block. But why does the text refer to a block? Why is the block not identifiable? It has the following name in the form: post[blocks_attributes][1][texts_attributes][0][value].
Addition 2
If in ActiveAdmin I first add (save to DB) only block (second block), and after I add text to this block, all two times the save to DB will successfully. That is, the problem is due to the lack of a block ID when creating text in a single scenario.
It turns out that this is a bug? When adding (using JS) a new HTML form code, must also add the block_id for text. But now this is not. Now only the existing block in the database has this field.

I remember that some time ago I had a similar issue with associations. Here, form error it looks like texts have no block_id. That's true because you're already saving it. Try that: https://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html

The solution is simple - need to use inverse_of. Documentation.
And everything will start to work as intended.

Related

Why won't rails create associated records/objects from nested form using strong parameters?

I'm trying to create a record and it's associated records from a nested form using strong parameters. My primary model is:
class MaterialDonationRequest < ActiveRecord::Base
has_many :donation_items, dependent: :destroy
accepts_nested_attributes_for :donation_items, allow_destroy: true
validates :name, presence: true
attr_accessor :due_on_event, :date, :donation_items_attributes, :event_id
end
My associated (nested) model is:
class DonationItem < ActiveRecord::Base
validates :name, presence: true
belongs_to :material_donation_request
belongs_to :global_profile
validates :name, presence: true
attr_accessor :_destroy
end
In my material_donation_requests_controller.rb, I have the following for strong parameters:
def material_donation_request_params
params.require(:material_donation_request).permit(:name, :description, :event_flag, :due_on_event, :date, :event_id, donation_items_attributes: [:id, :name, :description, :amount, :_destroy])
end
Here's the line in my create method where I create the object:
#material_donation_request = MaterialDonationRequest.new(material_donation_request_params)
After doing this, #material_donation_request is created and populated correctly from the form. But the associated donation_items do not get created. For instance, in the debugger, when I enter #material_donation_request.donation_items.first, Rails returns nil.
For reference, here is what Rails returns for material_donation_request_params in the manual tests I'm running:
{"name"=>"Name", "description"=>"", "due_on_event"=>"true", "date"=>"", "donation_items_attributes"=>{"0"=>{"name"=>"", "amount"=>"1", "_destroy"=>""}, "1427122183210"=>{"name"=>"", "amount"=>"2", "_destroy"=>""}}}
Why isn't Rails creating the associated objects from the form as well? Everywhere I've looked, it seems like this structure should create everything, and a subsequent save should save everything (or at least throw validation errors as in this case-see update below). Is there something I'm missing?
Update
Since it was brought up in the answers, yes, the material_donation_params shown above would not pass validation. That's the scenario I've been manually testing. It should generate a validation error on save, but instead, simply saves the MaterialDonationRequest with no errors of any kind, and saves nothing to DonationItems.
To be clear, though, if I fill out the form completely and get the following material_donation_request_params:
{"name"=>"Name", "description"=>"", "due_on_event"=>"true", "date"=>"", "donation_items_attributes"=>{"0"=>{"name"=>"first", "amount"=>"1", "_destroy"=>""}, "1427122183210"=>{"name"=>"second", "amount"=>"2", "_destroy"=>""}}}
and then do #material_donation_request.save, it only saves the MaterialDonationRequest, and not any of the DonationItems.
Final Update
Okay. I've deleted my previous "final update" because what I wrote, and what I wrote in some of the comments was wrong. What ended up fixing this was not an update to Rails 4.1.8. I ran the bundle update command before actually saving the gem file with the new Rails version. So really, what ended up fixing this was simply updating all the gems that didn't have fixed version numbers. God only knows why things weren't working with the previous set of gems. Sorry that this isn't so helpful...
From Rails Validations guide
presence
This helper validates that the specified attributes are not empty. It uses the blank? method to check if the value is either nil or a blank string, that is, a string that is either empty or consists of whitespace.
You are requiring donation_item to be present, but your resulting params hash clearly has donation names blank, validation is failing. Calling save! when debugging these things can be helpful since it would throw a error on failure.
I figured out the answer. In total desperation, I upgraded my Rails version from 4.0.2 which is what I had been using, to 4.1.8. After doing this, with no other changes whatsoever (except gem dependencies, of course), it just started working the way it's supposed to. So I guess Rails 4.0.2 has a problem with nested forms and strong parameters.

Saving nested model in Rails 4

Kinda new to the Rails thing, in a bit of a spot.
One of the models is dependent on the other in a has_many/belongs_to association.
Basically, when creating a "Post" on my application, a user can also attach "Images". Ideally these are two separate models. When a user chooses a photo, some JavaScript uploads it to Cloudinary and the returned data (ID, width, height, etc) are JSON stringified and set on a hidden field.
# The HTML
= f.hidden_field :images, :multiple => true, :class => "image-data"
# Set our image data on the hidden field to be parsed by the server
$(".image-data").val JSON.stringify(images)
And of course, the relationship exists in my Post model
has_many :images, :dependent => :destroy
accepts_nested_attributes_for :images
and my Image model
belongs_to :post
Where I'm lost is what to do with the serialized image data on the Post controller's create method? Simply parsing the JSON and saving it doesn't create the Image models with the data upon saving (and doesn't feel right):
params[:post][:images] = JSON.parse(params[:post][:images])
All of this essentially culminates to something like the following parameters:
{"post": {"title": "", "content": "", ..., "images": [{ "public_id": "", "bytes": 12345, "format": "jpg"}, { ..another image ... }]}}
This whole process seems a little convoluted -- What do I do now, and is there a better way to do what I'm trying to do in the first place? (Also are strong parameters required for nested attributes like this...?)
EDIT:
At this point I got this error:
Image(#91891690) expected, got ActionController::Parameters(#83350730)
coming from this line...
#post = current_user.reviews.new(post_params)
Seems like it's not creating the images from the nested attributes but it's expected to.
(The same thing happens when :autosave is there or not).
Just had this issue with that ActionController::Parameters error. You need to make sure you're permitting all the necessary parameters in your posts_controller, like so:
def post_params
params.fetch(:post).permit(:title, :content,
images_attributes: [:id, :public_id, :bytes, :format])
end
It's important to make sure you're permitting the image.id attribute.
You must build the params like this:
params[:post][:images_attributes] = { ... }
You need *_attributes on a key name of images.
The accepts_nested_attributes_for should care of this for you. So doing a Post.create(params[:post]) should also take care of the nested image attributes. What might be going wrong is that you have not specified an autosave on the has_many relationship. So you might want to see if this makes a difference:
has_many :images, :dependent => :destroy, :autosave => true
That should save the images too when you save your post.

Showing fields with errors for nested forms in Rails 3.2 + SimpleForm

I have a Flight model nested inside a FlightLog model. A FlightLog may contain many flights.
I'm using SimpleForm with the bootstrap installation, which makes it possible to surround form elements with errors with the error class when a validation fails.
The problem is, that even though validations are triggered for the nested model, the fields with errors inside the simple_fields_for are not being marked, so it's not possible to determine which attribute is not valid.
After examining the errors hash when calling the create action, I can see that it is correctly populated with the errors at the top level, and the errors of the nested resources inside each resource.
How could I modify the behavior of simple_form to add the errors class to the control group of each nested model to match the behavior of the parent?
Thanks in advance.
If you are using simple_form with bootstrap, this does work - you just need to set up a few items correctly:
1 - Use the simple_form bootstrap wrappers (from simple_form 2.0) - you can find them in the github repo under config/initializers/simple_form.rb (https://github.com/rafaelfranca/simple_form-bootstrap)
2 - For nested forms to display the errors, you must be sure you provide an object. f.simple_fields_for :nested_model won't work, you need to use f.simple_fields_for parent_model.nested_model or f.simple_fields_for :nested_model, parent_model.nested_model so that the form can get the necessary object.
If you still don't get anything, verify that the form is really getting the object you think it is, with errors, by outputting the errors data on your nested object: parent_model.nested_model.errors.full_messages.to_sentence
I have been using custom accessors instead of the _id fields, so that's why they weren't getting notified when they had errors. I finally resolved to use f.error :attr_name under each accessor and changing the styling manually with JS
There might be several things wrong along the way. I had issues with this as well using bootstrap simple form. After I fixed everything in the controller, model and form it worked.
For me I had several issues, especially the commented lines where crucial in my case.
check that you have the following in place:
survey.rb:
class Survey < ApplicationRecord
has_many :answers
accepts_nested_attributes_for :answers, allow_destroy: true
#errors have to come from answer validation for answer form
validates_associated :answers
validates :question, :answers, presence: true
answer.rb:
class Answer < ApplicationRecord
belongs_to :survey
# make sure there is a validation on answer
validates :answer, presence: true
end
_form.html.slim
# make sure you have given the right attributes for .input and simple_fields_for
= f.simple_fields_for :answers, #survey.answers do |answer_form|
= answer_form.input :answer
surveys_controller.rb
def new
#survey = Survey.new
#when answers are not builded it wont show any simple fields for
#survey.answers.build
end
def create
#survey = Survey.new(survey_params)
#survey.user = current_backend_user
if #survey.save
redirect_to backend_surveys_path, notice: 'Umfrage erfolgreich erstellt'
else
render :new
end
end
def survey_params
# make sure everything is permitted correctly
params.require(:survey).permit(:some_attribute, ..., answers_attributes: %i[id answer])
end
reject if on model validation can cause errors some times on this as well. be cautious.

Rails error messages from models, is there a way to order them? issues with accepts_nested_attributes_for

I have a form that displays inputs for 2 models, I am using accepts_nested_attributes_for.
In my main model that has the accepts_nested_attributes_for, it looks like:
class Account <
accepts_nested_attributes_for :primary_user ...
Now in my form, I have a form_for on the #account, and then have fields_for the primary_user model.
If I hit submit, for some reason all the errors for the primary_user are displayed first. I would rather have the errors display in the same order as the input fields on the web page.
Is this possible to re-order them according how they are ordered in my form_for?
Also, the error messages have 'primary username cannot be black', is it possible for me to change it to 'username cannot be blank'? I don't really need to confuse the end user with the word 'primary' as it really doesn't make sense to them, its more of an internal thing.
Not sure about the re-ordering but you can change the message for a model validations as follows:
validates :username, presence: { message: "Username cannot be blank" }

Checkbox for terms and conditions, without column in database

I need a "I accept terms of service" checkbox on a page, it has to be checked in order for the order to proceed. It seems hence illogical to have a column in the database to match this (whether user has accepted or declined terms).
I am using the form helper like this in my view:
<%= check_box("client", "terms") %>
And in my model:
validates_acceptance_of :terms
At the moment it is not working at all.
This seems like a really common piece of code, yet I can't find it used anywhere without having the terms in the model. Else I could use javascript to validate it, but would prefer to keep it all the in model.
This should work fine, without a database column or attr_accessor:
http://guides.rubyonrails.org/active_record_validations.html#acceptance
I would be inclined to check your params hash is as it should be i.e. that the 'terms' attribute is being passed within the 'client' hash, perhaps try adding raise params.inspect on your controller create action to help you debug?
What about having an attr_accessor :terms in your Client model?
I had this working with these settings:
In the controller, I have added :terms_of_service as a permitted field:
def application_params
params.require(:application).permit(. . . , :terms_of_service)
end
In the model:
attr_accessor :terms_of_service
validates :terms_of_service, :acceptance => true
In the view:
<%= f.check_box("terms_of_service", :checked => false) %>
attr_accessor :terms will do the trick nicely.
Either go with #neutrino's solution, or to reset :terms to "not accepted" if you need to redisplay the form (because validation may fail), use this:
def terms
nil
end
def terms=(val)
# do nothing
end

Resources