Remembering records returned from DB query in Rails - ruby-on-rails

I currently use a query of the form
#recordsfound = Model.where(...)
This is located in my controller and returns all records matching the query. I am using pagination to show a limited number of records at a time. However, when I select to show the next page the query is run again. Is there a way to store the records returned in a variable other than an instance variable and therefore not require the query to be rerun?
Thanks a lot guys

I'm not entirely sure I understand what you mean, but if you want the query to only run once per page even on subsequent visits, you can use fragment caching.
Rails will lazy load, and then use the cache when it hits the query in the view.
<% results.each do |result| %>
<% cache result do %>
<%= result.foo %>
<% end %>
<% end %>
If you have any dependent models, you'll have to make sure you expire the cache when they get updated if necessary:
belongs_to :result, touch: true
Note that if you are in development environment the query will still run. You can change this in your development.rb config file. If you do change this setting, don't forget to revert it . Otherwise strange things will happen and you'll waste your time trying to figure out why your changes aren't visible.
config.action_controller.perform_caching = true

Related

Rails: Fragment cache how to update when old data is not match with new

The problem is that I have a function call inside cache fragment which is dependent on the current time and it loads the correct data first time but not always. Some data I need to show is based on time but due to the use of the cache, it just loads old data since the record is not updated. Any idea how can I do that while still using cache.
<% cache "shop_items_page_#{#shop.cache_key}" do %>
...
<% cache "item_list_page_#{item.cache_key}_#{shop_cache(item)}" do %>
...
is_item_closed?(item) #a function returns true or false based on time
...
<%end%>
...
<%end%>
According to the documentation, you can pass an instance of a model to cache method: cache #shop and cache item. Then Rails will build a cache key based on updated_at fields, make sure that you have this field in your tables.
If you strictly need to use custom cache keys, then add updated_at directly in the view or inside cache_key methods.
<% cache "shop_items_page_#{#shop.cache_key}_#{#shop.updated_at}" do %>
...
<% cache "item_list_page_#{item.cache_key}_#{item.updated_at}_#{shop_cache(item)}" do %>
...
is_item_closed?(item) #a function returns true or false based on time
...
<%end%>
...
<%end%>

Strange ordering of ActiveRecord object

I am trying to get the records to order by :id in the view. I have records ordered by :id in the controller, like so
#bands = Band.where(available: true).order(:id)
Everything appears normal when the app starts, but as records are updated, the ordering behaviour goes wonky. Most often, recently edited records move to the very end, but not always. This shouldn't happen since the records are ordered by :id which shouldn't be changing
For context, in the view is something like
<% #bands.each do |b| %>
<%= b.name %>
<% end %>
Also note, in the rails console, both of these return the correctly ordered results (which makes this problem even stranger):
ActiveRecord::Base.connection.execute("SELECT * FROM bands WHERE available = 'true' ORDER BY id")
and
Band.where(available: true).order(:id)
Also note, when I load the index view, and observe the rails server, I can see that the results are not in the correct order.
I can also see the sql query that has been executed, and it ignores the order part, it simply doesn't have any mention of order in the query
Actually, I don't think it is possible.
But, try this:
#bands = Band.where(available: true).order(:id).to_a

Rails cache ActiveRecord result

My current homepage displays all available categories and all of the number of posts each category has. This of course is having a performance hit on the website and i was just wondering if this could be cached at all?
I don't mind if the cache is a little out of date and if the number of posts isn't 100% accurate at every refresh, but i would like it to only make the query every 30 minutes or so.
In Rails you can cache pretty much everything.
You can cache partials or queries. And you can expire them manually too.
For example
cache('categorylist') do
render partial: 'such'
end
And in the post.rb model, after each create of post, reset this and force it to evaluate on next run
after_create {
Rails.cache.delete('categorylist')
}
This way, your partial will only be evaluated (and written to cache) when a new post has been created. All other times, it will be fetched from cache.
ActiveRecord caches queries so that they don't have to be re-executed. However, this sort of caching does not persist between requests. View caching may work better for you - I'd look into Fragment Caching, the rails docs for which can be found here: http://edgeguides.rubyonrails.org/caching_with_rails.html
The crux though is that as you can cache objects as you render them like this (from the doc mentioned above):
<% #products.each do |product| %>
<% cache product do %>
<%= render product %>
<% end %>
<% end %>
This writes each product to a key/value store. It records the updated_at timestamp for the data so that you don't serve stale data.
Using the format from the above example, you could store the html listing the categories and posts per category in the cache.

Ruby Find last record value in has many

I'm trying to find the last Econ_Result that belongs to a Econ_Report. I want to display the last record of the Econ_Result (ordered by "release_date") for each Econ_Report on the index view. In the controller I tried to take the list of all reports and find the last result using the following:
#econ_reports = EconReport.all
if #econ_reports.econ_results.size >= 1
#last_result = #econ_report.econ_results.last.release_date
end
econ_report.econ_results.size works on the index view when I place it in for each loop. When I try to call the value of the last record I run into issues with the fact that some reports don't yet have results (a temporary issue) so I threw in the if then check in the controller which is currently failing.
Thanks in advance for the rookie help.
Since #econ_reports is a collection of EconReport objects, you can't call an instance method like .econ_results on it. Instead, you can only call it on instances within the collection:
#econ_reports.each do |econ_report|
if econ_report.econ_results.any?
last_result = econ_report.econ_results.last
end
end
However, this can be terribly inefficient for a large collection of #econ_reports: both lines with econ_report.econ_results will query the database separately, meaning that you'll query the database independently for each econ_report in the collection. This is known as the N+1 query problem.
Luckily for you, as discussed in the link, Rails has a built-in solution to optimize this code so you'll only query the database once:
<% #econ_reports.includes(:econ_results).each do |econ_report| %>
<% if econ_report.econ_results.any? %>
<% last_result = econ_report.econ_results.last %>
# do something to display last_result
<% end %>
<% end %>
If you just want the release date you might try:
#last_result = #econ_report.econ_results.order('release_date DESC').limit(1).pluck(:release_date).first
It's worth noting that a Ruby if statement generally looks like:
if condition
end
The then is almost always omitted even though it is allowed.

Is this way of calling object supposed to be bad practice when considering loading speed?

My way
controller pattern 1 (note: Here, it's calling all users!!)
#users = User.confirmed.joins(:profile)
view pattern 1 (note: Here, it only shows first 10 users but it show the number of all users!!)
<%= "ALL ("+ #users.count.to_s + " users)" %>
<% #users.limit(10).each do |users| %>
<%= render 'users/user', :user => users %>
<% end %>
Should it be just like this below if I'm considering page loading speed?
Or it won't be changed?
controller pattern 2 (note: I added limit(10), and #users_count to count all users)
#users = User.confirmed.joins(:profile).limit(10)
#users_count = User.confirmed.joins(:profile).count
view pattern 2 (note: I took it off limit(10) and use #users_count for count)
<%= "ALL ("+ #users_count.to_s + " users)" %>
<% #users.each do |users| %>
<%= render 'users/user', :user => users %>
<% end %>
If you have lazy loading disabled, then the second approach would be faster because Rails doesn't need to fetch all records from the database. You should really fetch only the records you need when performing queries.
If you have lazy loading enabled (by default), then it is the same, because the data is fetched when it is needed, so the effect will be the same. You can also put two variables in controller and write the same query as you did in the view and the data will be fetched only if and when it is needed.
#users = User.confirmed.joins(:profile)
#users_count = #users.count
#users = #users.limit(10)
You can check sql generated by the app in your rails console and then decide.
Also, if you are using profile in user.html.erb, consider using includes instead of join. Join can cause n+1 problem if you need associated records. If you don't, you do not want to fetch records you don't need. You can read more about it here, in 12 Eager Loading Associations.
The two options are exactly the same. Neither of them loads all the Users because you're just chaining scopes. The query is only run when you call .each in the view, at which point you've applied the .limit(10) anyway. I'd go with the first option because the code is cleaner.
#users.count does one query to get the count, it doesn't instantiate any User objects.
#users.limit(10).each ... does one query (actually two because you've used includes) with a limit, so it will instantiate 10 objects plus your includes.
you can try #users.find_in_batches
Please take a look
Find in batches
Please let me know
If you want speed loading
I can suggest you memcache Memcache

Resources