How to add a flash message to your Rails Twitter Bootstrap application

Leave a comment

To get these nice Twitter Bootstrap alerts to show up in your Rails application

screen-shot-2016-12-04-at-4-08-53-pm

Emit them from your Rails app like this.

SomeController

 class SomeController < ApplicationController

  before_action :last_page

  def what_is_automated_testing
    flash[:success] = 'Success'
    flash[:info] = 'Info'
    flash[:warning] = 'Warning'
    flash[:danger] = 'Danger'
    render layout: "static_page_with_sidebar"
  end

Now, if you have Twitter bootstrap correctly configured, you should see this.

screen-shot-2016-12-04-at-4-13-29-pm

If you are wondering why you don’t see this styling with the rest of your application, it’s because Rails by default doesn’t use this hashes (success, info, warning, etc) for it’s messages. It uses ‘notice’ and others instead (though some may overlap) which is defined in the defaul scaffold.scss so beware of that.

Happy styling!

Links that help

https://agilewarrior.wordpress.com/2014/04/26/how-to-add-a-flash-message-to-your-rails-page/
http://getbootstrap.com/components/#alerts

 

Advertisements

How to Rails nested layouts

Leave a comment

Say you have several static but similarly designed web pages, but you don’t want to copy and paste all the same HTML again and again.

Here is an example of two views, both static pages, the use nested Rails views to do slightly different rendered from one another.

One page is called bar. It has a sidebar. The other is called baz – it does not.

Give you application.html.erb a fancy content_for yield.

application.html.erb

<!DOCTYPE html>
<html>

<%= render 'layouts/header' %>

<body>

<%= render 'layouts/navbar' %>
<div class='container-fluid'>

  <%= render 'layouts/alerts' %>
<div class="row-fluid">
<div class="col-md-9">
<div id="content"><%= content_for?(:content) ? yield(:content) : yield %></div>
</div>
<div class="col-md-3">
      <%= render 'shared/site_sidebar' %></div>
</div>
</div>
</body>
</html>

This is how rails let’s you dynamically define content that can be later filled in.

Setup your routes

routes.rb

  get '/bar',   to: 'static_pages#bar'
  get '/baz',   to: 'static_pages#baz'

Setup your controller to use your custom static page layout

static_pages_controller.rb

class StaticPagesController < ApplicationController

  def bar
    render layout: "static_page"
  end

  def baz
    render layout: "static_page"
  end

end

Define your custom static page layout

static_page.html.erb

<% content_for :content do %>
    <%= content_for?(:static_page) ? yield(:static_page) : yield %>
<% end %>
<%= render template: "layouts/application" %>

There is some magic here. This is using Rails content_for as a means us letting you define content for this layout sometime later. We are going to set two different kinds of content here shortly – one for bar, one for baz.

But basically this magical line

    <%= content_for?(:static_page) ? yield(:static_page) : yield %>

Says: If there is content for an area defined for static_page, yield it. Else do a regular yield.

Define your two slightly different static pages

Now comes the fun part. Here we define two pages. Bar has a side bar, and baz hides it. But they both share the same layout.

bar.erb

<% provide(:title, 'Bar Page') %>

<% content_for :static_page do %>
<h1>What is Bar?</h1>
<% end %>

baz.erb

<% provide(:title, 'Baz Page') %>

<% content_for :stylesheets do %>
    #sidebar {display: none}
<% end %>

<% content_for :static_page do %>
<h1>What is Baz?</h1>
<% end %>

This reason this works is that in my header layout, I defined a yield on the style sheet.

<style><%= yield :stylesheets %></style>

Here is the full source for that

_header.html.erb

<head>
  <title><%= yield(:title) %></title>
<style><%= yield :stylesheets %></style>

  <%= csrf_meta_tags %>

  <%= stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload' %>
  <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %>
</head>

In baz, where we don’t want to show the sidebar, we can hide it by yielding some CSS into the style sheet like this.

baz.erb

<% content_for :stylesheets do %>
    #sidebar {display: none}
<% end %>

So now when baz renders, no side bar.

screen-shot-2016-12-04-at-9-55-48-am

But when bar renders, we get the full sidebar.

Screen Shot 2016-12-04 at 9.55.57 AM.png

I found this whole process of setting this up very confusing. So don’t feel bad if you don’t get it. There aren’t many examples out there.

But if you are determined not to repeat code over and over again like I felt, hopefully this will help.

Cheers – Jonathan

Links that kind of help

http://guides.rubyonrails.org/layouts_and_rendering.html#using-nested-layouts

Ruby hashes & symbols

Leave a comment

Some notes from Michael Hartl’s excellent Rails tutorial on the differences between Hashes, Symbols, and others stuff.

https://www.railstutorial.org/book/_single-page#sec-other_data_structures

Hashes

