Mongoid - Updating Nested Attributes - ruby-on-rails

From the mongoid docs:
Consider a member that has a number of posts:
class Member include Mongoid::Document has_many :posts
accepts_nested_attributes_for :posts end
You can now set or update attributes on an associated post model
through the attribute hash.
For each hash that does not have an id key a new record will be
instantiated, unless the hash also contains a _destroy key that
evaluates to true.
params = { member: { name: "joe", posts_attributes: [
{ title: "Kari, the awesome Ruby documentation browser!" },
{ title: "The egalitarian assumption..." },
{ title: "", _destroy: "1" } # this will be ignored ] }}
member = Member.create(params['member']) member.posts.length # => 2
member.posts.first.title # => 'Kari, the awesome Ruby documentation
browser!' member.posts.second.title # => 'The egalitarian
assumption...'
Is there a way to update nested attributes instead of creating them?

It relies on the nested documents having IDs.
In a Rails form, for instance, the corresponding attributes fields (in your case, posts_attributes) will be passed as part of the form. Rails then does an update for elements with an ID, and a create for those without an ID.

Related

What is the Strong Parameter for updating nested record?

I'm a junior backend developer starting with Rails 7.0.
Looking at the documentation of Rails here:
https://guides.rubyonrails.org/form_helpers.html#the-fields-for-helper-index-option
point 8.3 , it is stated that we can use :index option to output the form.
This is the example taken from the doc
{
"person" => {
"name" => "Bob",
"address" => {
"23" => {
"city" => "Paris"
},
"45" => {
"city" => "London"
}
}
}
}
I am able to output the form correctly in views,
but unable to create the correct strong parameters for that structure.
this is my current strong parameter:
params.require(:person)
.permit(:name, address: [:id, :city])
this produce error unknown attribute address, so I'm unable to put the record on database
The doc doesn't give any explanation on how to do strong parameter on nested attribute with index.
Thank you
Your model should have
accepts_nested_attributes_for :address
for nested attributes to work. Or, the attribute address is misspelled in your model.

Automatically remove associated records if not present in update params

We're using Rails 5.2.2.1 as an API with Vue for the frontend.
A Post has_many :tags. A Post also accepts_nested_attributes_for :tags, allow_destroy: true.
When creating a post, you create the tags at the same time and this works well. When editing tags, however, I'm running into an issue where tags that aren't present in the update payload are not deleted from the database. Here's an example of what I'd expect:
# A Post has currently tags with ID 1, 2, and 3.
# This (pseudo) update payload is sent
{
tags_attributes: [
{
id: 1,
name: 'Tech'
},
{
id: 3,
name: 'Sports'
},
{
id: nil,
name: 'Science'
}
]
}
# The post's tag with an ID of 2 is not present in the update payload, so it should be automatically removed
# Since we're also passing up a new tag without an ID, this tag should be created
I have a solution involving _destroy: '1', but it would be preferable for tags to be automatically marked for destruction based on their presence in the update payload.

trailblazer reform gem, how to handle this type of input validation?

We are looking at use the reform gem for validating input.
One of the issues we are facing is that we accept input in this format:
params = {
records: {
"record-id-23423424": {
name: 'Joe Smith'
}
"record-id-43234233": {
name: 'Jane Doe'
}
"record-id-345234555": {
name: 'Fox trot'
}
"record-id-34234234": {
name: 'Alex'
}
}
}
so if we were to create reform class
class RecordForm < Reform::Form
property :records
validates :records, presence: true
# ?????????
end
How do we validate the contents of the records to make sure each one has a name? The record-id-values are not known ahead of time.
Reform currently doesn't allow dynamic properties, and actually, it's not planned since Reform is supposed to be a UI-specific form object.
The solution would be to pre-parse your input into something what Laura suggests. You could then have nested properties for each field.
collection :records do
property :id # manually parsed
property :name
end

ActiveModel form using fields_for in Rails 5

I have an ActiveModel "form object" which "has many" patients (another ActiveModel "form object"). The idea being the user can fill in the form and add as many patients.
- #form.patients.each do |patient|
= form.fields_for 'patients[]', patient do |patient_form|
This worked fine in Rails 4 and as far as I remember inputs where named something like patients[][name].
This was an Array in params such as { patients: [ {name: 'foo'} ] }.
However it seems this may have changed with Rails 5, looking at the source it seems if name ends in [] and there is no index given it will try and insert an id.
Giving an input name such as patients[1][name].
However the form object (ActiveModel) has no #id method and I get an NoMethodError. And even if it did this would give a params has which would be a Hash such as { patients: { '1' => { name: 'foo' } } }.
If I just do form.fields_for 'patients', patient all input have the same name and thus overwrite each other in params.
One solution is to add a "fake" #id method to the "form object":
# form object (ActiveModel)
def id
SecureRandomn.hex(10)
end
This will give inputs named as such: patients[f0858de30df23c3e2305][name].
Then in the controller I can convert the params hash ({ id => attributes, id => attributes, ... }) to an array of attributes ([attributes, attributes, ...]):
params[:patients].values
While I don't mind 'fixing' the params too much adding a useless #id method to the form seems too much.

Create multiple documents with array

I have the following array:
#unregistered_users = ['my#email.com', 'your#email.com', ...]
Now, I want to create a document for each array element:
#unregistered_users.each do |email_address|
Model.create(email: email_address, user: self.user, detail: self)
end
But it only creates a single document (the first element of the array). The other array elements are simply not created. Why?
We're using Ruby 1.9.3-p385, Rails 3.2.12, MongoID 3.0.0 and MongoDB 2.2.3
Update #1
So, we had a custom _id field with a custom random token using SecureRandom.hex(64).to_i(16).to_s(36)[0..127].
After I removed it worked normally, but with regular mongo ID's (which is not what we want).
Update #2
This is how the token are being generated:
class Model
include Mongoid::Document
include Mongoid::Timestamps
...
field :_id, default: SecureRandom.hex(64).to_i(16).to_s(36)[0..127]
...
index( { _id: 1 }, { unique: true } )
end
Try something like this to check what are the errors on the mongoid model:
#unregistered_users.each do |email_address|
model = Model.create(email: email_address, user: self.user, detail: self)
puts model.errors.inspect unless model.persisted?
end
or use create! to raise an exception and see what's happening

Resources