Archive for the ‘Ruby on Rails’ Category
Uploading to multiple S3 buckets with Paperclip and Rails
When your application uses many static files (photos for example), you should consider placing these files on different hosts to improve the speed at which they are downloaded by web browsers.
Most web browsers have a limit on how many simultaneous connections can be made to a single named host. And this limit is usually two. It means that if you let’s say display many photos on a single page, user’s browser can only download two at a time. The broadband internet connection will be no help here. The browser will not fully use it, because it will keep opening and closing connections.
With little server and Rails configuration you pretend to be serving your files from different hosts and trick the browser (read more). This is easy if you keep all the files on your own server, but when you use Paperclip to upload files to Amazon’s S3 servers it gets more trickier. In fact Paperclip doesn’t support uploading to different hosts (buckets).
First, notice that these addresses means the same for Amazon’s S3:
- http://s3.amazonaws.com/bucket_name/filename.ext
- http://bucket_name.s3.amazonaws.com/filename.ext
So all we need to do is to make Paperclip upload to different buckets and return attachment’s url of the latter type. I wrote an extension that accomplishes this and included it in PaperclipExtended. All you need to do is download both Paperclip and PaperclipExtended and change your model definition.
class User < ActiveRecord::Base has_attached_file :avatar, :storage => :s3, :s3_credentials => "#{RAILS_ROOT}/config/s3.yml", :path => "avatars/:id/:style_:extension", :bucket => lambda do |attachment| i = attachment.instance.id % 4 "bucket_#{i}" end end
This will place each avatar in one of four buckets: bucket_0, bucket_1, bucket_2 or bucket_3. The exact bucket is chosen at runtime and in this case it’s based on models id.
Getting attachment’s url:
puts User.find(1).avatar.url(:original) # => http://bucket_1.s3.amazonaws.com/avatars/1/original.jpg
If you have a page on your website that displays many uploaded photos, placing them in different buckets should make noticeable difference to user - they will load faster.
Custom thumbnail generation with Paperclip
Paperclip is a great plugin for Ruby on Rails which eases the pain of image upload and resize process. The usage is very simple, results are what you want in most cases. Not in all cases though.
Your role is only to define the sizes of thumbnails that will be generated from original image. Thumnailing in is fact done by calling ImageMagick’s convert command with -scale argument, for example convert -scale ‘640×480>’.
You can alter how exactly the image is scaled by appending modifiers to desired thumbnail size, like ‘>’ in ‘640×480>, however it won’t give you unlimited power over thumbnailing process.
Consider following example. I have a small image (120×120).

Scaling it to smaller size gives me the result I want. But when the result image needs to be bigger than original, the original image is enlarged in a way that you’ll see single pixels.
![]()
What I want to achieve is the enlargment done not by scaling the original image, but by adding a border around it.