Hashes are arrays that aren’t limit to integer indexes. Their indexes, or keys, can be almost any object. For example can use strings as keys.

user = {}                          # {} is an empty hash.
user["first_name"] = "Michael"     # Key "first_name", value "Michael"

Instead of defining hashes one item at a time using square brackets, it’s easy to use a literal representation with keys and values separated by =>, called a “hashrocket”:

user = { "first_name" => "foo", "last_name" => "bar" } # hash from hash rocket

Symbols

While strings are fine as hash keys, in Rails symbols are much more popular. Symbols look like strings but are prefixed with a colon. For example :name is a symbol. Think of symbols as strings without all the baggage (faster all at once comparison).

user = { :name => "foo", :email => "bar" } # hash from symbol & hash rocket

Since symbols are so popular, Ruby no supports a new syntax for this special case:

user = { name: "foo", email: "bar" } # hash made from symbols

Note it can get confusing:

{ :name => "foo" }
{ name: "bar" }

:name is a valid symbol but name: has no meaning by itself (only has meaning inside a hash).

Rails generators cheat sheet

Leave a comment

Cheat sheet for seeing what happens when you use rails generators.

Tests

> rails generate integration_test users_edit

rails g scaffold Foo foo_string:string foo_text:text foo_integer:integer foo_float:float foo_decimal:decimal foo_timestamp:timestamp foo_time:time foo_date:date foo_binary:binary foo_boolean:boolean

rake db:migrate

new-generator

filled-generator

sqlite-output

http://guides.rubyonrails.org/command_line.html#rails-generate
http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/TableDefinition.html#method-i-column

rvm management workflow

Leave a comment

Here are some notes to remind me how to setup and flip between various ruby configurations.

Ruby setup

rvm list (current versions of ruby installed locally)
rvm list known (all known versions)
rvm 2.1.3 (to switch to this version of ruby)
rvm install 2.1.3
rvm --default use 2.1.3

Rails setup
Rails is setup on a per ruby install basis. So just because you install rails once, doesn’t mean it is available for all rubies.

To see what gems you currently have installed type

gem list

To create a named gemset for this combination of ruby & rails go:

rvm gemset create rails416
rvm gemset list

Now link this version of ruby new version of rails:

rvm 2.1.3@rails416

gemsets for ruby-2.1.3 (found in /Users/jrasmusson/.rvm/gems/ruby-2.1.3)
(default)
global
rails416

Now install Rails.

gem install rails -v 4.1.8 --no-ri --no-rdoc

Now to flip between environments we go:

$ rvm 2.1.3@rails410 ; rails --version
Rails 4.1.0
$ rvm 2.1.3@rails320 ; rails --version
Rails 3.2.0

If libxml2 is missing nokogiri

$ gem install nokogiri -- --use-system-libraries
$ bundle config build.nokogiri --use-system-libraries
$ bundle install

If fails on libv8

$ gem uninstall libv8
$ brew install v8
$ gem install therubyracer -v '0.12.1'
$ bundle install
$ gem install libv8 -v '3.16.14.7' -- --with-system-v8
$ bundle install

http://stackoverflow.com/questions/19673714/error-installing-libv8-error-failed-to-build-gem-native-extension

If pg problems

.
$ bundle install --without production
.

Links that help
http://rvm.io/workflow/examples
http://rvm.io/rubies/default
http://rvm.io/gemsets/basics

How to Create Rails SignUp Page

Leave a comment

Some notes from http://www.railstutorial.org/book/_single-page#cha-sign_up

sign-up

config/routes.rb

  match '/signup',  to: 'users#new',            via: 'get'
end

Gemfile

group :test do
  gem 'selenium-webdriver', '2.35.1'
  gem 'capybara', '2.1.0'
  gem 'factory_girl_rails', '4.2.0'
end

> bundle install

spec/factories.rb

 FactoryGirl.define do
  factory :user do
    name     "Michael Hartl"
    email    "michael@example.com"
    password "foobar"
    password_confirmation "foobar"
  end
end

config/environments/test.rb

 SampleApp::Application.configure do
  # Speed up tests by lowering bcrypt's cost function.
  ActiveModel::SecurePassword.min_cost = true
end

app/views/users/show.html.erb

 <% provide(:title, @user.name) %>
<h1>
  <%= gravatar_for @user %>
  <%= @user.name %>
</h1>

app/helpers/users_helper.rb

 module UsersHelper

  # Returns the Gravatar (http://gravatar.com/) for the given user.
  def gravatar_for(user)
    gravatar_id = Digest::MD5::hexdigest(user.email.downcase)
    gravatar_url = "https://secure.gravatar.com/avatar/#{gravatar_id}"
    image_tag(gravatar_url, alt: user.name, class: "gravatar")
  end
end

app/views/users/new.html.erb

<%= provide(:title, 'Sign Up') %>

