code tunes

Web applications, software engineering, Ruby on Rails, Cake PHP, JavaScript, etc.

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.

no comments yet

Written by Michał Szajbe

July 30th, 2008 at 12:34 pm

“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).

1 comment

Written by Michał Szajbe

July 26th, 2008 at 4:06 pm

Welcome to Code Tunes

Developing for web is fun! blog has been renamed to Code Tunes and moved to a new domain codetunes.com. Please update your RSS subscriptions to follow the new feed.

Thanks for reading and stay tuned for more. New stuff is coming.

no comments yet

Written by Michał Szajbe

July 23rd, 2008 at 12:33 am

Posted in Blog

Tagged with

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.

3 comments

Written by Michał Szajbe

July 20th, 2008 at 4:43 pm

Posted in Ruby on Rails

Tagged with , , ,

Migration to CakePHP RC2

So far one of my app was running on 1.2.0.6311 version released on 2 Jan 2008. It worked fine, and newer version could break something, so I didn’t bother to upgrade. However now, when new functionalities are to be implemented, I finally decided to migrate (to 1.2.0.7296 RC2). Of course some things stopped working as I expected, so I decided to list them here for others.

  • custom validation methods defined in a model must be public now
  • App::Import changed again, so my post about switching from vendors() to App::Import from March 2008 is not actual anymore
  • structure of core.php file was changed a little, so configuring the file from scratch would be a good idea
  • some ‘deprecated’ warnings, easy to correct

Well that is not much, even for such a quite simple application. Honestly, I expected to face more problems. Hopefully they don’t remain undiscovered.

no comments yet

Written by Michał Szajbe

July 11th, 2008 at 1:09 am

Posted in CakePHP

Tagged with ,

Not so obvious behavior of $uses in CakePHP

In CakePHP you declare what models you’re going to use in a controller by defining $uses variable. You can also define it in AppController to have a certain set of models available in every controller. However be careful when doing it because you may put yourself in an unexpected situation as I did recently.

I need an access to User model in beforeFilter and beforeRender actions on every request so I defined those actions in AppController along with:

$uses = array('User');

When I did this I found out that my SessionsController which handles log in and log out logic stopped working immediately. There is no Session model in the app, SessionController works with User model instead:

$uses = array('User');

The error said: “Database table sessions for model Session was not found”. So it tried to find Session model which doesn’t exist as I stated earlier. But why? I have not told Cake to include anything but User model.

To see what exactly has happened we need to look how Cake determines which models it needs to include and it’s done by merging AppController’s $uses with SessionController’s $uses. Merging is done in usual intuitive way with one exception: when SessionController’s $uses is equal to AppController’s $uses then SessionController’s “native” model (Session model here) is also loaded.

Do you have a rational explanation for such behavior? I have none. It’s very unintuitive for me.

Note that the equality of both controllers’ $uses can happen in two situations:

  • both controllers have $uses defined explicitely and they’re equal
  • SessionController doesn’t have $uses defined at all and derives it from AppController (parent)

When SessionController is (===) null or false no models are loaded at all, no mather what’s been defined in AppController.

no comments yet

Written by Michał Szajbe

July 7th, 2008 at 6:11 pm

Posted in CakePHP

Tagged with ,