How do I update children in a has_many relationship? - ruby-on-rails

I have a model called Tournament, which has_many Entries. There is a method in Tournament called reset_rankings, which traverses all of its entries and updates the current_rank field of each entry based on how many points that entry currently has. I call entry.save on each entry I update in this traversal.
This method seems to update the entries in memory just fine, but when I reload the Tournament I was working with, the current_rank fields all revert to what they were before calling reset_rankings.
I tried calling tournament.save after calling reset_rankings but that still didn't persist the changes
I am sure I am doing something stupid, but the rails magic isn't working like I expect.

When you call entry.save, are you checking the return value? If false then the save was blocked for some reason, eg didn't pass your validations.
Or change foo.save to foo.save! to raise an error when the save fails.

Related

Update attributes without touching the database in Rails

I have a situation where I have an ActiveRecord::Relation object, where the relation's objects have some has_many associations. There is another piece of code that deletes the database rows associated with this relation that doesn't go through the relation, using delete_all. I know what the new state of the relation's associations is even without going to the database so I want to be able to set the object's attributes in this relation manually without touching the database again.
I found this article which mentions the write_attribute method. This works, but it looks like it has been deprecated, so I'd rather not use. It also mentions attributes= as a way of doing this without accessing the database. Is there something that can achieve the effect of write_attribute where I won't access the database when modifying a relation's attributes?
assign_attributes
It's like update_attributes, without saving.

Rails - set attribute of parent based on child id after both created together

I have a model user_item with an attribute called :primary_image_id. A user_item has_many user_item_images. The purpose of the primary_image_id is to set it equal to the id of one of the user_item_images so then I can use logic so that object can't be deleted by the user in the view.
A user_item should never exist without this attribute set, or at least exist for a couple seconds until the attribute is set. In my form the user_item is created along with user_item_images.
It seems like the user_item_image.id is only set after create. I tried doing an after_create callback on the user_item
def set_primary_image_id
self.primary_image_id = self.user_item_images.first.id
end
but it didn't seem to work, I'm guessing because the child wan't created and so didn't have an id.
I guess I could do an after_create callback on the user_item_image model, something like:
def set_user_item_primary_image_id
user_item = self.user_item
user_item.primary_image_id = user_item.user_item_images.first.id
end
but this seems like overkill because it really shouldn't be done every time a user_item_image is created.
Your after_creates may be properly setting the field, but they're not writing anything to the database. Try:
update_attributes(primary_image_id: user_item_images.first.id)
If the user_item_images exist before you create the user_item, you could also set the field in a before_create, avoiding the need for a second write query. Whatever you do, sprinkle some prints or drop into a debugger to check whether you have no data vs. are not writing it.
If you're creating everything all at once using build, all objects will be created and have IDs before UserItem#set_user_item_primary_image_id runs, so this will work just fine:
ui = UserItem.new(params[:user_item])
image = ui.user_item_images.build(params[:user_item_image])
ui.save # Creates UserItem, creates UserItemImage, assigns primary_image_id
To make that even cleaner, check out accepts_nested_attributes_for.

Rails - create child and child's child at the same time

I have an association where user has_many user_items and user_items has_many user_item_images. With an already exiting user. I can create a new user_item
user_item = user.user_items.create(name: 'foo')
and I can create a new user_item_image
user_item.user_item_images.create(picture: file)
But I have a validation on user_item where a user_item can't exist without a user_item_image.
How can I create these two at the same time?
Firstly build both items and then save the parent. This will work because:
Validations are only called when saving the object in the database
Saving unsaved parent automatically saves all associated objects (via has_one and has_many, belongs_to object won't be saved without autosave option)
Validation is (most likely) based on the association and association includes non-saved but assigned objects in its target. Note however that you cannot use count in your validation, as it performs COUNT query and non-saved objects won't be included. Use size instead, or to be super sure (as size calls count for non-loaded associations) .to_a.size
Your code should like like:
user_item = user.user_items.build(name: 'foo')
user_item.user_item_images.build(picture: file)
user_item.save! # Bang for safety. If in controller, you can fork with if instead
BroiSatse has a correct answer. If you really want to do it in one single line you can:
user_item = user.user_items.create!(name: 'foo', user_item_images_attributes: { picture: file })
In my own code I usually make it look like BroiSatse's code simply for the sake of readability and maintainability - build the initial object, add related items, then save. It might be a little faster to do it with the single line, but unless you're doing it millions of times it's unlikely to make a difference.

Saving record fails due to uniqueness conflict with itself?

I have a procedure which receives two models, one which already exists, and another one which holds new attributes which I want to merge in the first one.
Since other parts of the program are holding the same reference to the new model, I can't just operate on the existing one. Therefor I do the following:
def merge(new_model, existing_model)
new_model.attributes = existing_model.attributes.merge(new_model.attributes)
new_model.id = existing_model.id
end
Now the new_model is being saved which gives me the uniqueness erorr (even though it's technically the same model). I also tried using the reload method, but that yields the same result.
Background:
The method above is run in a before_add callback on an association. I want to be able to call update on a model (with nested associations) without having to specify IDs of the nested models. This update is supposed to merge some associations, which is why I try to do the whole merge thing above.
You can't set the id of a model and then save the record expecting the id to be set since the id is the primary key of the database. So you are actually creating a whole new record and, thus, the uniqueness validation error. So you'll need to think of some other design to accomplish what you are wanting. It may help to know that what you are trying to do sounds similar to a deep_dup, except that ActiveRecord doesn't define this method (but Hash does).

How Should a has-many Association Behave When A Record That is Already Present Is Added?

I'm trying to work through a more complex issue, but I can't seem to find a definitive answer regarding the way has_many handles situations where an object already present in the association is added again.
What is the expected behaviour in the following situation (where alpha has many betas:
alpha.betas << beta_1
alpha.betas << beta_1
Should the second insertion be silently ignored (ensuring only unique betas in alpha's association?)
The docs state the following:
collection<<(object, …)
Adds one or more objects to the collection by setting their foreign keys to the collection's primary key. Note that this operation instantly fires update sql without waiting for the save or update call on the parent object, unless the parent object is a new record.
So my understanding is, that the object is referenced only once. It is therefor only once in the active_record relation alpha.betas. If you call it again, the foreign_key is set again, to the same value it already has. So this changes nothing.

Resources