This cannot be achieved by adding any of modifiers to desired thumbnail size, so why not to pass additional arguments to convert command? Yes, that would be a solution.
And here’s my solution - PaperclipExtended. It’s a plugin that modifies original Paperclip, so that now it accepts additional (optional) parameter :commands when defining the thumbnails sizes.
class User < ActiveRecord::Base has_attached_file :avatar, :styles => { :medium => "300x300>", :small => "100x100>" }, :commands => { :medium => "-background white -gravity center -extent 300x300 +repage" } end
During thumbnail generation Paperclip will now append given commands to convert command. Convert command for medium style will be now:
convert -scale ‘300×300>’ -background white -gravity center -extent 300×300 +repage
Convert command for small style will remain unchanged.
Passing such commands will help me with the enlargment I mentioned above. For what else could be done check the documentation of ImageMagick’s command line options.
PaperclipExtended is not a replacement for Paperclip, just an extension. It works with version 2.1.2 of Paperclip plugin (the current one and the only one I tested). Compatibility with future version is not guaranteed.
I put a plugin on GitHub.
http://github.com/netguru/paperclip-extended
How to install:
script/plugin install git://github.com/netguru/paperclip-extended.git
Configuration is the same as of original Paperclip plugin, but you can now add an optional :commands parameter.
Hope it’ll be helpful!
If you have any questions, post them in comments to this post.
“Send this page to friend” with polymorphic controller in Rails
Last week I wrote about Tableless models in Ruby on Rails, giving an example of Recommendation model which could be used to validate users’ recommendations. Today I will extend that example so that it will be more complete.
What the mentioned solution lacks is a controller handling recommendation actions. Let’s first make Article and Photo models recoommendable ones by defining their associations:
has_many :recommendations, :as => :recommendable
The models are ready, so let’s create a controller.
class RecommendationsController < ApplicationController def new if !params[:photo_id].nil? @recommedable = Photo.find params[:photo_id] elsif !params[:article_id].nil? @recommedable = Article.find params[:article_id] end raise ActiveRecord::RecordNotFound if @recommendable.nil? end def create @recommendable = Recommendable.new :params[:recommendation] if @recommendable.valid? # send it via e-mail flash[:notice] = 'Your recommendations has been processed' redirect_to @recommendation.recommendable else render :action => :new end end end
We’re almost there, but there’s more Rails can do for us here. Let’s define some new routes.
# config/routes.rb map.resources :recommendations map.resources :articles do |a| a.resources :recommendations end map.resources :photos do |p| p.resources :recommendations end
Now we’re able to use those pretty helpers in our views that will generate nice-looking URLs (like: article/1/recommendation/new).
<%= link_to 'recommend this article', new_article_recommendation_path(@article) %>
The link above will take us to the ‘new’ action of RecommendationsController. The last thing we need to do is to render the form, so that user can comment his recommendation.
<% form_for @recommendable.recommendations.build do |f| %> <%= f.hidden_field :recommendable_type, :value => @recommendable.class %> <%= f.hidden_field :recommendable_id, :value => @recommendable.id %> <%= f.text_field :email %> <%= f.text_area :body %> <%= f.submit 'Do it!' %> <% end %>
There are many more cases of use polymorphic controllers (with normal or tableless models) than sending recommendations. Commenting, ranking, abuse-reporting, tagging… The rule is that if you have a model that is associated to other models via polymorphic association, you should consider using polymorpic controller to manage it. Not only Rails will do much work for you behind the scenes, but the structure of your application will stay clear both inside (reusable code) and outside (nice links).
Tableless models in Rails
If you’re developing an application in Ruby on Rails framework, there are many situations when you should consider using tableless models. The advantages you’d get are:
- consistent application structure, because you’re using models to represent objects in your app
- routes available for free if you also define controllers dedicated for those models, resulting in RESTful application
- easy validation (just like with normal models) and other goodies shipped with Active Record
- easier testing with unit tests
- form building as easy as with normal models
If you’re familiar with “fat model, skinny controller” concept you’ll find more reasons.
An example situation: you give the users an ability to recommends object they find on the site to their friends. They can recommend photos and articles. You do not want to track those recommendations in database. Your model file would look like this.
class Recommendation < ActiveRecord::Base def self.columns() @columns ||= []; end def self.column(name, sql_type = nil, default = nil, null = true) columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null) end column :recommendable_type, :string column :recommendable_id, :integer column :email, :string column :body, :text belongs_to :recommendable, :polymorphic => true validates_presence_of :recommendable validates_associated :recommendable validates_format_of :email, :with => /^$|^\S+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,4}|[0-9]{1,4})(\]?)$/ix validates_presence_of :body end
As you can see the only difference in model definition is that you need to provide the columns yourself.
The idea was borrowed from this snippet.
In the next article I’ll cover polymorphic controllers which will help to convert above example into complete one.
Formatting dates for SQL queries in Rails
Here’s a simple and clean way to format date for use in SQL query in Ruby On Rails.
User.find :all, :conditions => ['created_at > ?', 7.days.ago.to_formatted_s(:db)]
You can use to_formatted_s(format) method with different data types - Date, Time, DateTime, Range, Array and BigDecimal. There are also more formats available apart from :db. Find out more in Rails API.
