Rails, given an array of items, how to delete in the console - ruby-on-rails

Given the following:
#users = User.where(:state => 1)
which 15 for: #users.length
In the rails console, is there a way to then delete all those records, perhaps by doing something like #users.delete?

#users = User.where(:state => 1)
The simplest way is to use destroy_all or delete_all:
#users.destroy_all
# OR
#users.delete_all
That's all

Your model class has a delete method (and a destroy method) that can take a single ID or a an Array of IDs to delete (or destroy). Passing an array will issue only a single DELETE statement, and is the best option in this case.
User.delete #users.map { |u| u.id }
# or
User.destroy #users.map { |u| u.id }
Note that when you call destroy on an ActiveRecord model, it creates an instance of the record before deleting it (so callbacks, etc. are run). In this case, since you already have fully instantiated objects, if you really do want to call destroy (instead of delete), it is probably more performant to use the method in J-_-L's answer and iterate over them yourself.

yep:
#users.each{ |u| u.destroy }

#users.destroy_all is the simplest way

the destroy_all or delete_all will execute a query like this: DELETE FROM USERS, so it will remove all the records in the table!! not only the records of the objects in the array!
So I think the best way is User.delete #users.map { |u| u.id }

Destroy all is the simplest way. Here is the syntax:
User.destroy_all(id: [2, 3])

In case someone need this. In Rails 6, if you already know what you want to delete and you want to do it in an efficient way on many records, you can use either:
Model.delete_by(id: array_of_id, other_condition: value)
Which is based on delete_all, documentation:
delete_by: https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-delete_by
delete_all: https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-delete_all
or:
Model.destroy_by(id: array_of_id, other_condition: value)
Which is based on destroy_all, documentation:
destroy_by: https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-destroy_by
destroy_all:
https://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-destroy_all
The difference is that delete_by will not run callbacks, validations nor will it delete dependent associations. While destroy_by will do all of that (but in a slower way). So it really depends on what you want to achieve for your application.
Both of those methods should be prefered over .where.something because they will create a single SQL query, not multiple of them.

Related

Rails console confusion

I'm trying to delete more that just one id. I'm currently using Person.find(1).destroy.
Is there a way that I can select more than just one data record?
Yes. You can write scripts in the console.
Person.find_each do |person|
if # condition for deleting
person.destroy
end
end
Alternatively, if you know all the ids...you can use a where clause and then destroy all of them.
ids = [1,2,3,4,5]
people = Person.where(id: ids) # where can take an array of ids
people.each { |person| person.destroy }

How to touch multiple records in ActiveRecord?

I'd like to update the updated_at for a few records:
users = User.in_mailing_list
users.update_all(:updated_at => Time.now)
Is there a shortcut for the purpose, say something like a users.touch_all method?
Not sure if rhernando's answer works in older versions of Ruby, but this is a much clearer method in my opinion and works in Ruby 2+
users.each(&:touch)
NB. As mentioned in the comments this will cause N requests as opposed to using update_all which would do it in a single command.
You can do it like this:
User.update_all({updated_at: Time.now}, {id: user_ids})
Note: The braces are required, otherwise it tries to set updated_at and id, instead of updating updated_at where id is in user_ids
Cheers!
If you need touch ActiveRelaton records you have to use update_all method. It touches multiple records in a single transaction:
User.update_all(updated_at: Time.current)
User.where(active: true).update_all(active: false)
But if you have Array of records, in this case, you use only each with update
users.each { |user| user.update(active: true) }
the disadvantage of this case: for each user will be a separate transaction
Not sure since when but the solution is much simpler:
users = User.in_mailing_list
users.touch_all
Or if you don't want to instantiate the user records:
users = User.in_mailing_list.touch_all
This should do it:
User.update(users, :updated_at => Time.now)

Given an Object form a query with 100s of items. How can I find an item in the object w/o having to re-query the db?

