Better Way to Randomize (Ruby/Rails 3) [duplicate] - ruby-on-rails

This question already has answers here:
Rails select random record
(8 answers)
Closed 5 years ago.
I'm currently using:
#users = User.order("RANDOM()").limit(6)
to generate a list of 6 random users - however, this method takes a 1200ms toll on page load times. Is there a faster/more efficient way to call 6 random users?

I ended up using the method described here:
Rails select random record
#ramc - Thank you for your comment. Load times are now much faster :)

Assuming auto increment ids, starting from 0 and that User objects are never deleted, the following should be quite fast:
#users = (0..5).map { User.find(rand * User.count) }

Have you tried using a random offset & limit?
class User < ActiveRecord::Base
def self.random num = 1
num.times.map { offset(rand(count)).limit(1) }
end
end
#users = User.random(6)
I've used something similar to get single random instances from AR. You'd have to make it a bit smarter if you wanted to guarantee unique results.

You could get a random Page of users instead. Consider this pagination call using Kaminari
User.order('created_at').page(rand(User.count)).per(6)
This will fire one count query and one query for the page of 6 Users

You could try using array shuffle instead of using mysql random as it can be slow:
#users = User.all.shuffle[0..5]
After all, a collection of ActiveRecord objects is still just an Array

Related

Why `where` method doesn't work, unlike `find`? [duplicate]

