The Very Short Book of Rails!
Step by Logical Step
By Nicholas Johnson
Document Version:
Last Updated:
Hello World
Hello World is the traditional way to start any new framework. Hello World in Rails is fairly simple, around six lines of code. In this section we’ll create an MVC hello world using a simple model, a controller, and a view, in fact the full MVC stack.
This will take us about three minutes. Rails is nice that way.
Create your rails application
At a terminal type:
rails new HelloWorld
This initialises a new blank application called HelloWorld
Start your rails application server
At a terminal navigate to your HelloWorld directory and type:
rails server
Wait for the application to start then in a web browser visit:
Creating a Controller
Create a new file in the app/controllers directory called hello_controller.rb
class HelloController < ApplicationController
def index
@text = "Hello World!"
end
end
This controller defines a single method called index. Index is the default controller action. If you’re familiar with HTML this should make sense.
The index method creates an instance variable called @text.
Creating a View
In the app/views/hello directory (you’ll need to create this directory) create a file called index.html.erb containing the following
<h1>Hello World</h1>
<%= @text %>
=code(code, :erb)
Views have access to the controller’s instance variables. You don’t need to do any work to pass them through, they’re just there.
Create a route
Visit config/routes.rb. Take a moment to read the helpful instructions, then add the line:
get '/hello' => 'hello#index', :as => :hello_world
This line creates a named route (called hello_world). It matches the HelloController index method.
Hey Presto
In a web browser visit:
And there we are. Of course we’re only touching on the awesome power of Rails here, but this should give you an idea of how easy things are to get going.
Exercise - Goodbye World
Change your app into a goodbye world app. Here are the thing’s you’ll need to do:
- Add a new controller goodbye_controller.rb.
- Add a route.
- Add a view
Make it so that you can visit http://localhost:3000/goodbye to see the goodbye view.
Exercise - Tell the time
We’re going to create a simple website that displays the current time and date.
- Create a controller called something like time_controller.
- Add a route
- In your controller create a variable called @time which contains the value: Time.now. This is just the current time.
- Write a view which outputs the value of the @time variable. Look up strftime for help with this.
Exercises - Root URL
Adjust your app so it responds to the root url. You should be able to visit:
and see hello world.
- First delete public/index.html
- Now view the instructions in config/routes.rb to create a default URL.
Debugging Rails
Rails comes with a couple of fully featured debugging systems.
The Console
The rails console lets you fire up an instance of your rails server on the command line. You have full access to Active Record and all of your models. It’s terribly good.
Fire it up with:
rails console
or just
rails c
You can create models, define methods, and do anything you can do in a regular rails instance.
If you make changes to your code, you can load them in to your console by typing:
reload!
The Debugger
If you need to poke around inside a running Rails instance to see what’s going on, you can use the debugger. To use it you must start your Rails instance in debug mode like so:
rails server --debugger
or just
rails s --debugger
Then insert breakpoints into your code by typing:
debugger
You can put breakpoints in your models, views or controllers. Within the debugger you have access to all of the variables defined at or before the breakpoint. You can change variables, execute code, write ruby, etc, etc.
To exit the debugger, continue with
c
Exercise - Debugging
In this exercise we’ll have a go at using the debugging tools.
Let’s use the debugger to debug our Hello World server. Fire up your hello world instance in debug mode. Now insert a breakpoint in the hello controller (or goodbye controller), right after you define the @text instance variable.
Hit the URL, and the app will freeze. Go to your terminal, and you’ll find it’s waiting for input. You can view the contents of the @text variable, and even change it’s value.
Layouts and Partials
Layouts allow you to wrap your code in a standard header and footer.
The default layout is called app/views/layouts.application.html.erb
Any changes we make to this file will be visible in all out views, unless we specify another layout.
Layouts Exercise
Your default layout file lives in app/views/application.html.erb. Any changes you make to this file will be visible in every page on your site, unless you explicitly use a different layout.
Add a header and footer to your application.html.erb file. It should be visible on each of your pages.
Passing a title
Try to pass through a @title variable from the controller to the view. Use this as the page title at the top of your layout file.
Partials
Partials are partial views. They allow you to break your code up into smaller sensible chunks.
Partials exercise
Partial filenames start with an underscore.
Create a header partial called /app/views/layouts/_header.html.erb
Include it in your template using the render method, like so:
<%= render partial: "layouts/header" %>
You may wish to create partials for things like:
- Headers and footers
- Metadata
- JavaScript includes
- Facebook widgets
- Tables
- Forms
Create a footer partial now.
Params
The params hash contains all the url and post parameters that were passed to your application.
You can access it from inside any controller, like so:
params[:key]
Say you have defined a route like so:
get '/hello/:hello/:everyone' => 'hello#index', :as => :hello_world
In your controller, you can access:
params[:hello]
and
params[:everyone]
These will automatically be extracted from the route.
Exercise - Make a calculator
We are going to make a simple calculator, you will be able to give it two numbers, and have it give you the sum.
First create a controller and route. The routes should have space for two numbers. It should be possible to hit a URL like this:
http://localhost:3000/calculate/12/15
Retrieve the values and store in instance variables
Now in your controller, retrieve these from the params hash and store them in instance variables, @number_1 and @number_2
Sum them in the view
Finally create a view. In the view, sum together the two numbers, giving a nice output.
Optional Extension
Try and think of a way you can make this work using a form. You will need to change the route to accomplish this.
Models, Validation and Resources
We are going to create a Cat model to held information about a single cat. Use the model generator to create a simple model:
rails g model cat name:string description:text
=code(code, :bash)
You will see we have made a nice cat class in the models directory, and also a migration in the db/migrations directory.
Run the migration using:
rake db:migrate
This has updated your database schema.
Test using the console
Drop into the rails console by typing:
rails c
Exercise - playing with cats
Now you can play with your Cat.
Try out the following commands in the console:
c = Cat.create name: "Markey"
Cat.all
c.name = "Dave"
c.save
Cat.first
Cat.first.name
Cat.find(1)
What do they all do? You can do all this in your controller.
Exercise - A basket full of tiny little cats
Now create 10 more cats, and try out the following.
Cat.order(:name)
Cat.order(:name).limit(5)
Cat.where(name: "Dave")
Cat.where("created_at > ?", Time.now - 1.minute)
Cat.count
Exercise - Making use of our cat
Now create a controller and route. The route should point to a method called show in the controller. It should be possible to visit:
In the controller retrieve the cat based on the id in the params hash.
Now make a view to display your cat. Output all the fields nicely.
Optional Extension, create an index page
Create a route of the form:
This should point to the index method in the cat_controller. This method should retrieve all of the cats with Cat.all.
Make a view which loops over the cats, displaying them all. To loop over a collection, you can do something like this:
<% @cats.each do |cat| %>
<%= cat.name %>
<% end %>
Scaffolding
The Scaffold Generator allows us to create a controller, model, views, partials, stylesheets and tests with a single call.
In this section we are going to look at using scaffolding to create a CRUD app in double quick time.
Scaffold the resource
It’s traditional to make a blog, but feel free to make a kitten, or similar.
First create a BlogPost scaffold and give it some attributes. You can use the following generator as a jumping off point. You will need to add a content:text
and probably also a date:datetime
too.
rails g scaffold blog_post title:string
Look over the controller
Take a look at the controller that was made, you will find 7 standard actions allowing you to create
, edit
, show
, index
and destroy
(edit and create get two methods each).
Spend a few minutes reading through the code and understanding it.
Look at the routes
You will find one line has been added to the routes file: resources
.
This single line generates all of the standard crud routes for you. Check them out from a console by typing:
rake routes
Views
Look at the views. See the form partial? It’s used by the new and edit templates. Have a read and try to understand what’s going on.
Tests
The tests that have been built for you should work right out of the box. Run:
rake test
to run all of the tests.
Further Exercise: Validation
Use validates_presence_of :title
to validate that the blog_post
has a title. It is now not possible to save a blog_post
without a title. Add validation for the content.
Try and create a blog post without a title, look at the error reporting. Do you see how it works?
Further Further Exercise: Homepage
Set blog_post#index
as the homepage, so when you visit your Blog, you see a list of entries.
Harder Exercise: Friendly URLs
Add a slug attribute to the blog_post
. Do a find_by_slug
instead of a regular find in your show method like this:
BlogPost.find_by_slug params[:id]
Use a migration to add the field, generate the migration like this:
rails g migration add_slug_to_blog_post
Within the migration you will want to do something like this:
add_column :blog_posts, :slug, :string, index: true
Finally, ensure slug is a required field.
You can now hit a URL like this
Associations Between Models
We’re going to add Comments to our blog. Comments belong to BlogPosts and blog_posts have many comments.
Scaffolding
First of all, scaffold the Comment model. Refer to the last exercise if you can’t remember how to do this.
Comments will need several fields, I’ll leave this part up to you, but crucially, comments will need a blog_post_id: integer field. Notice how I highlighted that part.
Now set up the relationships
You’ll need to extend your models something like the following:
class BlogPost
has_many :comments
end
class Comment
belongs_to :blog_post
end
Test the association
Drop into the console and check your association. You should be able to call something like:
post = BlogPost.first
post.comments
Validation
Add validation to your comment. A comment needs a bog_post_id to be valid, plus a couple of other fields. Enforce this.
Listing comments
On your blog_post show page, list all the comments for a particular blog post. Remember you can use @blog_post.comments to get an array of the comments.
Great. You can now create comments from the comments form, and see them when you view a blog but you will need to manually enter the blog_post_id when creating the comment. Let’s fix that.
Extension: Nested Routes
Use a nested route to nest your comment inside a BlogPost. Modify your routes.rb file like so:
resources :blog_posts do
resources :comments
end
Use rake routes to check the routes you have made.
rake routes
=code(code, :bash)
Now you can visit a URL like
http://localhost:3000/blog_post/1/comments/new to create a comment. Remove the blog_post_id field from the comment form view. Instead, in your controller set it using the params hash. You may need to drop into the debugger to check the params hash here.
Optional finishing up
Pick from the following:
- See if you can integrate your comment form right into your blog_post_show page. (tip, in the blog_post_show controller execute:
@comment = Comment.new.
This will let the comment form just work without modifications.)
Sessions
Rails allows you to store session data on the client in the form of an encrypted cookie. You can read from and write to the session cookie simply by accessing to the session hash:
session[:user_id] = 1234
It’s up to you how much data you store on the client, though if you store too much you will probably encounter a cookie overrun.
The most common use for sessions is for login. We might log a user in by storing the id of a User model in the session.
We might then log out by session this value to nil.
Exercise - Access the session hash
A simple one first.
- Create a _header partial which outputs session[:username].
- Now create a form which submits a username to a controller.
- In the controller, store the username in the session.
Now as long as the session persists, the username is visible in the header, even if you navigate away from the page.
Exercise - Login
We are going to implement login.
- Create a user model with an email and password. Use the console to create a few user objects.
- Your user model should have a login method that accepts a password. If the password matches, this method returns true.
- Create a session controller. Give it a new method. Have the view render a login form which accepts a username and password.
- Give your session controller a create method. Post the data from the login form to it. This method should find a user by email, and if one is found, call the login method with the password.
- If everything checks out, add the user id to the session
- Now in ApplicationController add a before_filter. This should check the session for a user_id, pull the user from the database and save it in @current_user instance variable.
- Finally write a partial which displays the session user email.
Assets and SCSS
Assets are files which belong to the web page, such as images, JavaScripts and CSS files. They live in the app/assets folder.
The asset pipeline is the conduit through which you import stylesheets, JavaScript files, images, etc. They can be served individually or else precached, minified, concatenated, etc.
In development, assets are served individually. This is set in config/environments/development.rb with this line:
config.assets.debug = true;
Asset Digest
A Digest is a string which is added to the end of your asset filename. It’s created by hashing the file. If any of your files change, the digest is updated, and so the filename is updated. This gets round caching issues.
Turn on digests like this:
config.assets.digest = true;
Now view source. You’ll see all your assets are compressed.
Asset Compilation
Let’s have a look at asset compilation:
By default your development server will serve all your assets as separate files. Turn off asset debugging in config/environments/development.rb by modifying the line:
config.assets.debug = true;
to:
config.assets.debug = false;
Restart your server to see the results.
Namespacing CSS
Because all your stylesheets are imported with each request, you have to be clever not to apply page specific rules everywhere. I typically add the controller and model as a class to the body element. This allows me to namespace my SCSS.
app/helpers/application_helper.rb is a module that’s imported by all your views. You can define helpful methods in it. The following method will create a string based on your controller, action, and optionally a version parameter. Add it to the application helper, or modify it your purposes.
def body_class
bc = []
bc << params[:controller].gsub('/', ' ')
bc << params[:action]
if @version
bc << "version_#{@version}"
end
bc.join(' ')
end
Now in your app/views/layouts/application.html.erb file, add the new class:
<body class="<%= body_class %>" >
In your SCSS files you can now write things like:
.home.index {
h1 {
font-size:5em;
}
}
Exercise - Compiling SCSS
In this section we will look at scss, Look in the app/assets/stylesheets folder, you’ll find a set of scss files in there which you can modify. If you create additional files they are automatically included.
- Create a stylesheet called header.css.scss.
- Add code to style the header on the page. Perhaps add a pretty pink background or something.
- Verify the header has been styled.
- View the page source in your browser. Look for the link tags that include the stylesheets.
Optionally style the footer.
Exercise - Sprockets
- Now edit config/environments/development.rb.
- Set config.assets.debug = false
- Restart the server (you need to restart if you change configuration settings)
- View the page source. You should see all the assets rolled into a single file.
Digest
- Edit config/environments/development.rb.
- config.assets.digest = true
- Restart and view the page source. See the filename has been rewritten to defeat caching.
RSpec Rails
We are going to use RSpec plus FactoryGirl to test our site.
First we need to add the gems to our Gemfile:
group :test, :development do
gem 'debugger'
gem 'rspec-rails'
gem 'factory_girl_rails'
end
Now bundle and restart the server.
We now need to initialise the spec directory and helpers. We can do this using:
rails generate rspec:install
Next Configure Rails use rspec in the generators. Inside Application.rb, add a config block:
config.generators do |g|
g.test_framework :rspec
g.integration_tool :rspec
end
Now when we run a generator we also get a spec file generated for us as well too.
Generate scaffold Rspec
We are going to create a User scaffold for our blog. Posts will have Users.
Generate the scaffold. Now look in the spec directory. See the spec there?
rails g scaffold user name:String age:Integer
Run rake spec to run the specs. It will tell you if you have any errors and where they are.
Exercise - Use FactoryGirl
The spec we generated creates a user model for us. This is OK, but not very scalable. We might need users in other places. We are going to use Factory Girl to make the Users for us.
Check out the FactoryGirl documentation here:
https://github.com/thoughtbot/factory_girl
Now create a User factory. Modify your generated spec to use the factory instead.
Modify the .rspec config file
Edit the .rspec file so it looks like this:
--color
--format documentation
You will find the output much easier to read.
Testing a model
Say we have a Product model we want to test. We would first create a file spec/models/product_spec.rb
require 'spec_helper'
describe Product do
it "can be initialised"
it "has a title"
end
Running the test would show yellow, as the tests are not yet implemented.
Here I have filled in the initialiser test:
require 'spec_helper'
describe Product do
before :all do
@product = Product.new
end
it "can be created" do
expect(@product).to be_a(Product)
end
it "has a title"
end
Now let’s implement a new feature, a title:
require 'spec_helper'
describe Product do
before :all do
@product = Product.new :arkham
end
it "can be created" do
expect(@product).to be_a(Product)
end
it "has a title" do
expect(@product.title).to be("Arkham")
end
end
Exercise - Write specs for your application
First have a looks through the Better Specs guide, it is good:
Now, using the generated User specs as a guide, write specs for your scaffold application. You might like to write some specs for your model and Controller. Remember Use FactoryGirl and run your specs often.
Concerns
Concerns are little modules that encapsulate reusable functionality. Rather than adding them to our models and controllers directly, we put them in a module and include them.
An example of a concern might be “Taggable”, or “Ratable” or “HasImage”
Exercise - HasImage
We are going to create a HasImage concern which will add standard paperclip functionality to our BlogPost. We will be able to use it like this:
class BlogPost < ActiveRecord::Base
include HasImage
end
Define your concern in a module in the app/models/concerns directory. To be automatically imported the file must be have the same name as the module, with underscores.
Because Paperclip requires you to work with the class directly we are going to use the included callback to add class methods to our module, like so:
module HasImage
def self.included(klass)
klass.extend ClassMethods
end
module ClassMethods
end
end
The Paperclip documentation can be found here:
https://github.com/thoughtbot/paperclip
Remember to write specs for your concern.
Further exercise
Create a Rateable concern to allow users to rate content. The model should gain thumbs_up and thumbs_down methods, and a rating method. Store the data either in a field, or in a separate Rating model which belongs to the User model (if you have one)
Alternatives to ERB - Haml and Markdown (also Slim)
We are going to try out Haml as an alternative templating engine. Haml is a beautiful, simple HTML preprocessor. Haml compiles directly to HTML, there’s a one to one mapping.
Haml has:
- Semantic indentation. Close tags are not necessary.
- Ruby style hashes for attributes.
Exercise
- We are going to convert one of our views into Haml. Pick one at random from your blog, you could also choose a partial.
- Add gem ‘haml’ to your Gemfile. bundle and restart your server.
- Now check out the Haml documentation here: http://haml.info/
- Manually convert your view to basic Haml
Exercise - Filters
We are going to use a markdown filter to write text in Markdown. Markdown is a simple text preprocessing language.
- Check out filters in the Haml docs
- Add a markdown filter to your template
- Now add a block of text. 2 newlines makea paragraph. A # makes an h1. ## makes an h2.
Writing text blocks in markdown can really save you time and make your ideas flow more clearly.
Internationalisation
Rails Locales
Rails defines Yaml files for internationalisation of strings. These are held in config/locales.
The Yaml files contain a set of strings. To access a string from your controller, use: I18n.t ‘hello’. In your view, you can use: <%= t(‘hello’) %>
If a string is not defined in your locale, the default locale is used instead. This can be set in application.rb.
Sample locales.
Check out Sven Fuchs’ rails i18n project for a pretty good basic locale set.
https://github.com/svenfuchs/rails-i18n/tree/master/rails/locale
Finding the locale from your URL.
Your locale will not be set for you. You can use your URL to set it, then retrieve it using a filter in application_controller.rb
before_action :set_locale
def set_locale I18n.locale = params[:locale] || I18n.default_locale end
Now if you hit a URL like this:
localhost:3000?locale=fr
The locale will be set to ‘fr’ (French).
There are various other options here. You can read about them in the documentation here: http://guides.rubyonrails.org/i18n.html
Exercise: Localise your blog
Create a locale file for a language you know. Now take one of the strings, e.g. ‘New Blog Post’. Create a locale file to localise this string.
Blog integration exercise
Create a new Rails App using:
rails.new;
We’re going to put everything together and make a simple blog.
Create the model
First create a BlogPost model and give it some attributes. You can use the following generator as a jumping off point. You will need to add a content:text and probably also a date:datetime too.
rails g model blog_post title:string
Create the controller
Next we’ll need a BlogPost controller. Use a generator like this:
rails g controller blog_post
Create your routes
Use resources to create the standard CRUD routes. Like this:
resources: blog_post;
Create the CRUD methods
Review your scaffold code and create the standard Rails CRUD actions in your controller (index, show, new, create, edit, update, destroy). Create views to go along with them.
Validation
Use validates_presence_of :title to validate that the blog_post has a title. It is now not possible to save a blog_post without a title. Add validation for the content.
Homepage
Set blog_post#index
as the homepage
Friendly URLs
Add a slug attribute to the blog_post. Do a find_by_slug instead of a regular find in your show method.
You can use a migration to add the field, something like:
rails g migration add_slug_to_blog_post
Within the migration you will want to do:
add_column :blog_posts, :slug, :string, index: true
Comments
We’re going to create a Comment model. Comments belong to BlogPosts and blog_posts have many comments. You’ll need something like the following:
class BlogPost
has_many :comments
end
class Comment
belongs_to :blog_post
end
Comment will need a blog_post_id:integer attribute.
Use the generator to create the post.
Routes. We’ll use a nested route to generate the resources. Modify your routes.rb file like so:
resources :blog_posts do
resources :comments
end
rake routes to check the routes you have made.
Comment Controller
Make a comment controller with a new and create method. It should receive a comment and create it. Make a matching view and form.
Form
Integrate the comment form into the BlogPost#show view. Integrate the list of comments into this view.
Optional finishing up
- Style your blog nicely using assets.
- Deploy to Heroku. https://devcenter.heroku.com/articles/getting-started-with-rails4