Setting up guard with minitest

Guard is a tool to autorun tests as and when they are created. It basically runs a background process that continuously checks for the tests. As soon as they are created, it runs and provides the results. I feel it is an essential thing to increase the pace of work with the tests.

We will start by adding guard and guard-minitest to the Gemfile. Make sure you also add “ruby-prof” to testing group in order to install the dependencies correctly.

group :development do
  gem 'guard'
  gem 'guard-minitest'
end

#testing gems
group :test do
  gem "minitest"
  gem 'minitest-spec-rails'
  gem 'ruby-prof'
end

Once, the installation is done, we need to generate a configuration file called Guardfile, for minitest.

$ guard init minitest

We can also uncomment the Rails 4 test watch rules. So, guard will now watch minitest unit and Rails 4 unit tests. In case you want to autorun minitest specs, you can also uncomment that part.

Guardfile
guard :minitest do
  # with Minitest::Unit
  watch(%r{^test/(.*)\/?test_(.*)\.rb$})
  watch(%r{^lib/(.*/)?([^/]+)\.rb$})     { |m| "test/#{m[1]}test_#{m[2]}.rb" }
  watch(%r{^test/test_helper\.rb$})      { 'test' }

  # with Minitest::Spec
  # watch(%r{^spec/(.*)_spec\.rb$})
  # watch(%r{^lib/(.+)\.rb$})         { |m| "spec/#{m[1]}_spec.rb" }
  # watch(%r{^spec/spec_helper\.rb$}) { 'spec' }

  # Rails 4
  watch(%r{^app/(.+)\.rb$})                               { |m| "test/#{m[1]}_test.rb" }
  watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
  watch(%r{^app/controllers/(.+)_controller\.rb$})        { |m| "test/integration/#{m[1]}_test.rb" }
  watch(%r{^app/views/(.+)_mailer/.+})                   { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
  watch(%r{^lib/(.+)\.rb$})                               { |m| "test/lib/#{m[1]}_test.rb" }
  watch(%r{^test/.+_test\.rb$})
  watch(%r{^test/test_helper\.rb$}) { 'test' }
end

We will try and run our guard now. Make sure you run the command using bundle exec guard else guard will throw a warning. This is to inform guard that we are using bundler to manage our gems.

$ bundle exec guard
12:21:21 - INFO - Guard is using NotifySend to send notifications.
12:21:21 - INFO - Guard is using TerminalTitle to send notifications.
12:21:21 - INFO - Guard::Minitest 2.3.1 is running, with Minitest::Unit 5.4.0!
12:21:21 - INFO - Running: all tests
[Coveralls] Set up the SimpleCov formatter.
[Coveralls] Using SimpleCov's 'rails' settings.
[Coveralls] Outside the Travis environment, not sending data.
Run options: --seed 52918

# Running:

......

Finished in 0.174430s, 34.3978 runs/s, 63.0627 assertions/s.

6 runs, 11 assertions, 0 failures, 0 errors, 0 skips

12:21:25 - INFO - Guard is now watching at '/home/saurabh/mealr'
[1] guard(main)> 

We are good to go now!

Rails 4.1 Integration testing basics

While working with Rails 4.1, I realized that the default unit test framework and minitest work very well together. So, I decided to look at several different scenarios in this context. We will start with creating a blank controller.

$ rails g controller welcome index

We now have a controller with a blank index method.

app/controllers/welcome_controller.rb
class WelcomeController < ApplicationController
  def index
  end
end

In order to test the index response,the method generated is as follows.

test/controllers/welcome_controller_test.rb
require 'test_helper'
require 'minitest/autorun'

class WelcomeControllerTest < ActionController::TestCase
  test "should get index" do
    get :index
    assert_response :success
  end
end

According to our specification, we need to make our welcome as route. We will first write a test for it. We need to create a test under the integration tests for the routes.

test/integration/root_test.rb
require 'test_helper'
require 'minitest/autorun'

class RootTest < ActionController::TestCase
	test "should route to root" do
		assert_routing "/", {controller: "welcome", action: "index"}
	end
end

We will now try to run this test, but it will fail.

$ rake test
Run options: --seed 57718
# Running:
F..
Finished in 0.159120s, 18.8537 runs/s, 25.1383 assertions/s.

  1) Failure:
