Eager load grandchild attribute without child in rails - ruby-on-rails

In Rails, I'm trying to eager load something to speed it up. I've got a Parent-Child-Grandchild situation, but only need the Parent and an attribute of the Grandchild. If I...
Parent.all.includes(:child => :grandchild)
then Bullet gripes about an unused eager load of the child (which is true, I don't need it). Is there an easier way to have access to the Parent record and Grandchild.some_attribute?

A couple options...
1) Cache the value of the grand-child (denormalize the db, add a column and store the attribute) on the parent. Do this if there is only one grandchild and you want to frequently show this value in a situation where you have many parents.
2) can you do a has_many :through on the grandchildren? Then you can just do an include(:grandchildren). That may save generating the child objects.
3) If it really is child and grandchild (has_ones, or belongs_to), you can probably find a simple way to do a query (or scope) on grandchild based on the parent id. You can then run two queries, one to get parent.all, one to get grandchild.grandchild_of(parents.map(&:id)), and then loop in ruby and add the attribute to the parent as a attribute, ready for use when you loop through the parents later.
4) Lastly, do a specific, hand worked sql query to select the parent attributes and the grandchild.attribute. The attribute will be attached to the parent and accessible by parent.grandchild_attribute_name. Parent.select('parents.*, grandchild.attribute').joins(:grandchild). This you'll need to experiment with, depending on your model.

Related

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.

Creating multiple items with a single creation workflow

I'm trying to create multiple items (associated classes) from the parent class to the very child one (4 level deep) in a single workflow. I'd like to be able to create the parent item, then click on "next" to be able to create one/many children, then click on "next" to create the children of the children, and so on... Finally, in the last screen, i'd like to be able to save all the items by clicking on save. If something is missing in the child item, the parent class cannot be created as well.
Do we have any ideas of how i can manage to do that?
Many thanks :)
You can use accepts_nested_attributes_for in the parent model to associate the child models,.
In the view you can use fields_for or simple_fields_for (if simple_form gem is used) to list the child model fields in the subsequent steps,.
In every step rather than saving the object in the db, you can check if the object is valid or not using .valid? instead of save. At the final step you can use .save method.
With .valid? you can get the errors at each step and at the final step you can save all the records by creating the parent object.

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.

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.

Can't grab foreign key during after_create callback because it doesn't exist yet!

I have some models all linked together in memory (parent:child:child:child) and saved at the same time by saving the top-most parent. This works fine.
I'd like to tap into the after_create callback of one of the children to populate a changelog table. One of the attributes I need to copy/push into the changelog table is the child's foreign_key to it's direct parent, but it doesn't exist at the time after_create fires!?!
Without the after_create callback, I can look in the log and see that the child is being saved before it's parent (foreign key blank) then the parent is inserted... then the child is updated with the id from the parent. The child's after_create is firing at the right time, but it happens before Rails has had a chance to update the child with the foreign_key.
Is there any way to force Rails to save such a linkage of models in a certain order? ie.parent, then child (parent foreign_key exists), then that child's child (again, foreign_key is accessible) etc. ?? If not, how would I have my routine fire after a record is created AND get the foreign_key?
Seems a callback like this would be helpful: after_create_with_foreign_keys
Since I was building all my associated models in memory and relying on Rails to save everything when I saved the top-most parent, I couldn't tap into the after_create callbacks and utilize a foreign key (for a change_log table entry) because of the order in which Rails would save the models. Everything always ended up connected in the proper way, but sometimes a child record would be saved first, then the parent, then an update to the child record to insert the parent_id would happen.
My solution was to not build my models in memory, abandoning the idea of saving everything in one fell swoop. Instead, I would save the top-most model and then create its child via the after_create of that parent. That child's after_create would then create its child and so on. I like this arrangement much better as I have more control over my callbacks in relation to foreign keys. Lastly, the whole thing was wrapped in a db transaction so as to undo any inserts if something went horribly wrong along the way. This was my original reason for building everything in memory, so I'd have all my ducks in a row before saving. Model/db transactions alleviates the worry.
Could you use after_update to catch the child after the parent_id is available? When after_update fires, the parent_id will be available, so if the child is not in the table, insert it.

Resources