Deleting objects from associations in Rails - ruby-on-rails

I am learning Rails and I am reading the Beginning Rails 3 book. When you have a has_many association you automatically receives methods.
Let say user has many articles.
user.articles.delete(article)
Now that line only set the foreign key of the article to "NULL". Is it correct that you also must destroy/delete the article if you want it to disappear from the database, or is there a method that does it both?
And what happens if you destroy an article that is in a relationship with a user before you delete the association?

There are some difference between delete and destroy.
The delete method essentially deletes a row.. that's it..
On the other hand, destroy allows you more options:
it will check any callbacks such as before_delete, or any dependencies specified on the model.
it will also keep the object that just got deleted in memory; So it allows you to leave a message saying for example: “Article #{article.id} deleted!!”
And the answer for your question: it will delete any child objects associated with the object.
So, instead of
user.articles.delete(article)
you can use
user.articles.destroy(article)
In this way you will prevent any orphaned rows on the database.

Related

How do I associate two entries in a database that are connected through a many-to-many relationship in Rails?

How do I associate two entries in a database that are connected through a many-to-many relationship in Rails?
I'm trying to associate Users and Issues for an issue tracker. I'm using has_and_belongs_to_many, not :through. I have a :user_id and :issue_id available to me, but there doesn't seem to be User.issues.find(id) or Issue.users.find(id) available to me. I have a route post "/", to: "home#create". I'm trying to make a create method in home_controller.rb.
From the look of it you're calling the method on the User class and not an instance.
If you want to get the issues connected to a user you need to fetch the user first:
User.find(id).issues
If you want to add a record to the association you can use the shovel method or any of the methods generated by the association macro:
User.find(id).issues << Issue.find(3)
User.find(id).issues.push(Issue.find(3))
User.find(id).issue_ids = [1, 2, 3]
Besides that you have a smattering of naming issues in your schema. Use snake_case everywhere in your database schema unless you have a good reason why you want to break the conventions and feel like explicitly configuring table and foreign key names.
I would also really question if you really want to use has_and_belongs_to_many. It should only really be used if you can't foresee that you ever will need to add additional attributes to the join table or never need to query the table directly - it seems pretty unrealistic that that would be true in an issue tracker. You want has_many through: - pretty much always.
I have a route post "/", to: "home#create". I'm trying to make a
create method in home_controller.rb.
Don't throw everything into a junk drawer controller. Think about your app in terms of resources that can be CRUD:ed and create controllers that handle just that resource. You should think about what the relation between a user and an issue is in your domain and how you can model it as an actual entity in the domain logic instead of just plumbing.
Maybe all I need to do is direct you to Rails Guides: Active Record Associations.
There is neither of these
User.issues.find(id)
Issue.users.find(id)
because when you are finding an issue or user by id, you don't use the association. Instead use these:
Issues.find(id)
Users.find(id)
Since the :id is unique this will work and should be what you want.
The only time you want to query issues or users using the association will be when you have the data for the other end of the relationship.
user = User.find(user_id)
issue = user.issues.where(id: issue_id)
Since the :id field is unique, this is the same as Issues.find(id). However if you want to get a collection of a user's issues with some other data, you can put the condition for that data in the where.
You can create an issue for a user this way:
user = User.find(user_id)
issue = User.issues.create( ... )

Rails Cookies to manipulate database entries

I am trying to create a Rails app and I have a database consisting of author and a quotation by that author.
Now different users can choose to destroy or kill quotations from the database however it must only be deleted for that particular user i.e other users should still be able to see quotes that they didn't delete even if another user did.
I know that I would need to implement cookies but other than that I am unsure how to proceed. Can anyone point me to a tutorial or give me some pointers to get started on this complex task?
You surely have a User model in your application - one 'Rails-like' way to go about this would be to add a has_and_belongs_to_many relationship between User and Quotation.
This creates a relationship between each individual user and 'their' quotations. This relationship can be deleted without actually deleting a quotation, so all quotations would still be available to other users. If you want each user to be able to see all quotations by default, you would need to set up the relationship in advance.
Assuming you are using Devise to log your users in, all you'd need to do then is to replace Quotation.all with current_user.quotations in whichever controller you are using to display quotations.
The Rails guide linked above is quite helpful but basically you just need to add something like the following:
class User
has_and_belongs_to_many :quotations
before_create :add_quotations
def add_quotations
self.quotations << Quotation.all
end
#etc...
end
class Quotation
has_and_belongs_to_many :users
#etc...
end
and then run a migration adding a new table called users_quotations with the columns user_id and quotation_id.
EDIT
As #Yule pointed out this wouldn't let users see any quotations that were created after they were, and it would be quite annoying to have to set up the join tables in advance, so a more efficient way would be to have an excluded_quotations join table instead. So users can see all quotations except the ones that they have excluded.

