Rails

software-development

Articles
Resources
Videos

Installation
Models
Models - Migrations
Models - Validation
View - Helpers
View - Partials
Controllers
Routes
Security and authentication
Gotchas
Admin interface
Miscellaneous
Unanswered Questions

On my Ubuntu virtual machine, do I use rbenv or rvm?

Currently, I am using rbenv

What is Rail?

Rails is a web application development framework written in the Ruby language. It is designed to make programming web applications easier by making assumptions about what every developer needs to get started. It allows you to write less code while accomplishing more than many other languages and frameworks. Experienced Rails developers also report that it makes web application development more fun.

Rails is opinionated software. It makes the assumption that there is the "best" way to do things, and it's designed to encourage that way - and in some cases to discourage alternatives. If you learn "The Rails Way" you'll probably discover a tremendous increase in productivity. If you persist in bringing old habits from other languages to your Rails development, and trying to use patterns you learned elsewhere, you may have a less happy experience.

What are Rails' major guiding principles?

  • Don't Repeat Yourself: DRY is a principle of software development which states that "Every piece of knowledge must have a single, unambiguous, authoritative representation within a system." By not writing the same information over and over again, our code is more maintainable, more extensible, and less buggy.
  • Convention Over Configuration: Rails has opinions about the best way to do many things in a web application, and defaults to this set of conventions, rather than require that you specify every minutiae through endless configuration files.

What are generators?

Rails comes with a number of scripts called generators that are designed to make your development life easier by creating everything that's necessary to start working on a particular task. One of these is the new application generator, which will provide you with the foundation of a fresh Rails application so that you don't have to write it yourself. To use a generator:

bin/rails ... otherArguments

where … is the name of the generator

What is the purpose of a controller?

A controller's purpose is to receive specific requests for the application. Routing decides which controller receives which requests. Often, there is more than one route to each controller, and different routes can be served by different actions. Each action's purpose is to collect information to provide it to a view.

What is the purpose of a view?

A view's purpose is to display this information in a human readable format. An important distinction to make is that it is the controller, not the view, where information is collected. The view should just display that information. By default, view templates are written in a language called eRuby (Embedded Ruby) which is processed by the request cycle in Rails before being sent to the user.

What is a resource?

A resource is the term used for a collection of similar objects, such as articles, people or animals.

What does CRUD abbreviate for?

Create, Read, Update, and Destroy

Can we use Rails to create a static web page or site?

Yes. To get Rails to do anything, you need to create at minimum a controller and a view. To create a static web page, you need to create one application, and within this application, you need to create a controller and a view. To create a static web site (a collection of static web pages), you just need to create one application, and within this application, you can create many controllers and many views. See the rest of this page for details.

What are the components of a Rails application?

  • Resources
  • Models
  • Views
  • Controllers
  • Actions
  • Routing Rules
  • Validation Rules

How to install rails on Fedora?

gem install rails

How to generate the initial skeleton for an application?

rails new applicationName -d mysql
rails new applicationName -d postgresql
rails new applicationName
cd blog

The rails new applicationName create a directory with the same name as the application. You then go inside this directory and do everything in there.

How to start the WEBrick server?

bin/rails server

This will fire up WEBrick, a web server distributed with Ruby by default. To see your application in action, open a browser window and navigate to http://localhost:3000. You should see the Rails default information page.

How to create a new welcome page and replace the default welcome page with our new welcome page?

To get Rails saying "Hello", you need to create at minimum a controller and a view. To create a new controller, you will need to run the "controller" generator and tell it you want a controller called "welcome" with an action called "index", just like this:

bin/rails generate controller welcome index

Rails will create several files and a route for you. Most important of these are of course the controller, located at app/controllers/welcome_controller.rb and the view, located at app/views/welcome/index.html.erb.

Open the app/views/welcome/index.html.erb file in your text editor. Delete all of the existing code in the file, and replace it with the following single line of code:

<h1>Hello, Rails!</h1>

Now that we have made the controller and view, we need to tell Rails when we want "Hello, Rails!" to show up. In our case, we want it to show up when we navigate to the root URL of our site, http://localhost:3000. At the moment, "Welcome aboard" is occupying that spot. Next, you have to tell Rails where your actual home page is located. Open the file config/routes.rb in your editor:

Rails.application.routes.draw do
  get 'welcome/index'

  # The priority is based upon order of creation:
  # first created -> highest priority.
  #
  # You can have the root of your site routed with "root"
  # root 'welcome#index'
  #
  # ...

