How to touch multiple records in ActiveRecord? - ruby-on-rails

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)

Related

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.

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.

ActiveRecord calculating multiple averages

I have a scores table with both score and time per user. I want to calculate both averages grouped by user. I can successfully calculate one of them but not sure how to do both at once.
#scores = SpellingScore.where(:user_id => users).average(:score, :group => :user)
Will produce sql like the following
SELECT AVG(`spelling_scores`.`score`) AS average_score, user_id AS user_id
FROM `spelling_scores`
WHERE (`spelling_scores`.`user_id` IN (78767, 78772, 78775)) GROUP BY user_id
I know how to do it in SQL but can't work out the ActiveRecord way.
This is what I want to do...
SELECT AVG(`spelling_scores`.`score`) AS average_score, AVG(`spelling_scores`.`time`) AS average_time, user_id AS user_id
FROM `spelling_scores`
WHERE (`spelling_scores`.`user_id` IN (78767, 78772, 78775)) GROUP BY user_id
Cheers,
Tim
Thanks for your help Macarthy.
I ended up doing it this way
SpellingScore.select("AVG(`spelling_scores`.`score`) AS average_score,
AVG(`spelling_scores`.`time`) AS average_time, COUNT(*) AS question_count, user_id AS
user_id").where(:user_id => users).group(:user_id).includes(:user).order('users.last')
At least I've retained some ActiveRecord chaining.
Just use SQL. Forget the ActiveRecord way, SQL is better for something like this. If you want to keep your logic in your model just create a new method in your model
Wouldn't this work SpellingScore.group(:user).average(:score)
Can't test this without a schema though

rails - activerecord ... grab first result

I want to grab the most recent entry from a table. If I was just using sql, you could do
Select top 1 * from table ORDER BY EntryDate DESC
I'd like to know if there is a good active record way of doing this.
I could do something like:
table.find(:order => 'EntryDate DESC').first
But it seems like that would grab the entire result set, and then use ruby to select the first result. I'd like ActiveRecord to create sql that only brings across one result.
You need something like:
Model.first(:order => 'EntryDate DESC')
which is shorthand for
Model.find(:first, :order => 'EntryDate DESC')
Take a look at the documentation for first and find for details.
The Rails documentation seems to be pretty subjective in this instance. Note that .first is the same as find(:first, blah...)
From:http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002263
"Find first - This will return the first record matched by the options used. These options can either be specific conditions or merely an order. If no record can be matched, nil is returned. Use Model.find(:first, *args) or its shortcut Model.first(*args)."
Digging into the ActiveRecord code, at line 1533 of base.rb (as of 9/5/2009), we find:
def find_initial(options)
options.update(:limit => 1)
find_every(options).first
end
This calls find_every which has the following definition:
def find_every(options)
include_associations = merge_includes(scope(:find, :include), options[:include])
if include_associations.any? && references_eager_loaded_tables?(options)
records = find_with_associations(options)
else
records = find_by_sql(construct_finder_sql(options))
if include_associations.any?
preload_associations(records, include_associations)
end
end
records.each { |record| record.readonly! } if options[:readonly]
records
end
Since it's doing a records.each, I'm not sure if the :limit is just limiting how many records it's returning after the query is run, but it sure looks that way (without digging any further on my own). Seems you should probably just use raw SQL if you're worried about the performance hit on this.
Could just use find_by_sql http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002267
table.find_by_sql "Select top 1 * from table ORDER BY EntryDate DESC"

How to get last N records with activerecord?

With :limit in query, I will get first N records. What is the easiest way to get last N records?
This is the Rails 3 way
SomeModel.last(5) # last 5 records in ascending order
SomeModel.last(5).reverse # last 5 records in descending order
Updated Answer (2020)
You can get last N records simply by using last method:
Record.last(N)
Example:
User.last(5)
Returns 5 users in descending order by their id.
Deprecated (Old Answer)
An active record query like this I think would get you what you want ('Something' is the model name):
Something.find(:all, :order => "id desc", :limit => 5).reverse
edit: As noted in the comments, another way:
result = Something.find(:all, :order => "id desc", :limit => 5)
while !result.empty?
puts result.pop
end
new way to do it in rails 3.1 is SomeModel.limit(5).order('id desc')
For Rails 5 (and likely Rails 4)
Bad:
Something.last(5)
because:
Something.last(5).class
=> Array
so:
Something.last(50000).count
will likely blow up your memory or take forever.
Good approach:
Something.limit(5).order('id desc')
because:
Something.limit(5).order('id desc').class
=> Image::ActiveRecord_Relation
Something.limit(5).order('id desc').to_sql
=> "SELECT \"somethings\".* FROM \"somethings\" ORDER BY id desc LIMIT 5"
The latter is an unevaluated scope. You can chain it, or convert it to an array via .to_a. So:
Something.limit(50000).order('id desc').count
... takes a second.
For Rails 4 and above version:
You can try something like this If you want first oldest entry
YourModel.order(id: :asc).limit(5).each do |d|
You can try something like this if you want last latest entries..
YourModel.order(id: :desc).limit(5).each do |d|
Solution is here:
SomeModel.last(5).reverse
Since rails is lazy, it will eventually hit the database with SQL like: "SELECT table.* FROM table ORDER BY table.id DESC LIMIT 5".
If you need to set some ordering on results then use:
Model.order('name desc').limit(n) # n= number
if you do not need any ordering, and just need records saved in the table then use:
Model.last(n) # n= any number
In my rails (rails 4.2) project, I use
Model.last(10) # get the last 10 record order by id
and it works.
Just try:
Model.order("field_for_sort desc").limit(5)
we can use Model.last(5) or Model.limit(5).order(id: :desc) in rails 5.2
I find that this query is better/faster for using the "pluck" method, which I love:
Challenge.limit(5).order('id desc')
This gives an ActiveRecord as the output; so you can use .pluck on it like this:
Challenge.limit(5).order('id desc').pluck(:id)
which quickly gives the ids as an array while using optimal SQL code.
Let's say N = 5 and your model is Message, you can do something like this:
Message.order(id: :asc).from(Message.all.order(id: :desc).limit(5), :messages)
Look at the sql:
SELECT "messages".* FROM (
SELECT "messages".* FROM "messages" ORDER BY "messages"."created_at" DESC LIMIT 5
) messages ORDER BY "messages"."created_at" ASC
The key is the subselect. First we need to define what are the last messages we want and then we have to order them in ascending order.
If you have a default scope in your model that specifies an ascending order in Rails 3 you'll need to use reorder rather than order as specified by Arthur Neves above:
Something.limit(5).reorder('id desc')
or
Something.reorder('id desc').limit(5)
A simple answer would be:
Model.limit(5).order(id: :desc)
There is a problem with this solution, as id can't be the sole determiner of when a record was created in the time.
A more reliable solution would be:
Model.order(created_at: :desc).limit(5)
As others have pointed out, one can also use Model.last(5). The only gotcha with this is that it returns Array, and not Model::ActiveRecord_Relation.
Add an :order parameter to the query

Resources