Overriding shovel method in rails in association case - ruby-on-rails

According to the has_many documentation, the "shovel" method 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.
If you want to construct a new-record without saving it to the database just yet, use collection.build.
Returns one or more new objects of the collection type that have been instantiated with attributes and linked to this object through a foreign key, but have not yet been saved.
Using Club and Member as example models:
club = Club.find(params[:id])
club.members.build(member_attributes) # member is not saved
club.save # saves club and members
BUT WHAT IF
I would like to use << to create associations and not firing SQL at all. How can I override << to behave like in scenario when the parent object is a new record?

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.

when to use new and create rails controller

this is a really simple question but I'm confused as to when I should use .new and .create in the controller. I guess what I am really asking is what are the uses for .new and what are the uses for .create? Thanks.
From the ActiveRecord::Base documentation:
create(attributes = nil) {|object| ...}
Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.
new(attributes = nil) {|self if block_given?| ...}
New objects can be instantiated as either empty (pass no construction parameter) or pre-set with attributes but not yet saved (pass a hash with key names matching the associated table column names). In both instances, valid attribute keys are determined by the column names of the associated table — hence you can‘t have attributes that aren‘t part of the table columns.
So create instantiates the new object, validates it, and then saves it to the database. And new only creates the local object but does not attempt to validate or save it to the DB.
From https://stackoverflow.com/a/2472416/634120

How to save 2 id in joint table for many-to-many relationship in rails 3.1

There are two models. One is rfq and another one is standard. It is like:
class Rfq << ActiveRecord::Base
has_and_belongs_to_many :standards
end
class Standard << ActiveRecord::Base
has_and_belongs_to_many :rfqs
end
Table rfqs_standards has been created. My question is when creating rfq, how to save the paid of rfq_id and standard_id in table rfqs_standards automatically.
Thought adding accepts_nested_attributes_for :standard in rfq model. However since there is no real attributes (but just pair of id) saved for this many-to-many relationship, this seems not the right way.
Both rfq and standard was declared in routes.rb as resources :rfqs and resources :standards.
The procedure is when creating rfq, standard will be picked up via a drop down list. Then rfq is saved and at the same time, a new entry in joint table is created. When creating new standard, there is no need to create entry in joint table.
Any suggestion to save the id in joint table? Thanks.
this is easier than you might think because it's handled automatically by ActiveRecord.
When you say "has_and_belongs_to_many", you're telling AR to associate those two models with a many-to-many relationship using the table, and for the most part you no longer need to worry about the join table. When you add an instance of Standard to an Rfq's list of standards, this will be done for you.
Here's an example:
rfq = Rfq.create
standard = Standard.create
rfq.standards << standard
We've created each of the objects, and the third line creates the connection, saving a new record in the rfqs_standards table with the proper ids. rqf.standards looks and acts like a normal array, but when you assign objects to it, ActiveRecord does the database work for you.
After creating the records, you could have also done:
standard.rfqs << rfq
You could also do both at the same time:
rfq = Rfq.create
standard rfq.standards.create
This created an rfq, then created a standard that is automatically connected to the rfq. You can do the same thing in reverse:
standard = Standard.create
rfq = standard.rfqs.create
I hope this helps!
UPDATE: Since you mentioned forms and automatic saving, read my article on nested attributes that shows how to implement that, including full code samples.

Rails ActiveRecord associations

Let's say you have a one-to-many relationship between Users and Orders (where one user can have many orders). Is it possible to create a User object, add orders to it, and save it in one go? Or do you have to save the User object first so that an ID is generated before you can save the orders collection?
You can check Railscasts for that. Here's an example of a nested model - Nested Model Form Part 1
Since the User is a new record, orders will be saved automatically once the user is saved.
I was able to solve this by using the "build" method. From the ActiveRecord::Associations::ClassMethods documentation:
Returns one or more new objects of the
collection type that have been
instantiated with attributes and
linked to this object through a
foreign key, but have not yet been
saved.

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