RootTest#test_0001_should route to root [/home/saurabh/mealr/test/integration/root_test.rb:6]:
No route matches "/"

3 runs, 3 assertions, 1 failures, 0 errors, 0 skips

We will edit our routes to make the test pass.

config/routes.rb
Rails.application.routes.draw do
  root 'welcome#index'
end

Let’s run the tests and see how it goes.

$ rake test
Run options: --seed 42870
# Running:
...
Finished in 0.153732s, 19.5145 runs/s, 45.5338 assertions/s.

3 runs, 3 assertions, 0 failures, 0 errors, 0 skips

Alright, now we need to write a test for getting last 5 meals, sorted by available date.
In order to create this test, we need to create a fixture.

test/fixtures/meals.yml
# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html

one:
  name: Pizza
  chef_id: 1
  meal_type_id: 1
  food_preference_id: 1
  availability: 2014-07-27 20:40:39

two:
  name: Rissoto
  chef_id: 1
  meal_type_id: 1
  food_preference_id: 1
  availability: 2014-07-26 20:40:39

three:
  name: Pasta
  chef_id: 1
  meal_type_id: 1
  food_preference_id: 1
  availability: 2014-07-24 20:40:39


four:
  name: Burger
  chef_id: 1
  meal_type_id: 1
  food_preference_id: 1
  availability: 2014-07-24 20:40:39

five:
  name: Taco
  chef_id: 1
  meal_type_id: 1
  food_preference_id: 1
  availability: 2014-07-23 20:40:39

six:
  name: Burrito
  chef_id: 1
  meal_type_id: 1
  food_preference_id: 1
  availability: 2014-07-23 20:40:39

We have not created associations yet, so we can keep chef_id, meal_type_id and food_preference_id as ids. Also assert_equal will match the expected results and actual results. The test now looks like the following:

test/controllers/welcome_controller_test.rb
  test "should get last 5 meals sorted by available_date" do
  	get :index
  	assert_response :success
  	expected_response = [meals(:one), meals(:two), meals(:three), meals(:four), meals(:five)]
  	assert_equal expected_response, assigns(:meals)
  	assert_equal 5, assigns(:meals).length
  end

In order to pass this test, we need to write the code.

app/controllers/welcome_controller.rb
class WelcomeController < ApplicationController
  def index
      @meals = Meal.order(availability: :desc).limit(5)
  end
end

Great! So we have tested the descending order and limit.

Custom Login with Rails Part-2

In the previous part we saw how to build the signup page. In this part we will create sessions and persist our user object in the session. To start this, we will create a controller to handle the sessions.

$ rails g controller sessions new
      create  app/controllers/sessions_controller.rb
       route  get 'sessions/new'
      invoke  erb
      create    app/views/sessions
      create    app/views/sessions/new.html.erb
      invoke  test_unit
      create    test/controllers/sessions_controller_test.rb
      invoke  helper
      create    app/helpers/sessions_helper.rb
      invoke    test_unit
      create      test/helpers/sessions_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/sessions.js.coffee
      invoke    scss
      create      app/assets/stylesheets/sessions.css.scss

Modify the routes to create resources for the sessions controller.

config/routes.rb
Rails.application.routes.draw do

  resources :users
  resources :sessions
  root 'home#index'
end

In sessions controller, we will modify our actions to check the user by user_name. In case you want, you can change it to email or both. Rails does some more magic behind the scenes using the user.authenticate(params[:password]). has_secure_password generates the salt and allows us to use .authenticate method which matches the salted password with the params passed. Also, we will persist the user_id in the session. In order to create a logout, we will simply set the session’s user_id to nil.

