Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why are Rails scopes preferable, if messy controllers are faster?

I've been trying to chain Arel queries using scopes, instead of just using some long-winded logic I wrote in the controller. But the scopes are slower than just getting all the records and then sifting through them with some logic. I'm wondering, then, why scopes are better.

Here's what I'm doing:

  • a question has many answers
  • an answer belongs to one question
  • a question has a "question_type" column that I use to sort it

First, the scopes way...

in question.rb:

scope :answered, joins(:answers).order('answers.created_at desc')
scope :dogs, where(:question_type => "dogs")
scope :cats, where(:question_type => "cats")
scope :mermaids, where(:question_type => "mermaids")

in questions_controller.rb:

@dogs_recently_answered = Question.answered.dogs.uniq[0..9]
@cats_recently_answered = Question.answered.cats.uniq[0..9]
@mermaids_recently_answered = Question.answered.mermaids.uniq[0..9]

Then in the view, I cycle through those instance variables (which are now arrays containing at most 10 elements) and display the results.

Here are the times it takes to load the page (five different times):

Completed 200 OK in 535ms (Views: 189.6ms | ActiveRecord: 46.2ms)

Completed 200 OK in 573ms (Views: 186.0ms | ActiveRecord: 46.3ms)

Completed 200 OK in 577ms (Views: 189.0ms | ActiveRecord: 45.6ms)

Completed 200 OK in 532ms (Views: 182.9ms | ActiveRecord: 46.1ms)

Completed 200 OK in 577ms (Views: 186.7ms | ActiveRecord: 46.9ms)

Now, the messy controller way...

@answers = Answer.order("created_at desc")
@all_answered = []
@answers.each {|answer| @all_answered << answer.question}
@recently_answered = @all_answered.uniq
@dogs_all_answered = []
@cats_all_answered = []
@mermaids_all_answered = []
@recently_answered.each do |q|
  if q.question_type == "dogs"
    @dogs_all_answered << q
    @dogs_recently_answered = @dogs_all_answered[0..9]
  elsif q.question_type == "cats"
    @cats_all_answered << q
    @cats_recently_answered = @cats_all_answered[0..9]
  elsif q.question_type == "mermaids"
    @mermaids_all_answered << q
    @mermaids_recently_answered = @mermaids_all_answered[0..9]
  end
end

And here are the times it takes to load the page now (five different times):

Completed 200 OK in 475ms (Views: 196.5ms | ActiveRecord: 34.5ms)

Completed 200 OK in 480ms (Views: 200.4ms | ActiveRecord: 36.4ms)

Completed 200 OK in 434ms (Views: 198.2ms | ActiveRecord: 35.8ms)

Completed 200 OK in 475ms (Views: 194.2ms | ActiveRecord: 36.4ms)

Completed 200 OK in 475ms (Views: 195.0ms | ActiveRecord: 35.4ms)

So...

Aside from readability, what's to be won by honing the query with a scope? Does it eventually become quicker when there are more records?

like image 795
Steve Cotner Avatar asked Nov 30 '10 13:11

Steve Cotner


People also ask

What is scope how and when it is to be used in rails models?

Scopes are custom queries that you define inside your Rails models with the scope method. Every scope takes two arguments: A name, which you use to call this scope in your code. A lambda, which implements the query.

How do you avoid N 1 queries ActiveRecord?

You can avoid most n+1 queries in rails by simply eager loading associations. Eager loading allows you to load all of your associations (parent and children) once instead of n+1 times (which often happens with lazy loading, rails' default). As seen above, . includes allows nested association eager loading!

What are scopes rails?

What is scope in Rails? Scopes in Rails are special methods to run SQL queries that you can build in any rails model. A scope will always return an ActiveRecord::Relation object, even if the conditional evaluates to false , whereas a class method, will return nil .

What is N 1 query problem rails?

The n+1 query problem is one of the most common scalability bottlenecks. It involves fetching a list of resources from a database that includes other associated resources within them. This means that we might have to query for the associated resources separately.


1 Answers

First, I'm not sure I understand how a question can be other than unique, so I'd look at trying to remove that. I don't know the logic of your data, so that might not be applicable, but it's an extra step that you might be able to avoid.

Here's how I would approach it:

scope :answered, joins(:answers).order('answers.created_at desc')
scope :recent, take(10)
scope :dogs, where(:question_type => "dogs")
scope :cats, where(:question_type => "cats")
scope :mermaids, where(:question_type => "mermaids")

@dogs_recently_answered = Question.answered.dogs.recent
@cats_recently_answered = Question.answered.dogs.recent
@mermaids_recently_answered = Question.answered.dogs.recent

This shifts the TOP portion of the query to the database where it belongs rather than fetching all of the rows and then discarding all but 10. Depending on your uniquing criteria, you might also use a scope like

scope :unique, select('DISTINCT column_name')

and then you can use Question.cats.unique.recent and get it all in one fast query that leverages the relational algebra that database systems are designed for.

like image 65
jxpx777 Avatar answered Sep 19 '22 02:09

jxpx777