In this iteration we’ll learn how to take advantage of the many plugins and libraries available to quickly add features to your application. First we’ll work with paperclip, a library that manages file attachments and uploading.
Rails plugins have traditionally been distributed a zip or tar files that got stored into your application’s file structure. One advantage of this method is that the plugin could be easily checked into your source control system along with everything you wrote in the app. The disadvantage is that it made upgrading to newer versions of the plugin, and dealing with the versions at all, complicated.
Most Rails plugins are now moving toward RubyGems. RubyGems is a package management system for Ruby, similar to how Linux distributions use Apt or RPM. There are central servers that host libraries, and we can install those libraries on our machine with a single command. RubyGems takes care of any dependencies, allows us to pick an options if necessary, and installs the library.
Let’s see it in action. If you have your server running in RubyMine, click the red square button to STOP it. If you have a console session open, type exit to exit. Then open up /config/environment.rb and look for the lines like this:
# Specify gems that this application depends on and have them installed with rake gems:install # config.gem "bj" # config.gem "hpricot", :version => '0.6', :source => "http://code.whytheluckystiff.net"
This lines are commented out because they start with the # character. By specifying a RubyGem with the config.gem command, we’ll tell the Rails application “Make sure this gem is loaded when you start up. If it isn’t available, freak out!” Here’s how we’ll require the paperclip gem, add this near those commented lines:
config.gem "paperclip"
When you’re writing a production application, you might specify additional parameters that require a specific version or a custom source for the library. With that config line declared, click the green arrow in RubyMine to startup your server. You should get an error like this:
Missing these required gems: paperclip You're running: ruby 1.8.7.72 at /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/ruby rubygems 1.3.5 at /Users/jcasimir/.gem/ruby/1.8, /Library/Ruby/Gems/1.8, /System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8 Run `rake gems:install` to install the missing gems.
The last line is key — since our config file is specifying which gems it needs, the rake command can help us install those gems. Go to your terminal and enter the command below (OS X or Linux note: add sudo to the beginning):
rake gems:install
It should then install the paperclip RubyGem with a version like 2.3. In some projects I work on, the config file specifies upwards of 12 gems. With that one rake command the app will check that all required gems are installed with the right version, and if not, install them.
Now we can start using the library in our application!
We want to add images to our articles. To keep it simple, we’ll say that a single article could have zero or one images. In later versions of the app maybe we’d add the ability to upload multiple images and appear at different places in the article, but for now the one will show us how to work with paperclip.
First we need to add some fields to the Article model that will hold the information about the uploaded image. Any time we want to make a change to the database we’ll need a migration. Go to your terminal and execute this:
ruby script/generate migration add_paperclip_fields_to_article
That will create a file in your /db/migrate/ folder that ends in _add_paperclip_fields_to_article.rb. Open that file now.
Remember that the code inside the self.up method is to migrate the database forward, while the self.down should undo those changes. We’ll use the add_column and remove_column methods to setup the fields paperclip is expecting:
class AddPaperclipFieldsToArticle < ActiveRecord::Migration
def self.up
add_column :articles, :image_file_name, :string
add_column :articles, :image_content_type, :string
add_column :articles, :image_file_size, :integer
add_column :articles, :image_updated_at, :datetime
end
def self.down
remove_column :articles, :image_file_name
remove_column :articles, :image_content_type
remove_column :articles, :image_file_size
remove_column :articles, :image_updated_at
end
end
The go to your terminal and run rake db:migrate. The rake command should show you that the migration ran and added columns to the database.
The gem is loaded, the database is ready, but we need to tell our Rails application about the image attachment we want to add. Open /app/models/article.rb and just below the existing has_many lines, add this line:
has_attached_file :image
This has_attached_file method is part of the paperclip library. With that declaration, paperclip will understand that this model should accept a file attachment and that there are fields to store information about that file which start with image_ in this model’s database table.
First we’ll add the ability to upload the file when editing the article, then we’ll add the image display to the article show template. Open your /app/views/articles/_form.html.erb view template. We need to make two changes…
In the very first line, we need to specify that this form needs to accept “multipart” data. This is an instruction to the browser about how to submit the form. Change your top line so it looks like this:
<% form_for(@article, :html => {:multipart => true}) do |f| %>
Then further down the form, right before the paragraph with the save button, let’s add a label and field for the file uploading:
<p>
<%= f.label :image, "Attach an Image" %><br />
<%= f.file_field :image %>
</p>
If your server isn’t running, start it up with the green play button in RubyMine. Then go to http://localhost:3000/articles/ and click EDIT for your first article. The file field should show up towards the bottom. Click the Choose a File and select one of the small images that I’ve distributed to you. Click SAVE and you’ll return to the article index. Click the title of the article you just modified. What do you see? Did the image attach to the article?
When I first did this, I wasn’t sure it worked. Here’s how I checked:
ruby script/console from terminal)http://localhost:3000/articles/1 so the ID number is just 1a = Article.find(1)image_file_name and other fields, so I think it worked.a.image to see even more data about the fileOk, it’s in there, but we need it to actually show up in the article. Open the /app/views/articles/show.html.erb view template. In between the line that displays the title and the one that displays the body, let’s add this line:
<%= image_tag @article.image.url %>
Then refresh the article in your browser. Tada!
When first working with the edit form I wasn’t sure the upload was working because I expected the file_field to display the name of the file that I had already uploaded. Go back to the edit screen in your browser for the article you’ve been working with. See how it just says “Choose File, no file selected” — nothing tells the user that a file already exists for this article. Let’s add that information in now.
So open that /app/views/articles/_form.html.erb and look at the paragraph where we added the image upload field. We’ll add in some new logic that works like this:
file_field button with the label “Attach a New Image”So, turning that into code…
<p>
<% if @article.image_file_name %>
<%= image_tag @article.image.url %><br/>
<% end %>
<%= f.label :image, "Attach a New Image" %><br />
<%= f.file_field :image %>
</p>
Test how that looks both for articles that already have an image and ones that don’t.
When you “show” an article that doesn’t have an image attached it’ll have an ugly broken link. Go into your /app/views/articles/show.html.erb and add a condition like we did in the form so the image is only displayed if it actually exists.
Now our articles can have an image and all the hard work was handled by paperclip!
Yes, a model (in our case an article) could have many attachments instead of just one. To accomplish this you’d create a new model, let’s call it “Attachment”, where each instance of the model can have one file using the same fields we put into Article above as well as an article_id field. The Attachment would then belong_to an article, and an article would have_many attachments.
Paperclip supports automatic image resizing and it’s easy. In your model, you’d add an option like this:
as_attached_file :image, :styles => { :medium => "300x300>", :thumb => "100x100>" }
This would automatically create a “medium” size where the largest dimension is 300 pixels and a “thumb” size where the largest dimension is 100 pixels. Then in your view, to display a specific version, you just pass in an extra parameter like this:
<%= image_tag @article.image.url(:medium) %>
If it’s so easy, why don’t we do it right now? The catch is that paperclip doesn’t do the image manipulation itself, it relies on a package called imagemagick. Image processing libraries like this are notoriously difficult to install. If you’re on Linux, it might be as simple as sudo apt-get install imagemagick. On OS X, if you have Mac Ports installed, it’d just be sudo port install imagemagick. On windows you need to download an copy some EXEs and DLLs. It can be a hassle, which is why we won’t do it during this class. If you’d like to check it out, try…
sudo port install imagemagickAnother plugin that I use in every project is actually two libraries in one. HAML is an alternative templating style to the default ERB (which you’ve been using, hence all the view templates ending in .erb). SASS is a library for writing CSS and it makes CSS much, much easier to work with.
Open your /config/environment.rb and add a config.gem for the gem haml. Go to your terminal and rake gems:install and it should pull down the gem library for you. Stop (with the red square) then restart (green play button) your server within RubyMine. Both HAML and SASS are installed and ready to use.
Look in RubyMine’s left navigation pane for the folder /public/stylesheets/. Right click on this folder, click NEW, then DIRECTORY, and name it sass. Then right click on the sass folder, click NEW, FILE, then enter the name styles.sass
All the details about Sass can be found here: http://sass-lang.com/
We’re not focusing on CSS development, so here are a few styles that you can copy & paste and modify to your heart’s content:
!primary_color = #AAA
body
:background-color = !primary_color
:font
:family Verdana, Helvetica, Arial
:size 14px
a
:color #0000FF
img
:border none
.clear
:clear both
:height 0
:overflow hidden
#container
:width 75%
:margin 0 auto
:background #fff
:padding 20px 40px
:border solid 1px black
:margin-top 20px
#content
:clear both
:padding-top 20px
But our application isn’t setup to load that stylesheet yet. We need to make a change to our view templates.
We’ve created about a dozen view templates between our different models. We could go into each of those templates and add a line like this at the top:
<%= stylesheet_link_tag 'styles' %>
Which would find the Sass file we just wrote. That’s a lame job, imagine if we had 100 view templates. What if we want to change the name of the stylesheet later? Ugh.
Rails and Ruby both emphasize the idea of “D.R.Y.” — Don’t Repeat Yourself. In the area of view templates, we can achieve this by creating a layout. A layout is a special view template that wraps other views. Look in your navigation pane for /app/views/layouts/, right click on that folder, click NEW and FILE then give it the name application.html.haml.
In this layout we’ll put the view code that we want to render for every view template in the application. Just so you can see what HAML looks like, I’ve used it to implement this layout. You’ll notice that HAML uses fewer marking characters than ERB, but you must maintain the proper whitespace/indentation. All indentations are two spaces from the containing element. Add this code to your application.html.haml:
!!! Strict
%html
%head
%title
JSBlogger
= stylesheet_link_tag 'styles'
%body
#container
#content
= yield
Now refresh your article listing page and you should see the styles take effect. Whatever code is in the individual view template gets inserted into the layout where you see the yield. Using layouts makes it easy to add site-wide elements like navigation, sidebars, and so forth.
NOTE: If you don’t see any change, look at your server log in RubyMine to see if there were any errors. At first I had a typo in one of the filenames so it wasn’t being picked up properly. You might also need to stop & restart your server if you didn’t do that after installing the haml gem.
Now that you’ve tried out three plugin libraries (Paperclip, HAML, and SASS), Iteration 4 is complete!