In this tutorial, we’ll be using Ruby 2+ and Rails 4+. We will create an online funnel with a landing page and a thanks page. Visiotrs will come to your landing page. You will ask them for their name, phone and email, and then deliver whatever you promised in exchange.
Create your new application
$ rails new name-phone-email $ cd name-phone-email
Create a new Lead model, where you’ll keep those precious leads
$ rails g model Lead name:string phone:string email:string token:string
Create your unit tests
Be sure to install RSpec, Capybara, and FactoryGirl. These unit tests will cover your model. First, check if the object is valid after creating from a factory, and check any other validations that you put on the model.
#spec/models/lead_spec.rb
require 'spec_helper'
describe Lead do
it "is valid with defaults" do
FactoryGirl.build(:lead).should be_valid
end
it "is invalid without a name" do
FactoryGirl.build(:lead, name: nil).should_not be_valid
end
it "is invalid without a phone" do
FactoryGirl.build(:lead, phone: nil).should_not be_valid
end
end
#spec/factories/leads.rb
FactoryGirl.define do
factory :lead do
name 'John Doe'
sequence(:email) { |n| "email#{n}@lvh.me" }
phone '800-555-1234'
end
end
#Run your tests
$ bundle exec rspec spec
Update the model with your validations
#app/models/lead.rb validates_presence_of :name validates_presence_of :phone
Create your routes
#config/routes.rb get '/' => 'website#landing' post '/create' => 'website#create' get '/thanks/:token' => 'website#thanks', as: :thanks #You can run `bundle exec rake routes` to see what they are
Create controller tests
For other great rails testing information, check out Everyday Rails.
Let’s write controller tests for the happy and very sad paths.
describe WebsiteController do
describe "GET #landing" do
it "renders the landing template" do
get :landing
response.should render_template :landing
end
end
describe "POST #create" do
context "with valid attributes" do
it "creates a new lead" do
expect {
post :create, lead: FactoryGirl.attributes_for(:lead)
}.to change(Lead, :count).by(1)
end
it "redirects to the thanks template" do
post :create, lead: FactoryGirl.attributes_for(:lead)
response.should redirect_to thanks_path(token: assigns(:lead).token)
end
end
context "with invalid attributes" do
it "does not save the lead in the database" do
expect {
post :create, lead: FactoryGirl.attributes_for(:invalid_lead)
}.to_not change(Lead, :count)
end
it "re-renders the landing template" do
post :create, lead: FactoryGirl.attributes_for(:invalid_lead)
response.should render_template :landing
end
end
end
describe "GET #thanks" do
it "assigns lead to @lead" do
lead = FactoryGirl.create(:lead)
get :thanks, token: lead.token
expect(assigns(:lead)).to eq lead
end
it "should raise RecordNotFound" do
expect {
get :thanks, token: 'abc123'
}.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
Create your website controller
$ touch app/controllers/website_controller.rb
#app/controllers/website_controller.rb
class WebsiteController < ActionController::Base
def landing
@lead = Lead.new
end
def create
@lead = Lead.new(lead_params)
if @lead.save
redirect_to thanks_path(@lead.token)
else
render :landing
end
end
def thanks
@lead = Lead.find_by_token!(params[:token])
end
private
def lead_params
params.require(:lead).permit(:name, :phone, :email)
end
end
Create your views
$ mkdir app/views/website $ touch app/views/website/landing.html.erb $ touch app/views/website/thanks.html.erb
Build your landing page
This is an example built with Bootstrap 3+. You can also use Foundation.
<%= form_for @lead, url: create_path do |f| %>
<%= render 'shared/error_messages', target: @lead %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :phone %>
<%= f.text_field :phone, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :email %>
<%= f.text_field :email, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.submit "Get more info" %>
</div>
<% end %>
Create the error_messages partial
$ mkdir app/views/shared
$ touch app/views/shared/_error_messages.html.erb
#app/views/shared/_error_messages.html.erb
<% if target.errors.any? %>
<div id="error-explanation">
<div id="please-correct">
<span>Please Correct <%= pluralize(target.errors.count, "Error") %></span>
</div>
<ul>
<% target.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
We have included a token on our Lead model. To set this column before the lead is validated, let's create a before filter.
#app/models/lead.rb
class Lead < ActiveRecord::Base
before_validation :generate_token, on: [:create]
#other stuff ...
private
def generate_token
self.token = loop do
random = SecureRandom.urlsafe_base64(nil, false)
break random unless self.class.where(token: random).exists?
end
end
end
This will create a unique token that is used in our thanks path. Now, visitors won't be able to view the thanks page without a valid token.