I'm probably missing something very simple here, but can't understand what.
I'm trying to cache a simple active record query but every time I touch the cache, it runs the query against the DB again.
Controller Code:
products = Rails.cache.read("search_results")
if products
render :text => products[0].id
else
products = Product.where('name LIKE ?", 'product_name?')
Rails.cache.write("search_results", products)
end
I can see that in my second call I get to the if block and not the else, but any time I'm trying to touch products (like rendering it) I also see an active record call to the DB.
What am I missing?
The line
products = Product.where('name LIKE ?", 'product_name?')
returns an ActiveRecord::Relation, but does not hit the database unless a kicker method is called on it.
While I would still recommend using fetch as mentioned in my comment above, try changing the line to:
products = Product.where('name LIKE ?", 'product_name?').all
which will force the database hit, and save the actual results of the query into the cache, instead of the relation.
Related
Ok, I got some records from a query, something like this.
works = Work.do_a_long_query_with_scope(....)
In my controller, I want to find something else base on this works' ids, then return this works in response
def index
works = Work.do_a_long_query_with_scope(....)
not_finished_tasks = Task.find_not_finished_tasks_of_user(works.ids, current_user.id)
# do some job with those tasks
render json: works
end
This works fine except that it created 2 query , 1 with SELECT works.id FROM works ... for that task function, 1 with SELECT works.* from works ... for serializer response.
So can I use works.ids without making another request to DB ?
You could just use #load to tell the relation to load everything from the database:
works = Work.do_a_long_query_with_scope(....)
works.load
not_finished_tasks = Task.find_not_finished_tasks_of_user(works.ids, current_user.id)
# do some job with those tasks
render json: works
After works.load, the works.ids call will pull the ids out of the already loaded Works instances rather than going back to the database.
Also, if your Task.find_not_finished_tasks_of_user call is doing something like:
where(id: array_of_work_ids)
then you could instead pass in the whole works relation and say:
where(id: works.select(:id))
to use a subquery instead of sending all the ids back to the database. The first one will do something like:
where id in (a_big_list_of_ids)
but the second will do:
where id in (select id from ...)
There may or may not be any noticeable difference depending on how big works.ids is. There's also a possibility that the subquery will give you different ids if something is changing the database between your load call and when the subquery executes.
I have been using Product.all but a lot of code that I have been seeing is using Product.where(nil) when populating a variable. this site has an example that I found for using where(nil). I have searched through documentation and found where where(nil) replaced scoped but can't make heads or tails of what it does.
I believe there used to be a difference, but from Rails 4 there no longer is. This is because from Rails 4 .all returns a relation whereas it used to return an array. So previously:
Product.all
immediately fires a query to the database to return all records, which would get loaded into an array in memory. Basically you are telling Rails that you want the data NOW. See this question for more info.
Product.where(nil)
creates a query (actually an anonymous scope that returns an ActiveRecord:Relation).
The query will only execute when you try to access the data for it. Since it is a scope, you can chain other scopes (without hitting the database each time) and have the entire chain execute as one query when you access the data.
In the Justin Weiss article linked to in the question, we see this code:
def index
#products = Product.where(nil) # creates an anonymous scope
#products = #products.status(params[:status]) if params[:status].present?
#products = #products.location(params[:location]) if params[:location].present?
#products = #products.starts_with(params[:starts_with]) if params[:starts_with].present?
end
That code will execute one database call when the index method ends and returns the data.
But if you changed the first line to:
#products = Product.all
that query would execute immediately. Then the scopes are added, and another query would fire when index method ends. This is more inefficient.
EDIT
I believe the database call would actually happen when you first try to access some data in #products (not when the index method ends). Eg. if you did #products.first then the query would execute.
For demo purposes, suppose that I have a class called DemoThing with a method called do_something.
Is there a way that (in code) I can check the number of times that do_something hits the database? Is there a way that I can "spy" on active record to count the number of times that the database was called?
For instance:
class DemoThing
def do_something
retVal = []
5.times do |i|
retVal << MyActiveRecordModel.where(:id => i)
end
retVal
end
end
dt = DemoThing.new
stuff = dt.do_something # want to assert that do_something hit the database 5 times
ActiveRecord should be logging each query in STDOUT.
But for the above code, it's pretty obvious that you're making 5 calls for each iteration of i.
Queries can be made more efficient by not mixing Ruby logic with querying.
In this example, gettings the ids before querying will mean that the query isn't called for each Ruby loop.
ids = 5.times.to_a
retVal = MyActiveRecordModel.where(id: ids) # .to_a if retVal needs to be an Array
Sure is. But first you must understand Rails' Query Cache and logger. By default, Rails will attempt to optimize performance by turning on a simple query cache. It is a hash stored on the current thread (one for every active database connection - Most rails processes will have just one ). Whenever a select statement is made (like find or where etc.), the corresponding result set is stored in a hash with the SQL that was used to query them as the key. You'll notice when you run the above method your log will show Model Load statement and then a CACHE statement. Your database was only queried one time, with the other 4 being loaded via cache. Watch your server logs as you run that query.
I found a gem for queries count https://github.com/comboy/sql_queries_count
For instance:
#examples = #user.examples.mostrecent.paginate(page: params[:page])
Where "mostrecent" is defined as:
def self.mostrecent
self.order('created_at DESC')
end
So basically the first call to the database is pull every User's example, and then on top of that, order them by most recent first. It seems like this should be doable, but for some reason I can't get it to work.
There is no defined order scope in the model I'm working with, and other calls to order work just fine. By checking the development.log I can see only the first database pulling example by users is respected. The mostrecent order is never called.
Is there a Rails way of doing this all in one line?
You could use a scope, as in:
scope :by_recent, lambda
{ |since_when| order("created_at") }
Is there a way to do this in Rails:
I have an activerecord query
#posts = Post.find_by_id(10)
Anytime the query is called, SQL is generated and executed at the DB that looks like this
SELECT * FROM 'posts' WHERE id = 10
This happens every time the AR query is executed. Similarly with a helper method like this
<%= f.textarea :name => 'foo' %>
#=> <input type='textarea' name='foo' />
I write some Railsy code that generates some text that is used by some other system (database, web browser). I'm wondering if there's a way to write an AR query or a helper method call that generates the text in the file. This way the text rendering is only done once (each time the code changes) instead of each time the method is called?
Look at the line, it may be going to the database for the first one but ones after it could be saying CACHE at the start of the line meaning it's going to ActiveRecord's query cache.
It also sounds to me like you want to cache the page, not the query. And even if it were the query, I don't think it's as simple as find_by_id(10) :)
Like Radar suggested you should probably look into Rails caching. You can start with something simple like the memory store or file cache and then move to something better like memcached if necessary. You can throw in some caching into the helper method which will cache the result after it is queried once. So for example you can do:
id = 10 # id is probably coming in as a param/argument
cache_key = "Post/#{id}"
#post = Rails.cache.read(cache_key)
if #post.nil?
#post = Post.find_by_id(id)
# Write back to the cache for the next time
Rails.cache.write(cache_key,#post)
end
The only other thing left to do is put in some code to expire the cache entry if the post changes. For that take a look at using "Sweepers" in Rails. Alternatively you can look at some of the caching gems like Cache-fu and Cached-model.
I'm not sure I understand your question fully.
If you're asking about the generated query, you can just do find_by_sql and write your own SQL if you don't want to use the active record dynamic methods.
If you're asking about caching the resulset to a file, it's already in the database, I don't know that if it was in a file it would be much more efficient.