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.