This is your application's routing file which holds entries in a special DSL (domain-specific language) that tells Rails how to connect incoming requests to controllers and actions. This file contains many sample routes on commented lines, and one of them actually shows you how to connect the root of your site to a specific controller and action. Find the line beginning with root and uncomment it. It should look something like the following:

root 'welcome#index'

root 'welcome#index' tells Rails to map requests to the root of the application to the welcome controller's index action and get 'welcome/index' tells Rails to map requests to http://localhost:3000/welcome/index to the welcome controller's index action. This was created earlier when you ran the controller generator (rails generate controller welcome index).

You'll see the "Hello, Rails!" message you put into app/views/welcome/index.html.erb, indicating that this new route is indeed going to WelcomeController's index action and is rendering the view correctly.

What are the rules for defining a model in Rails?

Models in Rails use a singular name, and their corresponding database tables use a plural name.

How to define a new model?

Rails provides a generator for creating models, which most Rails developers tend to use when creating new models. To create the new model, run this command in your terminal:

bin/rails generate model Article title:string text:text

With that command we told Rails that we want a Article model, together with a title attribute of type string, and a text attribute of type text. Those attributes are automatically added to the articles table in the database and mapped to the Article model.

Rails responded by creating a bunch of files. For now, we're only interested in app/models/article.rb and db/migrate/20140120191729_create_articles.rb (your name could be a bit different). The latter is responsible for creating the database structure.

What happens when you use the generator to define a model?

Rails responded by creating a bunch of files. For now, we're only interested in app/models/article.rb and db/migrate/20140120191729_create_articles.rb (your name could be a bit different). The latter is responsible for creating the database structure.

How to define a controller for the "articles" resource?

bin/rails g controller articles

If you open up the newly generated app/controllers/articles_controller.rb you'll see a fairly empty controller:

class ArticlesController < ApplicationController
end

A controller is simply a class that is defined to inherit from ApplicationController. It's inside this class that you'll define methods that will become the actions for this controller.

Where do we define the CRUD actions for a resource?

The actions are defined inside the controller that we just defined for the resource.

What are the actions that we must define?

  1. new: This action displays the form for the resource
  2. create: This action save the new resource to the database
  3. show: This action displays a given resource
  4. index: This action lists all instances of a given resource

After defining the controller for the 'article' resource, why do we get the error 'the action new could not be found'?

This error indicates that Rails cannot find the new action inside the ArticlesController that you just generated. This is because when controllers are generated in Rails they are empty by default, unless you tell it your wanted actions during the generation process.

How to manually define the 'new' action?

To manually define an action inside a controller, all you need to do is to define a new method inside the controller. Open app/controllers/articles_controller.rb and inside the ArticlesController class, define a new method like this:

def new
end

After defining the empty 'new' action, why do we get the error 'missing template article/new'?

We're getting this error now because Rails expects plain actions like this one to have views associated with them to display their information. With no view available, Rails errors out.

The first part of the error message identifies what template is missing. In this case, it's the articles/new template. Rails will first look for this template. If not found, then it will attempt to load a template called application/new. It looks for one here because the ArticlesController inherits from ApplicationController.

The next part of the message contains a hash. The :locale key in this hash simply indicates what spoken language template should be retrieved. By default, this is the English - or "en" - template. The next key, :formats specifies the format of template to be served in response. The default format is :html, and so Rails is looking for an HTML template. The final key, :handlers, is telling us what template handlers could be used to render our template. :erb is most commonly used for HTML templates, :builder is used for XML templates, and :coffee uses CoffeeScript to build JavaScript templates.

The final part of this message tells us where Rails has looked for the templates. Templates within a basic Rails application like this are kept in a single location, but in more complex applications it could be many different paths.

The simplest template that would work in this case would be one located at app/views/articles/new.html.erb. The extension of this file name is key: the first extension is the format of the template, and the second extension is the handler that will be used. Rails is attempting to find a template called articles/new within app/views for the application. The format for this template can only be html and the handler must be one of erb, builder or coffee. Because you want to create a new HTML form, you will be using the ERB language. Therefore the file should be called articles/new.html.erb and needs to be located inside the app/views directory of the application.

Go ahead now and create a new file at app/views/articles/new.html.erb and write this content in it:

<h1>New Article</h1>