<div class="container">

  <%= form_for(@user, :html => {:class => 'form-signin'}) do |f| %>
      <h2 class="form-signin-heading">Sign Up</h2>
      <%= render 'shared/error_messages' %>

      <%= f.label :name %>
      <%= f.text_field :name %>

      <%= f.label :email %>
      <%= f.text_field :email %>

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

      <%= f.label :password_confirmation, "Confirmation" %>
      <%= f.password_field :password_confirmation %>

      <div class="text-centered">
        <%= f.submit "Create my account", class: "btn btn-large btn-primary" %>
      </div>
  <% end %>

</div>

app/views/shared/_error_messages.html.erb

 <% if @user.errors.any? %>
  <div id="error_explanation">
    <div class="alert alert-error">
      The form contains <%= pluralize(@user.errors.count, "error") %>.
    </div>
    <ul>
    <% @user.errors.full_messages.each do |msg| %>
      <li>* <%= msg %></li>
    <% end %>
    </ul>
  </div>
<% end %>

app/assets/stylesheets/custom.css.scss

/* forms */
#error_explanation {
  color: #f00;
  ul {
    list-style: none;
    margin: 0 0 18px 0;
  }
}

.field_with_errors {
  @extend .control-group;
  @extend .error;
}

app/assets/stylesheets/signin.css.scss

/* sign in */
.form-signin {
    max-width: 300px;
    padding: 19px 29px 29px;
    margin: 0 auto 20px;
    background-color: #fff;
    border: 1px solid #e5e5e5;
    -webkit-border-radius: 5px;
    -moz-border-radius: 5px;
    border-radius: 5px;
    -webkit-box-shadow: 0 1px 2px rgba(0,0,0,.05);
    -moz-box-shadow: 0 1px 2px rgba(0,0,0,.05);
    box-shadow: 0 1px 2px rgba(0,0,0,.05);
}

.form-signin .form-signin-heading,
.form-signin .checkbox {
    margin-bottom: 10px;
}

.form-signin input[type="text"],
.form-signin input[type="password"] {
    font-size: 16px;
    height: auto;
    margin-bottom: 15px;
    padding: 7px 9px;
}

Adding flash

<strong>app/views/layouts/application.html.erb</strong>
 <!DOCTYPE html>
<html>
  .
  .
  .
  <body>
    <%= render 'layouts/header' %>
    <div class="container">
      <% flash.each do |key, value| %>
        <div class="alert alert-<%= key %>"><%= value %></div>
      <% end %>
      <%= yield %>
      <%= render 'layouts/footer' %>
      <%= debug(params) if Rails.env.development? %>
    </div>
    .
    .
    .
  </body>
</html>

app/controllers/users_controller.rb

 class UsersController < ApplicationController

  def create
    @user = User.new(user_params)
    if @user.save
      flash[:success] = "Welcome to the Sample App!"
      redirect_to @user
    else
      render 'new'
    end
  end

  private

    def user_params      	

params.require(:user).permit(:name, :email, :password_digest, :admin, :password, :password_confirmation)
    end
end

spec/requests/user_pages_spec.rb

require 'spec_helper'

describe 'User pages' do

  subject { page }

  describe 'signup page' do
    before { visit signup_path }

    it { should have_content('Sign Up') }
    it { should have_title('AC Sign Up') }
  end

  describe "signup" do

    before { visit signup_path }

    let(:submit) { "Create my account" }

    describe "with invalid information" do
      it "should not create a user" do
        expect { click_button submit }.not_to change(User, :count)
      end
    end

    describe "with valid information" do
      before do
        fill_in "Name",         with: "Example User"
        fill_in "Email",        with: "user@example.com"
        fill_in "Password",     with: "foobar"
        fill_in "Confirmation", with: "foobar"
      end

      it "should create a user" do
        expect { click_button submit }.to change(User, :count).by(1)
      end

      describe "after saving the user" do
        before { click_button submit }
        let(:user) { User.find_by(email: 'user@example.com') }

        # it { should have_link('Sign out') }
        it { should have_title(user.name) }
        it { should have_selector('div.alert.alert-success', text: 'Welcome') }
      end
    end
  end

end

How to create User Rails

Leave a comment

As summary of notes take from Michael Haretl’s excellent tutorial
http://www.railstutorial.org/book/_single-page#cha-modeling_users

Create User
> rails generate scaffold User name:string email:string password_digest:string remember_token:string admin:boolean
> rails generate controller Users new –no-test-framework
> rake db:migrate
> rake test:prepare

Setup Gemfile

source 'https://rubygems.org'

gem 'rails', '4.0.0'
gem 'sqlite3'
gem 'sass-rails', '~> 4.0.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.0.0'
gem 'jquery-rails'
gem 'turbolinks'
gem 'jbuilder', '~> 1.2'

