Skip to content

Instantly share code, notes, and snippets.

@SalahHamza
Last active February 21, 2019 21:30
Show Gist options
  • Select an option

  • Save SalahHamza/ed50c379d8f93dda3b8db49ffb21d94d to your computer and use it in GitHub Desktop.

Select an option

Save SalahHamza/ed50c379d8f93dda3b8db49ffb21d94d to your computer and use it in GitHub Desktop.
Professional Ruby on Rails Developer with Rails 5 Course Notes

Professional Ruby on Rails Developer with Rails 5

Ruby Introduction

Ruby On Rails

MVC pattern - Quick intro

Rails follow the MVC pattern.

How it works (oversimplified)

  • Client sends a request to a web server (one that is running a rails app),
  • within the rails app there is a router that checks what the client is requesting, then delegates that request to the appropriate controller,
  • the controller interacts with a model, which in return interacts with a database to get some data and sends it back to the controller
  • the controller then fills the view with that data (if there is any, and if that's how the app works) and renders the view

Naming Conventions

  • Controller name must be snake_case e.g name_controller.rb (in controllers/ folder)
  • For the controller, there has to be a folder that has the same name as the controller e.g example/ under views/folder, that's if views are to be created
  • For every view there needs to be an action defined (in the controller) and a view with the same name as the action in views/example/ e.g. If the action name is home (actions are procedures/functions) then the view should be named home.html.erb (inside views/example/)

Note: .erb stands for embeded ruby

Create a Table and CRUD operations

Migrations

Rails Migration allows you to use Ruby to define changes to your database schema, making it possible to use a version control system to keep things synchronized with the actual code.

Read More about migrations:

Creating a Table/Model and Naming Conventions

If we are creating a Todos app and we want to create a Todos table, we need to make:

  • Controller name - todos_controller.rb in controllers/ folder
  • Table name - todos table, plural since it contains lots of tables (multiple words table name should be snake_case)
  • Model name - todo.rb in models/, with a class definition Todo, singular since the model describes one todo

You can read more about it in Convention over Configuration in Active Record.

To do the above, we first use a generator:

rails generate migration create_todos

This will generate a migration file (inside db/migrate) with a timestamp YYYYMMDDHHMMSS_create_todos.rb (the YYYYMMDDHHMMSS is the timestamp), the file contains a CreateTodos class, exactly like so:

class CreateTodos < ActiveRecord::Migration[5.2]
  def change
    create_table :todos do |t|
    end
  end
end

which we can add columns to. Columns can be added in this (basic) format t.type :column_name, where type is the column type and column_name is the column name.

So if we want to add a title and description columns to the todos table, the class will become like this:

class CreateTodos < ActiveRecord::Migration[5.2]
  def change
    create_table :todos do |t|
      ## we specify the type/name of the field
      ## t.type :name
      t.string :title # 'String' type is limited to 256 characters or less
      t.text :description
    end
  end
end

Doing this alone will not impact the database. To do that (create the table) we need to run the migration, and we do that with this rails task:

rails db:migrate

This will update your db/schema.rb file to match the structure of your database.

If we try to interact with the created table, in this case the todos table in the rails console, it will throw an error, and that's because we need a model to interact with items in the database.

To create a model, we create a file singular_table_name.rb (snake_case) in models/ folder, in this case todo.rb, within the file there has to be a class named SingularTableName (CamelCase) in this case Todo.

class Todo < ApplicationRecord

end

Note: In rails 5 all models are subclasses of the ApplicationRecord provided by rails in models/application_record.rb.

Now we can interact with the table in the rails console, but so far the table is empty.

Note: Validation is also done here. Read more about ActiveRecord Validations

Create Items

Creating an item in a table is easy and takes three steps (do this in the rails console):

  1. Initiate a new item object (Todo object in our example), that can be done using ModelName.new (Todo.new)
> newTodo = Todo.new 
  1. Fill the object with values corresponding to the table columns (todo title and description in our case)
> newTodo.title = "Learn Rails" 
> newTodo.description = "Learn Ruby on Rails and become a better developer"
  1. Save the object to the database
> newTodo.save

Notes:

  • Since we didn't specify a primary_key in the todo model, active record will assign an id (starting at 1) field as the primary_key and will autoincrement it everytime a todo is added.
  • To both create and save the object to the database you can use create method. e.g.
> Todo.create(title: "Learn Rails", description: "Learn Ruby on Rails and become a better developer")
Get Items

Typing Todo.all will return all todos in the database.

If we want to find a specific todo by id we can use .find:

# looking for todo with id=2
> todoFound = Todo.find 2

we can get the first todo

> firstTodo = Todo.first

or the last todo

> lastTodo = Todo.last
Update Items

We can manipulate the object we got and save it to update the todo:

> todoFound = Todo.find 2
> todoFound.title = "Do something else"
> todoFound.description = "Do something else entirely"
> todoFound.save
Delete Items

To Delete a todo from the database we can use the destroy method:

> todo1 = Todo.find 1
> todo1.destroy

Or to delete all todos we can use .destroy_all

> Todo.destroy_all

Read more about it in Active Record Basics - CRUD: Reading and Writing Data

Routing

Adding/Deleting/Updating items in the database through the console is only a developer thing, the user doesn't (and shouldn't) have access to the console. That's is why usually a model has associated routes for CRUD action. This table illustrates these routes and what they are responsible for:

HTTP Verb Path Controller#Action Used for
GET /photos photos#index display a list of all photos
GET /photos/new photos#new return an HTML form for creating a new photo
POST /photos photos#create create a new photo
GET /photos/:id photos#show display a specific photo
GET /photos/:id/edit photos#edit return an HTML form for editing a photo
PATCH /PUT /photos/:id photos#update update a specific photo
DELETE /photos/:id photos#destroy delete a specific photo

Specifying every single route can be tiresome, fortunately rails can generate these routes for you. To that open the /config/routes.rb and write this line of code resources :todos, so the file will look this:

Rails.application.routes.draw do
  # Other routes here ...
  resources :todos

  # Other routes here ...
end

Navigating to one of the created routes will throw an error which will say that the controller doesn't exist, in this case the TodosController which we will need to create under controller/todos_controller.rb. Writing a TodosController will solve the error:

class TodosController < ApplicationController

end

Trying again will throw another error stating that there is no action corresponding to that route, for example if we visit /todos/new the error will be The action 'new' could not be found for TodosController. Writing a new action will solve the error:

class TodosController < ApplicationController
  def new
  end
end

And now the error will state that TodosController#new is missing a template for this request format..., which means we need to create a view template file corresponding to this action. To that we first need to create a todos/ folder under views/ and in that folder we create a template for every action (actions that don't need templates are create, update and destroy).

So we create views/todos/new.html.erb. Visiting /todos/new will now show a blank page, all we need to do is fill it accordingly (/todos/new should show a form) and this can be done for other routes too.

Form Helpers

Forms in web applications are an essential interface for user input. However, form markup can quickly become tedious to write and maintain because of the need to handle form control naming and its numerous attributes. Rails does away with this complexity by providing view helpers for generating form markup. However, since these helpers have different use cases, developers need to know the differences between the helper methods before putting them to use.

In the past section we created a TodosController with a new action (method) and a template under views/todos/new.html.erb, which enabled us to visit the /todos/new route, but only shows us a blank page and that's because the template is empty.

To create form content we can simple, init a new Todo object and pass it to an instance variable (variables inside classes with @ in the beginning) inside the new action:

  def new
    @todo = Todo.new
  end

and in the new.html.erb file we can create form field with th help of form helpers:

<%= form_for @todo do |f| %>

<%= f.label :title  %>
<%= f.text_field :title, placeholder: "Give your todo a title" %>

<%= f.label :description  %>
<%= f.text_area :description, placeholder: "describe your todo" %>

<%= f.submit "Add"  %>
<% end %>

Submitting this form will throw an error as there is no controller to handle the creating of the todo or create route.

For that we create a create action in the TodosController. The action should:

  • get the data of the form
  • create a new todo and saves it
    • if todo is saved we redirect to the created todo show page.
    • else we redirect to the new todo form again and we prompt the errors
class TodosController < ApplicationController
  # new action ...

  def create
  end
end

Note: As the create action isn't concerned with showing anything, we shouldn't create a template for it. Same goes for the update and delete.

The form data is stored in a hash under the name of params, and since we created the form using rails form helpers and a @todo object, rails sets the new todo form data under params[:todo].

We can now easily create a new todo in the database using the Todo.new(params[:todo]) and then we save the ActiveRecord object we get back, but that would be a bit unsafe since the user can send anything. To avoid that we use Strong Parameters which allows us to "protect attributes from end-user assignment" and that's by creating a private method that checks what we require and permit:

# ...
private
  def todo_params
    params.require(:todo).permit(:title, :description)
  end

What we are saying here is that we require todo and we only permit title and description on that todo. And we cans simply use it in the create action. We can also use it in the update action later on since it requires the same thing.

