Rails console confusion - ruby-on-rails

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 }

Related

Select from DB all records or by array if present

In RoR app I want to write a model method that will return some records.
It should select by ids if ids are present or all records if not
def viewing_duration(ids = nil)
if ids
tracks.where(video_id: ids).sum('age(updated_at, created_at)')
else
tracks.sum('age(updated_at, created_at)')
end
end
Question:
How I can write query in one line and pass the expression right to where method?
Something like this:
tracks.where(video_id: ids.nil? ? 'all' : ids).sum('age(updated_at, created_at)')
Keeping it as more lines probably makes it easier to understand. I suggest keeping it close to as it is while removing repetition:
def viewing_duration(ids = nil)
if ids
tracks.where(video_id: ids)
else
tracks
end.sum('age(updated_at, created_at)')
end
If you do want to pass something into where to find all records, you can use an sql statement that will always evaluate to true. For example:
tracks.where(ids.nil? ? '1=1' : { video_id: ids }).sum('age(updated_at, created_at)')
It is not one line, but as idea how to organize your code using chaining
def viewing_duration(ids = nil)
entities = tracks
entities = entities.where(video_id: ids) if ids
entities.sum('age(updated_at, created_at)')
end

Ruby on Rails inefficient database query

My Rails app has the following conditions:
Each Style has many Bookings
Each Booking has a single warehouse value, and a single netbooked value
I need to update the warehouse_netbooked column of every Style with a hash containing the total netbooked sum for each warehouse across all of the style's bookings.
My current code works, but is way too slow (each iteration is taking ~0.5s, and there are thousands of styles):
def assign_warehouse_bookings
warehouses = ["WH1","WH2","WH3"]
Style.all.each do |s|
style_warehouse_bookings = Hash.new
warehouses.each do |wh|
total_netbooked = s.bookings.where(warehouse: wh).sum(:netbooked)
style_warehouse_bookings[wh] = total_netbooked
end
s.update(warehouse_netbooked: "#{style_warehouse_bookings}")
end
end
Here a small change to your code to avoid to do many queries following #eric-duminil advise
#styles = Style.includes(:bookings)
#styles.each do |s|
style_warehouse_bookings = Hash.new
warehouses.each do |wh|
total_netbooked = s.bookings.map {|book| book.warehouse.eql?(wh) ? book.netbooked : 0}.sum
style_warehouse_bookings[wh] = total_netbooked
end
s.update(warehouse_netbooked: "#{style_warehouse_bookings}")
end
end
I hope it help you.
In your case, you have for a single style, you are fetching booking with 3 type of warehouses finding sum of netbooked for each type of warehouse. This is highly inefficient.
One good rule is, first fetch required data from database, and after fetching data use ruby to handle data. It's important to fetch only required data. So, in your case, you can fetch styles and all related bookings. Then you can iterate through collection of bookings and prepare style_warehouse_bookings hash.
use find_each instead of each
use includes or preload to preload data.
Here is simple example which will definitely improve performance,
warehouses = ["WH1","WH2","WH3"]
# preload bookings with style, `preload` used explicitely instead of `includes` to prevent cross join queries.
styles = Style.joins(:bookings).where('bookings.warehouse' => warehouses).preload(:bookings)
# find_each fetches data in batches of 1000 records
styles.find_each do |s|
style_warehouse_bookings = Hash.new
warehouses.each do |wh|
# select and sum methods of ruby are used instead of where and sum of active-record
total_netbooked = s.bookings.select{ |booking| booking.warehouse = wh }.sum(&:netbooked)
style_warehouse_bookings[wh] = total_netbooked
end
s.update(warehouse_netbooked: "#{style_warehouse_bookings}")
end
Read in depth about preload, includes and joins at eager loading associations documentation. Apart from that I wrote an article on when to use preload, includes and joins here which can help.
I think you want to do batch update. If I am correct check the following
link
Is there anything like batch update in Rails?
Also you can introduce transaction to avoid too many commits
For example
def assign_warehouse_bookings
Style.transaction do
<your remaining code goes here>
end
end

Sorting a Rails active record

I have a table A(:name, :address, :country_id). I run this query
ids = [1,2,3]
#result = A.where(country_id: ids)
But, I want to get the results in this order : records containing country_id = 2 first, then 1 and then 3.
How should I write this query?
Edit :
#people = []
#result.each do |r|
#people.push([r,r.name])
end
I have an array #people of arrays now and want to sort it. What should be the query now?
Another Edit :
#people.sort_by! {|p| preferred_order.index(p.first.country_id)}
This worked.
You can use sort_by :
ids = [1,2,3]
preferred_order = [2,1,3]
#result = A.where(country_id: ids)
#result.sort_by! { |u| preferred_order.index(u.country_id) }
One option would be adding an extra column
rails g migration add_sortorder_to_A sort_order:integer
Then add a
before_save :update_sort_order
def update_sort_order
if self.country_id == 1
self.update_attributes(sort_order:2)
elsif self.country_id ==2
self.update_attributes(sort_order:1)
........
end
Alternatively, put your sort_order in the countries and sort on that.
Alternatively, create a dynamic field which will give you the sort_order.
There is probably a more elegant way of doing this, but this should work in a quick and dirty way and allow you to use standard activerecord queries and sorting. (i.e. you can just forget about it once you've done it the once)

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, given an array of items, how to delete in the console

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.

Resources