In this tutorial 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.
Using the Gemfile to Set up a RubyGem
In the past Rails plugins were distributed in 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.
These days, all Rails plugins are now ‘gems.’ 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 any options if necessary, and installs the library.
Let’s see it in action. Go to your terminal where you have the rails server running, and type Ctrl-C
. If you have a console session open, type exit
to exit. Then open up Gemfile
and look for the lines like this:
gem 'turbolinks', '~> 5' |
Some lines are commented out because they start with the #
character. By specifying a RubyGem with the 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:
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, go back to your terminal and run rails server
to start the application again. You should get an error like this:
$ rails server |
The last line is key – since our config file is specifying which gems it needs, the bundle
command can help us install those gems. Go to your terminal and:
bundle |
Some output might like that: |
It should then install the paperclip RubyGem with a version like 5.1.0. In some projects I work on, the config file specifies upwards of 18 gems. With that one bundle
command the app will check that all required gems are installed with the right version, and if not, install them.
Note: You may need to reload your rails server for the paperclip methods to work.
Now we can start using the library in our application!
Setting up the Database for Paperclip
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:
rails 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 change
method is to migrate the database forward, and Rails should automatically figure out how to undo those changes. We’ll use the add_column
and remove_column
methods to setup the fields paperclip is expecting:
class AddPaperclipFieldsToArticle < ActiveRecord::Migration |
Then go to your terminal and run
rake db:migrate |
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.
From version 4.0, all attachments are required to include a content_type validation, a file_name validation, or to explicitly state that they’re not going to have either. Paperclip raises MissingRequiredValidatorError error if you do not do this. So, we add the validates_attachment_content_type line so that our model will validate that it is receiving a proper filetype.
We also have to deal with mass assignment! Modify your app/helpers/articles_helper.rb
and update the article_params
method to permit an :image
as:
def article_params |
Modifying the Form Template
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> |
Trying it Out
If your server isn’t running, start it up (rails server
in your terminal). 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 a small image file (a suitable sample image can be found at http://www.hungryacademy.com/images/beast.png). 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:
- Open a console session (
rails console
from terminal) - Find the ID number of the article by looking at the URL. In my case, the url was
http://localhost:3000/articles/1
so the ID number is just1
- In console, enter
article = Article.find(1)
- Right away I see that the article has data in the
image_file_name
and other fields, so I think it worked. - Enter
article.image
to see even more data about the file
Ok, 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. Before the line that displays the body, let’s add this line:
<p><%= image_tag @article.image.url %></p> |
Then refresh the article in your browser. Tada!
Improving the Form
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:
- If the article has an image filename
*Display the image - Then display the
file_field
button with the label “Attach a New Image”
So, turning that into code…
<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!
Further Notes about 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:
has_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 Homebrew installed, it’d be brew install imagemagick
. On windows you need to download and copy some EXEs and DLLs. It can be a hassle, which is why we won’t do it during this class.
If you do manage to get imagemagick installed, be advised that the custom sizes will only take affect on those images uploaded after the imagemagick installation. In otherwords, when the image is uploaded - Paperclip will use Imagemagick to create the customized sizes specified on the has_attached_file
line. However, there is a fix for this! Whenever you change your has_attached_file
styles - whether adding, editing, or deleting - you can easily reprocess all existing images with rake paperclip:refresh CLASS=Article
. Just replace ‘Article’ with the model name containing your Paperclip images in your other projects. For more information, see this helpful post in the Paperclip documentation.
A Few Sass Examples
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.
Place the following styles in a new file and save it as styles.css.scss in app/assets/stylesheets/
.
$primary_color: #AAA; |
If you refresh the page, it should look slightly different! But we didn’t add a reference to this stylesheet in our HTML; how did Rails know how to use it? The answer lies in Rails’ default layout.
Working with Layouts
We’ve created about a dozen view templates between our different models. Imagine that Rails didn’t just figure it out. How would we add this new stylesheet to all of our pages? 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. Rails has given us one already: app/views/layouts/application.html.erb
.
Check out your app/views/layouts/application.html.erb
:
<!DOCTYPE html> |
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.
See the stylesheet_link_tag
line? It mentions ‘application.’ That means it should load up app/assets/stylesheets/application.css
… Check out what’s in that file:
/* |
There’s that huge comment there that explains it: the require_tree .
line automatically loads all of the stylesheets in the current directory, and includes them in application.css
. Fun! This feature is called the asset pipeline
, and it’s pretty new to Rails. It’s quite powerful.
Now that you’ve tried out a plugin library (Paperclip), the “A Few Gems” tutorial is complete!
Saving to GitHub.
git add . |