This question already has answers here:
Rails .where vs .find
(3 answers)
Closed 2 years ago.
Faced a problem completely incomprehensible to me.
I am getting an error when using the method where.
#winner = Team.where(id: params[:winner_id])
#winner.update(rating: #winner.rating += 20)
undefined method `rating' for #<Team::ActiveRecord_Relation:0x00007faed9018490>
However, if I change #winner = Team.where(id: params[:winner_id]) to #winner = Team.find(params[:winner_id])it will work.
Why where method doesn't work?
Because where always gives you an ActiveRecord_Relation, not a single object. In your case you're filtering on the id, so if that's the primary key, as I suppose, you only get one record, but that's into a relation anyway, because that's the semantic of where.
#winner = Team.where(id: params[:winner_id]).first
or
#winner = Team.find_by(id: params[:winner_id])
One of these should do the trick
where works this way because you could even filter for some not so restrictive columns and get many rows in returns, so you always get a collection of rows. For example, think about this
#people = Person.where(age: 20)
this query retrieves all the people 20 years old. That could get zero, one or many rows but you always get a relation, for sure.

Group by decade with counts

I have a table of Album's that has a date column named release_date.
I want to get a list of all the decades along with the number of albums released in that decade.
So, the output might be something like:
2010 - 11
2000 - 4
1990 - 19
1940 - 2
Ruby 2.3.1 w/ Rails 5 on Postgres 9.6, FWIW.
This is essentially a followup question to a previous one I had: Group by month+year with counts
Which may help with the solution...I'm just not sure how to do the grouping by decade.
Using Ruby for processing db data is inefficient in all senses.
I would suggest doing it on the database level:
Album.group("(DATE_PART('year', release_date)::int / 10) * 10").count
What happens here, is basically you take a year part of the release_date, cast it to integer, take it's decade and count albums for this group.
Say, we have a release_date of "2016-11-13T08:30:03+02:00":
2016 / 10 * 10
#=> 2010
Yes, this is pretty similar to your earlier question. In this case, instead of creating month/year combinations and using the combinations as your grouping criteria, you need a method that returns the decade base year from the album year.
Since you have a pattern developing, think about writing the code so it can be reused.
def album_decades
Album.all.map { |album| album.release_date.year / 10 * 10 }
end
def count_each(array)
array.each_with_object(Hash.new(0)) { |element, counts| counts[element] += 1 }
end
Now you can call count_each(album_decades) for the result you want. See if you can write a method album_months_and_years that will produce the result you want from your earlier question by calling count_each(album_months_and_years).
There are more than one possible solution to your problem, but I would try:
Add a new column to the Album table, called decade. You can use a migration for this porpoise.
Create a callback (its like a trigger, but in the programmer side) that set the decade value before saving the Album in the DB.
Finally you can use this useful query to group the Albums by decade. In your case would be Album.group(:decade).count wich would give you a hash with the numbers of Albums by decade.
...
Profit ?
Jokes aside, the callback should be something like:
class Album < ActiveRecord::Base
# some code ...
before_save :set_decade # this is the 'callback'
# ...
private
def set_decade
self.decade = self.release_date.year / 10
end
Then, if you use the step 3, it would return something like:
# => { '195' => 7, '200' => 12 }
I did not test the answer, so try it out and tell me how it went.

How can I query all records that don't have a specific association without loading them all into memory?

If I want a list of all the shops that are open on Sunday, I do
Shop.includes(:opening_times).where("opening_times.day =?", 'Sunday')
Is there any way to get a list of all the shops that are closed on Sundays? That is, all the shops that are not associated with a record where the day column is 'Sunday'?
I asked this question here and accepted an answer. However, now my database is getting too big to solve this by loading all the open shops into memory first. Is there a way to do this without first getting an array of all the open shops and passing that array back to the database?
There could be a more Railsy way, but with a single query (and subquery):
Shop.where("shop_id NOT IN (select opening_times.shop_id from opening_times where opening_times.day = 'Sunday')")
Or, based on the linked question, you could improve by using pluck:
shop_ids = Shop.includes(:opening_times).where("opening_times.day = ?", 'Sunday').pluck(:id)
shops = Shop.where("id NOT IN(?)", shop_ids)
The current method you're using (map(&:id)) is instantiating objects for every row, whereas pluck(:id) will perform a select id query.
A faster way without map would be to pluck the id's instead.
open = Shop.includes(:opening_times).where(opening_times: { day: 'Sunday' }).pluck(:id)
closed = Shop.where.not(id: open)
I'm not sure I understand what you're asking, but I'll have a go.
When I need to query for objects that don't have an association, I normally created a counter cache for the # of associated objects and query that. So, in the end, your query would look like this:
Shop.include(:opening_times).where("opening_times.sunday_count", 0)

Ruby On Rails - Geocoder - Near with condition

I'm using GeoCoder in my application. Now I need to search for objects in my database which are close to a position OR have specific attribute set. I would like to perform this action in one database query, because the database is realy huge.
I would like to have something like
Spot.near([lat,long],distance).where("visited = ?",true).
The distance and the visited attribute should be combined with an OR, not with an AND.
Does anyone have an idea how to do this?
Thank you!
Based off of this answer, you should be able to do something like:
near = Spot.near([lat, long], distance)
visited = Spot.where(visited: true)
near = near.where_values.reduce(:and)
visited = visited.where_values.reduce(:and)
Spot.where(near.or(visited))
I'm in the process of upgrading a Rails application from Rails 4 to Rails 7 and ran into this problem. While I have no doubt Luke's suggestion worked in earlier versions, it doesn't work in Rails 7 (I'm currently running activerecord-7.0.3.1.
In my particular case, I am using the geocoder near() method to return results that are within a 20 mile radius of the query, but I also wanted to use OR conditions to return results where the query was similar to the text values in either the name or location columns from the items table in an attempt to return relevant items that haven't been assigned latitude and longitude values.
In Rails 4, my solution was:
select("items.*").near(q, 20, select: :geo_only).tap do |near_query|
near_query.where_values.last << sanitize_sql([" OR items.location LIKE ? OR items.name LIKE ?", "%#{q}%", "%#{q}%"])
end
In Rails/ActiveRecord 7, the where_values() method no longer exists. Searching for an alternate solution led me to this post. I wound up spending a fair amount of time perusing the latest ActiveRecord and Arel code for a solution. Here's what I came up with,
Rails 7 solution:
t = Item.arel_table
arel_geo_conditions = Item.near(q, 20).where_clause.ast # ast: Abstract Syntax Tree
Item.where(arel_geo_conditions.or(t[:location].matches("%#{q}%").or(t[:name].matches("%#{q}%"))))

Given a query how to reduce the number of items

I'm trying to do the following in rails 3:
#groups_active = Group.active_groups(current_user)
active_groups is a scope. This query works fine. I then want to do the following:
if #groups_active.count > 9
#groups_active[0..10]
end
Meaning if there are more than 10 items in the #groups_active, take just the TOP 10, which thanks to the scope ordering are the most active.
Suggestions? Thanks
I'm not sure what your problem is. You can limit the number of results from a query with Model.your_scope.limit(10), and if it is a query that doesn't work with a SQL LIMIT then you can use Model.your_scope.first(10). That's an Array#first, which accepts a fixnum argument to be used as expected…
#groups_active = Group.active_groups(current_user).limit(10)
or you could add the .limit(10) part to your scope.
Edited
I would go with one limit(10) request and one count. It could be more effective then retrieving 20, 50, or more records and using only the first 10 of them.
But still, it requires testing and benchmarking.

Resources