When you refresh http://localhost:3000/articles/new you'll now see that the page has a title. The route, controller, action and view are now working harmoniously!

How to use form_builder to create a form?

The primary form builder for Rails is provided by a helper method called form_for. To use this method, add this code into app/views/articles/new.html.erb:

<%= form_for :article do |f| %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

When you call form_for, you pass it an identifying object for this form. In this case, it's the symbol :article. This tells the form_for helper what this form is for. Inside the block for this method, the FormBuilder object - represented by f - is used to build two labels and two text fields, one each for the title and text of an article. Finally, a call to submit on the f object will create a submit button for the form.

There's one problem with this form though. If you inspect the HTML that is generated, by viewing the source of the page, you will see that the action attribute for the form is pointing at /articles/new. This is a problem because this route goes to the very page that you're on right at the moment, and that route should only be used to display the form for a new article.

The form needs to use a different URL in order to go somewhere else. This can be done quite simply with the :url option of form_for. Typically in Rails, the action that is used for new form submissions like this is called "create", and so the form should be pointed to that action.

Edit the form_for line inside app/views/articles/new.html.erb to look like this:

<%= form_for :article, url: articles_path do |f| %>

In this example, the articles_path helper is passed to the :url option. To see what Rails will do with this, we look back at the output of rake routes. The articles_path helper tells Rails to point the form to the URI Pattern associated with the articles prefix; and the form will (by default) send a POST request to that route. This is associated with the create action of the current controller, the ArticlesController.

How to define the 'create' action?

You can define a create action within the ArticlesController class in app/controllers/articles_controller.rb, underneath the new action:

class ArticlesController < ApplicationController
  def new
  end

  def create
  end
end

When a form is submitted, the fields of the form are sent to Rails as parameters. These parameters can then be referenced inside the controller actions, typically to perform a particular task. To see what these parameters look like, change the create action to this:

def create
  render plain: params[:article].inspect
end

The render method here is taking a very simple hash with a key of plain and value of params[:article].inspect. The params method is the object which represents the parameters (or fields) coming in from the form. The params method returns an ActiveSupport::HashWithIndifferentAccess object, which allows you to access the keys of the hash using either strings or symbols. In this situation, the only parameters that matter are the ones from the form.

How to modify your 'create' action to save the article to the database?

Back in ArticlesController, we need to change the create action to use the new Article model to save the data in the database. Open app/controllers/articles_controller.rb and change the create action to look like this:

def create
  @article = Article.new(params[:article])

  @article.save
  redirect_to @article
end