gem 'rspec-rails', '2.13.1'

group :test do
  gem 'selenium-webdriver', '2.35.1'
  gem 'capybara', '2.1.0'
end

gem 'bcrypt-ruby', '~> 3.0.0'

> bundle install

Create test file

spec/models/user_spec.rb

require 'spec_helper'

describe User do

  before do
    @user = User.new(name: "Example User", email: "user@example.com",
                     password: "foobar", password_confirmation: "foobar")
  end

  subject { @user }

  it { should respond_to(:name) }
  it { should respond_to(:email) }
  it { should respond_to(:password_digest) }
  it { should respond_to(:password) }
  it { should respond_to(:password_confirmation) }
  it { should respond_to(:remember_token) }
  it { should respond_to(:authenticate) }
  it { should respond_to(:admin) }

  it { should be_valid }
  it { should_not be_admin }

  describe "with admin attribute set to 'true'" do
    before do
      @user.save!
      @user.toggle!(:admin)
    end

    it { should be_admin }
  end

  describe "when name is not present" do
    before { @user.name = " " }
    it { should_not be_valid }
  end

  describe "when email is not present" do
    before { @user.email = " " }
    it { should_not be_valid }
  end

  describe "when name is too long" do
    before { @user.name = "a" * 51 }
    it { should_not be_valid }
  end

  describe "when email format is invalid" do
    it "should be invalid" do
      addresses = %w[user@foo,com user_at_foo.org example.user@foo.
                     foo@bar_baz.com foo@bar+baz.com foo@bar..com]
      addresses.each do |invalid_address|
        @user.email = invalid_address
        expect(@user).not_to be_valid
      end
    end
  end

  describe "when email format is valid" do
    it "should be valid" do
      addresses = %w[user@foo.COM A_US-ER@f.b.org frst.lst@foo.jp a+b@baz.cn]
      addresses.each do |valid_address|
        @user.email = valid_address
        expect(@user).to be_valid
      end
    end
  end

  describe "when email address is already taken" do
    before do
      user_with_same_email = @user.dup
      user_with_same_email.email = @user.email.upcase
      user_with_same_email.save
    end

    it { should_not be_valid }
  end

  describe "when password is not present" do
    before do
      @user = User.new(name: "Example User", email: "user@example.com",
                       password: " ", password_confirmation: " ")
    end
    it { should_not be_valid }
  end

  describe "when password doesn't match confirmation" do
    before { @user.password_confirmation = "mismatch" }
    it { should_not be_valid }
  end

  describe "with a password that's too short" do
    before { @user.password = @user.password_confirmation = "a" * 5 }
    it { should be_invalid }
  end

  describe "return value of authenticate method" do
    before { @user.save }
    let(:found_user) { User.find_by(email: @user.email) }

    describe "with valid password" do
      it { should eq found_user.authenticate(@user.password) }
    end

    describe "with invalid password" do
      let(:user_for_invalid_password) { found_user.authenticate("invalid") }

      it { should_not eq user_for_invalid_password }
      specify { expect(user_for_invalid_password).to be_false }
    end
  end

  describe "remember token" do
    before { @user.save }
    its(:remember_token) { should_not be_blank }
  end

end

Setup Model

app/models/user.rb

class User < ActiveRecord::Base

  before_save { self.email = email.downcase }
  before_create :create_remember_token
  validates :name, presence: true, length: { maximum: 50 }
  VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i
  validates :email, presence: true, format: { with: VALID_EMAIL_REGEX },
                    uniqueness: { case_sensitive: false }
  has_secure_password
  validates :password, length: { minimum: 6 }

  def User.new_remember_token
    SecureRandom.urlsafe_base64
  end

  def User.hash(token)
    Digest::SHA1.hexdigest(token.to_s)
  end

  private

    def create_remember_token
      self.remember_token = User.hash(User.new_remember_token)
    end
end

Deprecation warning

To get rid of [deprecated] I18n.enforce_available_locales will default to true in the future

config/application.rb

require File.expand_path('../boot', __FILE__)

module SampleApp
  class Application < Rails::Application
    I18n.enforce_available_locales = true
  end
end

To handle uniqueness
> rails generate migration add_index_to_users_email

db/migrate/[timestamp]_add_index_to_users_email.rb

 class AddIndexToUsersEmail < ActiveRecord::Migration
  def change
    add_index :users, :email, unique: true
  end
end

> rake db:migrate
> rake test:prepare

Configure capybara
spec/spec_helper.rb

 # This file is copied to spec/ when you run 'rails generate rspec:install'
RSpec.configure do |config|
  config.include Capybara::DSL
end

Comment out a couple PUT/POST tests in users_controller_spec.rb as these pages don’t
contain password and password confirmation fields. Will test those separate using capybara.

Older Entries

%d bloggers like this: