Ordering records returned from ActiveRecord in a random manner is on the chopping block today.
Scenario: I want to return 5 random users
How would we go about solving this? Well maybe a first pass would look something like this?
So on this first pass we are grabbing all
User records, shuffling them around, and then grabbing the first 5 records. This solves the problem but why would we do something different?
First we are returning all
User records just to return 5! Doing something like this is almost always a bad idea especially when dealing with
User type records. They are likely to have a high record count in your application. When you return them all, each one is taking up memory just for you to return 5.
Let's alleviate this problem a bit with another solution.
ids = User.pluck(:id).shuffle[0..4] User.where(id: ids)
Now we are making an extra database call in favor of not returning a bunch of unnecessary
ActiveRecord objects. First we
id column which returns an
id for all
User records. Then we
shuffle that array and choose the first 5 records.
Well this is pretty good, we are saving our memory for the records we actually want. This again solves our problem but it's a bit more to look at (with the extra DB call).
We can alleviate this problem a bit with yet another solution.
# postgresql User.limit(5).order("RANDOM()")
Let's review what is happening here. We are querying for
User records ordered randomly and limiting the return set to 5 results. Our original problem to solve was: I want to return 5 random users. Not only have we written code to solve this issue but the code written is expressed very closely to what we wanted if it were read out loud.
What changed? The main difference is the
RANDOM() passed to
order. For those who think this looks a bit odd, this is a database specific function. You can pass all sorts of
SQL fragments to
ActiveRecord that just get joined in to make a comple
SQL statement. This is just another fragment. The call being made actually looks like this:
SELECT * FROM users ORDER BY RANDOM() LIMIT 5
Now I know what you may be thinking... "ActiveRecord is database agnostic! Why would you bind yourself to a specific implementation!?" The simple answer is yes,
ActiveRecord is database agnostic and yes it is great. It gives the developer the option to choose which database is best to solve your specific problems. Now just think that once your database choice has been made, how often if ever are you going to make a change to another? Databases are very good at what they were made to do... let them be good at it. Don't be afraid to use database specific features and functions to improve your development. This particular example has been granted to us by PostgreSQL, which is my go-to DB of choice.
Not using PostgreSQL? This similar functionality can be had by most databases. Consult with your documentation. Here is a MySQL example to show how the same functionality can be had:
# mysql User.limit(5).order("RAND()")