Delete all in rails console

i have an association for a user as user has_many agents and agent belongs_to user.
in rails console,i am trying to use different users to test a particular scenario and i want a user with no agents,hence i want to delete the user.agents.
i tried user.agents.map(&:destroy),but it gives error as ActiveRecord::StaleObjectError: Attempted to delete a stale object.i even tried user.agents.delete_all but it too does not work.can i delete the users agents with a single command in rails console.
You better use destroy because it goes through all the Rails magic (callbacks and such)
user.destroy #For a single record
user.agents.destroy_all #For a collection
You are looking for a .destroy_all method. It destroys all records of a given collection.
So user.agents.destroy_all, would return an empty array for user.agents.
You could not have used .delete_all because it is a class method and it deletes records that match a given condition. Like this, Agent.delete_all(condition). If used without a condition it deletes all records from a matched table.
Keep in mind that .destroy methods are instance methods. They instantiate an object and perform callbacks before erasing it. .delete methods are class methods and they directly erase an object.
This works for me
user.agents.find_each(&:destroy)
ActiveRecord::StaleObjectError
Is for Optimistic locking, remove any locks you have on it before trying to delete again. Check if anyone else is using the system or submit any forms you have open.

model relations and bulk deletes

I have a model relation of dependant=>destroy that has to do 50K+ deletes when the destroy is triggered. Looking at the console, rails is trying to do an explicit delete with ID for every single row, which is taking a while. Is there a way for me to force rails to do a bulk delete? Or, I can remove the model dependency, is there a way to do this kind of bulk delete from the code?
Thanks
You should be able to set dependent: delete_all
If you can't get that to work, you might want to use delete_all in your own callback.
To be clear, delete_all should generate a single statement to delete all child objects

Calling ActiveRecord's #relationship_ids = [1,2,3] saves immediately. Any workarounds?

I've come across an oddity in ActiveRecord's #relationship_ids method (that's added automatically when you declare 'has_many'), which saves immediately for existing records, which is causing me some issues, and I wonder if anyone had any useful advice.
I'm running Rails 2.3.5.
Consider this simple scenario, where an article has_many tags, say:
a = Article.first
a.name = "New Name" # No save yet
a.author_id = 1 # No save yet
a.tag_ids = [1,2,3] # These changes are saved to the database
# immediately, even if I don't subsequently
# call 'a.save'
This seems surprising to me. It's specifically causing problems whilst trying to build a preview facility - I want to update a bunch of attributes and then preview the article without saving it - but in this instance the tag changes do get saved, even though no other fields do.
(Of possible relevance is that if 'a' is a new article, rather than an existing one, things behave as I'd expect - nothing is saved until I call 'a.save')
I have a fairly nasty workaround - I can override the tag_ids= method in my model to instead populate an instance variable, and actually save the related models in a before_save callback.
But I'd love to know of a simpler way than me having to do this for every model with a has_many relationship I'd like to create a preview facility for.
Does anyone have any fixes/workarounds/general advice? Thanks!
There's a reason things are this way. It's called foreign keys. In a has many relationship, the information that links to the model that has many is stored outside of that model as a foreign key.
As in Articles, has many tags. The information that links a tag to an article is stored either in the tags table or in a join table. When you call save on an article you're only saving the article.
Active record modifies those other records immediately. Except in the case where you're working with a new article that hasn't been saved yet. Rails will delay creating/updating the associated records if it doesn't know which id to place in the foreign key.
However, if you're modifying existing records, the solution you've decided on is really all that you can do. There's an even uglier hack using accepts_nested_attributes_for, but it's really not worth the effort.
If you're looking to add this behaviour to many models but not all models, you might want to consider writing a simple plugin to redefine the assigment the method you need and add the call back in a single class method call. Have a look at the source of something like acts_as_audited to see how it's done.
If you're looking to add this behaviour to all models, you can probably write a wrapper for has_many to do that.

Resources