How to install Paperclip in a Rails 3 app

Update June 2010: I just heard from Jon Yurek in the comments below that he has, in fact, finished up the Rails 3 changes for Paperclip. This means that you can now just install Paperclip as usual in a Rails 3 app as a plugin:

$ rails plugin install git://github.com/thoughtbot/paperclip.git

... or as a gem by adding it to your Gemfile if you’ve already installed it with “gem install paperclip:”

source 'http://rubygems.org'

gem 'rails', '3.0.0.beta3'

# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'

gem 'paperclip'

I’ll leave my original article here as a reference – it was a fun learning experience trying out Paperclip with Rails 3, and the same ideas around Bundler, generators, etc., might still be helpful while using other gems or plugins with Rails 3.


 

To get Paperclip to work in a Rails 3 application, use this in your Gemfile:

gem 'paperclip', :git => 'git://github.com/thoughtbot/paperclip.git',
                 :branch => 'rails3'

… and this in application.rb:

module YourPaperclipApp
  class Application < Rails::Application

Paperclip::Railtie.insert
etc... end end

Right now it looks like Thoughtbot is finishing Rails 3 related changes in a “rails3” branch in their Paperclip github repository. The best thing to do if you have a Paperclip app you want to migrate to Rails 3 is simply to wait a bit longer for them to finish that work, test it and merge it back into the master branch.

The rest of this article is really not about Paperclip at all, but about Rails 3. Here’s what I learned about Rails 3 while troubleshooting Paperclip:

  1. The command line has changed
  2. Plugin generators have moved
  3. Rails 2.x generators don’t work at all
  4. You use Bundler and a “Gemfile” to declare gems
  5. You can install a gem from a specific git repository branch
  6. Rails 3 frameworks are now based on Rails::Railtie
  7. Bundler does not call rails/init.rb in each gem
Let’s go ahead and troubleshoot Paperclip together in a new Rails 3 app. First I’ll begin by verifying the versions of Ruby and Rails I’m using now:

$ ruby -v
ruby 1.8.7 (2010-01-10 patchlevel 249) [i686-darwin9.8.0]
$ rails -v
Rails 3.0.0.beta3

And next I’ll create a sample app to use with Paperclip:

$ rails new paperclip-sample-app
      create  
      create  README
      create  .gitignore
      create  Rakefile
      create  config.ru
      create  Gemfile
      create  app
      create  app/controllers/application_controller.rb
etc…

Now we’re ready to install Paperclip into my new app. But what should I do exactly? Should I use Paperclip as a plugin or a gem? I wasn’t sure what to do, so I simply tried both.

Fact 1: the command line has changed

First let’s install it as a plugin, since that’s the most straightforward. In Rails 3 the plugin install command has changed a bit vs. Rails 2.x:

$ cd paperclip-sample-app
$ rails plugin install git://github.com/thoughtbot/paperclip.git
Initialized empty Git repository in .../vendor/plugins/paperclip/.git/
remote: Counting objects: 77, done.
remote: Compressing objects: 100% (68/68), done.
remote: Total 77 (delta 12), reused 20 (delta 0)
Unpacking objects: 100% (77/77), done.
From git://github.com/thoughtbot/paperclip
 * branch            HEAD       -> FETCH_HEAD

Next let’s use scaffolding to create a “User” model with a couple of attributes:

$ rails generate scaffold user name:string email:string
      invoke  active_record
      create    db/migrate/20100521034815_create_users.rb
      create    app/models/user.rb
      invoke    test_unit
      create      test/unit/user_test.rb
      create      test/fixtures/users.yml
       route  resources :users
      invoke  scaffold_controller
      create    app/controllers/users_controller.rb
      invoke    erb
      create      app/views/users
      create      app/views/users/index.html.erb
etc…
$ rake db:migrate

Fact 2: plugin generators have moved

The next step is to create a second migration for the additional database columns required by Paperclip. To make this easy, Paperclip provides a “paperclip” generator; let’s try that and specify that we want an “avatar” file attachment saved on the user model:

$ rails generate paperclip user avatar
Could not find generator paperclip.

Uhh… not what I expected. It looks like something has changed about Rails 3 generators that has broken the Paperclip generator. For now, let’s take a look at the Paperclip code to see if we can find the generator:

$ find vendor/plugins/paperclip -name *generator*
vendor/plugins/paperclip/generators
vendor/plugins/paperclip/generators/paperclip/paperclip_generator.rb

There it is… After some research, I found out that for Rails 3, plugin/gem generators need to be located inside a folder called “BASE_DIR/lib/generators” – we can see here that the Paperclip generator needs to be moved in order to comply with this new standard.

Fact 3: Rails 2.x generators don’t work at all

So let’s try just moving it and see what happens:

mv vendor/plugins/paperclip/generators vendor/plugins/paperclip/lib/.
$ rails generate paperclip user avatar
[WARNING] Could not load generator "generators/paperclip/paperclip_generator"
because it's a Rails 2.x generator, which is not supported anymore.
Error: uninitialized constant Rails::Generator.

Things are looking worse and worse! It turns out that the generators architecture for Rails 3 has been completely rewritten, and that generators written for Rails 2.x will simply not work at all in Rails 3. What to do now? Of course, I could simply hand code the migration for adding the avatar columns to the users table, and continue to work on my sample application. Instead, I decided to give up on the plugin entirely and to try using Paperclip as a gem.

Fact 4: You use Bundler and a “Gemfile” to declare gems

Let’s take a look at how gems are installed for a Rails 3 app. Rails 3 uses a new file called the “Gemfile,” which specifies which gems should be included in your application. This file is read and used by Bundler, which manages gems and their dependencies. We can specify that our application uses the Paperclip gem by adding a single line to the Gemfile like this:

source 'http://rubygems.org'
gem 'rails', '3.0.0.beta3'

# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'

gem 'paperclip'
etc…

This simply tells Bundler to install Paperclip from your default gem source: probably rubygems.org. Now I’ll delete the plugin I installed earlier and install the gem, using the “bundle install” command to install all of the gems in my Gemfile:

$ rm -rf vendor/plugins/paperclip/
$ bundle install
Fetching source index from http://rubygems.org/
Using rake (0.8.7) from system gems 
Using abstract (1.0.0) from bundler gems 

etc…

Installing paperclip (2.3.1.1) from rubygems repository at http://rubygems.org/ 

etc…

Bundler indicated that it found the official version of Paperclip on rubygems.org, downloaded and installed it. It also told us we have version 2.3.1.1. Let’s use the bundle show command and take a look at where Paperclip was installed to:

$ bundle show paperclip
  /Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/paperclip-2.3.1.1

Bundler simply installed the gem in the standard location where all my other gems are located for my RVM version of Ruby 1.8.7, just as if I had run a gem install command manually. Now let’s try that generate command again and see if it works any better:

$ rails generate paperclip user avatar
DEPRECATION WARNING: RAILS_ROOT is deprecated! Use Rails.root instead. (called from expand_path at /Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/paperclip-2.3.1.1/lib/paperclip.rb:39)
/Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/paperclip-2.3.1.1/lib/paperclip.rb:39:in `expand_path': can't convert # into String (TypeError)
    from /Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/paperclip-2.3.1.1/lib/paperclip.rb:39
    from /Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/bundler-0.9.25/lib/bundler/runtime.rb:46:in `require'
    from /Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/bundler-0.9.25/lib/bundler/runtime.rb:46:in `require'
    from /Users/pat/.rvm/gems/ruby-1.8.7-p249/gems/bundler-0.9.25/lib/bundler/runtime.rb:41:in `each'

Still broken! It looks like I’m just not running code that was intended to be used with Rails 3.

Fact 5: You can install a gem from a specific git repository branch

After more investigation, I noticed that there had been a lot of recent changes to the Paperclip github repository. At the time I wrote this, Thoughtbot was actively developing on a branch called “rails3.” I decided the best thing to do would be to try the code from the rails3 branch, hoping it might work better for me. Bundler makes this easy, since you can just specify git as a source for downloading a gem using a “git” option, as well as optionally a specific branch using a “branch” option, like this:

source 'http://rubygems.org'

gem 'rails', '3.0.0.beta3'

# Bundle edge Rails instead:
# gem 'rails', :git => 'git://github.com/rails/rails.git'

gem 'paperclip', :git => 'git://github.com/thoughtbot/paperclip.git', :branch => 'rails3'
etc

After saving this change to Gemfile, let’s re-run the bundle install command:

$ bundle install
Updating git://github.com/thoughtbot/paperclip.git
Fetching source index from http://rubygems.org/
Updating git://github.com/thoughtbot/paperclip.git
Using rake (0.8.7) from system gems 
Using abstract (1.0.0) from bundler gems 

…etc…

Using paperclip (2.3.2.beta1)
  from git://github.com/thoughtbot/paperclip.git (at rails3)

Hmm… interesting. Bundler is showing that it’s downloaded Paperclip from the github repository, and that it got the code at the head of the rails3 branch. Another interesting detail here is that I apparently now have version “2.3.2 beta1” of Paperclip. This is a good sign, since I have a more recent version than 2.3.1.1 (the rubygems.org version) and also it seems that Thoughtbot is actively working on it since it’s labeled “beta1.”

If we run bundle show again, we can see that Bundler has saved a special copy of Paperclip downloaded from github, along with the git commit id and branch of the version I have:

$ bundle show paperclip
      /Users/pat/.rvm/gems/ruby-1.8.7-p249/bundler/gems/paperclip-61f74de14812cabc026967a2b2c3ca8cbd2eed69-rails3

Now let’s try that generator once more:

$ rails generate paperclip user avatar
            create  db/migrate/20100521003113_add_attachment_avatar_to_user.rb

Yes! It’s working now!

Fact 6: Rails 3 frameworks are now based on Rails::Railtie

Let’s continue to put together my sample application by running the migration:

$ rake db:migrate

… and by editing my User model to call “has_attached_file:”

class User < ActiveRecord::Base
  has_attached_file :avatar
end

Before I start editing my views and adding the code to upload and display the avatar attachment, let’s start the server and see if Paperclip is working. Opening the users index page I get…

… more trouble! I’m definitely having a bad day… what now? Well it seems that Paperclip is just not being loaded at all, or is being initialized improperly for some reason. At this point I started to poke around the Paperclip source code a bit, and found that the code that includes the Paperclip module into ActiveRecord::Base was moved and is no longer being called. Since Paperclip is not included in my User/ActiveRecord class I get the error has_attached_file not defined, since that’s defined by Paperclip.

I found the include code in a file called “lib/paperclip/railtie.rb:”

require 'paperclip'

module Paperclip
  if defined? Rails::Railtie
    require 'rails'
    class Railtie < Rails::Railtie
      config.after_initialize do
        Paperclip::Railtie.insert
      end
    end
  end

  class Railtie
    def self.insert
      ActiveRecord::Base.send(:include, Paperclip)
      File.send(:include, Paperclip::Upfile)
    end
  end
end

I’m not quite sure what Thoughtbot’s plans are for Paperclip, but if you take some time to read through Yehuda Katz’s write up Rails and Merb Merge: Rails Core (Part 4 of 6), you’ll learn about how Rails frameworks like ActiveRecord and ActiveController have been recast as instances of this “Rails::Railtie” class. Possibly Paperclip will become one of these. Rails 3 has a new API for declaring how Railties are loaded and initialized, but it looks like this version of Paperclip and this version of Rails aren’t quite working correctly now.

Fact 7: Bundler does not call rails/init.rb in each gem

For now, the problem I’m having in my sample application is that the Paperclip::Railtie.insert method is not being called – the two lines I highlighted above need to be executed in order to enable “has_attached_file” to be present as a class method for ActiveRecord models. To make things more interesting, Thoughtbot did include a call to insert inside rails/init.rb, like this:

require 'paperclip/railtie'
Paperclip::Railtie.insert

… but for Rails 3, it turns out that Bundler no longer calls rails/init.rb.

Moving this line instead to config/application.rb will solve the problem:

module PaperclipSampleApp
  class Application < Rails::Application

    Paperclip::Railtie.insert

    etc…
    
  end
end

Alternatively, you could just create a file called “config/initializers/paperclip.rb” and put the call to insert there.

Now reloading the users index page we finally get Paperclip to work:

Instead of proceeding with my sample app now, I’m going to wait a few weeks while Thoughtbot finishes off the Rails 3 changes for Paperclip.

I don’t think troubleshooting these problems was a waste of time at all; in fact, it was a good excuse to get my hands dirty with Rails 3 and Bundler. Once Thoughtbot has finished their changes in the rails3 branch and merged them into the master I’ll update my tutorial from last year, and also update my Paperclip fork to support database BLOB storage for Rails 3.