Given an object like contacts:
Contact.rb (id, fname, lname, key_tag)
#contacts = Contacts.where(:user_id => #user.id)
Given #contacts comes back with 1000s of records in that one DB query. How can I then get/see if a object exists in #contacts that matches a given 'key_tag'.
I tried:
#contacts.where(:key_tag => 'def12')
But that requeries the database which is exactly what I want to avoid. Ideas? Thanks
what you Have is an Relation there. So adding the other where changes the relation and queries that data back. It should be lazily loaded. So in your code
Contact.rb (id, fname, lname, key_tag)
#contacts = Contacts.where(:user_id => #user.id)
If you don't touch #contacts, it shouldn't even hit the DB. But once you try to get data from it, it will then be executed and data comes back. If you are going to show all of them, you could use something like #all to return them as an array, and then use Array#select to search through it. Depending upon the speed of the system and/or your db, it might be more efficient to the DB do the select.
#contacts = Contacts.where(:user_id => #user.id).all
#contacts_with_key = #contacts.select { |c| c.key_tag == 'def12' }
You can use Enumerable find method:
#contacts.find {|c| c.key_tag == 'def12'}
Try Enumerable#find_all
#contacts.find_all {|a| a[:key_tag] == 'def12'}
Or if you want to only find first one(given that key_tag is unique)
#contacts.find {|a| a[:key_tag] == 'def12'}
be sure to:
include Enumerable

Is There a Way To Make ActiveRecord Do A Multiple Insert

I have a one-to-many relationship where one Thing :has_many Elements
I'm looking for a way to create a Thing and all its N Elements without doing N+1 queries. I tried:
[loop in Thing model]
self.elements.build({...})
...
self.save
But it does a separate insert for each Element.
This capability is not built in.
One option is to use a transaction, which will not eliminate the multiple INSERTs but will send all of them in one request, which will help with performance some. For example:
ActiveRecord::Base.transaction do
1000.times { MyModel.create(options) }
end
To do a true bulk INSERT, though, you'll either have to write and execute a raw query, or use a gem such as activerecord-import (formerly part of ar-extensions). An example from the documentation:
books = []
10.times do |i|
books << Book.new(:name => "book #{i}")
end
Book.import books
I think this may be the best option for you.

rails where() sql query on array

I'll explain this as best as possible. I have a query on user posts:
#selected_posts = Posts.where(:category => "Baseball")
I would like to write the following statement. Here it is in pseudo terms:
User.where(user has a post in #selected_posts)
Keep in mind that I have a many to many relationship setup so post.user is usable.
Any ideas?
/EDIT
#posts_matches = User.includes(#selected_posts).map{ |user|
[user.company_name, user.posts.count, user.username]
}.sort
Basically, I need the above to work so that it uses the users that HAVE posts in selected_posts and not EVERY user we have in our database.
Try this:
user.posts.where("posts.category = ?", "Baseball")
Edit 1:
user.posts.where("posts.id IN (?)", #selected_posts)
Edit 2:
User.select("users.company_name, count(posts.id) userpost_count, user.username").
joins(:posts).
where("posts.id IN (?)", #selected_posts).
order("users.company_name, userpost_count, user.username")
Just use the following:
User.find(#selected_posts.map(&:user_id).uniq)
This takes the user ids from all the selected posts, turns them into an array, and removes any duplicates. Passing an array to user will just find all the users with matching ids. Problem solved.
To combine this with what you showed in your question, you could write:
#posts_matches = User.find(#selected_posts.map(&:user_id).uniq).map{ |user|
[user.company_name, user.posts.size, user.username]
}
Use size to count a relation instead of count because Rails caches the size method and automatically won't look it up more than once. This is better for performance.
Not sure what you were trying to accomplish with Array#sort at the end of your query, but you could always do something like:
#users_with_posts_in_selected = User.find(#selected_posts.map(&:user_id).uniq).order('username DESC')
I don't understand your question but you can pass an array to the where method like this:
where(:id => #selected_posts.map(&:id))
and it will create a SQL query like WHERE id IN (1,2,3,4)
By virtue of your associations your selected posts already have the users:
#selected_posts = Posts.where("posts.category =?", "Baseball")
#users = #selected_posts.collect(&:user);
You'll probably want to remove duplicate users from #users.

Resources