app/controllers/sessions_controller.rb
class SessionsController < ApplicationController
  def new
  end

  def create
    user = User.find_by(user_name: params[:user_name]) rescue nil
    if (user && user.authenticate(params[:password]))
      session[:user_id] = user.id.to_s
      redirect_to dashboards_path
    else
      flash.now.alert = "Invalid Username or Password"
      render "new"
    end
  end

  def destroy
    session[:user_id] = nil
    redirect_to root_url, :notice => "Logged out!"
  end
end

Our login form will look like the following and will be accessible at sessions/new.

app/views/sessions/new
<% flash.each do |key, msg| %>
 <%= content_tag :p, msg, :class => [key] %>
<% end %>

<%= form_tag sessions_path do %>
 <label>User Name</label>
 <%= text_field_tag :user_name, params[:user_name] %>

 <label>Password</label>
 <%= password_field_tag :password, nil %>

 <%= submit_tag "Login"%>
<% end %>

We will also create a separate route for logout.

config/routes.rb
Rails.application.routes.draw do

  resources :users
  resources :sessions

  get "logout", to: "sessions#destroy", as: "logout"

  resources :dashboards
  root 'home#index'
end

We will create a blank controller where we can redirect the page after the session is created.

$ rails g controller dashboards index
      create  app/controllers/dashboards_controller.rb
       route  get 'dashboards/index'
      invoke  erb
      create    app/views/dashboards
      create    app/views/dashboards/index.html.erb
      invoke  test_unit
      create    test/controllers/dashboards_controller_test.rb
      invoke  helper
      create    app/helpers/dashboards_helper.rb
      invoke    test_unit
      create      test/helpers/dashboards_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/dashboards.js.coffee
      invoke    scss
      create      app/assets/stylesheets/dashboards.css.scss

Now, to access the persisted user as an object in the session we will create a current_user method. Through this we can access the user’s details when the he or she is in the session.

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
  helper_method :current_user

  private

  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end
end

We also need a filter method to protect our methods that we need to keep only for the logged in Users. In order to do that, we will create a method called authenticate_user. This method will check the presence of user_id and accordingly redirect to the respective page.

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
  helper_method :current_user

  private

  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end

  protected

  def authenticate_user
    if session[:user_id]
      # set current user object to @current_user object variable
      @current_user = User.find(session[:user_id]) rescue nil
      return true
    else
      redirect_to new_session_path
      return false
    end
  end
end

We can now put our dashboard behind the login.

app/controllers/dashboards_controller.rb
class DashboardsController < ApplicationController
  before_action :authenticate_user

  def index
  end
end

Also, we can use current_user object to show conditional login and logout links and also show the details of the session user.

app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
  <title>Login 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>

<% if current_user.present? %>
  <%= current_user.user_name %> <%= link_to "Logout", logout_path%>
<%else%>
  <%= link_to "Login", new_session_path %>
<% end %>

<%= yield %>

</body>
</html>

So, we have successfully created sessions, made method to protect actions and also persist the user in the session. In the Part-3 of the this tutorial, we will look at confirmation and password recovery.

Create custom login in Rails – Part 1

We will start with creating a model for the user. We need to create a field called password_digest for storing the encrypted password.

$ rails g model user user_name:string password_digest:string
      invoke  mongoid
      create    app/models/user.rb
      invoke    test_unit
      create      test/models/user_test.rb
      create      test/fixtures/users.yml

We need to add ‘bcrypt’ gem to the Gemfile as it is used by rails to encrypt the password internally.

Gemfile
gem 'bcrypt'

ActiveModel::SecurePassword is a module required for generating and validating passwords in rails. In order to enable this module on a particular model, we need to include it. Then access these methods using has_secure_password.