Here's what's going on: every Rails model can be initialized with its respective attributes, which are automatically mapped to the respective database columns. In the first line we do just that (remember that params[:article] contains the attributes we're interested in). Then, @article.save is responsible for saving the model in the database. Finally, we redirect the user to the show action.

What is this ActiveModel::ForbiddenAttributesError error?

Rails has several security features that help you write secure applications, and you're running into one of them now. This one is called strong_parameters, which requires us to tell Rails exactly which parameters we want to accept in our controllers. In this case, we want to allow the title and text parameters, so add the new article_params method, and change your create controller action to use it, like this:

def create
  @article = Article.new(article_params)

  @article.save
  redirect_to @article
end

private
  def article_params
    params.require(:article).permit(:title, :text)
  end

See the permit? It allows us to accept both title and text in this action.

Note that def article_params is private. This new approach prevents an attacker from setting the model's attributes by manipulating the hash passed to the model. For more information, refer to this blog article about Strong Parameters.

How to add the show action?

The output of rake routes:

article GET    /articles/:id(.:format)      articles#show

The special syntax :id tells rails that this route expects an :id parameter, which in our case will be the id of the article. As we did before, we need to add the show action in app/controllers/articles_controller.rb and its respective view.

def show
  @article = Article.find(params[:id])
end

A couple of things to note. We use Article.find to find the article we're interested in, passing in params[:id] to get the :id parameter from the request. We also use an instance variable (prefixed by @) to hold a reference to the article object. We do this because Rails will pass all instance variables to the view. Now, create a new file app/views/articles/show.html.erb with the following content:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

How to list all articles?

The route for this as per output of rake routes is:

articles GET    /articles(.:format)          articles#index

Add the corresponding index action for that route inside the ArticlesController in the app/controllers/articles_controller.rb file:

def index
  @articles = Article.all
end

And then finally, add view for this action, located at app/views/articles/index.html.erb:

<h1>Listing articles</h1>

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
  </tr>

  <% @articles.each do |article| %>
    <tr>
      <td><%= article.title %></td>
      <td><%= article.text %></td>
    </tr>
  <% end %>
</table>

Now if you go to http://localhost:3000/articles you will see a list of all the articles that you have created.

How to create the edit action for updating the article?

The first step we'll take is adding an edit action to the ArticlesController.

def edit
  @article = Article.find(params[:id])
end

The view will contain a form similar to the one we used when creating new articles. Create a file called app/views/articles/edit.html.erb and make it look as follows:

<h1>Editing article</h1>

<%= form_for :article, url: article_path(@article), method: :patch do |f| %>
  <% if @article.errors.any? %>
  <div id="error_explanation">
    <h2><%= pluralize(@article.errors.count, "error") %> prohibited
      this article from being saved:</h2>
    <ul>
    <% @article.errors.full_messages.each do |msg| %>
      <li><%= msg %></li>
    <% end %>
    </ul>
  </div>
  <% end %>
  <p>
    <%= f.label :title %><br>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :text %><br>
    <%= f.text_area :text %>
  </p>

  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Back', articles_path %>

This time we point the form to the update action, which is not defined yet but will be very soon.

The method: :patch option tells Rails that we want this form to be submitted via the PATCH HTTP method which is the HTTP method you're expected to use to update resources according to the REST protocol.

The first parameter of form_for can be an object, say, @article which would cause the helper to fill in the form with the fields of the object. Passing in a symbol (:article) with the same name as the instance variable (@article) also automagically leads to the same behavior. This is what is happening here. More details can be found in form_for documentation.

Next we need to create the update action in app/controllers/articles_controller.rb:

def update
  @article = Article.find(params[:id])

  if @article.update(article_params)
    redirect_to @article
  else
    render 'edit'
  end
end

private
  def article_params
    params.require(:article).permit(:title, :text)
  end

The new method, update, is used when you want to update a record that already exists, and it accepts a hash containing the attributes that you want to update. As before, if there was an error updating the article we want to show the form back to the user.

We reuse the article_params method that we defined earlier for the create action.

You don't need to pass all attributes to update. For example, if you'd call @article.update(title: 'A new title') Rails would only update the title attribute, leaving all other attributes untouched.

Finally, we want to show a link to the edit action in the list of all the articles, so let's add that now to app/views/articles/index.html.erb to make it appear next to the "Show" link:

<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="2"></th>
  </tr>

<% @articles.each do |article| %>
  <tr>
    <td><%= article.title %></td>
    <td><%= article.text %></td>
    <td><%= link_to 'Show', article_path(article) %></td>
    <td><%= link_to 'Edit', edit_article_path(article) %></td>
  </tr>
<% end %>
</table>

And we'll also add one to the app/views/articles/show.html.erb template as well, so that there's also an "Edit" link on an article's page. Add this at the bottom of the template:

<%= link_to 'Back', articles_path %>
| <%= link_to 'Edit', edit_article_path(@article) %>

How to implement the delete part of CRUD?

The route entry for deleting a resource (in this case, an article):

DELETE /articles/:id(.:format)      articles#destroy

The delete routing method should be used for routes that destroy resources. If this was left as a typical get route, it could be possible for people to craft malicious URLs like this:

<a href='http://example.com/articles/1/destroy'>look at this cat!</a>

We use the delete method for destroying resources, and this route is mapped to the destroy action inside app/controllers/articles_controller.rb, which doesn't exist yet, but is provided below:

def destroy
  @article = Article.find(params[:id])
  @article.destroy

  redirect_to articles_path
end

You can call destroy on Active Record objects when you want to delete them from the database. Note that we don't need to add a view for this action since we're redirecting to the index action.

Finally, add a 'Destroy' link to your index action template (app/views/articles/index.html.erb) to wrap everything together.

<h1>Listing Articles</h1>
<%= link_to 'New article', new_article_path %>
<table>
  <tr>
    <th>Title</th>
    <th>Text</th>
    <th colspan="3"></th>
  </tr>

<% @articles.each do |article| %>
  <tr>
    <td><%= article.title %></td>
    <td><%= article.text %></td>
    <td><%= link_to 'Show', article_path(article) %></td>
    <td><%= link_to 'Edit', edit_article_path(article) %></td>
    <td><%= link_to 'Destroy', article_path(article),
                    method: :delete, data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>
</table>

Here we're using link_to in a different way. We pass the named route as the second argument, and then the options as another argument. The :method and :'data-confirm' options are used as HTML5 attributes so that when the link is clicked, Rails will first show a confirm dialog to the user, and then submit the link with method delete. This is done via the JavaScript file jquery_ujs which is automatically included into your application's layout (app/views/layouts/application.html.erb) when you generated the application. Without this file, the confirmation dialog box wouldn't appear.

How to implement the article-comments relationship?

Like with any blog, our readers will create their comments directly after reading the article, and once they have added their comment, will be sent back to the article show page to see their comment now listed. Due to this, our CommentsController is there to provide a method to create comments and delete spam comments when they arrive.

So first, we'll wire up the Article show template (app/views/articles/show.html.erb) to let us make a new comment:

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Back', articles_path %>
| <%= link_to 'Edit', edit_article_path(@article) %>

This adds a form on the Article show page that creates a new comment by calling the CommentsController create action. The form_for call here uses an array, which will build a nested route, such as /articles/1/comments.

Let's wire up the create in app/controllers/comments_controller.rb:

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

You'll see a bit more complexity here than you did in the controller for articles. That's a side-effect of the nesting that you've set up. Each request for a comment has to keep track of the article to which the comment is attached, thus the initial call to the find method of the Article model to get the article in question.

In addition, the code takes advantage of some of the methods available for an association. We use the create method on @article.comments to create and save the comment. This will automatically link the comment so that it belongs to that particular article.

Once we have made the new comment, we send the user back to the original article using the article_path(@article) helper. As we have already seen, this calls the show action of the ArticlesController which in turn renders the show.html.erb template. This is where we want the comment to show, so let's add that to the app/views/articles/show.html.erb.

<p>
  <strong>Title:</strong>
  <%= @article.title %>
</p>

<p>
  <strong>Text:</strong>
  <%= @article.text %>
</p>

<h2>Comments</h2>
<% @article.comments.each do |comment| %>
  <p>
    <strong>Commenter:</strong>
    <%= comment.commenter %>
  </p>

  <p>
    <strong>Comment:</strong>
    <%= comment.body %>
  </p>
<% end %>

<h2>Add a comment:</h2>
<%= form_for([@article, @article.comments.build]) do |f| %>
  <p>
    <%= f.label :commenter %><br>
    <%= f.text_field :commenter %>
  </p>
  <p>
    <%= f.label :body %><br>
    <%= f.text_area :body %>
  </p>
  <p>
    <%= f.submit %>
  </p>
<% end %>

<%= link_to 'Edit Article', edit_article_path(@article) %> |
<%= link_to 'Back to Articles', articles_path %>

How to delete comment?

Another important feature of a blog is being able to delete spam comments. To do this, we need to implement a link of some sort in the view and a destroy action in the CommentsController. So first, let's add the delete link in the app/views/comments/_comment.html.erb partial:

<p>
  <strong>Commenter:</strong>
  <%= comment.commenter %>
</p>

<p>
  <strong>Comment:</strong>
  <%= comment.body %>
</p>

<p>
  <%= link_to 'Destroy Comment', [comment.article, comment],
               method: :delete,
               data: { confirm: 'Are you sure?' } %>
</p>

Clicking this new "Destroy Comment" link will fire off a DELETE /articles/:article_id/comments/:id to our CommentsController, which can then use this to find the comment we want to delete, so let's add a destroy action to our controller (app/controllers/comments_controller.rb):

class CommentsController < ApplicationController
  def create
    @article = Article.find(params[:article_id])
    @comment = @article.comments.create(comment_params)
    redirect_to article_path(@article)
  end

  def destroy
    @article = Article.find(params[:article_id])
    @comment = @article.comments.find(params[:id])
    @comment.destroy
    redirect_to article_path(@article)
  end

  private
    def comment_params
      params.require(:comment).permit(:commenter, :body)
    end
end

The destroy action will find the article we are looking at, locate the comment within the @article.comments collection, and then remove it from the database and send us back to the show action for the article.

How to delete associated objects?

If you delete an article then its associated comments will also need to be deleted. Otherwise they would simply occupy space in the database. Rails allows you to use the dependent option of an association to achieve this. Modify the Article model, app/models/article.rb, as follows:

class Article < ActiveRecord::Base
  has_many :comments, dependent: :destroy
  validates :title, presence: true,
                    length: { minimum: 5 }
end
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License