This is not a Rails tutorial. There a billion of them out there, and many of them quite good. My goal is not even to explain everything I’m doing. If you know Rails, you’ll get it, if you don’t, I refer back to my previous sentence and encourage you to at least take a look, and even if you don’t, you’ll should see that ruby is pretty easy to read. Having said that, my goal is to just take a look at a small web app I wrote, and evaluate the actual amount of code I had to write.
I want to do this because it seems that when people are doing framework comparisons, they can get a little hung up on the folder structure and volume of files. Look, with any new framework, you’re going to have to learn something new, it’s a given, but once you do, all that structure and code just sort of fades into the background, and you start concentrating on the actual work. So, we’re here to look at the actual work to create a simple application I’ve dubbed [click].
The App
It’s probably worth at least briefly describing the app, so you know what we’re trying to accomplish. The idea was for a simple photo portfolio-style site. I didn’t want a slideshow, I only wanted to display a few pictures, one at a time, with simple keyboard (or mouse) navigation. I wanted it all in HTML5, all AJAX-y (if that’s a word), but with shareable urls. Since a picture is worth a 1,000 words, a picture app must be worth 1000x, where x is the number of pictures on the site. (Sorry, math humor.) Anyway the point is: it’s up, so you can see the final product here.
Setup
On to the meat. Let’s make an app. First, we’re going to need Rails. For our purposes all commands are from the root folder that was created for this project. Assume your environment is already setup (again, part of learning the framework, not using it.), and all lines starting with $ are bash/shell.
$ rails new click
Files and folders created. Now we’re going to need a few libraries to accomplish what we want, so we edit /Gemfile to be:
source 'http://rubygems.org' gem 'rails', '3.0.7' gem 'sqlite3-ruby', '1.3.2', :require => 'sqlite3' gem "paperclip", "~> 2.3" gem 'typus' gem 'exifr' group :development do gem 'rspec-rails', '2.5.0' gem 'annotate-models', '1.0.4' end group :test do gem 'rspec', '2.5.0' gem 'webrat', '0.7.1' end
Typus will generate our admin for us, paperclip performs attachment magic, exifr reads EXIF data, and sitemap_generator does what it’s name suggests. The rest are for testing/convenience. Now we:
$ bundle install
And all our libraries are ready.
The Model
We need to store our images and some info, so let’s create a model.
$ rails generate model photo name:string taken_on:date exposure:string f_stop:string focal_length:string iso:string description:string $ rails generate paperclip photo image
Now, for our functionality. The model is the meat of this application, and thus follows /app/models/photo.rb:
class Photo < ActiveRecord::Base
has_attached_file :image, :styles => { :display => "1050x700>", :rss => "400x300>" }
validates :name, :presence => true
validates :description, :presence => true
validates_attachment_presence :image
validates_attachment_content_type :image, :content_type => ['image/jpeg']
scope :sorted, order('taken_on DESC')
before_save :load_exif
def load_exif
# Pull the Exif data
exif_data = EXIFR::JPEG.new(self.image.to_file)
return if exif_data.nil? or not exif_data.exif?
# Set the attributes
self.exposure = exif_data.exposure_time.to_s
self.f_stop = exif_data.f_number.to_f.to_s
self.focal_length = exif_data.focal_length.to_f.round.to_s
self.iso = exif_data.iso_speed_ratings
self.taken_on = exif_data.date_time.to_date
rescue
false
end
def info_line
"#{self.focal_length}mm #{self.f_stop}f #{self.exposure} ISO #{self.iso}"
end
def self.id_list
@id_list = Array.new
self.sorted.each { |photo| @id_list << photo.id }
@id_list
end
def next_id
@id_list = Photo.id_list
@id_list.last.id == self.id ? @id_list.first : @id_list[@id_list.index(self.id) + 1]
end
def prev_id
@id_list = Photo.id_list
@id_list.first.id == self.id ? @id_list.last : @id_list[@id_list.index(self.id) - 1]
end
end
What’s that do? It adds file attachment support to the photo object, and automatically creates two more versions of the file at the specified sizes. It makes name, description, and an image that is a jpeg mandatory. Before saving the record, it pull the EXIF data from the image file. Finally, there are some helpers for display and ordering.
Done. Let’s create the DB.
$ rake db:migrate
We now have the database created, table for our model created, some helpers for display and all our image attachment handling taking care of.
The Controller
Time to power up the retrieval of this data. For that we’ll need a controller.
$ rails generate controller Photos
And we’ll need to respond to json requests only (we’re all AJAX).
class PhotosController < ApplicationController
responds_to :json
# GET /photos/1.json
def show
@photo = Photo.find(params[:id])
@photo_json = {
'title' => @photo.name,
'description' => @photo.description,
'taken' => @photo.taken_on.strftime('%b %d, %Y'),
'info' => @photo.info_line,
'url' => @photo.image.url(:display),
'next_id' => @photo.next_id,
'prev_id' => @photo.prev_id
}
respond_with @photo_json
end
# GET /photos/first.json
def first
params[:id] = Photo.id_list.first
show()
end
end
Now when you call /photos/X.json, you’ll get JSON encoded information about the photo (id=X) to display.
The Admin
Typus will handle our admin interface. It will provide a user system, understands ActiveRecord models and handles paperclip attachments, so we just need a few commands.
$ rails generate typus $ rails generate typus:migration $ rails generate typus:controller photo $ rake db:migrate
Here’s what that looks like:
We’re Done!
Well, more specifically, we’re done the Rails part. We will ultimately add a static html homepage, css styles, and an application javascript file that powers the display. This page and javascript has nothing to do with Rails, and would be the same regardless of framework, so it’s not really part of this discussion. Things I didn’t do include write any SQL, extend the framework at all, handle image uploads and/or resizing, put one line of code into a backend, manage any JSON encoding, manually manage any revisions to the DB during development (migrations are fantastic!), or set up a development and test environment or server (done automatically).
Disclaimer/Conclusion
I’m not Rails expert. In fact, I’m barely qualified to be called a beginner. There are things above that could be done better, there are even more things like caching and minification that Rails could do for me with a few lines of code. There’s a whole testing component to the app that was 75% auto-generated by Rails that I didn’t even mention. To scale this app up, you’d want to run the database on a database server. If you’re really interested, you can get the full app from my github repo. Having said that, I don’t feel that any of that matters for this example. What matters is that having been working programmer in Java/PHP/Perl/ColdFusion for over a decade, I can see why so many of the frameworks in those languages are basically taking some or all of their feature set from Rails. I enjoyed working on this codebase, and hope to continue improving it as I have free time or think of new ideas because it doesn’t require jumping through hoops. I’m not about to start pushing hard to switch everything to Rails, because I tend to be a bit of a language agnostic, and I think there’s a right tool for every job. However, I have a feeling that Rails IS the right tool for many MVC web projects, and definitely think it warrants a deeper look if you haven’t had the opportunity to do so.