app/models/user.rb
class User
  include Mongoid::Document
  include Mongoid::Timestamps
  include ActiveModel::SecurePassword

  field :user_name, type: String
  field :password_digest, type: String

  has_secure_password
end

We have already added the ability to create users, password and password confirmation to our system. Let’s quickly check what we have done.

$ rails c
Loading development environment (Rails 4.1.1)
2.1.1 :001 > u = User.new
 => #
2.1.1 :002 > u.user_name = "saurabh"
 => "saurabh"
2.1.1 :003 > u.password = "123456"
 => "123456"
2.1.1 :004 > u.password_confirmation = "123456"
 => "123456"
2.1.1 :005 > u.save
  MOPED: 127.0.0.1:27017 COMMAND      database=admin command={:ismaster=>1} runtime: 2.0463ms
  MOPED: 127.0.0.1:27017 INSERT       database=learning_development collection=users documents=[{"_id"=>BSON::ObjectId('53c24217676172180d000000'), "user_name"=>"saurabh", "password_digest"=>"$2a$10$AvOo2g.RD4Sb31xHzspcJe34uzz6tY9roaZHQoYU4nwWZN8GyCe1C", "updated_at"=>2014-07-13 08:24:23 UTC, "created_at"=>2014-07-13 08:24:23 UTC}] flags=[]
                         COMMAND      database=learning_development command={:getlasterror=>1, :w=>1} runtime: 5.5310ms
 => true
2.1.1 :006 > u
 => #

In order to handle the creation of users, we will create a controller.

$ rails g controller users new
      create  app/controllers/users_controller.rb
       route  get 'users/new'
      invoke  erb
      create    app/views/users
      create    app/views/users/new.html.erb
      invoke  test_unit
      create    test/controllers/users_controller_test.rb
      invoke  helper
      create    app/helpers/users_helper.rb
      invoke    test_unit
      create      test/helpers/users_helper_test.rb
      invoke  assets
      invoke    coffee
      create      app/assets/javascripts/users.js.coffee
      invoke    scss
      create      app/assets/stylesheets/users.css.scss

We also need to setup the routes for users and create a root page. We will modify the users route to make it resful.

config/routes.rb
Rails.application.routes.draw do
  resources :users
  root 'home#index'
end

Let’s modify the controller and create a new and create method. Make sure you whitelist a limited set of params not permit all.

app/controllers/users_controller.rb
class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
      if @user.save
  	redirect_to root_path
  	flash["notice"] = "Signed Up Successfully"
      else
  	render "new"
        flash["error"] = "Problems with your Signup"
      end
  end

  def user_params
     params.require(:user).permit(:password, :password_confirmation, :user_name, :email)
  end
end

In order to create signup for the user, we need to add a form for it.

app/views/users/new.html.erb
<% if flash["notice"].present? -%>
   <%= flash[:notice]%>
<% end -%>

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

   <label>User Name</label>
   <%= f.text_field :user_name %>

   <label>Email</label>
   <%= f.email_field :email %>

   <label>Password</label>
   <%= f.password_field :password %>

   <label>Password Confirmation</label>
   <%= f.password_field :password_confirmation %>

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

We will create session and session objects in the next part.

Update: You can read the Part 2 of the tutorial here .

Rails engines with Mongoid

In order to create a rails engine that loads mongoid by default instead of activerecord, we will start with creating a full rails engine with an option -O. This is to skip the inclusion of any database settings or active record.

