New Learning Rails

Personal notes on Learning Rails from Ruby on Rails Tutorial 3RD ED.. This covers from Chapter 1 to Chapter 3.

Install RubyGems

which gem
# install the latest
gem update --system 

# Creating a gem configuration file.
$ vim ~/.gemrc

# Suppressing the ri and rdoc documentation in .gemrc.
# Otherwise it will take more time to install
install: --no-rdoc --no-ri
update:  --no-rdoc --no-ri

$ gem install rails --version 4.2.2

$ rails -v

The first application

$ mkdir rails_projects
$ cd rails_projects
$ rails _4.2.2_ new hello_app

Bundler and Gemfile

$ cd hello_app/
$ vim Gemfile

# Gemfile
source 'https://rubygems.org'

gem 'rails',                '4.2.2'
gem 'sass-rails',           '5.0.2'
gem 'uglifier',             '2.5.3'
gem 'coffee-rails',         '4.1.0'
gem 'jquery-rails',         '4.0.3'
gem 'turbolinks',           '2.3.0'
gem 'jbuilder',             '2.2.3'
gem 'sdoc',                 '0.4.0', group: :doc

group :development, :test do
  gem 'sqlite3',     '1.3.9'
  gem 'byebug',      '3.4.0'
  gem 'web-console', '2.0.0.beta3'
  gem 'spring',      '1.1.3'
end

$ bundle install 

#rails server
$ rails server

Hello, world!

In app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  def hello
    render text: "hello, world!"
  end
end

In config/routes.rb

Rails.application.routes.draw do
  .
  .
  # You can have the root of your site routed with "root"
  root 'application#hello'
  .
end

git init, gitignore, add and commit

$ git config --global user.name "Your Name"
$ git config --global user.email your.email@example.com
$ git config --global push.default matching
$ git config --global alias.co checkout

git init

# change .gitignore
# Ignore bundler config.
/.bundle

# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal

# Ignore all logfiles and tempfiles.
/log/*.log
/tmp

# Ignore other unneeded files.
doc/
*.swp
*~
.project
.DS_Store
.idea

git add .

git commit -m "Initial commit"

git log

q # to quit

Undoing by checkout

# you do something wrong
$ ls app/controllers/
application_controller.rb
$ rm -rf app/controllers/
$ ls app/controllers/
ls: app/controllers/: No such file or directory

# the changes are only on the working tree. The changes are not commited yet.
git status 

# Undo the chnages with -f flag to force overwriting
git checkout -f
# or delete the branch
git checkout master
git -D mess-branch
git status

GitHub

# adding to github
# first create a repo in github

git remote add origin https//github.com/<username>/first_app.git
git push -u origin master

Branch, edit, commit, merge

# Branch
# create a branch called modify-README
git checkout -b modify-README

# Switched to a new branch 'modify-README'
$ git branch
master
* modify-README

# Edit
git mv README.rdoc README.md
subl README.md

# Commit
git status
git commit -a -m "Improve the README file"

# Merge
# Switched to branch 'master'
git checkout master

git merge modify-README

# delete the brance if you want
git branch -d modify-README


# For illustration only; don't do this unless you mess up a branch
$ git checkout -b topic-branch
$ <really screw up the branch>
$ git add .
$ git commit -a -m "Major screw up"
$ git checkout master
# to abandon your topic branch changes, in this case with git branch -D:
# -D flag will delete the branch even though we haven’t merged in the changes.
$ git branch -D topic-branch

#Push
git push

Heroku setup

Install Heroku toolbelt.

# Heroku uses PostgreSQL
# in Gemfile add this
group :production do
  gem 'pg', '0.17.1'
  gem 'rails_12factor', '0.0.2'
end

bundle install --without production

rails_12factor gem is to serve static assets such as images and stylesheets

In order to install it,

$ bundle install --without production

$ git commit -a -m "Update Gemfile.lock for Heroku"

--without production prevents installing production gems.

$ heroku login
$ heroku keys:add
$ cd /Desktop/rails_projects/first_app
$ heroku create

$ heroku git:remote -a name-inheroku-1234

# Heroku deployment
$ git push heroku master

# Open it in a browser
$ heroku open
Opening ****-****-**** ... done

Heroku commands

# if you need to rename
$ heroku rename new-name

Chapter 2 A toy app

The Users resource

Rails scaffolding is generated by passing the scaffold command to the rails generate script. The argument of the scaffold command is the singular version of the resource name (in this case, User), together with optional parameters for the data model’s attributes.

# create a new project 
$ cd /Desktop/rails_projects
$ rails _4.2.2_ new toy_app
$ cd toy_app

Open the Gemfile and change it to the followings.

source 'https://rubygems.org'

gem 'rails',        '4.2.2'
gem 'sass-rails',   '5.0.2'
gem 'uglifier',     '2.5.3'
gem 'coffee-rails', '4.1.0'
gem 'jquery-rails', '4.0.3'
gem 'turbolinks',   '2.3.0'
gem 'jbuilder',     '2.2.3'
gem 'sdoc',         '0.4.0', group: :doc

group :development, :test do
  gem 'sqlite3',     '1.3.9'
  gem 'byebug',      '3.4.0'
  gem 'web-console', '2.0.0.beta3'
  gem 'spring',      '1.1.3'
end

group :production do
  gem 'pg',             '0.17.1'
  gem 'rails_12factor', '0.0.2'
end

Install these without production

$ bundle install --without production

Using git create a new github repository of demo_app.

$ git init
# Add .DS_Store to .gitignore for Mac users.

$ add .
$ git commit -m "Initial commit"
$ git remote add origin git@bitbucket.org/sokada/toy_app.git
$ git push -u origin -all

Add "hello world!" in route

$ git commit -am "Add hello"
$ heroku create
$ git push heroku master

The Users resource

$ rails generate scaffold User name:string email:string

# Migrate DB, This simply updates the database with our new users data model. 
# This simply updates the database with our new users data model.
# You may be able to use rake command
$ bundle exec rake db:migrate

# check it on a server http://localhost:3000/, s stands for server.
$ rails s

# To see all the Rake tasks available, run
$ bundle exec rake -T
# for rake db
$ bundle exec rake -T db

Check out

  • http://localhost:3000/users
  • http://localhost:3000/users/new
  • http://localhost:3000/users/1
  • http://localhost:3000/users/1/edit

Now check config/routes.rb. resources :users is the key code which coresponds url and source codes.

Rails.application.routes.draw do
  resources :users
  root 'users#index'
  .
  .
ends

Check the source codes. Open app/controllers/users_controller.rb.

index, show, new and edit correspond to pages. create, update, destroy modify information in the database.

A variable @users stores all the users.

class UsersController < ApplicationController

  def index
    @users = User.all
  end


end

Open app/views/users/index.html.erb to see the index view.

<% @users.each do |user| %>
...
  <tr>
    <td><%= user.name %></td>
    <td><%= user.email %></td>
    <td><%= link_to 'Show', user %></td>
    <td><%= link_to 'Edit', edit_user_path(user) %></td>
    <td><%= link_to 'Destroy', user, method: :delete,
                                     data: { confirm: 'Are you sure?' } %></td>
  </tr>
<% end %>
...
<%= link_to 'New User', new_user_path %>

And check app/models/user.rb for it's model.

class User < ActiveRecord::Base
end

@users get from the user model (user.rb) with all method.

Microposts scaffold

$ rails generate scaffold Micropost content:string user_id:integer

$ bundle exec rake db:migrate

Check app/config/routes.rb to see a new resource.

DemoApp::Application.routes.draw do
  resources :microposts
  resources :users
  ...
end

Validation

app/models/micropost.rb

class Micropost < ActiveRecord::Base
  validates :content, length: { maximum: 140 }
end

Try to add more than 140 characters at http://localhost:3000/microposts/new to see the validation at work.

A user has_many microposts

# app/models/user.rb
class User < ActiveRecord::Base
  has_many :microposts
end

# app/models/micropost.rb
class Micropost < ActiveRecord::Base
  belongs_to :user
  validates :content, length: { maximum: 140 }
end

Invoking rails console.

# terminal
$ rails console
>> first_user = User.first
>> first_user # to display first_user
>> first_user.name
>> first_user.email
>> first_user.microposts # to display all microposts by first_user

>> User.all # to display all users

>> micropost = first_user.mocroposts.first
>> micropost.user

$ exit # or ctrl+d

Inheritance hierarchies

User model and the Micropost model inherit (via the left angle bracket <) from ActiveRecord::Base which is the base class for models provided by ActiveRecord.

The User class, with inheritance. app/models/user.rb

class User < ActiveRecord::Base
  .
  .
  .
end

The Micropost class, with inheritance. app/models/micropost.rb

class Micropost < ActiveRecord::Base
  .
  .
  .
end

The Users controller and the Microposts controller inherit from the Application controller.

The UsersController class, with inheritance. app/controllers/users_controller.rb

class UsersController < ApplicationController
  .
  .
  .
end

The MicropostsController class, with inheritance. app/controllers/microposts_controller.rb

class MicropostsController < ApplicationController
  .
  .
  .
end

The ApplicationController class, with inheritance. app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  .
  .
  .
end

Deploying the demo app

Add to git and push it.

# git
$ git add .
$ git commit -m "Finish demo app"
$ git push

$ heroku create # if you have not created it
$ git push heroku 
$ heroku run rake db:migrate

# To get the application’s database to work, you’ll also have to migrate the production database
$ heroku run rake db:migrate

$ heroku logs

Check the Heroku site on a browser. If it gives an error, check it with heroku logs.

Sample app Lesson 3

Setup

$ cd ~/rails_projects
$ rails _4.2.2_ new sample_app
$ cd sample_app

source 'https://rubygems.org'

gem 'rails',        '4.2.2'
gem 'sass-rails',   '5.0.2'
gem 'uglifier',     '2.5.3'
gem 'coffee-rails', '4.1.0'
gem 'jquery-rails', '4.0.3'
gem 'turbolinks',   '2.3.0'
gem 'jbuilder',     '2.2.3'
gem 'sdoc',         '0.4.0', group: :doc

group :development, :test do
  gem 'sqlite3',     '1.3.9'
  gem 'byebug',      '3.4.0'
  gem 'web-console', '2.0.0.beta3'
  gem 'spring',      '1.1.3'
end

group :test do
  gem 'minitest-reporters', '1.0.5'
  gem 'mini_backtrace',     '0.1.3'
  gem 'guard-minitest',     '2.3.1'
end

group :production do
  gem 'pg',             '0.17.1'
  gem 'rails_12factor', '0.0.2'
end

Install, init git, heroku setup.

$ bundle install --without production

$ git init
$ git add -A
$ git commit -m "Initialize repository"
$ git mv README.rdoc README.md
# modify README.md
$ git commit -am "Improve the README"
$ git remote add origin git@bitbucket.org:<username>/sample_app.git
$ git push -u origin --all
# add hello to config/routes and application_controller.rb as we did in the first chapter
$ git commit -am "Add hello"
$ heroku create
$ git push heroku master
$ heroku logs

Static pages

$ git checkout master
$ git checkout -b static-pages

Generated static pages

Generating a static pages controller for Home and Help.

$ rails generate controller StaticPages home help

Rails shortcuts

$ rails s # shortcut for rails server
$ rails g # shorcut for rails generate
$ rails c # for rails console
$ bundle # for bundle install
$ rake # for rake test


$ git status
$ git add -A
$ git commit -m "Add a Static Pages controller"
$ git push -u origin static-pages

Dynamically generating a secret token

Add config/initializers/secret_token.rb to .gitignore file

config/initializers/secret_token.rb

# Be sure to restart your server when you modify this file.

# Your secret key is used for verifying the integrity of signed cookies.
# If you change this key, all old signed cookies will become invalid!

# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
# You can use `rake secret` to generate a secure secret key.

# Make sure your secret_key_base is kept private
# if you're sharing your code publicly.
require 'securerandom'

def secure_token
  token_file = Rails.root.join('.secret')
  if File.exist?(token_file)
    # Use the existing token.
    File.read(token_file).chomp
  else
    # Generate a new token and store it in token_file.
    token = SecureRandom.hex(64)
    File.write(token_file, token)
    token
  end
end

SampleApp::Application.config.secret_key_base = secure_token

Installing rspec and git

$ rails generate rspec:install

$ git init
# add DS_Store to .gitignore

$ git add .
$ git commit -m "Initial commit"

# Change README.rdoc file
$ git mv README.rdoc README.md
$ git commit -am "Improve the README"
# Change the README file content.

# Create a new rep in github first. Then
$ git remote add origin https://github.com/<username>/sample_app.git
$ git push -u origin master

Deploy the app to Heroku.

$ heroku create
$ rake assets:precompile
$ git commit -a -m "Add precompiled assets for Heroku"
$ git push heroku master
$ heroku run rake db:migrate

$ git push
$ git push heroku
$ heroku run rake db:migrate

$ heroku logs

In the video,

$ heroku create --stack cedar
$ git push heroku master

To find URL run less .git/config

$ less .git/config

[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
        ignorecase = true
        precomposeunicode = false
[remote "origin"]
        url = https://github.com/shinokada/sample_app.git
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
        remote = origin
        merge = refs/heads/master
[remote "heroku"]
        url = git@heroku.com:whispering-spire-8180.git
        fetch = +refs/heads/*:refs/remotes/heroku/*
.git/config (END)

# to quit/exit
$ q 

Easier way to open it is heroku open.

$ heroku open

Static pages

Serving assets in production

/config/environments/production.rb

SampleApp::Application.configure do
  ...
  config.serve_static_assets = true
  ...
end

$ git push
$ git push heroku
$ heroku logs

Undoing

# controller
$ rails generate controller StaticPages home help
$ rails destroy  controller StaticPages home help

# model
$ rails generate model User name:string email:string
$ rails destroy model User

# Migrageion
$ bundle exec rake db:migrate
# or
$ rake db:migrate
$ bundle exec rake db:rollback
# or
$ rake db:rollback
$ bundle exec rake db:migrate VERSION=0 # to go all the way back to the beginning
# or
$ rake db:migrate VERSION=0

Check it on a browser to visit http://localhost:3000/static_pages/help and http://localhost:3000/static_pages/home.

rails s

routes and controller

/config/routes.rb

SampleApp::Application.routes.draw do
  get "static_pages/home"
  get "static_pages/help"
  ...
end

Find the view at app/views/static_pages/home.html.erb. Find the controller at app/controllers/staticpagescontroller.rb

class StaticPagesController < ApplicationController

  def home
  end

  def help
  end
end

Add some contents to app/views/static_pages/home.html.erb and help.html.erb.

Test-driven development

Scafold create test/controllers/staticpagescontroller_test.rb.

Run tests.

$ bundle exec rake test 
# or
$ rake test

Red

test/controllers/staticpagescontroller_test.rb

require 'test_helper'

class StaticPagesControllerTest < ActionController::TestCase

  test "should get home" do
    get :home
    assert_response :success
  end

  test "should get help" do
    get :help
    assert_response :success
  end

  test "should get about" do
    get :about
    assert_response :success
  end
end

Run a test

$ bundle exec rake test # or rake test

Green

Add the following to config/routes.rb

get 'static_pages/about'

Run a test to fail again.

Modify app/controllers/staticpagescontroller.rb

class StaticPagesController < ApplicationController

  def home
  end

  def help
  end

  def about
  end
end

Run a test and it will give an error about missing template static_pages/about.

Add the following to app/views/static_pages/about.html.erb

<h1>About</h1>
<p>
  This is the sample application for the tutorial.
</p>

Run a test to pass.

Adding a title Test

$ mv app/views/layouts/application.html.erb foobar   # temporary change

Add to test/controllers/staticpagescontroller_test.rb

require 'test_helper'

class StaticPagesControllerTest < ActionController::TestCase

  test "should get home" do
    get :home
    assert_response :success
    assert_select "title", "Home | Ruby on Rails Tutorial Sample App"
  end

  test "should get help" do
    get :help
    assert_response :success
    assert_select "title", "Help | Ruby on Rails Tutorial Sample App"
  end

  test "should get about" do
    get :about
    assert_response :success
    assert_select "title", "About | Ruby on Rails Tutorial Sample App"
  end
end

And run a test to fail

$ bundle exec rake test 
# Or
$ rake test

Passing title tests

ERB is the primary template system for including dynamic content. <% ... %> executes the code inside and <%= ...%> executes and inserts the result.

app/views/static_pages/home.html.erb

<% provide(:title, "Home") %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>Sample App</h1>
    <p>
      This is the home page for the
      <a href="http://www.railstutorial.org/">Ruby on Rails Tutorial</a>
      sample application.
    </p>
  </body>
</html>

app/views/static_pages/help.html.erb

<% provide(:title, 'Help') %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>Help</h1>
    <p>
      Bla bla
    </p>
  </body>
</html>

app/views/static_pages/about.html.erb

<% provide(:title, 'About') %>
<!DOCTYPE html>
<html>
  <head>
    <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  </head>
  <body>
    <h1>About Us</h1>
    <p>
      Bla bla
    </p>
  </body>
</html>

Refactor

$ mv layout_file app/views/layouts/application.html.erb

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
<head>
     <title><%= yield(:title) %> | Ruby on Rails Tutorial Sample App</title>
  <%= stylesheet_link_tag    "application", media: "all",
                                            "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>
</head>
<body>

<%= yield %>

</body>
</html>

<%= yield %>

page /static_pages/home converts the contents of home.html.erb to HTML and then inserts it in place of <%= yield %>

app/views/static_pages/home.html.erb

<% provide(:title, 'Home') %>
<h1>Sample App</h1>
<p>
Bla bla
</p>

app/views/static_pages/help.html.erb

<% provide(:title, 'Help') %>
<h1>Help</h1>
<p>
Bla bla
</p>

app/views/static_pages/about.html.erb

<% provide(:title, 'About') %>
<h1>About Us</h1>
<p>
Bla bla
</p>

And run a test to pass

$ bundle exec rake test 
# Or
$ rake test

Setting the root route.

config/routes.rb

Rails.application.routes.draw do
  root 'static_pages#home'
  get  'static_pages/help'
  get  'static_pages/about'
end

git commit

$ git add .
$ git commit -m "Finish static pages"

$ git checkout master
$ git merge static-pages

$ git push
$ rake test
$ git push heroku

Advanced testing setup

MiniTest reporters

test/test_helper.rb

ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
require 'rails/test_help'
require "minitest/reporters"
Minitest::Reporters.use!

class ActiveSupport::TestCase
  # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical
  # order.
  fixtures :all

  # Add more helper methods to be used by all tests here...
end

Backtrace silencer

config/initializers/backtrace_silencers.rb

# Be sure to restart your server when you modify this file.

# You can add backtrace silencers for libraries that you're using but don't
# wish to see in your backtraces.
Rails.backtrace_cleaner.add_silencer { |line| line =~ /rvm/ }

Using Guard

Guard automates the running of the tests. Guard monitors changes in the filesystem so that when we change a file, only those tests get run. Or we can configure Guard so that a file is modified, the staticpagescontroller_test.rb runs.

Initialize Guard.

$ bundle exec guard init

Modify Guardfile.

Defines the matching rules for Guard.

guard :minitest, spring: true, all_on_start: false do
  watch(%r{^test/(.*)/?(.*)_test\.rb$})
  watch('test/test_helper.rb') { 'test' }
  watch('config/routes.rb')    { integration_tests }
  watch(%r{^app/models/(.*?)\.rb$}) do |matches|
    "test/models/#{matches[1]}_test.rb"
  end
  watch(%r{^app/controllers/(.*?)_controller\.rb$}) do |matches|
    resource_tests(matches[1])
  end
  watch(%r{^app/views/([^/]*?)/.*\.html\.erb$}) do |matches|
    ["test/controllers/#{matches[1]}_controller_test.rb"] +
    integration_tests(matches[1])
  end
  watch(%r{^app/helpers/(.*?)_helper\.rb$}) do |matches|
    integration_tests(matches[1])
  end
  watch('app/views/layouts/application.html.erb') do
    'test/integration/site_layout_test.rb'
  end
  watch('app/helpers/sessions_helper.rb') do
    integration_tests << 'test/helpers/sessions_helper_test.rb'
  end
  watch('app/controllers/sessions_controller.rb') do
    ['test/controllers/sessions_controller_test.rb',
     'test/integration/users_login_test.rb']
  end
  watch('app/controllers/account_activations_controller.rb') do
    'test/integration/users_signup_test.rb'
  end
  watch(%r{app/views/users/*}) do
    resource_tests('users') +
    ['test/integration/microposts_interface_test.rb']
  end
end

# Returns the integration tests corresponding to the given resource.
def integration_tests(resource = :all)
  if resource == :all
    Dir["test/integration/*"]
  else
    Dir["test/integration/#{resource}_*.rb"]
  end
end

# Returns the controller tests corresponding to the given resource.
def controller_test(resource)
  "test/controllers/#{resource}_controller_test.rb"
end

# Returns all tests for the given resource.
def resource_tests(resource)
  integration_tests(resource) << controller_test(resource)
end

Add spring/ directory to the .gitignore file.

Ignore Spring files.

/spring/*.pid

Unix processes

To see all the processes on your system

$ ps aux
# filter the processes by type
$ ps aux | grep spring
sokada          31639   0.2  1.2  2559416 104196   ??  Ss    9:20PM   0:04.58 spring app    | sample_app | started 17 mins ago | development mode

# To eliminate an individual unwanted process
$ kill -9 31639

# kill all spring processes
$ spring stop
# if this doesn't work 
$ pkill -9 -f spring

Start guard.

$ bundle exec guard
# or guard
$ guard

# To exit guard
Ctrl+D
Written on June 20, 2015