I'm using the following low-level caching for the five most recent news articles in my Rails application:
#recent_news = Rails.cache.fetch("recent_news", :expires_in => 1.hour) do
News.order("created_at desc").limit(5)
end
Is there a way to keep this query cached until a new news article is created? I was thinking of manually expiring the cache using an observer, but wasn't sure if there was a way to actually do this, e.g:
class NewsObserver < ActiveRecord::Observer
def after_create
#expire recent_news cache
end
end
You can manually expire the cache using the .delete method:
Rails.cache.delete("recent_news")
Related
I'm working on a small app that uses CarrierWave for image uploading, but (for reasons that are a bit long and not really relevant) we have to stop using CarrierWave and start uploading images with our own custom requests to different cloud storage services.
But I still need to have the URLs to images that were already uploaded using CarrierWave, and I won't have access to the image_url method once I unmount the CarrierWave uploader, so I want to save all the urls to a new column on my model, and to have that migration be as optimized as possible.
I've searched for ways to access directly the url from the database (I mean, CarrierWave has to store that somewhere) but no luck so far.
I could do something like this (business_image is the column that the uploader is mounted on, image_url is the new column where I want to store the url):
def change
add_column :business_cards, :image_url, :string
BusinessCard.all.each do |bc|
bc.update(image_url: bc.business_image_url)
end
end
But that is obviously not optimized at all since it loads all business cards and updates them one by one. Is there a way to do it in a single SQL statement?
(I'm using Rails 5.1.4, PostgreSQL 10.3 and carrierWave 1.2.1)
One way would be too use batch function and update record in batches, you can use transaction so that if there are any errors the changes would be rolled back.
def change
add_column :business_cards, :image_url, :string
BusinessCard.find_each(batch_size: xxx) do |bc|
ActiveRecord::Base.transaction do
bc.update(image_url: bc.business_image_url)
rescue => e
raise ActiveRecord::Rollback
end
end
end
Hope it helps!!
Following.rb
belongs_to :show
def cached_show
Rails.cache.fetch([self, :show]) do
show
end
end
View:
<% #recently_favorited.each do |following| %>
<li>
<%= link_to "#{following.cached_show.name}", show_path(:permalink => following.cached_show.permalink) %> <span><%= "(#{pluralize(following.cached_show.followers, "follower")})" %></span>
</li>
<% end %>
Result in the console:
Cache read: followings/632770-20120929132253/show
Cache generate: followings/632770-20120929132253/show
Show Load (0.7ms) SELECT `shows`.* FROM `shows`WHERE `shows`.`id` = 617 LIMIT 1
Cache write: followings/632770-20120929132253/show
Cache read: followings/632770-20120929132253/show
Cache fetch_hit: followings/632770-20120929132253/show
Cache read: followings/632770-20120929132253/show
Cache fetch_hit: followings/632770-20120929132253/show
Question:
Is this even a "correct" implementation of fetching/caching an association?
And what about performance?
In some views (as in the example) it will hit the cache 3 times per loop. In my case I'm looping 10 items in the footer, so it will make 30 hits on every request. Is this fine, or will a single n+1 query per loop be better?
Advise and general best practices appreciated :)
Creating a distinct method to hit the cache vs getting it fresh is not common from what I can tell.
Most of the time, you'd just call a method that asks the cache all the time, since if you include an object in the cache-key, the updated_at field is used to build the key.
For your example now, the weird part is that you don't actually do anything with the Following model apart accessing its association. Therefore, you should query directly on the Show model :
#recently_favorited_shows = Show.joins(:followings).order("followings.created_at DESC").uniq
Then in your view, loop on the shows. Only one query, no n+1
If you expect thousands of hits then, I'd just suggest to cache the result of #recently_favorited_shows and expire it every X minutes :
#recently_favorited_shows = cache_store.fetch('recently_favorited_shows', expires_in: 5.minutes){Show.joins(:followings).order("followings.created_at DESC").uniq}
On another note, here's a good write-up on cache usage on the view side if you want to do it some time: http://37signals.com/svn/posts/3113-how-key-based-cache-expiration-works
No joins solution
Edit : now, if you have gazillions of rows in followings table, here's what I'd do :
Create a field last_followed_at on the shows table, with an index on it
In Following.rb : belongs_to :show, touch: :last_followed_at. This way, as soon as you add a new entry in Following, it'll update the field on the shows table
Then, to get the latest followed shows, do :
#shows = Show.order("last_followed_at DESC").limit(10) # Fast query of course
This doesn't answer my question, but it solves my problem. Here's how I'll do it instead:
#shows = Rails.cache.fetch("recently_favorited_shows", expires_in: 1.minutes) do
Show.find(Following.order("created_at DESC").limit(10).collect(&:show_id))
end
The queries are pretty fast (~0.8ms each says the IRB console)
I'm currently rendering a JQuery Plugin on one of my partials in my Application layout. This partial calls one my application helper method which retrieves data generated using Sunspot-solr. The data it's retrieving is the top 30 recently created records created across various models, Uploads, Users, Articles etc. I was wondering what the best practice would be for loading this data if it appears on every view in my application?
Obviously performance is my biggest concern.
This is how I'm currently retrieving the data in my Application helper:
def get_updates
#updates = Sunspot.search(Upload,Help,User) do
order_by(:created_at, :desc)
paginate page: 1, per_page: 30
end
#updates = #updates.results
end
You could fetch that data in a before_filter in your ApplicationController. Then it would be available to any view rendered in any controller throughout the application.
pseudo code:
class ApplicationController
before_filter :load_top_30
private
def load_top_30
#top_30 = Records.find() # whatever code you need to fetch the records
end
end
Now you can use #top_30 in any view. The helper does not (and arguably should not) need to fetch the data since its already in an instance variable now, just pass it in.
If this is only this one item you wish to display globally then doing it in the ApplicationController should be fine, if you have many such elements then you might want to have a look at Cells which basically are reusable sub-controllers which you can call form your views or controllers.
What I want to do is basically have a user obtain the lock on a record and have it for a specific amount of time so they can make changes to it, like wikipedia. So lets say a wikipedia article gives the user an hour to edit it before other users may edit it.
How could I achieve that with Rails 3? I have read up and found that pessimistic locking is what I should use for the lock. Given that... What kind of mechanism would I use for releasing the lock say after an hour?
My stack is Rails 3, Heroku, PostgreSQL.
Thanks for any answers and I love to see code if you can that would be so awesome!
Here's an example that creates locks, but doesn't delete them.
I leave that up to you.
The locks do expire after an hour in this example, but to complete the app they should automatically be deleted on a successful update of a post.
working example
or read the
relevant commit
You can do this with acts_as_lockable_by gem.
Imagine you have a patient (ActiveRecord) class that can only be edited by one user and it should be locked to this user till he decides to release it:
class Patient < ApplicationRecord
acts_as_lockable_by :id, ttl: 30.seconds
end
Then you can do this in your controller:
class PatientsController < ApplicationController
def edit
if patient.lock(current_user.id)
# It will be locked for 30 seconds for the current user
# You will need to renew the lock by calling /patients/:id/renew_lock
else
# Could not lock the patient record which means it is already locked by another user
end
end
def renew_lock
if patient.renew_lock(current_user.id)
# lock renewed return 200
else
# could not renew the lock, it might be already released
end
end
private
def patient
#patient ||= Patient.find(params[:id])
end
end
Add a field called "editable_until":datetime and set a specific date (Time.now + 30.min f.e.) when creating your record. And simply query this field to find out if the user has the right to update the record or not.
class Post << AR
before_validation :set_editable_time
validate :is_editable
def editable?
self.editable_until.nil? || self.editable_until >= Time.now
end
protected
def is_editable
self.errors[:editable_until] << "cannot be edited anymore" unless editable?
end
def set_editable_time
self.editable_until ||= Time.now + 30.min
end
end
Post.create(:params....)
=> <Post, ID:1, :editable_until => "2011-10-13 15:00:00">
Post.first.editable?
=> true
sleep 1.hour
Post.first.editable?
=> false
I have a table that I'd like to keep pruned to the 500 most recent rows. What's the most efficient way to do this in rails?
One way to do it:
class MyModel
after_create do
self.class.prune(500)
end
def self.prune(max)
if count > max
order('created_at DESC').offset(max).each do |model|
model.destroy
end
end
end
end
The prune class method could also be added to ActiveRecord::Base if you want to use that on multiple models.
This is definitely one way to do it, although someone may chime in with a more efficient way. Create a method in your controller, for this example I'll call it "prune", and call it after your create action (there may be an after_filter or something similar you can use.) It should look something like this.
def prune
if MyModel.count > 500
#models = MyModel.all(:offset => 500)
#models.each do |m|
m.destroy!
end
end
end
A basic solution would be to use the following script under a scheduling application like whenever https://github.com/javan/whenever to run the following command :
Mould.order('updated_at DESC').offset(20).each {|m| m.destroy }
Substitute Mould with the name of your model. Usage of cron and scheduling has been discussed in detail in following post : A cron job for rails: best practices?