Rails - create child and child's child at the same time - ruby-on-rails

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.

Related

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.

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).

Validating and saving related models together

I have 2 related models and I need to validate and create them together.
Application
class Application < ActiveRecord::Base
has_many :application_sessions, inverse_of: :application
ApplicationsSession
class ApplicationSession < ActiveRecord::Base
belongs_to :application, inverse_of: :application_sessions
If it was possible I'd like to create an Application through an ApplicationSession butapplication_session.build_application won't work because it will never be a valid record.
application_session.create_application wont work either because even if ApplicationSession is not a valid record, it will create an Application.
For the first one; it validates Application and ApplicationSession. This logic might work fine if I only skip application_id validation for ApplicationSession if when Application is a valid record. Still I prefer to use a more elegant solution if there is any.
For the second one; I can delete the Application afterwards if ApplicationSession is not a valid record but I didn't quite like this solution.
What is best approach to create/not to create those dependent records together with Rails?
Clarification:
Simply, I want the parent and child to be created together while there is no parent exists and a valid child about to save (valid expect it does not have any parent). If child is not a valid record, nothing should be created.
I think using new and build would work.
application = Application.new({attr1: val1, attr2: val2 ..})
application.application_sessions.build({attr1: val1, attr2: val2 ..})
application.save
In this way, if application is invalid, application and its new application_session would not be saved. The same goes if the application_session is invalid.
In terminal where you fired-up your rails console command, you would see something like:
(0.1ms) begin transaction
(0.1ms) rollback transaction
If application and its new application_session are both valid, both would be saved :)
From a data modelling point of view I'm not sure what you're trying to do.
I think you have a parent/child relationship where the parent is optional?
If you create a parent and then delete it you will have dangling keys in the child that point to nothing, be far better to just have nulls in there.
Is there any reason you can't just call new or create with a null parent id? Is the parent key mandatory? If so turn off the mandatory requirement and it should work. Build only works from parent to child, not the other way round when all the keys are null. I think you have to call save on the parent and the children are saved once the parent ID is known.
If the parent must exist for your app to work create a dummy one and have all the children you don't want to have a specific parent belonging to it, then you can easily find them again. Without knowing the flow of your app I'm not sure what else I could advise.

How do I update children in a has_many relationship?

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.

Rails nested form with uniquness condition

Rails 2.3.5, Ruby 1.8.7.
I have three models, Person, AcademicTerm, and PersonTermStatus.
class PersonTermStatus {
belongs_to :academic_term
belongs_to :person
validates_uniquness_of :academic_term_id, :scope => :person_id
# ...
}
class Person {
has_many :person_term_statuses
}
In a dynamic nested form for a Person record, I allow the editing of the person_term_statuses. But I get validation errors if the user does either of the following:
Deletes a status and creates a new one with the same academic term in the same change.
Swaps the academic terms between two existing statuses.
I understand why this is happening. In (1), the status marked for deletion is not actually deleted before validation of the new status's uniquness condition. In (2), the uniquness condition again is applied before any changes, and it finds another record with the same academic_term.
The problem is, I can't figure a way around this. Is there a known solution?
(My nested form implmenetation is currrently using pretty much exactly the technique from RailsCast [ Part I and Part II )
There is no workaround for this that I know of. However, you can add foreign keys to your database to enforce the uniqueness on the database side and then use the following approach.
Add a before_validation to the parent model that deletes and recreates as new records all the children. Then add a custom validation function that manually checks the children records for uniqueness based on what's in memory (rather than what's in the database).
The downsides to this approach include:
The children don't retain the same IDs.
The created timestamp changes.

Resources