$ rails plugin new new_engine --full -O
      create  
      create  README.rdoc
      create  Rakefile
      create  new_engine.gemspec
      create  MIT-LICENSE
      create  .gitignore
      create  Gemfile
      create  app/models
      create  app/models/.keep
      create  app/controllers
      create  app/controllers/.keep
      create  app/views
      create  app/views/.keep
      create  app/helpers
      create  app/helpers/.keep
      create  app/mailers
      create  app/mailers/.keep
      create  app/assets/images/new_engine
      create  app/assets/images/new_engine/.keep
      create  config/routes.rb
      create  lib/new_engine.rb
      create  lib/tasks/new_engine_tasks.rake
      create  lib/new_engine/version.rb
      create  lib/new_engine/engine.rb
      create  app/assets/stylesheets/new_engine
      create  app/assets/stylesheets/new_engine/.keep
      create  app/assets/javascripts/new_engine
      create  app/assets/javascripts/new_engine/.keep
      create  bin
      create  bin/rails
      create  test/test_helper.rb
      create  test/new_engine_test.rb
      append  Rakefile
      create  test/integration/navigation_test.rb
  vendor_app  test/dummy
         run  bundle install
Fetching gem metadata from https://rubygems.org/...........
Fetching additional metadata from https://rubygems.org/..
Resolving dependencies...
................

Despite this, as rails loads all the railties, it even loads activerecord with it.Hence, we need to customize our list of railties in rails/bin file.

rails/bin
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.

ENGINE_ROOT = File.expand_path('../..', __FILE__)
ENGINE_PATH = File.expand_path('../../lib/new_engine/engine', __FILE__)

# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])

require "action_controller/railtie"
require "action_mailer/railtie"
require "sprockets/railtie"
require "rails/test_unit/railtie"
require "mongoid"
require 'rails/engine/commands'

We will now try to create a model inside the engine.

$ rails g model product title:string
bin/rails:15:in `require': cannot load such file -- mongoid (LoadError)
	from bin/rails:15:in `<main>'

In order to load mongoid, we will add it as a dependency to the gem. Make sure you use the same version of mongoid inside your application and rails engine.

new_engine.gemspec
$:.push File.expand_path("../lib", __FILE__)

# Maintain your gem's version:
require "new_engine/version"

# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
  s.name        = "new_engine"
  s.version     = NewEngine::VERSION
  s.authors     = ["TODO: Your name"]
  s.email       = ["TODO: Your email"]
  s.homepage    = "TODO"
  s.summary     = "TODO: Summary of NewEngine."
  s.description = "TODO: Description of NewEngine."
  s.license     = "MIT"

  s.files = Dir["{app,config,db,lib}/**/*", "MIT-LICENSE", "Rakefile", "README.rdoc"]
  s.test_files = Dir["test/**/*"]

  s.add_dependency "rails", "~> 4.1.1"
  s.add_dependency 'mongoid', '4.0.0'
end

At this point we will run bundle install again and try to create a model.


$ rails g model product title:string
      invoke  mongoid
      create    app/models/product.rb
      invoke    test_unit
      create      test/models/product_test.rb
      create      test/fixtures/products.yml

Voila! You can now develop your engine using mongoid.

Multiple Gemfiles in a rails app

In a large rails app, with several extensions as rails engines, it is a good idea to abstract them into different file. This is to keep the gems required for the app separate from custom built rails engines. We need to create a file in the app folder, and load it inside the main Gemfile.

Gemfile
..........
#default apps
eval(File.read(File.dirname(__FILE__) + '/default_apps.rb'))
#purchased apps
eval(File.read(File.dirname(__FILE__) + '/purchased_apps.rb'))
..........

Passing values to a concern

In order to pass values to a controller concern, we first need to create a variable and bind it to a callback. We will define a variable called app_title and pass it to a method in the concern called fetch_variables. We will use class initialize method to define the variable value.

app/controllers/admin/custom_app_controller.rb
class Admin::CustomAppController < AdminController
  include Authorize
  before_action ->(app_title = @app_title) { fetch_variables app_title }

  def initialize
    super
    @app_title = "custom_app"
  end
end

Now we will define a method in the concern to find and set variable on the callback.

app/controllers/concerns/authorize.rb
module Authorize
  extend ActiveSupport::Concern

  private

  def fetch_variables(app_title)
    @app = App.find_by(key: app_title)
    @app_categories = @app.categories
  end
end