class TodosController < ApplicationController
  # new action ...

  def create
    @todo = Todo.new(todo_params)

    if @todo.save
      redirect_to todo_path(@todo)
    else
      render 'new'
    end
  end
  
  private

    def todo_params
      params.require(:todo).permit(:title, :description)
    end
end

We could also show the errors when the todo save fails, and we have access to them in new.html.erb (since we redirected to it on save fail) through @todo.errors, so we could put a conditional statement to check if we have any errors and we show a message

<% if @todo.errors.any? %>
  <p>Failed to create todo, here is why:</p>
  <ul>
    <% @todo.errors.full_messages.each do |msg| %>
    <li><%= msg %></li>
    <% end %>
  </ul>
<% end %>

When the todo is successfully created the server will redirect to the show page of that todo /todos/:id (:id is the created todo id), so we need to create a show action and a template under views/todos/show.html.erb.

In the show action we need to find the created todo by its :id and like we did earlier set it to an instance method so that we can access it in the view and show the todo.

We previously worked with params. params hash also holds the route params. e.g. when visiting a specific todo /todos/:id we have access to the id attribute through params[:id]. So the show action should look something like this:

def show
  @todo = Todo.find(params[:id])
end

Note: We can also add a condition to check if we actually found the todo, since the show route isn't only accessed when the an item is created.

All we need now is the edit,update and index actions, they are similar to the new and create actions, except in the the edit action we don't show an empty form, we show the form filled with the todo info we want to edit.

Fortunately rails does that for us. So it would look something like this:

# app/controllers/todo_controller.rb
class TodosController < ApplicationController
  # other actions
  def edit
    @todo = Todo.find(params[:id])
  end
  
  def update
    @todo = Todo.find(params[:id])
    # we check if the todo was updated
    if @todo.update(todo_params)
      # we set a flash message 
      flash[:notice] = "Todo was updated successfully"
      redirect_to todo_path(@todo)
    else
      flash[:alert] = "Todo was not updated"
      render 'edit'
    end
  end
  # other actions
end
<%# /app/views/todos/edit.html.erb  %>
<%= form_for @todo do |f| %>
 <%# form fields go here %>
<% end %>

The index action (show all todos route) is easier, we just need to get all todos using Todo.all and loop over them with the .each method in the index.html.erb.

For the destroy action that takes care of deleting a todo, all we need to do is find the todo and delete it, if we successfully delete the todo then we redirect to the todos listing or the index route, else then we show the todo. This can easily be done like so:

def destroy
  @todo = Todo.find(params[:id])
  if(@todo.destroy)
    flash[:notice] = "Todo was deleted successfully"
    redirect_to todos_path
  else
    flash[:alert] = "Todo was not updated"
    render todo_path(@todo)
  end

Note: I also set flash messages, which we will see in the next section

Flash

The best way to prompt to the user if the creation of an item has been successful or not is using flash, and that's because they just live to fulfill their purpose which is to show once to the user and get destroyed.

A flash message can be set on the flash hash, and accessed in the view with the same name. Read more about flash in rails.

Links

Having to manually write the url for each route is a cumbersome task, rails comes with helper to make it easy and that's the link_to helper. It takes this form <%= link_to "anchor tag text here" path %>. So if you want to make:

  • New todo form link:
<%= link_to "Create new todo", new_todo_path %>
  • Edit todo form link (we give it the todo object):
<%= link_to "Edit new todo", edit_todo_path(todo) %>`
  • Show todo link:
<%= link_to "Create new todo", todo_path(todo) %>
  • Delete todo link (we specify that the method is delete):
<%= link_to "Delete Todo", todo_path(todo), method: :delete, data: {confirm: "Are you sure?"} %>
  • Show all todos link:
<%= link_to "Show me all todos", todos_path %>

Partials

Partials help us to split the layout into manageable chunks. For example we can take some code that we think can be split into its own file like a header or footer and create separate files for them, for the header chunk create a _header.html.erb and for the footer a _footer.html.erb under views/layouts. If you notice then you'll see that the partial file names start with an _ (underscore).

To reference a partial in a certain file you need to do it like so <%= render 'path/to/partial'%> the path/to/partial must be under the views/ folder. So for example to reference the header partial we talked about above we can just write <%= render 'layouts/header'%>. If you notice again you'll see we don't need to prefix the partial name with an underscore when referencing it.

Note: It's a convention that the partial resides in the same folder (or a child folder) the file referencing it resides in. So for partials that are added to application.html.erb they should reside in views/layouts and for partials that are added to a todos view then they can be added to views/todos.

Must Reads

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment