Permalink
Find file
Fetching contributors…
Cannot retrieve contributors at this time
2423 lines (2277 sloc) 141 KB
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Rails Tutorial for a Startup Prelaunch Signup Site &#183; RailsApps</title>
<link href="https://plus.google.com/u/0/b/117374718581973393536/117374718581973393536/posts/" rel="publisher" />
<link rel="stylesheet" href="http://railsapps.github.com/css/bootstrap.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="http://railsapps.github.com/css/screen.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="http://railsapps.github.com/css/gollum.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="http://railsapps.github.com/css/site.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="http://railsapps.github.com/css/syntax.css" type="text/css" charset="utf-8" />
<script src="http://code.jquery.com/jquery-1.6.min.js" type="text/javascript"></script>
<script src="http://railsapps.github.com/javascript/jquery.text_selection-1.0.0.min.js" type="text/javascript"></script>
<script src="http://railsapps.github.com/javascript/jquery.previewable_comment_form.js" type="text/javascript"></script>
<script src="http://railsapps.github.com/javascript/jquery.tabs.js" type="text/javascript"></script>
<script src="http://railsapps.github.com/javascript/gollum.js" type="text/javascript"></script>
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-5109366-14']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>
</head>
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a href="http://railsapps.github.com/" class="brand">RailsApps Project</a>
<ul class="pull-right nav">
<li><a href="http://blog.railsapps.org/" class="twitter">Blog</a></li>
<li><a href="http://twitter.com/rails_apps" class="twitter">Twitter</a></li>
<li><a href="https://plus.google.com/117374718581973393536" class="google">Google +</a></li>
<li><a href="https://github.com/RailsApps" class="github">GitHub Repository</a></li>
</ul>
</div>
</div>
</div>
<div class="container">
<div class="content wikistyle gollum textile">
<h1>Rails Tutorial for a Startup Prelaunch Signup Site</h1>
<h4>by Daniel Kehoe</h4>
<p><em>Last updated 24 June 2012</em></p>
<p>Ruby on Rails tutorial showing how to create a “beta launching soon” application for a startup prelaunch site with a signup page. You can clone the <a href="http://github.com/RailsApps/rails-prelaunch-signup/">rails-prelaunch-signup</a> repository for the complete example application.</p>
<p>Screenshot:</p>
<p><img src="http://railsapps.github.com/images/rails-prelaunch-signup.png" title="Rails Application for a Startup Prelaunch Signup Site" alt="Rails Application for a Startup Prelaunch Signup Site"></p>
<p>Read an <a href="http://blog.railsapps.org/post/22543541226/powered-up-by-the-railsapps-project-xplaygrounds-com">interview with Michael Gajda</a> of <a href="http://xplaygrounds.com/">XPlaygrounds.com</a> about how he used the <a href="http://github.com/RailsApps/rails-prelaunch-signup/">rails-prelaunch-signup</a> example to launch his startup site. If you build a site using this example, get some link juice by adding your site to the list of <a href="http://railsapps.github.com/rails-applications-from-examples.html">Rails Applications Built from the Examples</a> or leave a comment below.</p>
<h2>
<a href="http://www.twitter.com/rails_apps"><img src="http://twitter-badges.s3.amazonaws.com/t_logo-a.png" title="Follow on Twitter" alt="Follow on Twitter"></a> Follow on Twitter</h2>
<p>Follow the project on Twitter: <a href="http://twitter.com/rails_apps">@rails_apps</a>. Tweet some praise if you like what you’ve found.</p>
<h2>Introduction</h2>
<p>The initial app for a typical web startup announces the founders’ plans and encourages visitors to enter an email address for future notification of the site’s launch. It’s not difficult to build such an app in Rails.</p>
<p>But why build it yourself if others have already done so? This project aims to:</p>
<ul>
<li>eliminate effort spent building an application that meets a common need;</li>
<li>offer code that is already implemented and tested by a large community;</li>
<li>provide a well-thought-out app containing most of the features you’ll need.</li>
</ul><p>By using code from this project, you’ll be able to:</p>
<ul>
<li>direct your attention to the design and product offer for your prelaunch site;</li>
<li>get started faster building the ultimate application for your business.</li>
</ul><p>The tutorial can help founders who are new to Rails to learn how to build a real-world Ruby on Rails web application.</p>
<h3>Why We Use Devise</h3>
<p>We’ve chosen to use the <a href="http://github.com/plataformatec/devise">Devise</a> gem for our authentication and user management.</p>
<p>We use Devise because it offers a full set of features used in more complex applications, such as recovering a user’s forgotten password or allowing users to invite friends. By using Devise for the prelaunch signup application, you can use the same user database for your post-launch product. You’ll also have the benefit of receiving help from a large community of developers using Devise, should you need help in troubleshooting or customizing the implementation.</p>
<h3>How We Configure Devise</h3>
<p>Devise offers many features and configuration choices; in fact, exploring its options can take more time than actually implementing authentication from scratch. This app will save you time; we’ve selected the configuration that best accommodates a typical web startup signup site.</p>
<p>Devise handles signing up users through a registration feature. We use the Devise registration process to collect email addresses and create user accounts. Instead of confirming an email address immediately, we email a “thank you for your request” acknowledgment and postpone the email confirmation step. When you are ready to invite users to the site, go to the app’s administrative interface. Then select users to receive an invitation that instructs them to confirm their email address and set a password.</p>
<p>If you want something different, look for help on the <a href="https://github.com/plataformatec/devise/wiki">Devise wiki</a> and <a href="http://stackoverflow.com/questions/tagged/devise">Stack Overflow</a>.</p>
<h3>RailsApps Examples and Tutorials</h3>
<p>This is one in a series of Rails example apps and tutorials from the <a href="http://railsapps.github.com/">RailsApps Project</a>.</p>
<p>This application is based on two of the RailsApps example apps:</p>
<ul>
<li><a href="https://github.com/RailsApps/rails3-devise-rspec-cucumber">rails3-devise-rspec-cucumber</a></li>
<li><a href="https://github.com/RailsApps/rails3-bootstrap-devise-cancan">rails3-bootstrap-devise-cancan</a></li>
</ul><p>The first example shows how to set up Devise for user authentication. It also shows how to set up the app to use RSpec and Cucumber for testing.</p>
<p>The second example shows how to set up Devise and add CanCan to manage access to administrative pages. It also shows how to set up Twitter Bootstrap as a front-end framework for <span class="caps">CSS</span> styling.</p>
<p>You can use this tutorial without studying these example applications; if you find you are lost, it may be helpful to look at the two simpler examples.</p>
<p>If you want to use the MongoDB datastore instead of ActiveRecord and a <span class="caps">SQL</span> database, look at <a href="https://github.com/RailsApps/rails3-mongoid-devise">rails3-mongoid-devise</a> example.</p>
<h2>
<img src="http://railsapps.github.com/images/rails-36x36.jpg" title="Tutorial" alt="Tutorial"> Tutorial</h2>
<p>This tutorial documents each step that you must follow to create this application. Every step is documented concisely, so a complete beginner can create this application without any additional knowledge. However, little explanation is offered for any of the steps, so if you are a beginner, you’re advised to look for an introduction to Rails elsewhere. See resources for getting started with <a href="http://railsapps.github.com/rails.html">Rails</a>.</p>
<p>Most of the tutorials from the RailsApps project take about an hour to complete. This tutorial is more complex; it will take you about three hours to build the complete app. (Is our estimate accurate? Please leave a comment when you are done.)</p>
<h2>Before You Start</h2>
<p>If you follow this tutorial closely, you’ll have a working application that closely matches the example app in this GitHub repository. The example app is your reference implementation. If you find problems with the app you build from this tutorial, download the example app (in Git speak, clone it) and use a file compare tool to identify differences that may be causing errors. On a Mac, <a href="http://stackoverflow.com/questions/187064/graphical-diff-for-mac-os-x">good file compare tools</a> are <a href="http://en.wikipedia.org/wiki/Apple_Developer_Tools#FileMerge">FileMerge</a>, <a href="http://sourcegear.com/diffmerge/">DiffMerge</a>, <a href="http://www.kaleidoscopeapp.com/">Kaleidoscope</a>, or Ian Baird’s <a href="http://www.changesapp.com/">Changes</a>.</p>
<p>If you clone and install the example app and find problems or wish to suggest improvements, please create a <a href="http://github.com/RailsApps/rails-prelaunch-signup/issues">GitHub issue</a>.</p>
<p>To improve this tutorial, leave comments below.</p>
<h2>Accounts You May Need</h2>
<p>Before you start, you may need to set up accounts for hosting, email, and a source control repository.</p>
<h3>Hosting</h3>
<p>For easy deployment, use a “platform as a service” provider such as:</p>
<ul>
<li><a href="http://www.heroku.com/">Heroku</a></li>
<li><a href="http://www.cloudfoundry.com/">CloudFoundry</a></li>
<li><a href="http://www.engineyard.com/">EngineYard</a></li>
<li><a href="https://openshift.redhat.com/app/">OpenShift</a></li>
</ul><p>Instructions are provided for deployment to Heroku.</p>
<p>It’s common for technically skilled people to want to set up their own servers. Please, do yourself a favor, and unless system administration is your most dearly loved recreation, let the platform providers do it for you.</p>
<h3>Email Service Providers</h3>
<p>You’ll need infrastructure for three types of email:</p>
<ul>
<li>company email</li>
<li>email sent from the app (“transactional email”)</li>
<li>broadcast email for newsletters or announcements</li>
</ul><p>No single vendor is optimal for all three types of email; you likely will use several vendors.</p>
<p>For “company email,” that is, sending individual email to customers or business associates, you’ll probably use Gmail or <a href="http://www.google.com/enterprise/apps/business/index.html">Google Apps for Business</a>. For a single address, you can set up a single Gmail account to receive and <a href="http://support.google.com/mail/bin/answer.py?hl=en&amp;ctx=mail&amp;answer=22370">send email from a different address</a>. More likely, you’ll want several email addresses for your company mail. For that, use Google Apps for Business.</p>
<h3>Transactional Email</h3>
<p>For simple testing of email, it’s easy to use Gmail to send email messages from the application. For deployment, when the application must send dozens or thousands of acknowledgments or invitations, you will need a hosted <span class="caps">SMTP</span> relay service (also known as an <span class="caps">ESP</span> or “email service provider”). Considerable expertise is required to keep email from being filtered as spam (see MailChimp’s <a href="http://mailchimp.com/resources/guides/html/email-delivery-for-it-professionals/">Email Delivery For IT Professionals</a>). Use an <span class="caps">ESP</span> to increase reliability of delivery. The best services track deliveries and show how well your email is being delivered.</p>
<p>Many of these services offer a free plan allowing up to 200 emails/day:</p>
<ul>
<li><a href="http://mandrill.com/">Mandrill by MailChimp</a></li>
<li><a href="http://sendgrid.com/pricing.html">SendGrid</a></li>
<li><a href="http://mailgun.net/pricing">Mailgun</a></li>
<li><a href="http://aws.amazon.com/ses/pricing/" title="SES">Amazon Simple Email Service</a></li>
<li><a href="http://elasticemail.com/pricing">Elastic Email</a></li>
<li><a href="http://www.critsend.com/pricing">CritSend</a></li>
<li><a href="https://www.mailjet.com/pricing">Mailjet</a></li>
<li><a href="http://www.messagegears.com/">MessageGears</a></li>
<li><a href="https://secure.postageapp.com/register">PostageApp</a></li>
<li><a href="http://postmarkapp.com/pricing">Postmark</a></li>
<li><a href="https://www.serversmtp.com/en/cart.php?systpl=turbo-smtp&amp;systpl=turbo-smtp&amp;currency=3">turboSMTP</a></li>
</ul><p>This tutorial provides instructions for <a href="http://mandrill.com/">Mandrill by MailChimp</a>. The Mandrill transactional email service integrates well with the MailChimp email list manager service. Plus, you can send up to 12,000 emails/month from the service for free.</p>
<p>Sign up for a MailChimp account to get started. After you’ve created your MailChimp account, see the instructions to <a href="http://help.mandrill.com/customer/portal/articles/464750-use-mandrill-with-mailchimp">Use Mandrill with MailChimp</a>. Then get the <a href="http://help.mandrill.com/customer/portal/articles/464828-access-information">Access Information</a> (your <span class="caps">SMTP</span> username and password, which is an <span class="caps">API</span> key).</p>
<h3>Mailing List</h3>
<p>In addition to sending transactional email messages, you likely will want to send newsletters or announcements to your entire mailing list. <span class="caps">SMTP</span> relay services such as SendGrid often offer rudimentary bulk email services but you may want to consider using a dedicated service for mass emailing. You’ll get features such as management of “unsubscribe” requests and templates to design attractive messages.</p>
<p>For broadcast email, consider using a service such as:</p>
<ul>
<li><a href="http://mailchimp.com/pricing/">MailChimp</a></li>
<li><a href="https://www.madmimi.com/service_agreements/choose_plan">MadMimi</a></li>
<li><a href="http://www.campaignmonitor.com/pricing/">CampaignMonitor</a></li>
<li><a href="http://www.constantcontact.com/pricing/email-marketing.jsp">Constant Contact</a></li>
<li><a href="http://www.ymlp.com/pricing.html"><span class="caps">YMLP</span></a></li>
<li><a href="http://www.jangomail.com/pricing.asp">JangoMail</a></li>
<li><a href="http://www.icontact.com/affordable-email-marketing">iContact</a></li>
<li><a href="http://www.verticalresponse.com/pricing">VerticalResponse</a></li>
</ul><p>This tutorial shows how to add visitors who request an invitation to a <a href="http://mailchimp.com/">MailChimp</a> list. MailChimp allows you to send up to 12,000 emails/month to list of 2000 or fewer subscribers for free. After you sign up for a MailChimp account, get your <span class="caps">API</span> key. Look under “Account” for “<span class="caps">API</span> Keys and Authorized Apps.” Note that the Mandrill <span class="caps">API</span> key (which you get on the <a href="http://mandrill.com/">mandrill.com</a> site) is different from the MailChimp <span class="caps">API</span> key (which you get on the <a href="http://mailchimp.com/">mailchimp.com</a> site).</p>
<h3>Domain Registration</h3>
<p>You’ve likely already selected and registered a domain name. If not, you’ll need a domain before you start sending email messages from the application. If you’re disgusted by GoDaddy, consider <a href="http://www.namecheap.com/">NameCheap</a> and other popular alternatives.</p>
<h3>GitHub</h3>
<p>Get a <a href="https://github.com/signup/free">free GitHub account</a> if you don’t already have one. You’ll need a GitHub account if you plan to deploy to Heroku. If you’re not going to use Heroku, you’ll still need to use git to manage your source code. See <a href="http://railsapps.github.com/rails-git.html">GitHub and Rails</a> if you need more information about working with git for code source control.</p>
<h2>Creating the Application</h2>
<h3>Option One</h3>
<p><em>Follow this tutorial.</em></p>
<p>To create the application, you can cut and paste the code from the tutorial into your own files. It’s a bit tedious and error-prone but you’ll have a good opportunity to examine the code closely.</p>
<h3>Option Two</h3>
<p><em>Use the ready-made application template to generate the code.</em></p>
<p>You can use an application template to generate a new Rails app with code that closely matches the tutorial. You’ll find an application template for this tutorial in the <a href="https://github.com/RailsApps/rails3-application-templates">Rails Application Templates</a> repository.</p>
<p>You’ll be able to give it your own project name when you generate the app. Generating the application (described below) allows you to choose from several options.</p>
<p>Use the command:</p>
<pre>
$ rails new rails-prelaunch-signup -m https://raw.github.com/RailsApps/rails3-application-templates/master/rails-prelaunch-signup-template.rb -T
</pre>
<p>Use the <code>-T</code> flag to skip Test::Unit files.</p>
<p>The <code>$</code> character indicates a shell prompt; don’t include it when you run the command.</p>
<p>This creates a new Rails app named <code>rails-prelaunch-signup</code> on your computer. You can use a different name if you wish.</p>
<p>The application generator template will ask you for your preferences.</p>
<p>To produce an application exactly like the tutorial, make the following selections:</p>
<ul>
<li>How will you send email? <strong>#4</strong>
<ol>
<li>
<span class="caps">SMTP</span> account</li>
<li>Gmail account</li>
<li>SendGrid account</li>
<li>Mandrill by MailChimp account</li>
</ol>
</li>
<li>Use MailChimp to send news and welcome messages? <strong>yes</strong>
</li>
<li>Add ‘therubyracer’ JavaScript runtime (for Linux users without node.js)? <strong>no</strong>
</li>
</ul><p>You can choose other selections if you don’t care about matching the example application exactly.</p>
<h2>Assumptions</h2>
<p>Before beginning this tutorial, you need to install</p>
<ul>
<li>The Ruby language (version 1.9.3)</li>
<li>Rails 3.2</li>
</ul><p>Check that appropriate versions of Ruby and Rails are installed in your development environment:<br><code>$ ruby -v</code><br><code>$ rails -v</code></p>
<p>Be sure to read <a href="http://railsapps.github.com/installing-rails.html">Installing Rails</a> for detailed instructions and advice.</p>
<h2>About the Software Development Process</h2>
<p>Arguably, for a simple application like this, you don’t need a lot of “ceremony.” There’s no need for a written specification. And no need for tests, right? Just write some code. However, for this tutorial, I will show practices that establish an effective software development process. If you’re a solo operator, these practices can help; if you plan on growing a company, they are fundamental.</p>
<p>Here’s the software development process we’ll use:</p>
<ul>
<li>write user stories</li>
<li>write a short specification for our feature set using Cucumber scenarios</li>
<li>create acceptance tests using Cucumber step definitions</li>
<li>code each feature</li>
<li>run acceptance tests as each feature is completed</li>
</ul><p>By practicing the process that leads from concept to code, you’ll learn how <a href="http://en.wikipedia.org/wiki/Behavior_Driven_Development">behavior-driven development</a> (<span class="caps">BDD</span>) can be used to manage software development. And you’ll be better prepared to build more complex applications.</p>
<p>We’ll also (briefly) discuss alternatives to Cucumber (RSpec and Capybara) that may be more appropriate for smaller projects.</p>
<h3>Write Your User Stories</h3>
<p>User stories (definition: <a href="http://en.wikipedia.org/wiki/User_story">user stories</a>) are a way to discuss and describe the requirements for a software application. The process of writing user stories will help you identify all the features that are needed for your application. Breaking down the application’s functionality into discrete user stories will help you organize your work and track progress toward completion.</p>
<p>User stories are generally expressed in the following format:</p>
<pre>
As a &lt;role&gt;, I want &lt;goal&gt; so that &lt;benefit&gt;
</pre>
<p>As an example, here are four user stories we will implement for this application.</p>
<p>You may have ideas for additional features for this application. If you’d like to make a suggestion, create a <a href="http://github.com/RailsApps/rails-prelaunch-signup/issues">GitHub issue</a> describing the feature you’d like to see.</p>
<pre>
*Request Invitation*
As a visitor to the website
I want to request an invitation
so I can be notified when the site is launched
*See Invitation Requests*
As the owner of the site
I want to view a list of visitors who have requested invitations
so I can know if my offer is popular
*Send Invitations*
As the owner of the site
I want to send invitations to visitors who have requested invitations
so users can try the site
*Collect Email Addresses*
As the owner of the site
I want to collect email addresses for a mailing list
so I can send announcements before I launch the site
</pre>
<p>We’ll use this list of user stories as our to-do list in implementing the application. But first we’ll step through the process of creating a basic Rails application.</p>
<h2>Create the Rails Application</h2>
<p>Before you write any code, you’ll start by generating an example app using an application template script.</p>
<p>If you’ve developed other applications in Rails, you’ll know that the <code>rails new</code> command creates a basic Rails application. Here we’ll use an application template to create a more complex “starter” app. The <a href="https://github.com/RailsApps/rails3-application-templates">application templates</a> from the Rails Apps project give you more fully-featured starter apps than you get with the default <code>rails new</code> command. With an application template, you’ll get your choice of various popular gems; Devise will be installed with Cancan for authorization; and Twitter Bootstrap will be set up as a front end for <span class="caps">CSS</span> styling.</p>
<p>For a starter app using ActiveRecord and an <span class="caps">SQL</span> database, use the command:</p>
<pre>
$ rails new rails-prelaunch-signup -m https://raw.github.com/RailsApps/rails3-application-templates/master/rails3-bootstrap-devise-cancan-template.rb -T
</pre>
<p>Use the <code>-T</code> flags to skip Test::Unit files since we’ll be using RSpec.</p>
<p>The <code>$</code> character indicates a shell prompt; don’t include it when you run the command.</p>
<p>This creates a new Rails app named <code>rails-prelaunch-signup</code> on your computer. You can use a different name if you wish.</p>
<p>The application generator template will ask you for your preferences. For this tutorial, choose the following preferences:</p>
<ul>
<li>Would you like to use <a href="http://en.wikipedia.org/wiki/Haml">Haml</a> instead of <span class="caps">ERB</span>? <strong>yes</strong>
</li>
<li>Would you like to use <a href="http://rspec.info/">RSpec</a> instead of TestUnit? <strong>yes</strong>
</li>
<li>Would you like to use <a href="https://github.com/thoughtbot/factory_girl">factory_girl</a> for test fixtures with RSpec? <strong>yes</strong>
</li>
<li>Would you like to use <a href="https://github.com/notahat/machinist">machinist</a> for test fixtures with RSpec? <strong>no</strong>
</li>
<li>Would you like to use <a href="http://cukes.info/">Cucumber</a> for your <span class="caps">BDD</span>? <strong>yes</strong>
</li>
<li>Would you like to use <a href="http://intridea.com/posts/hire-a-guard-for-your-project">Guard</a> to automate your workflow? <strong>no</strong>
</li>
<li>How will you send email? <strong>#4</strong>
<ol>
<li>
<span class="caps">SMTP</span> account</li>
<li>Gmail account</li>
<li>
<a href="http://sendgrid.com/">SendGrid</a> account</li>
<li>
<a href="http://mandrill.com/">Mandrill by MailChimp</a> account</li>
</ol>
</li>
<li>Would you like to use <a href="http://github.com/plataformatec/devise">Devise</a> for authentication? <strong>#4</strong>
<ol>
<li>No</li>
<li>Devise with default modules</li>
<li>Devise with Confirmable module</li>
<li>Devise with Confirmable and Invitable modules</li>
</ol>
</li>
<li>Would you like to manage authorization with <a href="https://github.com/ryanb/cancan">CanCan</a> &amp; <a href="https://github.com/EppO/rolify">Rolify</a>? <strong>yes</strong>
</li>
<li>Which front-end framework would you like for HTML5 and CSS3? <strong>#4</strong>
<ol>
<li>None</li>
<li><a href="http://foundation.zurb.com/">Zurb Foundation</a></li>
<li>
<a href="http://twitter.github.com/bootstrap/">Twitter Bootstrap</a> (less)</li>
<li>
<a href="http://twitter.github.com/bootstrap/">Twitter Bootstrap</a> (sass)</li>
<li><a href="http://www.getskeleton.com/">Skeleton</a></li>
<li>Normalize <span class="caps">CSS</span> for consistent styling</li>
</ol>
</li>
<li>Which form gem would you like? <strong>#3</strong>
<ol>
<li>None</li>
<li>simple form</li>
<li>simple form (bootstrap)</li>
</ol>
</li>
<li>Would you like to use <a href="https://github.com/josevalim/rails-footnotes">rails-footnotes</a> during development? <strong>no</strong>
</li>
<li>Would you like to set a robots.txt file to ban spiders? <strong>no</strong>
</li>
<li>Would you like to add ‘will_paginate’ for pagination? <strong>no</strong>
</li>
<li>Add ‘therubyracer’ JavaScript runtime (for Linux users without node.js)?</li>
</ul><p>If you are just experimenting, you can choose <strong>Gmail</strong> as a temporary solution. Choose the <strong>Mandrill by MailChimp</strong> option for email if you’ve set up a Mandrill account for future deployment.</p>
<p>Be sure to choose:</p>
<ul>
<li>the <strong>Devise with Confirmable and Invitable modules</strong> option for authentication</li>
<li>the <strong>CanCan &amp; Rolify</strong> option for managing authorization</li>
<li>the <strong>Twitter Bootstrap (sass)</strong> option for a front-end framework</li>
<li>the <strong>simple form (bootstrap)</strong> option for forms</li>
</ul><p>You can choose other selections if you don’t care about matching the example application exactly.</p>
<p>If you get an error “You have already activated (…) but your Gemfile requires (…)”, try deleting the rails-prelaunch-signup folder and running the command again.</p>
<p>After you create the application, switch to its folder to continue work directly in the application:</p>
<p><code>$ cd rails-prelaunch-signup</code></p>
<h4>Replace the READMEs</h4>
<p>Please edit the <span class="caps">README</span> files to add a description of the app and your contact info. Changing the <span class="caps">README</span> is important if your app will be publicly visible on GitHub. Otherwise, people will think I am the author of your app (though please leave an acknowledgment and a link to the <a href="http://railsapps.github.com/">RailsApps project</a> if you like).</p>
<h2>Set Up Source Control (Git)</h2>
<p>When you generate the starter app, the template sets up a source control repository and makes an initial commit of the code.</p>
<p>At this point, you should create a GitHub repository for your project.</p>
<p>See detailed instructions for <a href="http://railsapps.github.com/rails-git.html">Using Git with Rails</a>.</p>
<p>Git has already been initialized by the application template script but you will need to check your code into your new GitHub repository.</p>
<h2>Set Up Gems</h2>
<p>It’s a good idea to create a new gemset using rvm, the <a href="http://rvm.beginrescueend.com/">Ruby Version Manager</a>, as described in the article <a href="http://railsapps.github.com/installing-rails.html">Installing Rails</a>.</p>
<p>The starter app script sets up your gemfile.</p>
<p>Open your <strong>Gemfile</strong> and you should see the following. Gem version numbers may differ:</p>
<p><strong>Gemfile</strong></p>
<pre>
source 'https://rubygems.org'
gem 'rails', '3.2.6'
gem 'sqlite3'
group :assets do
gem 'sass-rails', '~&gt; 3.2.3'
gem 'coffee-rails', '~&gt; 3.2.1'
gem 'uglifier', '&gt;= 1.0.3'
end
gem 'jquery-rails'
gem "haml", "&gt;= 3.1.6"
gem "haml-rails", "&gt;= 0.3.4", :group =&gt; :development
gem "rspec-rails", "&gt;= 2.10.1", :group =&gt; [:development, :test]
gem "factory_girl_rails", "&gt;= 3.3.0", :group =&gt; [:development, :test]
gem "email_spec", "&gt;= 1.2.1", :group =&gt; :test
gem "cucumber-rails", "&gt;= 1.3.0", :group =&gt; :test, :require =&gt; false
gem "capybara", "&gt;= 1.1.2", :group =&gt; :test
gem "database_cleaner", "&gt;= 0.8.0", :group =&gt; :test
gem "launchy", "&gt;= 2.1.0", :group =&gt; :test
gem "hominid"
gem "devise", "&gt;= 2.1.0"
gem "devise_invitable", "&gt;= 1.0.2"
gem "cancan", "&gt;= 1.6.7"
gem "rolify", "&gt;= 3.1.0"
gem "google_visualr", "&gt;= 2.1.2"
gem "jquery-datatables-rails", "&gt;= 1.10.0"
gem "bootstrap-sass", "&gt;= 2.0.3"
gem "simple_form"
</pre>
<p>Check for the <a href="http://rubygems.org/gems/rails">current version of Rails</a> and replace <code>gem 'rails', '3.2.6'</code> accordingly.</p>
<p>The hominid gem will be included if you selected the “Mandrill by MailChimp” option for email when you generated the app.</p>
<p><em>Note:</em> The RailsApps examples are generated with application templates created by the <a href="https://github.com/RailsApps/rails_apps_composer">Rails Apps Composer Gem</a>. For that reason, groups such as <code>:development</code> or <code>:test</code> are specified inline. You can reformat the Gemfiles to organize groups in an eye-pleasing block style. The functionality is the same.</p>
<h3>Install the Required Gems</h3>
<p>When you add a new gem to the Gemfile, you should run the <code>bundle install</code> command to install the required gems on your computer. In this case, the starter app script has already run the <code>bundle install</code> command.</p>
<p>You can check which gems are installed on your computer with:</p>
<p><code>$ gem list --local</code></p>
<p>Keep in mind that you have installed these gems locally. When you deploy the app to another server, the same gems (and versions) must be available.</p>
<h2>Haml</h2>
<p>In this example, we’ll use Haml instead of the default “<span class="caps">ERB</span>” Rails template engine. The starter app script sets up Haml. You can see details about <a href="http://railsapps.github.com/rails-haml.html">Haml and Rails</a> with a discussion of the benefits and drawbacks in using Haml.</p>
<h2>RSpec</h2>
<p>The starter app script sets up RSpec for unit testing. Run <code>rake -T</code> to check that rake tasks for RSpec are available. You should be able to run <code>rake spec</code> to run all specs provided with the example app. To learn more about using RSpec, refer to <a href="http://www.pragprog.com/titles/achbd/the-rspec-book">The RSpec Book</a>.</p>
<h2>Cucumber</h2>
<p>The starter app script sets up Cucumber for specifications and acceptance testing. To learn more about using Cucumber, refer to <a href="http://pragprog.com/book/hwcuc/the-cucumber-book">The Cucumber Book</a> or the free introduction to Cucumber, <a href="http://cuke4ninja.com/">The Secret Ninja Cucumber Scrolls</a>.</p>
<p>You should be able to run <code>rake cucumber</code>, or more simply, <code>cucumber</code>, to run the Cucumber scenarios and steps provided with the example app. You can run a single Cucumber feature with a command such as:</p>
<pre>
$ cucumber features/visitors/request_invitation.feature
</pre>
<p>If you’ve used Cucumber, you may know that you need to add <code>--require features</code> to run a single Cucumber feature. Here it is not necessary to do so; the starter app script sets up the <strong>config/cucumber.yml</strong> file so it is not necessary to add <code>--require features</code>.</p>
<h2>Configure Email</h2>
<p>You must configure the app for your email account so your application can send email messages, for example, to acknowledge invitation requests or send welcome messages.</p>
<p>The starter app script sets up a default email configuration. You must add details about your email account.</p>
<h3>Configure ActionMailer</h3>
<p>ActionMailer is configured for development in the <strong>config/environments/development.rb</strong> file:</p>
<pre>
# ActionMailer Config
config.action_mailer.default_url_options = { :host =&gt; 'localhost:3000' }
config.action_mailer.delivery_method = :smtp
# change to true to allow email to be sent during development
config.action_mailer.perform_deliveries = false
config.action_mailer.raise_delivery_errors = true
config.action_mailer.default :charset =&gt; "utf-8"
</pre>
<p>ActionMailer is configured for production in the <strong>config/environments/production.rb</strong> file:</p>
<pre>
config.action_mailer.default_url_options = { :host =&gt; 'example.com' }
# ActionMailer Config
# Setup for production - deliveries, no errors raised
config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = false
config.action_mailer.default :charset =&gt; "utf-8"
</pre>
<p>ActionMailer is configured for testing in the <strong>config/environments/test.rb</strong> file:</p>
<pre>
# ActionMailer Config
config.action_mailer.default_url_options = { :host =&gt; 'example.com' }
</pre>
<p>This will set the example application to deliver email in production. Email messages are visible in the log file so there is no need to send email in development. The configuration above will raise delivery errors in development but not in production.</p>
<p>In development, <code>config.action_mailer.default_url_options</code> is set for a host at <code>localhost:3000</code> which will enable links in Devise confirmation email messages to work properly during development.</p>
<p>For testing, <code>config.action_mailer.default_url_options</code> is set for a host at <code>example.com</code>. Any value allows tests to run.</p>
<p>For production, you’ll need to change the <code>config.action_mailer.default_url_options</code> host option from <code>example.com</code> to your own domain.</p>
<h3>Use a Gmail account</h3>
<p>Use Gmail for experimenting, if you want to keep things simple.</p>
<p>You’ll need to modify the files <strong>config/environments/development.rb</strong> and <strong>config/environments/production.rb</strong>:</p>
<pre>
config.action_mailer.smtp_settings = {
address: "smtp.gmail.com",
port: 587,
domain: "example.com",
authentication: "plain",
enable_starttls_auto: true,
user_name: ENV["GMAIL_USERNAME"],
password: ENV["GMAIL_PASSWORD"]
}
</pre>
<p>You can replace <code>ENV["GMAIL_USERNAME"]</code> and <code>ENV["GMAIL_PASSWORD"]</code> with your Gmail username and password. However, committing the file to a public GitHub repository will expose your secret password.</p>
<h3>Use a Mandrill account</h3>
<p>Use an <span class="caps">SMTP</span> relay service such as Mandrill if you want to increase deliverability for email messages from your app.</p>
<p>You’ll need to modify the files <strong>config/environments/development.rb</strong> and <strong>config/environments/production.rb</strong>:</p>
<pre>
config.action_mailer.smtp_settings = {
:address =&gt; "smtp.mandrillapp.com",
:port =&gt; 25,
:user_name =&gt; ENV["MANDRILL_USERNAME"],
:password =&gt; ENV["MANDRILL_API_KEY"]
}
</pre>
<p>Note that the password will be your Mandrill <span class="caps">API</span> key.</p>
<p>You can replace <code>ENV["MANDRILL_USERNAME"]</code> and <code>ENV["MANDRILL_API_KEY"]</code> with your Mandrill username and <span class="caps">API</span> key. However, committing the file to a public GitHub repository will expose your secret <span class="caps">API</span> key.</p>
<h3>Keep Email Account Passwords Secret</h3>
<p>If you’re familiar with setting <a href="http://en.wikipedia.org/wiki/Environment_variable">Unix environment variables</a>, it’s advisable to leave <code>config.action_mailer.smtp_settings</code> unchanged and set your environment variables in the file that is read when starting an interactive shell (the <strong>~/.bashrc</strong> file for the bash shell). This will keep the password out of your repository.</p>
<p>Are you using a bash shell? Use <code>echo $SHELL</code> to find out. For a bash shell, edit the <strong>~/.bashrc</strong> file and add (for Gmail):</p>
<pre>
export GMAIL_USERNAME="myname@gmail.com"
export GMAIL_PASSWORD="secret"
</pre>
<p>or for Mandrill:</p>
<pre>
export MANDRILL_USERNAME="myname"
export MANDRILL_API_KEY="secret"
</pre>
<p>If you are using MailChimp for managing a mailing list, add your MailChimp <span class="caps">API</span> key:</p>
<pre>
export MAILCHIMP_API_KEY="secret"
</pre>
<p>After you deploy to Heroku, you will need to set the username and password as Heroku environment variables.</p>
<p>For Gmail:</p>
<pre>
$ heroku config:add GMAIL_USERNAME=myname@gmail.com GMAIL_PASSWORD=secret
</pre>
<p>or for Mandrill:</p>
<pre>
$ heroku config:add MANDRILL_USERNAME=myname MANDRILL_API_KEY=secret
</pre>
<p>and MailChimp:</p>
<pre>
$ heroku config:add MAILCHIMP_API_KEY=secret
</pre>
<p>Open a new shell or restart your terminal application to continue.</p>
<h3>Configure Devise for Email</h3>
<p>Complete your email configuration by modifying</p>
<p><strong>config/initializers/devise.rb</strong></p>
<p>and setting the <code>config.mailer_sender</code> option for the return email address for messages that Devise sends from the application.</p>
<h2>Set Up the Database</h2>
<h3>Create a Default User</h3>
<p>You’ll want to set up a default user so you can test the app. The file <strong>db/seeds.rb</strong> already contains:</p>
<pre>
puts 'SETTING UP DEFAULT USER LOGIN'
user = User.create! :name =&gt; 'First User', :email =&gt; 'user@example.com', :password =&gt; 'please', :password_confirmation =&gt; 'please', :confirmed_at =&gt; Time.now.utc
puts 'New user created: ' &lt;&lt; user.name
user2 = User.create! :name =&gt; 'Second User', :email =&gt; 'user2@example.com', :password =&gt; 'please', :password_confirmation =&gt; 'please', :confirmed_at =&gt; Time.now.utc
puts 'New user created: ' &lt;&lt; user2.name
user.add_role :admin
</pre>
<p>You can change the values for name, email, and password as you wish.</p>
<p>If you include your private password in the file, be sure to add the filename to your <strong>.gitignore</strong> file so that your password doesn’t become available in your public GitHub repository.</p>
<p>Note that it’s not necessary to personalize the <strong>db/seeds.rb</strong> file before you deploy your app. You can deploy the app with an example user and then use the application’s “Edit Account” feature to change name, email address, and password after you log in.</p>
<h3>Seed the Database</h3>
<p>The starter app script has already set up the database and added the default user by running:</p>
<pre>
$ rake db:migrate
$ rake db:seed
</pre>
<p>If you change the default user’s name, email, or password you’ll need to reset the database:</p>
<pre>
$ rake db:reset
</pre>
<p>You can run <code>$ rake db:reset</code> whenever you need to recreate the database.</p>
<p>You’ll also need to set up the database for testing:</p>
<pre>
$ rake db:test:prepare
</pre>
<p>If you’re not using rvm, the <a href="https://rvm.io/">Ruby Version Manager</a>, you should preface each rake command with <code>bundle exec</code>. You don’t need to use <code>bundle exec</code> if you are using rvm version 1.11.0 or newer.</p>
<h2>Test the Starter App</h2>
<p>At this point, the app is identical to the <a href="https://github.com/RailsApps/rails3-bootstrap-devise-cancan">rails3-bootstrap-devise-cancan</a> starter app.</p>
<p>You can check that the example app runs properly by entering the command</p>
<p><code>$ rails server</code></p>
<p>To see your application in action, open a browser window and navigate to <a href="http://localhost:3000">http://localhost:3000/</a>. You should see the default user listed on the home page. When you click on the user’s name, you should be required to log in before seeing the user’s detail page.</p>
<p>Stop the server with Control-C.</p>
<p>If you test the app by starting the web server and then leave the server running while you install new gems, you’ll have to restart the server to see any changes. The same is true for changes to configuration files in the <strong>config</strong> folder. This can be confusing to new Rails developers because you can change files in the <strong>app</strong> folders without restarting the server. Stop the server each time after testing and you will avoid this issue.</p>
<h2>Modify the Starter App</h2>
<p>If you’ve tested the example app, you’ve seen that any user who logs in will see a list of all the users on the home page. That’s fine for an example app but it’s not what we want for production.</p>
<h3>Update the Home Page</h3>
<p>Replace the contents of the file <strong>app/views/home/index.html.haml</strong>:</p>
<pre>
%h3 Welcome
</pre>
<p>You can embellish the page as you wish.</p>
<p>Modify the file <strong>app/controllers/home_controller.rb</strong> to remove the <code>index</code> method:</p>
<pre>
class HomeController &lt; ApplicationController
end
</pre>
<h3>Create an Initializer File</h3>
<p>We will set a configuration constant <code>Rails.configuration.launched</code> in an initializer file. This constant will be set to false (before we launch our site) or true (after we launch our site).</p>
<p>Create a file <strong>config/initializers/prelaunch-signup.rb</strong>:</p>
<pre>
# change to "true" (and restart) when you want visitors to sign up without an invitation
Rails.configuration.launched = false
</pre>
<p>We’ll use the configuration constant anywhere in the application where we want behavior to be dependent on whether we’ve launched the site.</p>
<h3>Commit to Git</h3>
<p>Commit your changes to git:</p>
<pre>
$ git add .
$ git commit -m "home page update and initializer file"
</pre>
<h2>Feature: Request Invitation</h2>
<p>Now we’ll pick a user story and turn it into a specification that will guide implementation of a feature.</p>
<p>First we’ll set up our git workflow for adding a new feature.</p>
<h3>Git Workflow</h3>
<p>When you are using git for version control, you can commit every time you save a file, even for the tiniest typo fixes. If only you will ever see your git commits, no one will care. But if you are working on a team, either commercially or as part of an open source project, you will drive your fellow programmers crazy if they try to follow your work and see such “granular” commits. Instead, get in the habit of creating a git branch each time you begin work to implement a feature. When your new feature is complete, merge the branch and “squash” the commits so your comrades see just one commit for the entire feature.</p>
<p>Create a new git branch for this feature:</p>
<pre>
$ git checkout -b request-invitation
</pre>
<p>The command creates a new branch named “request-invitation” and switches to it, analogous to copying all your files to a new directory and moving to work in the new directory (though that is not really what happens with git).</p>
<h3>User Story</h3>
<p>Here’s the user story we’ll specify and implement:</p>
<pre>
*Request Invitation*
As a visitor to the website
I want to request an invitation
so I'll be notified when the site is launched
</pre>
<p>In many situations, your user story is all you need to guide you in coding the implementation. If you are both the product owner and a hands-on developer, you don’t need a written specification to implement a feature. Many developers code directly from a user story. However, I recommend wriitng a specification before coding.</p>
<p>Consider the benefits of writing a specification:</p>
<ul>
<li>It helps us discover the functionality we need to implement.</li>
<li>It is a “to-do list” to identify what needs to be accomplished and helps us track progress.</li>
<li>It helps us describe and discuss features with our business partners.</li>
<li>It is the basis for acceptance testing or integration testing.</li>
</ul><p>For the purposes of this tutorial, acceptance tests and integration tests are synonymous. Acceptance tests demonstrate that developers have succeeded in implementing a specification. Integration tests assure you that your application runs as intended. We will use the specification to create an automated acceptance test so we know when a feature has been successfully implemented. Our acceptance tests also serve as integration tests so we can continue to test the code as we build out or maintain the application. Automated acceptance tests are the key to managing risk for a software development project.</p>
<p>We’ll use Cucumber to create our specification and acceptance tests. Not all developers use Cucumber. Some developers create integration tests using <a href="http://inancgumus.com/66712574">Capybara in combination with RSpec</a> as described in Ryan Bates’s <a href="http://railscasts.com/episodes/275-how-i-test">How I Test</a> Railscast. Cucumber is appropriate when a team includes nonprogrammers who are involved in defining product requirements or there is a need for a specification and acceptance tests to be maintained independently of implementation (for example, when implementation is outsourced). For this tutorial, we may all be programmers, but using Cucumber to create a specification helps to describe the features, organize the tutorial, and break the work into discrete tasks.</p>
<p>Cucumber is a tool for managing software development. It’s up to you to decide if you need tools for managing development processes at this stage of your business.</p>
<h3>Cucumber Scenario</h3>
<p>Now we begin writing our specification.</p>
<p>The <strong>features</strong> directory contains our Cucumber feature files. We can organize Cucumber feature files any way we wish by placing them in subdirectories. For this application, we’ll organize features by roles in subdirectories for “visitors” and “owner”. Create a subdirectory <strong>features/visitors</strong> and then create the following file:</p>
<p><strong>features/visitors/request_invitation.feature</strong></p>
<pre>
Feature: Request Invitation
As a visitor to the website
I want to request an invitation
so I can be notified when the site is launched
Background:
Given I am not logged in
Scenario: User views home page
When I visit the home page
Then I should see a button "Request Invitation"
Scenario: User views invitation request form
When I visit the home page
And I click a button "Request Invitation"
Then I should see a form with a field "Email"
Scenario: User signs up with valid data
When I request an invitation with valid user data
Then I should see a message "Thank You"
And my email address should be stored in the database
And my account should be unconfirmed
And I should receive an email with subject "Request Received"
Scenario: User signs up with invalid email
When I request an invitation with an invalid email
Then I should see an invalid email message
</pre>
<p>This Cucumber feature file contains the specification needed to implement the user story “Request Invitation.”</p>
<p>It’s important to note that user stories don’t necessarily translate directly into Cucumber features. In this case, our first user story is easily transformed into a set of Cucumber scenarios that describe the user story completely. This is not always the case; don’t be concerned if <a href="http://blog.mattwynne.net/2010/10/22/features-user-stories/">Features != User Stories</a> as explained in Matt Wynne’s blog post.</p>
<h3>Cucumber Step Definitions</h3>
<p>Here we turn our specification into an automated acceptance test.</p>
<p>Cucumber scenarios can be read as plain English text. Alone, they serve as specifications. To create an acceptance test or integration test, we must write test code for each step in a scenario, called “step definitions.” Our test code interacts directly with our application, as if it was a visitor to the website using the application.</p>
<p>We’ll create step definitions for all the scenario steps in our “Feature: Request Invitation” file.</p>
<p>Create the following file:</p>
<p><strong>features/step_definitions/visitor_steps.rb</strong></p>
<pre>
def new_user
@user ||= { :email =&gt; "example@example.com",
:password =&gt; "please", :password_confirmation =&gt; "please" }
end
def invitation_request user
visit '/users/sign_up'
fill_in "Email", :with =&gt; user[:email]
click_button "Request Invitation"
end
When /^I visit the home page$/ do
visit root_path
end
Then /^I should see a button "([^\"]*)"$/ do |arg1|
page.should have_button (arg1)
end
When /^I click a button "([^"]*)"$/ do |arg1|
click_button (arg1)
end
Then /^I should see a form with a field "([^"]*)"$/ do |arg1|
page.should have_content (arg1)
end
Then /^I should see a message "([^\"]*)"$/ do |arg1|
page.should have_content (arg1)
end
Then /^my email address should be stored in the database$/ do
test_user = User.find_by_email("example@example.com")
test_user.should respond_to(:email)
end
Then /^my account should be unconfirmed$/ do
test_user = User.find_by_email("example@example.com")
test_user.confirmed_at.should be_nil
end
When /^I request an invitation with valid user data$/ do
invitation_request new_user
end
When /^I request an invitation with an invalid email$/ do
user = new_user.merge(:email =&gt; "notanemail")
invitation_request user
end
</pre>
<p>These step definitions accommodate all scenarios in our “Request Invitation” feature.</p>
<p>Cucumber uses all the step definitions in separate files in the <strong>features/step_definitions/</strong> directory so we can group the step definitions in as many files as we want.</p>
<p>Be sure you’ve set up the database for testing before running Cucumber:</p>
<pre>
$ rake db:test:prepare
</pre>
<p>Then we can run our integration test with the following command:</p>
<pre>
$ cucumber features/visitors/request_invitation.feature
</pre>
<p>The test should fail because our feature is not yet implemented.</p>
<h3>Add an “Opt-In” Field to the Database</h3>
<p>We begin implementing the feature here.</p>
<p>Devise provides almost all the functionality we need for managing users, including migrations that set up the database. We will need one extra field in the database that is not provided by Devise: an “opt-in” attribute so we can record if a visitor has consented to receiving news and announcements by email.</p>
<p>Create a database migration with this command:</p>
<pre>
$ rails generate migration AddOptinToUsers opt_in:boolean
</pre>
<p>After you’ve created the migration, update the database:</p>
<pre>
$ rake db:migrate
$ rake db:seed
</pre>
<h3>Implement “Request Invitation” Form</h3>
<p>The application’s home page doesn’t contain a “Request Invitation” form. We have some choices. Should we add a sign-up form to the home page? We already have a sign-up form provided by Devise, our authentication gem. It’ll be easier to adapt the existing Devise mechanism.</p>
<p>We’ll modify the Devise form to use the <a href="https://github.com/plataformatec/simple_form">SimpleForm</a> gem. The SimpleForm gem lets us create forms that include tags that apply attractive Twitter Bootstrap form styles.</p>
<p>Take a look at the file <strong>app/views/devise/registrations/new.html.haml</strong>. We’ll modify it to make it a “Request Invitation” form.</p>
<ul>
<li>We’ll use <a href="https://github.com/plataformatec/simple_form">SimpleForm</a>.</li>
</ul><ul>
<li>We’ll change the heading from “Sign up” to “Request Invitation.”</li>
</ul><ul>
<li>We’ll remove the password fields.</li>
</ul><ul>
<li>We’ll add the “opt-in” field.</li>
</ul><ul>
<li>We’ll remove the user name field (you could keep it if you wish).</li>
</ul><ul>
<li>We’ll change the submit button text from “Sign up” to “Request Invitation.”</li>
</ul><pre>
%h2 Request Invitation
= simple_form_for(resource, :as =&gt; resource_name, :url =&gt; registration_path(resource_name)) do |f|
= devise_error_messages!
= f.input :email, :placeholder =&gt; 'user@example.com'
= f.input :opt_in, :label =&gt; false, :inline_label =&gt; 'Please send announcements.'
= f.submit "Request Invitation"
</pre>
<h3>Update the User Model</h3>
<p>Devise won’t let us create a new user without a password when we use the default <code>:validatable</code> module. We want Devise to validate the email address but ignore the missing password, so we’ll override Devise’s <code>password_required?</code> method.</p>
<p>We also must add the <code>:opt_in</code> attribute to the list of attributes that can be updated by mass assignment.</p>
<p>Modify the file <strong>app/models/user.rb</strong> to override methods supplied by Devise:</p>
<pre>
class User &lt; ActiveRecord::Base
rolify
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :confirmed_at, :opt_in
# override Devise method
# no password is required when the account is created; validate password when the user sets one
def password_required?
if !persisted?
false
else
!password.nil? || !password_confirmation.nil?
end
end
end
</pre>
<h3>Adjust Acceptance Tests</h3>
<p>To make sure our acceptance tests continue to pass, we’ll need to make changes to the Cucumber step definition file <strong>features/step_definitions/user_steps.rb</strong>.</p>
<p>We no longer ask the visitor to set a password when the account is created.</p>
<p>Replace the <code>sign_up</code> method with the following:</p>
<pre>
def sign_up
delete_user
visit '/users/sign_up'
fill_in "Email", :with =&gt; @visitor[:email]
click_button "Request Invitation"
find_user
end
</pre>
<p>We no longer ask the visitor to supply a name when the account is created.</p>
<p>In the <strong>features/users/sign_up.feature</strong> file, remove the following:</p>
<pre>
Scenario: User signs up without password
When I sign up without a password
Then I should see a missing password message
Scenario: User signs up without password confirmation
When I sign up without a password confirmation
Then I should see a missing password confirmation message
Scenario: User signs up with mismatched password and confirmation
When I sign up with a mismatched password confirmation
Then I should see a mismatched password message
</pre>
<p>We’ve also removed the list iof users from the home page.</p>
<p>In the <strong>features/users/user_show.feature</strong> file, remove the following:</p>
<pre>
Scenario: Viewing users
Given I exist as a user
When I look at the list of users
Then I should see my name
</pre>
<h3>Use Devise Registrations Page for the Home Page</h3>
<p>Examine the <strong>config/routes.rb</strong> file to learn how to use the Devise registrations page as our home page.</p>
<p>The starter app script sets up the <strong>config/routes.rb</strong> file with a route to the home page for authenticated users (those who have an account and are logged in) and the same route for all other users (those who have no account or are not logged in).</p>
<pre>
authenticated :user do
root :to =&gt; 'home#index'
end
root :to =&gt; "home#index"
</pre>
<p>To make the Devise registrations page serve as the home page for users who have no account (or are not logged in), replace the second <code>root :to =&gt; "home#index"</code> so you see this:</p>
<pre>
RailsPrelaunchSignup::Application.routes.draw do
authenticated :user do
root :to =&gt; 'home#index'
end
devise_scope :user do
root :to =&gt; "devise/registrations#new"
end
devise_for :users
resources :users, :only =&gt; [:show, :index]
end
</pre>
<p>With this change, casual visitors will see a “Request Invitation” form on the home page. And users who log in (presumably only an administrator or invited guests) will see the application “home#index” page.</p>
<h3>Create a “Thank You” Page</h3>
<p>For a simple “thank you” page, create a static web page. There’s no need to create a dynamic page with a controller and view. Instead, create a file for a static web page:</p>
<p><strong>public/thankyou.html</strong></p>
<pre>
&lt;h1&gt;Thank You&lt;/h1&gt;
</pre>
<p>Obviously, you can embellish this page as needed.</p>
<p>Later, this tutorial will show you how to eliminate the “thank you” page and use <span class="caps">AJAX</span> to update the sign up page with a thank you message. For now, it’s helpful to see a simple implementation without <span class="caps">AJAX</span>.</p>
<h3>Redirect to “Thank You” Page After Successful Sign Up</h3>
<p>We want the visitor to see the “thank you” page after they request an invitation.</p>
<p>Override the Devise::RegistrationsController with a new controller. Create a file:</p>
<p><strong>app/controllers/registrations_controller.rb</strong></p>
<pre>
class RegistrationsController &lt; Devise::RegistrationsController
protected
def after_inactive_sign_up_path_for(resource)
# the page prelaunch visitors will see after they request an invitation
'/thankyou.html'
end
def after_sign_up_path_for(resource)
# the page new users will see after sign up (after launch, when no invitation is needed)
redirect_to root_path
end
end
</pre>
<p>When a visitor creates an account by requesting an invitation, Devise will call the <code>after_inactive_sign_up_path_for</code> method to redirect the visitor to the thank you page. In the future, after you launch the application and allow visitors to become active as soon as they create an account, you may use the <code>after_sign_up_path_for</code> to redirect the new user to a welcome page.</p>
<p>Modify <strong>config/routes.rb</strong> to use the new controller. Replace <code>devise_for :users</code> with:</p>
<pre>
devise_for :users, :controllers =&gt; { :registrations =&gt; "registrations" }
</pre>
<p>To make sure our acceptance tests continue to pass, we’ll need to make changes to the Cucumber step definition file <strong>features/step_definitions/user_steps.rb</strong>.</p>
<p>Replace the “successful sign up message” step definition content with “Thank You” (or anything else you’ve added to the thank you page):</p>
<pre>
Then /^I should see a successful sign up message$/ do
page.should have_content "Thank You"
end
</pre>
<p>For more information, the Devise wiki explains <a href="https://github.com/plataformatec/devise/wiki/How-To%3a-Redirect-to-a-specific-page-on-successful-sign-up-%28registration%29/">How to Redirect to a Specific Page on Successful Sign Up</a>.</p>
<h3>Postpone Confirmation of New Accounts</h3>
<p>We want a visitor to create a new account when they request an invitation but we don’t want to confirm the email address and activate the account until we send an invitation.</p>
<p>There are several ways to implement this functionality. One approach is to add an “active” attribute to the User model and designate the account as “inactive” when it is created. Another approach is to simply revise the confirmation email to make it a simple “welcome” without the confirmation request but this would require re-implementing the confirmation request process later. The simplest approach is to postpone sending the user a request to confirm their email address, leaving the account unconfirmed until after we send the user an invitation.</p>
<p>We’ll modify the file <strong>app/models/user.rb</strong> to override methods supplied by Devise:</p>
<pre>
class User &lt; ActiveRecord::Base
rolify
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :confirmed_at, :opt_in
# override Devise method
# no password is required when the account is created; validate password when the user sets one
def password_required?
if !persisted?
false
else
!password.nil? || !password_confirmation.nil?
end
end
# override Devise method
def confirmation_required?
false
end
# override Devise method
def active_for_authentication?
confirmed? || confirmation_period_valid?
end
end
</pre>
<p>Devise uses a conditional “after_create” callback to generate a confirmation token and send the confimation request email. It is only called if <code>confirmation_required?</code> returns true. By indicating that “confirmation is not required,” no confimation email will be sent when the account is created.</p>
<p>When we tell Devise that “confirmation is not required,” Devise will assume that any new account is “active_for_authentication.” We don’t want that, so we override the <code>active_for_authentication?</code> method so that unconfirmed accounts are not active.</p>
<h3>Send a Welcome Email</h3>
<p>Ordinarily, when the Devise Confirmable module is configured and a new user requests an invitation, Devise will send an email with instructions to confirm the account. We’ve changed this behavior so the user doesn’t get a confirmation request. However, we still want to send a welcome email.</p>
<p>We didn’t revise the confirmation email to make it a welcome message. That might seem simpler but it would require us to re-implement the confirmation request process later. Instead we’ll send an email welcome message using a new ActionMailer method when the account is created.</p>
<p>You can skip this step if you plan on using MailChimp to manage a mailing list of visitors who have requested invitations. Later in this tutorial, we show how to set up MailChimp to collect email addresses. MailChimp offers an option to send a welcome email when a new user is subscribed to the mailing list. MailChimp makes it easy to design an attractive welcome email so we recommend using MailChimp’s welcome message. For now, we suggest following along so you can see how we send email from Rails.</p>
<p>Generate a mailer with views and a spec:</p>
<pre>
$ rails generate mailer UserMailer
</pre>
<p>Modify the file <strong>spec/mailers/user_mailer_spec.rb</strong> to create a test:</p>
<pre>
require "spec_helper"
describe UserMailer do
before(:all) do
@user = FactoryGirl.create(:user, email: "example@example.com")
@email = UserMailer.welcome_email(@user).deliver
end
it "should be delivered to the email address provided" do
@email.should deliver_to("example@example.com")
end
it "should contain the correct message in the mail body" do
@email.should have_body_text(/Welcome/)
end
it "should have the correct subject" do
@email.should have_subject(/Request Received/)
end
end
</pre>
<p>Add a <code>welcome_email</code> method to the mailer by editing the file <strong>app/mailers/user_mailer.rb</strong>:</p>
<pre>
class UserMailer &lt; ActionMailer::Base
default :from =&gt; "notifications@example.com"
def welcome_email(user)
mail(:to =&gt; user.email, :subject =&gt; "Invitation Request Received")
headers['X-MC-Track'] = "opens, clicks"
headers['X-MC-GoogleAnalytics'] = "example.com"
headers['X-MC-Tags'] = "welcome"
end
end
</pre>
<p>Replace the “notifications@example.com” string with your email address.</p>
<p>If you’re not using Mandrill for an <span class="caps">SMTP</span> relay service, you can leave out these statements:</p>
<pre>
headers['X-MC-Track'] = "opens, clicks"
headers['X-MC-GoogleAnalytics'] = "example.com"
headers['X-MC-Tags'] = "welcome"
</pre>
<p>The <code>X-MC-Track</code> header sets up Mandrill to track message delivery by adding an invisible image (a web beacon) to <span class="caps">HTML</span> emails to track emails that have been opened. It also tracks whether links in the email were clicked. The <code>X-MC-GoogleAnalytics</code> header sets up tracking with your Google Analytics account (set your domain name here). The <code>X-MC-Tags</code> header identifies each email as a welcome message for analyzing delivery success.</p>
<p>Create a mailer view by creating a file <strong>app/views/user_mailer/welcome_email.html.erb</strong>. This will be the template used for the email, formatted in <span class="caps">HTML</span>:</p>
<pre>
&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta content="text/html; charset=UTF-8" http-equiv="Content-Type" /&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Welcome&lt;/h1&gt;
&lt;p&gt;
We have received your request for an invitation to example.com.
&lt;/p&gt;
&lt;p&gt;
We'll contact you when we launch.
&lt;/p&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p>It is a good idea to make a text-only version for this message. Create a file <strong>app/views/user_mailer/welcome_email.text.erb</strong>:</p>
<pre>
Welcome!
We have received your request for an invitation to example.com.
We'll contact you when we launch.
</pre>
<p>When you call the mailer method, ActionMailer will detect the two templates (text and <span class="caps">HTML</span>) and automatically generate a multipart/alternative email. If you use the Mandrill service, you can skip this step if you configure Mandrill to automatically generate a plain-text version of all emails.</p>
<p>Now we’ll wire up the User model to send the welcome message when an account is created.</p>
<p>Modify the file <strong>app/models/user.rb</strong> to add the <code>send_welcome_email</code> method:</p>
<pre>
class User &lt; ActiveRecord::Base
rolify
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :confirmed_at, :opt_in
after_create :send_welcome_email
# override Devise method
# no password is required when the account is created; validate password when the user sets one
def password_required?
if !persisted?
false
else
!password.nil? || !password_confirmation.nil?
end
end
# override Devise method
def confirmation_required?
false
end
# override Devise method
def active_for_authentication?
confirmed? || confirmation_period_valid?
end
private
def send_welcome_email
unless self.email.include?('@example.com') &amp;&amp; Rails.env != 'test'
UserMailer.welcome_email(self).deliver
end
end
end
</pre>
<p>Now the visitor will get a welcome email when they request an invitation.</p>
<p>There’s an unintended side effect of adding the <code>after_create :send_welcome_email</code> method. When you reset the database with <code>rake db:reset</code> you’ll send welcome messages to the users listed in the <strong>db/seeds.rb</strong> file. To avoid bounce notices warning that mail could not be delivered to our bogus users, we don’t send a welcome email to addresses at the “example.com” domain (except when we’re running a test).</p>
<h3>Tweak the User Interface</h3>
<p>The required functionality is largely complete. But there are a few small changes to make.</p>
<p>If the user visits the sign-in page immediately after requesting an invitation, they may see this flash message:</p>
<p>“A message with a confirmation link has been sent to your email address. Please open the link to activate your account.”</p>
<p>In the file <strong>config/locales/devise.en.yml</strong>, find this message:</p>
<pre>
signed_up_but_unconfirmed: 'A message with a confirmation link has been sent to your email address. Please open the link to activate your account.'
</pre>
<p>Replace it with this:</p>
<pre>
signed_up_but_unconfirmed: 'Your invitation request has been received. You will receive an invitation when we launch.'
</pre>
<p>Be careful not to use an apostrophe or single quote in the message unless you surround the text with double quotes.</p>
<p>If the visitor attempts to sign in, they will see this message:</p>
<p>“You have to confirm your account before continuing.”</p>
<p>In the file <strong>config/locales/devise.en.yml</strong>, find this message:</p>
<pre>
unconfirmed: 'You have to confirm your account before continuing.'
</pre>
<p>Replace it with this:</p>
<pre>
unconfirmed: 'Your account is not active.'
</pre>
<p>The user will also see links on the sign-in page: “Didn’t receive confirmation instructions?” and “Forgot your password?”. We want visitors to see these links after we launch. Before we launch, we don’t want visitors to see these links. The sign-in page is provided from a view template in the Devise gem. We don’t have to modify the sign-in page. Instead we’ll modify the Devise “links” partial.</p>
<p>In the “links” partial we’ll use a configuration constant: <code>Rails.configuration.launched</code>. This constant is set to false (before we launch our site) or true (after we launch our site). When the constant is false, the links for “Didn’t receive confirmation instructions?” and “Forgot your password?” will not appear.</p>
<p>Modify the file <strong>app/views/devise/shared/_links.html.haml</strong>:</p>
<pre>
- if devise_mapping.recoverable? &amp;&amp; controller_name != 'passwords' &amp;&amp; Rails.configuration.launched == true
= link_to "Forgot your password?", new_password_path(resource_name)
%br/
- if devise_mapping.confirmable? &amp;&amp; controller_name != 'confirmations' &amp;&amp; Rails.configuration.launched == true
= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name)
%br/
- if devise_mapping.lockable? &amp;&amp; resource_class.unlock_strategy_enabled?(:email) &amp;&amp; controller_name != 'unlocks'
= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name)
%br/
</pre>
<h3>Tweak the Acceptance Tests</h3>
<p>To make sure our acceptance tests continue to pass, we’ll need to make changes to the Cucumber scenario file <strong>features/users/sign_in.feature</strong>.</p>
<p>Remove the “Scenario: User has not confirmed account” Cucumber scenario:</p>
<pre>
Scenario: User has not confirmed account
Given I exist as an unconfirmed user
And I am not logged in
When I sign in with valid credentials
Then I see an unconfirmed account message
And I should be signed out
</pre>
<p>Now our feature is complete.</p>
<h3>Test the Implemention of the “Request Invitation” Feature</h3>
<p>Be sure you’ve set up the database for testing before running Cucumber:</p>
<pre>
$ rake db:test:prepare
</pre>
<p>Run the integration test with the following command:</p>
<pre>
$ cucumber features/visitors/request_invitation.feature
</pre>
<p>The test should succeed.</p>
<p>Evaluating only the functionality, the feature is complete. In the next section, we’ll improve the look and feel of the feature.</p>
<h3>Git Workflow</h3>
<p>If you haven’t commited any changes yet, commit your changes to git:</p>
<pre>
$ git add .
$ git commit -am "implement 'Request Invitation' feature"
</pre>
<p>Since the new feature is complete, merge the working branch to “master” and squash the commits so you have just one commit for the entire feature:</p>
<pre>
$ git checkout master
$ git merge --squash request-invitation
$ git commit -am "implement 'Request Invitation' feature"
</pre>
<p>You can delete the working branch when you’re done:</p>
<pre>
$ git branch -D request-invitation
</pre>
<h2>Improving the Design: Modal Window</h2>
<p>The functionality of the “Request Invitation” feature is complete. Let’s improve the look and feel of the feature.</p>
<h3>Git Workflow</h3>
<p>Create a new git branch for the changes you’ll make to the page design:</p>
<pre>
$ git checkout -b modal
</pre>
<p>The command creates a new branch named “modal” and switches to it.</p>
<h3>Add a Modal Window</h3>
<p>It’d be nice for the “request invitation” page to include some “romance copy” to convince the visitor of the value of the site. The “call to action” could be a big button that says, “Request Invitation,” which opens a modal window and invites the visitor to enter an email address and click a submit button.</p>
<p>Twitter Bootstrap gives us everything we need to implement this in a few lines of code.</p>
<p>Open the file <strong>app/views/devise/registrations/new.html.haml</strong> and replace the contents with this new code:</p>
<pre>
#request-invite.modal{:style =&gt; "display: none;"}
= simple_form_for resource, :as =&gt; resource_name, :url =&gt; registration_path(resource_name) , :html =&gt; {:class =&gt; 'form-horizontal' } do |f|
.modal-header
%a.close{"data-dismiss" =&gt; "modal"} &amp;#215;
%h3 Request Invitation
.modal-body
= f.error_notification
= f.input :email, :placeholder =&gt; 'user@example.com'
.modal-footer
= f.submit "Request Invitation", :class =&gt; "btn.btn-success", :id =&gt; 'invitation_button'
%a.btn{"data-dismiss" =&gt; "modal", :href =&gt; "#"} Close
#romance-copy{:style =&gt; "text-align: center; margin-top: 80px"}
%h2 Want in?
#call-to-action{:style =&gt; "text-align: center; margin-top: 80px"}
%a.btn.btn-primary.btn-large{"data-toggle" =&gt; "modal", :href =&gt; "#request-invite"} Request invite
</pre>
<p>The revised view code includes <span class="caps">CSS</span> classes from Twitter Bootstrap that implement the modal window and apply style to the buttons. You’ll note that our code includes some simple style rules to position the romance copy and the call-to-action button. These styles could be moved to an external stylesheet; for now, it’s easier to include them in the view file.</p>
<h3>Display Errors</h3>
<p>Displaying the “request invitation” form inside a modal window is a nice touch but it creates a problem: Form validation error messages are hidden. See for yourself by running the application. Open the modal window, leave the email field blank and click the submit button. You’ll see the home page without any indication that there was a problem. Click the home page “Request Invite” button again and you’ll see there is an error message in the modal window.</p>
<p>Our first option is to force the form to be displayed when errors are present. We can do this by modifying the first line like this:</p>
<pre>
#request-invite.modal{:style =&gt; "display: #{@user.errors.any? ? 'block' : 'none'};"}
</pre>
<p>When you test this by submitting a form without an email address, you’ll see the form appears with error messages. However, we don’t see the full effect of the modal window because the page is not opaque as it should be with the modal window. We can keep the modified code, but we really need some jQuery code to trigger the modal window when an error message is present.</p>
<p>Add the following to the file <strong>app/assets/javascripts/application.js</strong>:</p>
<pre>
$('document').ready(function() {
// display validation errors for the "request invitation" form
if ($('.alert-error').length &gt; 0) {
$("#request-invite").modal('toggle');
}
})
</pre>
<p>When an element is present with the <code>alert-error</code> class, and it contains text, the page will refresh and the modal window will be displayed properly. We use jQuery’s selector syntax to determine if an error message is present. We start with the jQuery object <code>$()</code>, find any elements with the <code>alert-error</code> class attribute using the <a href="http://api.jquery.com/class-selector/">jQuery class selector</a>, and then test if the element has a length greater than zero. If our condition is met, we use the <a href="http://api.jquery.com/id-selector/">jQuery ID selector</a> to find the div named <code>request-invite</code> and we toggle the modal window.</p>
<h2>Improving the Design: Adding <span class="caps">AJAX</span>
</h2>
<p><em>Thank you to <a href="http://andreapavoni.com">Andrea Pavoni</a> for the <span class="caps">AJAX</span> implementation.</em></p>
<p>We’ve improved the appearance of the “invitation request” page by adding a modal window using Twitter Bootstrap. We can make further improvements. As implemented, the page refreshes with a roundtrip to the server when we submit the form successfully (or get an an error). Our app will look more sophisticated if we use <span class="caps">AJAX</span> techniques to update the page without a page refresh.</p>
<p>We don’t need changes to the “invitation request” page. We’ll add a partial to provide a “thank you” message. Add a file <strong>app/views/devise/registrations/_thankyou.html.haml</strong>:</p>
<pre>
%h1 Thank you
#request-invite.modal{:style =&gt; "display: 'block';"}
.modal-header
%a.close{"data-dismiss" =&gt; "modal"} &amp;#215;
%h3 Thank you!
.modal-body
%p We have received your request for an invitation to example.com.
%p We'll contact you when we launch.
</pre>
<p>Next, add some JavaScript to the file <strong>app/assets/javascripts/application.js</strong>. This will trigger an <span class="caps">AJAX</span> submission action when the “request invitation” button is clicked and update the <code>#request-invite</code> div on completion.</p>
<pre>
$('document').ready(function() {
// display validation errors for the "request invitation" form
if ($('.alert-error').length &gt; 0) {
$("#request-invite").modal('toggle');
}
// use AJAX to submit the "request invitation" form
$('#invitation_button').live('click', function() {
var email = $('form #user_email').val();
if($('form #user_opt_in').is(':checked'))
var opt_in = true;
else
var opt_in = false;
var dataString = 'user[email]='+ email + '&amp;user[opt_in]=' + opt_in;
$.ajax({
type: "POST",
url: "/users",
data: dataString,
success: function(data) {
$('#request-invite').html(data);
loadSocial();
}
});
return false;
});
})
</pre>
<p>Finally, we need to override the <code>create</code> method in the Devise registration controller to render the “thank you” partial. Modify the file <strong>app/controllers/registrations_controller.rb</strong>:</p>
<pre>
class RegistrationsController &lt; Devise::RegistrationsController
# ovverride #create to respond to AJAX with a partial
def create
build_resource
if resource.save
if resource.active_for_authentication?
sign_in(resource_name, resource)
(render(:partial =&gt; 'thankyou', :layout =&gt; false) &amp;&amp; return) if request.xhr?
respond_with resource, :location =&gt; after_sign_up_path_for(resource)
else
expire_session_data_after_sign_in!
(render(:partial =&gt; 'thankyou', :layout =&gt; false) &amp;&amp; return) if request.xhr?
respond_with resource, :location =&gt; after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords resource
render :action =&gt; :new, :layout =&gt; !request.xhr?
end
end
protected
def after_inactive_sign_up_path_for(resource)
'/thankyou.html'
end
def after_sign_up_path_for(resource)
# the page new users will see after sign up (after launch, when no invitation is needed)
redirect_to root_path
end
end
</pre>
<p>To make sure our acceptance tests continue to pass, we’ll need to make a tweak to the Cucumber step definition file <strong>features/step_definitions/user_steps.rb</strong>.</p>
<p>Replace the “unconfirmed account message” step definition to accommodate the SimpleForm output:</p>
<pre>
Then /^I should see an invalid email message$/ do
page.should have_content "Emailis invalid"
end
</pre>
<p>Now we can say we have built a “Web 2.0” application. When the visitor submits the form, the modal window changes to display a “thank you” message (or an error message) without a page refresh.</p>
<h3>Git Workflow</h3>
<p>If you haven’t commited any changes yet, commit your changes to git:</p>
<pre>
$ git add .
$ git commit -am "improve design for 'Request Invitation' feature"
</pre>
<p>Merge the working branch to “master”:</p>
<pre>
$ git checkout master
$ git merge --squash modal
$ git commit -am "improve design for 'Request Invitation' feature"
$ git branch -D modal
</pre>
<h2>Feature: View Progress</h2>
<p>Now we’ll implement the next user story. As the owner of the site, you’ll want to visit and see how many visitors have requested invitations. You likely don’t want anyone else to view that information, so we’ll need some form of authorization to restrict access to yourself or approved administrators.</p>
<p>First we’ll set up our git workflow so we can add a new feature.</p>
<h3>Git Workflow</h3>
<p>Create a new git branch for this feature:</p>
<pre>
$ git checkout -b view-progress
</pre>
<p>The command creates a new branch named “view-progress.”</p>
<h3>User Story</h3>
<p>Here’s the user story we’ll specify and implement:</p>
<pre>
*View Progress*
As the owner of the site
I want to know how many visitors have requested invitations
so I can know if my offer is popular
</pre>
<h3>Cucumber Scenario</h3>
<p>Let’s write our specification.</p>
<p>Create a subdirectory <strong>features/admin</strong> and then create the following file:</p>
<p><strong>features/admin/view_progress.feature</strong></p>
<pre>
Feature: View Progress
As the owner of the site
I want to know how many visitors have requested invitations
so I can know if my offer is popular
Scenario: Administrator views list of users
Given I request an invitation with valid user data
And I am logged in as an administrator
When I visit the users page
Then I should see a list of users
Scenario: User cannot view list of users
Given I am logged in
When I visit the users page
Then I should see an access denied message
</pre>
<p>This Cucumber feature file contains the specification needed to implement the user story “View Progress.”</p>
<h3>Cucumber Step Definitions</h3>
<p>Turn the specification into an automated acceptance test. Create step definitions for all the scenario steps in our “Feature: View Progress” file.</p>
<p>Create the following file:</p>
<p><strong>features/step_definitions/admin_steps.rb</strong></p>
<pre>
Given /^I am logged in as an administrator$/ do
@admin = FactoryGirl.create(:user, email: "admin@example.com")
@admin.add_role :admin
@visitor ||= { :email =&gt; "admin@example.com",
:password =&gt; "please", :password_confirmation =&gt; "please" }
sign_in
end
When /^I visit the users page$/ do
visit users_path
end
Then /^I should see a list of users$/ do
page.should have_content @user[:email]
end
Then /^I should see an access denied message$/ do
page.should have_content "Not authorized as an administrator"
end
</pre>
<p>Be sure you’ve set up the database for testing before running Cucumber:</p>
<pre>
$ rake db:test:prepare
</pre>
<p>Then we can run our integration test with the following command:</p>
<pre>
$ cucumber features/admin/view_progress.feature
</pre>
<p>The test will succeed. That’s because the application template we used to generate the application already implements the functionality we want. Let’s see how.</p>
<h3>The Administrative Page</h3>
<p>We already have a page that shows a list of users.</p>
<p>Take a look at the file <strong>app/views/users/index.html.haml</strong>:</p>
<pre>
%h2 Users
- @users.each do |user|
%br/
#{link_to user.email, user} signed up #{user.created_at.to_date}
</pre>
<p>We can use this page as our “administrative dashboard.” It’s fine for our example application; if you later build a more complex application, you may wish to create an AdminController with corresponding Admin views.</p>
<h3>Restricting Access to the Administrative Page</h3>
<p>Take a look at the controller file <strong>app/controllers/users_controller.rb</strong>:</p>
<pre>
class UsersController &lt; ApplicationController
before_filter :authenticate_user!
def index
authorize! :index, @user, :message =&gt; 'Not authorized as an administrator.'
@users = User.all
end
def show
@user = User.find(params[:id])
end
end
</pre>
<p>Notice the index method contains a statement which limits access to administrators only:</p>
<pre>
authorize! :index, @user, :message =&gt; 'Not authorized as an administrator.'
</pre>
<p>When we generated our starter app, we answered “yes” to the question, “Would you like to manage authorization with CanCan &amp; Rolify?” The application template set up everything we need for limiting access to administrative pages. You can take a look at the <a href="http://railsapps.github.com/tutorial-rails-bootstrap-devise-cancan.html">tutorial for the rails3-bootstrap-devise-cancan example app</a> to see how it is done. In a nutshell, Rolify allows us to designate a user as an “admin.” CanCan provides an <code>Ability</code> class and the <code>authorize!</code> method. We add the <code>authorize!</code> method to any controller action that should be restricted to administrators only. The <code>authorize!</code> method makes a call to the <code>Ability</code> class to determine which users are authorized for access.</p>
<p>If you look at the file <strong>app/models/ability.rb</strong>, you’ll see this:</p>
<pre>
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.has_role? :admin
can :manage, :all
end
end
end
</pre>
<p>Very simply, if we add the <code>authorize!</code> method to any controller action, it will check the <code>Ability</code> class and allow access for users in the “admin” role. By default, other users will not be allowed. Of course, if the <code>authorize!</code> method is not included in a controller action, any user will have access.</p>
<h3>Test the Implemention of the “View Progress” Feature</h3>
<p>Run the integration test with the following command:</p>
<pre>
$ cucumber features/admin/view_progress.feature
</pre>
<p>The test should succeed.</p>
<h3>Git Workflow</h3>
<p>If you haven’t commited any changes yet, commit your changes to git:</p>
<p>$ git add .<br>
$ git commit -am “implement ‘View Progress’ feature”</p>
<p>Since the new feature is complete, merge the working branch to “master” and squash the commits so you have just one commit for the entire feature:</p>
<pre>
$ git checkout master
$ git merge --squash view-progress
$ git commit -am "implement 'View Progress' feature"
</pre>
<p>You can delete the working branch when you’re done:</p>
<pre>
$ git branch -D view-progress
</pre>
<h2>Improving the Design: Adding a Chart</h2>
<p>Our “administrative dashboard” shows a list of visitors who have requested invitations. It meets our basic requirements but I’m sure we’d prefer to see the trend of sign-ups over time. Let’s add a chart.</p>
<p>If you look at The Ruby Toolbox site in the <a href="https://www.ruby-toolbox.com/categories/graphing">Graphing category</a>, you’ll see ten gems of varying popularity, age and activity. Google offers an online service, <a href="https://developers.google.com/chart/">Google Chart Tools</a>, that uses Javascript in the browser to generate charts and graphs. Two of the gems listed use Google Chart Tools to generate charts: Matt Aimonetti’s <a href="https://github.com/mattetti/googlecharts">GoogleCharts</a> and Winston Teo’s <a href="https://github.com/winston/google_visualr">GoogleVisualr</a>. We’ll use the GoogleVisualr gem; it includes a useful view helper.</p>
<p><em>Note:</em> GoogleVisualr is adequate but it would be nice to have a chart that used HTML5 features instead of Adobe Flash Player. I like the <a href="http://www.humblesoftware.com/envision/demos/timeseries">Envision.js Time Series Chart</a>. Maybe we could use this in the future. Patches?</p>
<p>First we’ll set up our git workflow so we can add a new feature.</p>
<h3>Git Workflow</h3>
<p>Create a new git branch for this feature:</p>
<pre>
$ git checkout -b add-chart
</pre>
<h3>GoogleVisualr Gem</h3>
<p>Be sure the GoogleVisualr gem is in the <strong>Gemfile</strong>:</p>
<pre>
gem "google_visualr", "&gt;= 2.1.2"
</pre>
<p>Run the <code>bundle install</code> command to install the required gem on your computer:</p>
<pre>
$ bundle install
</pre>
<p>If your web server is running, remember to restart before testing to pick up the new gem.</p>
<h3>Javascript for Google Chart Tools</h3>
<p>We’ll add the Javascript library that loads the Google Chart Tools <span class="caps">API</span>. We won’t add it to the Javascript files in our asset pipeline. Instead, we’ll add it to the application layout for the administrative page, loading it directly from Google’s content delivery network (<span class="caps">CDN</span>).</p>
<p>We want to add it to a single page of our application. Our default application layout will accept a page-specific addition to the <code>head</code> section. The <code>= yield(:head)</code> statement in the file <strong>app/views/layouts/application.html.haml</strong> will incorporate anything we add to a page with the <code>content_for :head</code> tag.</p>
<p>Modify the file <strong>app/views/users/index.html.haml</strong>:</p>
<pre>
- content_for :head do
= javascript_include_tag 'http://www.google.com/jsapi'
%h2 Users
- @users.each do |user|
%br/
#{link_to user.email, user} signed up #{user.created_at.to_date}
</pre>
<p>The default application layout will pick up the <code>content_for :head</code> code and add it to the <code>head</code> section of the application layout. The page will load the Javascript code for the Google Chart Tools <span class="caps">API</span> from Google’s content delivery network.</p>
<h3>Generate a Chart with the User Controller</h3>
<p>We’ll use the GoogleVisualr gem in the user controller to retrieve data and generate a chart.</p>
<p>Modify the controller file <strong>app/controllers/users_controller.rb</strong>:</p>
<pre>
class UsersController &lt; ApplicationController
before_filter :authenticate_user!
def index
authorize! :index, @user, :message =&gt; 'Not authorized as an administrator.'
@users = User.all
@chart = create_chart
end
def show
@user = User.find(params[:id])
end
private
def create_chart
users_by_day = User.group("DATE(created_at)").count
data_table = GoogleVisualr::DataTable.new
data_table.new_column('date')
data_table.new_column('number')
users_by_day.each do |day|
data_table.add_row([ Date.parse(day[0]), day[1]])
end
@chart = GoogleVisualr::Interactive::AnnotatedTimeLine.new(data_table)
end
end
</pre>
<p>The UsersController will use the GoogleVisualr gem to interact with the Google Chart Tools <span class="caps">API</span> to generate a chart. Google Chart Tools offers a wide variety of charts (<a href="https://developers.google.com/chart/interactive/docs/gallery">see examples</a>) and the <a href="http://googlevisualr.herokuapp.com/">GoogleVisualr documentation</a> shows how to implement many of the available charts. We use the <a href="http://googlevisualr.herokuapp.com/examples/interactive/annotated_time_line">Annotated Time Line</a> chart.</p>
<h3>Add a Chart to the Administrative Page</h3>
<p>Let’s add the chart to the page that shows a list of users.</p>
<p>Modify the file <strong>app/views/users/index.html.haml</strong>:</p>
<pre>
- content_for :head do
= javascript_include_tag 'http://www.google.com/jsapi'
#chart{:style =&gt; "width: 700px; height: 240px;"}
= render_chart @chart, 'chart'
%h2 Users
- @users.each do |user|
%br/
#{link_to user.email, user} signed up #{user.created_at.to_date}
</pre>
<p>The Annotated Time Line chart must be placed inside a <code>div</code> tag with a fixed width and height because it uses Adobe Flash Player to provide interactive features.</p>
<p>We now have a simple chart on the administrative page.</p>
<h3>Git Workflow</h3>
<p>If you haven’t commited any changes yet, commit your changes to git:</p>
<p>$ git add .<br>
$ git commit -am “add chart to admin dashboard”</p>
<p>Since the new feature is complete, merge the working branch to “master” and squash the commits so you have just one commit for the entire feature:</p>
<pre>
$ git checkout master
$ git merge --squash add-chart
$ git commit -am "add chart to admin dashboard"
</pre>
<p>You can delete the working branch when you’re done:</p>
<pre>
$ git branch -D add-chart
</pre>
<h2>Improving the Design: Sorting a Table</h2>
<p>We can make our administrative dashboard more useful by improving the display of the table of users. As implemented so far, it is just a simple list. We can add a jQuery plugin to give us a table that is sortable, searchable, and adds pagination. The <a href="http://www.datatables.net/">DataTables jQuery plugin</a> is popular and integrates with Twitter Bootstrap. Ryan Bates offers a <a href="http://railscasts.com/episodes/340-datatables">DataTables RailsCast</a> that shows how to set it up using Robin Wenglewski’s <a href="https://github.com/rweng/jquery-datatables-rails">jquery-datatables-rails</a> gem.</p>
<p>First we’ll set up our git workflow so we can add a new feature.</p>
<h3>Git Workflow</h3>
<p>Create a new git branch for this feature:</p>
<pre>
$ git checkout -b sort-table
</pre>
<h3>jQuery-Datatables-Rails Gem</h3>
<p>Be sure the jquery-datatables-rails gem is in the <strong>Gemfile</strong>:</p>
<pre>
gem "jquery-datatables-rails"
</pre>
<p>Run the <code>bundle install</code> command to install the required gem on your computer:</p>
<pre>
$ bundle install
</pre>
<h3>Add the jQuery Plugin</h3>
<p>Update the file <strong>app/assets/javascripts/application.js</strong> to include the jQuery plugin and its Bootstrap support library:</p>
<pre>
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require dataTables/jquery.dataTables
//= require dataTables/jquery.dataTables.bootstrap
//= require_tree .
</pre>
<h3>Add <span class="caps">CSS</span> Rules</h3>
<p>Update the file <strong>app/assets/stylesheets/application.css.scss</strong> to include <span class="caps">CSS</span> rules for DataTables that integrate with Twitter Bootstrap:</p>
<pre>
*= require_self
*= require dataTables/jquery.dataTables.bootstrap
*= require_tree .
</pre>
<h3>Add JavaScript</h3>
<p>We’ll need JavaScript for the administrative page where we will use DataTables. Add CoffeeScript to the file <strong>app/assets/javascripts/users.js.coffee</strong>:</p>
<pre>
#// For fixed width containers
jQuery -&gt;
$('.datatable').dataTable({
"sDom": "&lt;'row'&lt;'span6'l&gt;&lt;'span6'f&gt;r&gt;t&lt;'row'&lt;'span6'i&gt;&lt;'span6'p&gt;&gt;",
"sPaginationType": "bootstrap"
});
</pre>
<h3>Add a Table to the Administrative Page</h3>
<p>Let’s add a table of users to the administrative page.</p>
<p>Modify the file <strong>app/views/users/index.html.haml</strong>:</p>
<pre>
- content_for :head do
= javascript_include_tag 'http://www.google.com/jsapi'
%h2 Users
.span9
#chart{:style =&gt; "width: 700px; height: 240px"}
= render_chart @chart, 'chart'
%br
.span9
%table.datatable.table.table-bordered.table-condensed
%thead
%tr
%th Email
%th Signed up
%tbody
- @users.each do |user|
%tr
%td #{link_to user.email, user}
%td #{user.created_at.to_date}
</pre>
<p>We now have a sortable, searchable table of users on the administrative page.</p>
<p><em>Note:</em> The top line of the table (containing “Show entries” and “Search”) isn’t aligned nicely. Suggestions to improve it?</p>
<h3>Scalability Issues</h3>
<p>As implemented, our administrative page has a scalability constraint. In the Users controller, we query the database for all the users in the database and render a page containing a table of all the users. If we have many thousands of users requesting invitations, the administrative page will be slow to load. Ryan Bates shows how to implement pagination using server-side processing in his <a href="http://railscasts.com/episodes/340-datatables">DataTables RailsCast</a>. For a startup launch with a few thousand users, our implementation should be adequate.</p>
<h3>Git Workflow</h3>
<p>If you haven’t commited any changes yet, commit your changes to git:</p>
<pre>
$ git add .
$ git commit -am "add sortable table on the admin dashboard"
</pre>
<pre>
$ git checkout master
$ git merge --squash sort-table
$ git commit -am "add sortable table on the admin dashboard"
</pre>
<p>You can delete the working branch when you’re done:</p>
<pre>
$ git branch -D sort-table
</pre>
<h2>Feature: Send Invitations</h2>
<p>As implemented, our application collects invitation requests from visitors. We’ve built a simple administrative dashboard displaying a graph of requests over time and a list of visitors’ email addresses. This is all you need while you are promoting and building your application. But when you ready for users to begin trying your site, you’ll want to select users for your beta test, send each an invitation, and ask each to confirm their email address and set an account password. We’ll call this functionality the “Send Invitations” feature and we’ll start implementing it with a user story and acceptance tests.</p>
<h3>Git Workflow</h3>
<p>Create a new git branch for this feature:</p>
<pre>
$ git checkout -b invitations
</pre>
<h3>User Story</h3>
<p>Here’s the user story we’ll specify and implement:</p>
<pre>
*Send Invitations*
As the owner of the site
I want to send invitations to visitors who have requested invitations
so users can try the site
</pre>
<h3>Cucumber Scenario</h3>
<p>Let’s write our specification.</p>
<p>Add the following file:</p>
<p><strong>features/admin/send_invitations.feature</strong></p>
<pre>
Feature: Send Invitations
As the owner of the site
I want to send invitations to visitors who have requested invitations
so users can try the site
Scenario: Administrator sends invitation
Given I request an invitation with valid user data
And I am logged in as an administrator
When I visit the users page
And I click a link "send invitation"
And I open the email with subject "Confirmation instructions"
Then I should see "confirm your email address" in the email body
</pre>
<p>This Cucumber feature file contains the specification needed to implement the user story “Send Invitations.”</p>
<h3>Cucumber Step Definitions</h3>
<p>Turn the specification into an automated acceptance test.</p>
<p>Add these steps to the existing file:</p>
<p><strong>features/step_definitions/admin_steps.rb</strong></p>
<pre>
When /^I click a link "([^"]*)"$/ do |arg1|
click_on (arg1)
end
</pre>
<p>Be sure you’ve set up the database for testing before running Cucumber:</p>
<pre>
$ rake db:test:prepare
</pre>
<p>Then we can run our integration test with the following command:</p>
<pre>
$ cucumber features/admin/send_invitations.feature
</pre>
<p>The test will fail because we haven’t yet implemented the functionality.</p>
<h3>Implementation</h3>
<p>We’ll add a “send invitation” link to each user listed on the administrative dashboard. We’ll need a corresponding <code>invite</code> action in the user controller and a matching route. We also want to make sure only an administrator is authorized to initiate the <code>invite</code> action.</p>
<p>Devise already knows how to send an account confirmation email to a user, with its <code>user.send_confirmation_instructions</code> method, which will generate a confirmation token and send an email message to the user. We’ll call the Devise <code>user.send_confirmation_instructions</code> method from the <code>invite</code> action in the user controller.</p>
<h3>Add a User Controller Action</h3>
<p>So far, our application architecture has been very simple. We’ve used a few RESTful actions supplied by Devise and improved on a simple <code>index</code> method to prepare our administrative dashboard page. Now we’ll need a custom <code>invite</code> action in the user controller.</p>
<p>Modify the controller file <strong>app/controllers/users_controller.rb</strong>:</p>
<pre>
class UsersController &lt; ApplicationController
before_filter :authenticate_user!
def index
authorize! :index, @user, :message =&gt; 'Not authorized as an administrator.'
@users = User.all
@chart = create_chart
end
def show
@user = User.find(params[:id])
end
def invite
authorize! :invite, @user, :message =&gt; 'Not authorized as an administrator.'
@user = User.find(params[:id])
@user.send_confirmation_instructions
redirect_to :back, :notice =&gt; "Sent invitation to #{@user.email}."
end
private
def create_chart
users_by_day = User.group("DATE(created_at)").count
data_table = GoogleVisualr::DataTable.new
data_table.new_column('date')
data_table.new_column('number')
users_by_day.each do |day|
data_table.add_row([ Date.parse(day[0]), day[1]])
end
@chart = GoogleVisualr::Interactive::AnnotatedTimeLine.new(data_table)
end
end
</pre>
<p>CanCan’s <code>authorize!</code> method ensures that only an administrator is authorized to initiate the <code>invite</code> action.</p>
<p>The <code>invite</code> action will use Devise’s supplied <code>send_confirmation_instructions</code> method to generate a confirmation token and send an email to the selected user. Then it will redirect the administrator back to a previous page.</p>
<h3>Add a Route</h3>
<p>Modify <strong>config/routes.rb</strong> to add the new action. Replace <code>resources :users, :only =&gt; [:show, :index]</code> with:</p>
<pre>
resources :users, :only =&gt; [:show, :index] do
get 'invite', :on =&gt; :member
end
</pre>
<p>The Rails Guide, <a href="http://guides.rubyonrails.org/routing.html#adding-more-restful-actions">Routing from the Outside In</a>, shows how we add additional routes to a RESTful resource.</p>
<h3>Sending Invitations from the Administrative Page</h3>
<p>We will modify the file <strong>app/views/users/index.html.haml</strong> to add a link to the <code>invite</code> action for each user. While we’re at it, we’ll add columns to show when the visitor joined (that is, confirmed their account), the number of times they’ve logged it, and the date of the most recent login.</p>
<pre>
- content_for :head do
= javascript_include_tag 'http://www.google.com/jsapi'
%h2 Users
.span9
#chart{:style =&gt; "width: 700px; height: 240px"}
= render_chart @chart, 'chart'
%br
.span9
%table.datatable.table.table-bordered.table-condensed
%thead
%tr
%th Email
%th Requested
%th Invitation
%th Joined
%th Visits
%th Most Recent
%tbody
- @users.each do |user|
%tr
%td #{link_to user.email, user}
%td #{user.created_at.to_date}
%td #{(user.confirmation_token.nil? ? (link_to "send invitation", invite_user_path(user)) : (link_to "resend", invite_user_path(user))) unless user.confirmed_at}
%td #{user.confirmed_at.to_date if user.confirmed_at}
%td #{user.sign_in_count if user.sign_in_count}
%td #{user.last_sign_in_at.to_date if user.last_sign_in_at}
</pre>
<p>Now, as the site administrator, you can invite individual users to complete the account confirmation process and obtain access to the site.</p>
<p>There’s a bit of complex logic in the display of the “send invitation” link. First, with <code>unless user.confirmed_at</code>, we check to see if the user has already confirmed the account. Next, we check the user’s <code>confirmation_token</code> attribute to see if we’ve already sent an invitation (in which case, a confirmation token was set by Devise). If no confirmation token was set, we display the link “send invitation”; otherwise, we display “resend”.</p>
<h3>Customizing the Devise Confirmation Email</h3>
<p>Here’s the mailer view that Devise uses for the confirmation email:</p>
<pre>
&lt;p&gt;Welcome &lt;%= @resource.email %&gt;!&lt;/p&gt;
&lt;p&gt;You can confirm your account email through the link below:&lt;/p&gt;
&lt;p&gt;&lt;%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token =&gt; @resource.confirmation_token) %&gt;&lt;/p&gt;
</pre>
<p>You may want to customize the confirmation email to be more attractive or informative.</p>
<p>Devise hides all its logic and views (including mailer templates) inside the Devise gem package. Devise is like any other Rails engine; we can override its views in our application.</p>
<p>First, create a folder <strong>app/views/devise/mailer</strong>.</p>
<p>Create a file <strong>app/views/devise/mailer/confirmation_instructions.html.erb</strong>:</p>
<pre>
&lt;p&gt;Welcome &lt;%= @resource.email %&gt;!&lt;/p&gt;
&lt;p&gt;We're pleased to invite you to try &lt;%= link_to 'example.com', root_url %&gt;.&lt;/p&gt;
&lt;p&gt;Please click the link below to confirm your email address and set your password:&lt;/p&gt;
&lt;p&gt;&lt;%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token =&gt; @resource.confirmation_token) %&gt;&lt;/p&gt;
</pre>
<p>Here’s a tip. If you’re using MailChimp or another mailing list service, you can use the templates offered by the service as a basis for your confirmation email message. For example, start with one of the templates suggested for a welcome message. Copy the <span class="caps">HTML</span> version of the message and use it for your confirmation email message.</p>
<h3>Customizing Other Devise Emails</h3>
<p>When you are ready to launch your website, you may want to customize other Devise email messages. Here are the other messages Devise provides.</p>
<p>“Reset Password” instructions can be overridden in the file <strong>app/views/devise/mailer/reset_password_instructions.html.erb</strong>:</p>
<pre>
&lt;p&gt;Hello &lt;%= @resource.email %&gt;!&lt;/p&gt;
&lt;p&gt;Someone has requested a link to change your password, and you can do this through the link below.&lt;/p&gt;
&lt;p&gt;&lt;%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token =&gt; @resource.reset_password_token) %&gt;&lt;/p&gt;
&lt;p&gt;If you didn't request this, please ignore this email.&lt;/p&gt;
&lt;p&gt;Your password won't change until you access the link above and create a new one.&lt;/p&gt;
</pre>
<p>“Unlock Account” instructions can be overridden in the file <strong>app/views/devise/mailer/unlock_instructions.html.erb</strong>:</p>
<pre>
&lt;p&gt;Hello &lt;%= @resource.email %&gt;!&lt;/p&gt;
&lt;p&gt;Your account has been locked due to an excessive amount of unsuccessful sign in attempts.&lt;/p&gt;
&lt;p&gt;Click the link below to unlock your account:&lt;/p&gt;
&lt;p&gt;&lt;%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token =&gt; @resource.unlock_token) %&gt;&lt;/p&gt;
</pre>
<h3>Bulk Invitations</h3>
<p>If you are selecting only a few dozen (or a few hundred) initial users, this process of manual selection will be adequate. If you are ready to launch and want to invite many thousand users, you’ll need a way to invite multiple users with a single action. We need to implement a “bulk invitations” feature.</p>
<p>You should set up an <span class="caps">SMTP</span> relay service such as <a href="http://mandrill.com/">Mandrill</a> or <a href="http://sendgrid.com/">SendGrid</a> before you attempt to send more than a few dozen email messages.</p>
<p>We’ll add a <code>bulk_invite</code> action to the controller file <strong>app/controllers/users_controller.rb</strong>:</p>
<pre>
class UsersController &lt; ApplicationController
before_filter :authenticate_user!
def index
authorize! :index, @user, :message =&gt; 'Not authorized as an administrator.'
@users = User.all
@chart = create_chart
end
def show
@user = User.find(params[:id])
end
def invite
authorize! :invite, @user, :message =&gt; 'Not authorized as an administrator.'
@user = User.find(params[:id])
@user.send_confirmation_instructions
redirect_to :back, :notice =&gt; "Sent invitation to #{@user.email}."
end
def bulk_invite
authorize! :bulk_invite, @user, :message =&gt; 'Not authorized as an administrator.'
users = User.where(:confirmation_token =&gt; nil).order(:created_at).limit(params[:quantity])
users.each do |user|
user.send_confirmation_instructions
end
redirect_to :back, :notice =&gt; "Sent invitation to #{users.count} users."
end
private
def create_chart
users_by_day = User.group("DATE(created_at)").count
data_table = GoogleVisualr::DataTable.new
data_table.new_column('date')
data_table.new_column('number')
users_by_day.each do |day|
data_table.add_row([ Date.parse(day[0]), day[1]])
end
@chart = GoogleVisualr::Interactive::AnnotatedTimeLine.new(data_table)
end
end
</pre>
<p>The <code>bulk_invite</code> action has a few peculiarities. First, we only select users where the <code>:confirmation_token</code> attribute is null. Devise sets a confirmation token when we send an invitation; with this constraint, we only invite users who have not been previously invited. Second, we make sure we select the oldest records first with the <code>order(:created_at)</code> method. Finally, we limit the number of records that are retrieved to the size of the batch we want; then we loop over each to send an invitation.</p>
<p>Modify <strong>config/routes.rb</strong> to add the new action:</p>
<pre>
RailsPrelaunchSignup::Application.routes.draw do
authenticated :user do
root :to =&gt; 'home#index'
end
devise_scope :user do
root :to =&gt; "devise/registrations#new"
match '/user/confirmation' =&gt; 'confirmations#update', :via =&gt; :put, :as =&gt; :update_user_confirmation
end
devise_for :users, :controllers =&gt; { :registrations =&gt; "registrations" }
match 'users/bulk_invite/:quantity' =&gt; 'users#bulk_invite', :via =&gt; :get, :as =&gt; :bulk_invite
resources :users, :only =&gt; [:show, :index] do
get 'invite', :on =&gt; :member
end
end
</pre>
<p>Notice that we add two new routes. The route <code>match '/user/confirmation'</code> is there to accommodate the confirmation link in the email message received by invited users. The route <code>match 'users/bulk_invite/:quantity'</code> handles the <code>bulk_invite</code> action we initiate from the administrative dashboard.</p>
<p>Finally, we will modify the file <strong>app/views/users/index.html.haml</strong> to add links for the <code>bulk_invite</code> action:</p>
<pre>
- content_for :head do
= javascript_include_tag 'http://www.google.com/jsapi'
%h2 Users
.span9
#chart{:style =&gt; "width: 700px; height: 240px"}
= render_chart @chart, 'chart'
%br
.span9
%p
Send Bulk Invitations:
= link_to "10 &amp;#183;".html_safe, bulk_invite_path(:quantity =&gt; '10')
= link_to "50 &amp;#183;".html_safe, bulk_invite_path(:quantity =&gt; '50')
= link_to "100 &amp;#183;".html_safe, bulk_invite_path(:quantity =&gt; '100')
= link_to "500 &amp;#183;".html_safe, bulk_invite_path(:quantity =&gt; '500')
= link_to "1000", bulk_invite_path(:quantity =&gt; '1000')
%table.datatable.table.table-bordered.table-condensed
%thead
%tr
%th Email
%th Requested
%th Invitation
%th Joined
%th Visits
%th Most Recent
%tbody
- @users.each do |user|
%tr
%td #{link_to user.email, user}
%td #{user.created_at.to_date}
%td #{(user.confirmation_token.nil? ? (link_to "send invitation", invite_user_path(user)) : (link_to "resend", invite_user_path(user))) unless user.confirmed_at}
%td #{user.confirmed_at.to_date if user.confirmed_at}
%td #{user.sign_in_count if user.sign_in_count}
%td #{user.last_sign_in_at.to_date if user.last_sign_in_at}
</pre>
<p>We now have the option to send 10, 50, 100, 500, or 1000 invitations at once.</p>
<h3>Git Workflow</h3>
<p>If you haven’t commited any changes yet, commit your changes to git:</p>
<pre>
$ git add .
$ git commit -am "enable admin to send invitations"
</pre>
<pre>
$ git checkout master
$ git merge --squash invitations
$ git commit -am "enable admin to send invitations"
</pre>
<p>You can delete the working branch when you’re done:</p>
<pre>
$ git branch -D invitations
</pre>
<h2>Setting the User’s Password</h2>
<p>Once the user has been invited and has confirmed the account, we need to provide a way for the user to set a password.</p>
<p>The Devise wiki explains <a href="https://github.com/plataformatec/devise/wiki/How-To:-Override-confirmations-so-users-can-pick-their-own-passwords-as-part-of-confirmation-activation">How to Override Confirmations So Users Can Pick Their Own Passwords As Part of Confirmation Activation</a>. So far, we’ve made small modifications to customize the behavior of Devise; now we’ll make extensive modifications, overriding the Confirmations controller and view as well as adding methods to the User model.</p>
<h3>Git Workflow</h3>
<p>Create a new git branch for this improvement:</p>
<pre>
$ git checkout -b fix-password
</pre>
<h3>Customize the Confirmations View</h3>
<p>When an invited user clicks a link in the invitation email to confirm an account, we want to ask them to choose a password for their account. The Devise gem supplies a Confirmations view; we need to replace it with a page that includes fields for password and password confirmation.</p>
<p>Create a new folder <strong>app/views/devise/confirmations/</strong>.</p>
<p>Create a file <strong>app/views/devise/confirmations/show.html.haml</strong>:</p>
<pre>
%h2 Account Activation
= form_for resource, :as =&gt; resource_name, :url =&gt; update_user_confirmation_path, :html =&gt; {:method =&gt; 'put'}, :id =&gt; 'activation-form' do |f|
= devise_error_messages!
%fieldset
%legend
Account Activation
- if resource.email
for #{resource.email}
- if @requires_password
%p
= f.label :password,'Choose a Password:'
= f.password_field :password
%p
= f.label :password_confirmation, 'Password Confirmation:'
= f.password_field :password_confirmation
= hidden_field_tag :confirmation_token, @confirmation_token
%p= f.submit "Activate"
</pre>
<h3>Customize the Confirmations Controller</h3>
<p>The Devise gem supplies a Confirmations controller; we need to replace it with one that will accommodate our customized view that asks the user to set a password. We’ll override the Devise Confirmations controller by creating our own Confirmations controller. Often, in overriding a controller, we inherit from the original controller. In this case, we’ll inherit from the Devise Passwords controller that provides methods to set the user’s password.</p>
<p>Create a file <strong>app/controllers/confirmations_controller.rb</strong>:</p>
<pre>
class ConfirmationsController &lt; Devise::PasswordsController
# Remove the first skip_before_filter (:require_no_authentication) if you
# don't want to enable logged users to access the confirmation page.
skip_before_filter :require_no_authentication
skip_before_filter :authenticate_user!
# POST /resource/confirmation
def create
self.resource = resource_class.send_confirmation_instructions(resource_params)
if successfully_sent?(resource)
respond_with({}, :location =&gt; after_resending_confirmation_instructions_path_for(resource_name))
else
respond_with(resource)
end
end
# PUT /resource/confirmation
def update
with_unconfirmed_confirmable do
if @confirmable.has_no_password?
@confirmable.attempt_set_password(params[:user])
if @confirmable.valid?
do_confirm
else
do_show
@confirmable.errors.clear #so that we won't render :new
end
else
self.class.add_error_on(self, :email, :password_allready_set)
end
end
if !@confirmable.errors.empty?
render 'devise/confirmations/new'
end
end
# GET /resource/confirmation?confirmation_token=abcdef
def show
with_unconfirmed_confirmable do
if @confirmable.has_no_password?
do_show
else
do_confirm
end
end
if !@confirmable.errors.empty?
render 'devise/confirmations/new'
end
end
protected
def with_unconfirmed_confirmable
@confirmable = User.find_or_initialize_with_error_by(:confirmation_token, params[:confirmation_token])
self.resource = @confirmable
if !@confirmable.new_record?
@confirmable.only_if_unconfirmed {yield}
end
end
def do_show
@confirmation_token = params[:confirmation_token]
@requires_password = true
render 'devise/confirmations/show'
end
def do_confirm
@confirmable.confirm!
set_flash_message :notice, :confirmed
sign_in_and_redirect(resource_name, @confirmable)
end
# The path used after resending confirmation instructions.
def after_resending_confirmation_instructions_path_for(resource_name)
new_session_path(resource_name)
end
# The path used after confirmation.
def after_confirmation_path_for(resource_name, resource)
after_sign_in_path_for(resource)
end
end
</pre>
<h3>Add a Route</h3>
<p>Modify <strong>config/routes.rb</strong> to use the new controller:</p>
<pre>
RailsPrelaunchSignup::Application.routes.draw do
authenticated :user do
root :to =&gt; 'home#index'
end
devise_scope :user do
root :to =&gt; "devise/registrations#new"
match '/user/confirmation' =&gt; 'confirmations#update', :via =&gt; :put, :as =&gt; :update_user_confirmation
end
devise_for :users, :controllers =&gt; { :registrations =&gt; "registrations", :confirmations =&gt; "confirmations" }
match 'users/bulk_invite/:quantity' =&gt; 'users#bulk_invite', :via =&gt; :get, :as =&gt; :bulk_invite
resources :users, :only =&gt; [:show, :index] do
get 'invite', :on =&gt; :member
end
end
</pre>
<h3>Modify the User Model</h3>
<p>We need to modify the User model to allow the new user to set a password when they confirm their account.</p>
<p>Add the following methods to the file <strong>app/models/user.rb</strong>:</p>
<pre>
# new function to set the password
def attempt_set_password(params)
p = {}
p[:password] = params[:password]
p[:password_confirmation] = params[:password_confirmation]
update_attributes(p)
end
# new function to determine whether a password has been set
def has_no_password?
self.encrypted_password.blank?
end
# new function to provide access to protected method pending_any_confirmation
def only_if_unconfirmed
pending_any_confirmation {yield}
end
</pre>
<h3>Git Workflow</h3>
<p>If you haven’t commited any changes yet, commit your changes to git:</p>
<pre>
$ git add .
$ git commit -am "enable a user to set a password"
</pre>
<p>Merge the new code into the master branch and commit it:</p>
<pre>
$ git checkout master
$ git merge --squash fix-password
$ git commit -am "enable a user to set a password"
</pre>
<p>You can delete the working branch when you’re done:</p>
<pre>
$ git branch -D fix-password
</pre>
<h2>Feature: Collect Email Addresses</h2>
<p>You might like to send announcements or newsletters to all your visitors before you launch the site.</p>
<p>You could implement some code to iterate through each user record and send each an email message. That is a bad idea because your web app would have to open a connection to your <span class="caps">SMTP</span> server for every message (which are all identical). Some <span class="caps">SMTP</span> relay services such as SendGrid provide an <span class="caps">API</span> call that makes it easy to send an array of email addresses in a single request for processing by the relay service. That’s an option. But the most practical approach is to use a dedicated service such as MailChimp for managing and sending broadcast email. You won’t tie up your app sending bulk email and you’ll be able to use services such as delivery tracking and well-designed templates.</p>
<p>When each visitor requests an invitation, you’ll need to subscribe them to a MailChimp mailing list.</p>
<h3>Git Workflow</h3>
<p>Create a new git branch for this feature:</p>
<pre>
$ git checkout -b mailchimp
</pre>
<h3>Hominid gem</h3>
<p>If you have set up a <a href="http://mailchimp.com/">MailChimp</a> account and selected the “Mandrill by MailChimp” option when you generated the base application, the <a href="https://github.com/terra-firma/hominid">Hominid gem</a> will already be in your Gemfile. If not, add the Hominid gem to the <strong>Gemfile</strong>:</p>
<pre>
gem "hominid"
</pre>
<p>and run the <code>bundle install</code> command to install the required gem on your computer.</p>
<h3>Modify the User Model</h3>
<p>Modify the file <strong>app/models/user.rb</strong>:</p>
<pre>
class User &lt; ActiveRecord::Base
rolify
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :invitable, :database_authenticatable, :registerable, :confirmable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :confirmed_at, :opt_in
after_create :add_user_to_mailchimp unless Rails.env.test?
before_destroy :remove_user_from_mailchimp unless Rails.env.test?
# override Devise method
# no password is required when the account is created; validate password when the user sets one
def password_required?
if !persisted?
false
else
!password.nil? || !password_confirmation.nil?
end
end
# override Devise method
def confirmation_required?
false
end
# override Devise method
def active_for_authentication?
confirmed? || confirmation_period_valid?
end
# new function to set the password
def attempt_set_password(params)
p = {}
p[:password] = params[:password]
p[:password_confirmation] = params[:password_confirmation]
update_attributes(p)
end
# new function to determine whether a password has been set
def has_no_password?
self.encrypted_password.blank?
end
# new function to provide access to protected method pending_any_confirmation
def only_if_unconfirmed
pending_any_confirmation {yield}
end
private
def add_user_to_mailchimp
unless self.email.include?('@example.com') or !self.opt_in?
mailchimp = Hominid::API.new(ENV["MAILCHIMP_API_KEY"])
list_id = mailchimp.find_list_id_by_name "visitors"
info = { }
result = mailchimp.list_subscribe(list_id, self.email, info, 'html', false, true, false, true)
Rails.logger.info("MAILCHIMP SUBSCRIBE: result #{result.inspect} for #{self.email}")
end
end
def remove_user_from_mailchimp
unless self.email.include?('@example.com')
mailchimp = Hominid::API.new(ENV["MAILCHIMP_API_KEY"])
list_id = mailchimp.find_list_id_by_name "visitors"
result = mailchimp.list_unsubscribe(list_id, self.email, true, false, true)
Rails.logger.info("MAILCHIMP UNSUBSCRIBE: result #{result.inspect} for #{self.email}")
end
end
end
</pre>
<p>We’ve removed one method, <code>send_welcome_email</code>, and added two methods:</p>
<ul>
<li><code>add_user_to_mailchimp</code></li>
<li><code>remove_user_from_mailchimp</code></li>
</ul><p>See the documentation for the <a href="http://apidocs.mailchimp.com/api/rtfm/listsubscribe.func.php">MailChimp <span class="caps">API</span> listSubscribe function</a> for a list of the parameters. Notice the final parameter sends a welcome email when set to “true.”</p>
<pre>
listSubscribe(string list_id, string email_address, array merge_vars, string email_type, bool double_optin, bool update_existing, bool replace_interests, bool send_welcome)
</pre>
<p>We remove the <code>send_welcome_email</code> method because MailChimp gives us an option to send a welcome email when a visitor subscribes to a mailing list. It’s easier to compose an attractive welcome email using MailChimp’s templates than to create one from scratch. Be sure to log in to MailChimp and create a welcome email message for members of the “visitors” email list before testing the application.</p>
<p>The <code>add_user_to_mailchimp</code> method will connect with the MailChimp server and issue an <span class="caps">API</span> call to subscribe the user to the “visitors” email list. We don’t call the method if we are creating new users when running tests or if the user did not select the “opt-in” attribute when he or she requested an invitation. And we don’t subscribe users with addresses at <code>example.com</code>.</p>
<p>Be sure to log in to MailChimp and create a “visitors” email list before deploying the application. When you want to send a newsletter or announcement, log in to your MailChimp account and use the service to send your broadcast email.</p>
<p>We add <code>remove_user_from_mailchimp</code> method to delete the user as necessary. The documentation for the <a href="http://apidocs.mailchimp.com/api/rtfm/listUnsubscribe.func.php">MailChimp <span class="caps">API</span> listUnsubscribe function</a> offers a few options:</p>
<pre>
listUnsubscribe(string list_id, string email_address, boolean delete_member, boolean send_goodbye, boolean send_notify)
</pre>
<h3>Modify Integration Tests</h3>
<p>We’ve removed the <code>send_welcome_email</code> method and we’ll rely on MailChimp to send a welcome message.</p>
<p>We need to modify the file <strong>features/visitors/request_invitation.feature</strong>. Remove the statement:</p>
<pre>
And I should receive an email with subject "Request Received"
</pre>
<p>Our test suite doesn’t allow us to test for receipt of email from an external service such as MailChimp.</p>
<p>Our “Collect Email Addresses” feature is complete.</p>
<h3>Git Workflow</h3>
<p>If you haven’t commited any changes yet, commit your changes to git:</p>
<pre>
$ git add .
$ git commit -am "collect email addresses with mailchimp"
</pre>
<p>Merge the new code into the master branch and commit it:</p>
<pre>
$ git checkout master
$ git merge --squash mailchimp
$ git commit -am "collect email addresses with mailchimp"
</pre>
<p>You can delete the working branch when you’re done:</p>
<pre>
$ git branch -D mailchimp
</pre>
<h2 id="social_sharing">Feature: Encourage Visitors to Share on Facebook and Twitter</h2>
<p>If your visitors are excited about your offer, they may want to share their discovery with friends. You can encourage visitors to share on Facebook and Twitter by providing “social sharing” buttons with your “thank you” message. We’ll also include Google+; if you want to add social sharing buttons for other services you’ll find the code is very similar.</p>
<h3>User Story</h3>
<p>Here’s the user stories we’ll implement:</p>
<pre>
*Post to Twitter After Sign Up*
As a user
I want an option to post to Twitter after I sign up
so my followers will learn about the site
*Post to Facebook After Sign Up*
As a user
I want an option to post to Facebook after I sign up
so my Facebook friends will learn about the site
</pre>
<h3>Cucumber Scenario</h3>
<p>We’ll add two scenarios to the <strong>features/visitors/request_invitation.feature</strong> file:</p>
<pre>
Scenario: User sees Facebook "Share" button
When I request an invitation with valid user data
Then I should see a button "Like"
Scenario: User sees Twitter "Share" button
When I request an invitation with valid user data
Then I should see a button "Tweet"
</pre>
<p><em>Note:</em> We need appropriate Cucumber step definitions to test if Facebook and Twitter sharing buttons are present.</p>
<h3>Git Workflow</h3>
<p>Create a new git branch for this feature:</p>
<pre>
$ git checkout -b social-share
</pre>
<h3>Implementation of Social Sharing</h3>
<p>Social sharing buttons for Twitter, Facebook, and Google+ are implemented in <span class="caps">HTML</span> and JavaScript. There are several gems that provide Rails view helpers for “Facebook Like” and “Tweet” buttons. And there are numerous jQuery plugins for the same. But it’s easy to add the markup directly.</p>
<p>Twitter, Facebook, and Google+ give you documentation and tools to create social sharing buttons for your website:</p>
<ul>
<li><a href="https://dev.twitter.com/docs/tweet-button">Twitter Tweet Button</a></li>
<li><a href="https://developers.facebook.com/docs/reference/plugins/like/">Facebook Like Button</a></li>
<li><a href="https://developers.google.com/+/plugins/+1button/">Google+ +1 Button</a></li>
</ul><p>We won’t use the example code that the social networks provide; instead, we’ll set up our page to download JavaScript widgets from each of the social networks. The implementation is similar for each service. A JavaScript widget determines if a link or div is present on the page and applies a set of transformations to create a graphical button.</p>
<h3>JavaScript for Social Sharing Buttons</h3>
<p>Modify the file <strong>app/assets/javascripts/application.js</strong>:</p>
<pre>
$('document').ready(function() {
// display validation errors for the "request invitation" form
if ($('.alert-error').length &gt; 0) {
$("#request-invite").modal('toggle');
}
// use AJAX to submit the "request invitation" form
$('#invitation_button').live('click', function() {
var email = $('form #user_email').val();
if($('form #user_opt_in').is(':checked'))
var opt_in = true;
else
var opt_in = false;
var dataString = 'user[email]='+ email + '&amp;user[opt_in]=' + opt_in;
$.ajax({
type: "POST",
url: "/users",
data: dataString,
success: function(data) {
$('#request-invite').html(data);
loadSocial();
}
});
return false;
});
})
// load social sharing scripts if the page includes a Twitter "share" button
function loadSocial() {
//Twitter
if (typeof (twttr) != 'undefined') {
twttr.widgets.load();
} else {
$.getScript('http://platform.twitter.com/widgets.js');
}
//Facebook
if (typeof (FB) != 'undefined') {
FB.init({ status: true, cookie: true, xfbml: true });
} else {
$.getScript("http://connect.facebook.net/en_US/all.js#xfbml=1", function () {
FB.init({ status: true, cookie: true, xfbml: true });
});
}
//Google+
if (typeof (gapi) != 'undefined') {
$(".g-plusone").each(function () {
gapi.plusone.render($(this).get(0));
});
} else {
$.getScript('https://apis.google.com/js/plusone.js');
}
}
</pre>
<p>We call the function <code>loadSocial()</code> after the “request invitation” form is successfully submitted. After the <span class="caps">AJAX</span> call returns “success”, we render the “#request-invite” div with the <strong>_thankyou.html.haml</strong> partial (defined in the RegistrationsController); immediately following, we call the function <code>loadSocial()</code>. The <code>loadSocial()</code> function uses the jQuery <code>.getScript</code> method to load a JavaScript file from each of the social networks. The script won’t attempt to download a Javascript file if the widget is already present.</p>
<h3>Add Social Sharing Buttons to the “Thank You” Message</h3>
<p>Modify the file file <strong>app/views/devise/registrations/_thankyou.html.haml</strong>:</p>
<pre>
%h1 Thank you
#request-invite.modal{:style =&gt; "display: 'block';"}
.modal-header
%a.close{"data-dismiss" =&gt; "modal"} &amp;#215;
%h3 Thank you!
.modal-body{:style =&gt; "margin-bottom: 120px; overflow: visible"}
%p We have received your request for an invitation to example.com.
%p We'll contact you when we launch.
%p Share your discovery with your friends!
#tweet{:style =&gt; "display:inline-block;"}
%a.twitter-share-button{"data-count" =&gt; "horizontal", "data-text" =&gt; "A new site I've discovered", "data-url" =&gt; "http://railsapps.github.com/rails-prelaunch-signup/", "data-via" =&gt; "launchrails", "data-related" =&gt; "launchrails", :href =&gt; "https://twitter.com/share"}
%span{:style =&gt; "display:inline-block; vertical-align:text-bottom;"}
.fb-like{"data-href" =&gt; "http://railsapps.github.com/rails-prelaunch-signup/", "data-layout" =&gt; "button_count", "data-send" =&gt; "false", "data-show-faces" =&gt; "false", "data-width" =&gt; "90"}
.g-plusone{"data-annotation" =&gt; "inline", "data-href" =&gt; "http://railsapps.github.com/rails-prelaunch-signup/", "data-size" =&gt; "medium", "data-width" =&gt; "120"}
</pre>
<p>The results are pretty: graphical buttons for Twitter, Facebook, and Google+ sharing aligned nicely in a row. To get that result, we need a mess of elements with tweaked style rules. Twitter expects a link styled with the class <code>twitter-share-button</code>; the Twitter JavaScript widget then transforms it into a dynamic button that displays a count of past tweets. We have to apply a <code>display:inline-block</code> style rule to force it to appear in a row with the other buttons. The Facebook JavaScript widget expects a div with the class <code>fb-like</code>; we wrap it in a span element and apply some style rules to force it to align nicely. The Google+ JavaScript widget expects a div with the class <code>g-plusone</code>; this one plays nicely with others and doesn’t need any tweaking. One last tweak to the class <code>modal-body</code> applies a style rule that increases the height of the modal window so there’s room for the dialog box that appears when the visitor clicks the Facebook “Like” button.</p>
<p>For demonstration purposes we’ve set up the social sharing buttons to point to the project page for the Rails Prelaunch Signup app. You’ll want to replace the placeholders with pointers to your own website:</p>
<table>
<tr>
<th>Item </th>
<th>Placeholder </th>
<th>Comment </th>
</tr>
<tr>
<td> Twitter data-via </td>
<td> launchrails </td>
<td> Your Twitter username </td>
</tr>
<tr>
<td> Twitter data-related </td>
<td> launchrails </td>
<td> Recommendations of Twitter accounts for the visitor to follow </td>
</tr>
<tr>
<td> Twitter data-text </td>
<td> A new site I’ve discovered </td>
<td> Default text for the tweet </td>
</tr>
<tr>
<td> Twitter data-url </td>
<td> http://railsapps.github.com/rails-prelaunch-signup/ </td>
<td> Web address for your site </td>
</tr>
<tr>
<td> Facebook data-href </td>
<td> http://railsapps.github.com/rails-prelaunch-signup/ </td>
<td> Web address for your site </td>
</tr>
<tr>
<td> Google+ data-href </td>
<td> http://railsapps.github.com/rails-prelaunch-signup/ </td>
<td> Web address for your site </td>
</tr>
</table><h3>Test the Feature</h3>
<p>Be sure you’ve set up the database for testing before running Cucumber:</p>
<pre>
$ rake db:test:prepare
</pre>
<p>Run the integration test with the following command:</p>
<pre>
$ cucumber features/visitors/request_invitation.feature
</pre>
<p><em>Note:</em> The test will fail because we don’t have proper step definitions yet. The tutorial is not finished; we’ll add these soon.</p>
<h3>Git Workflow</h3>
<p>If you haven’t commited any changes yet, commit your changes to git:</p>
<pre>
$ git add .
$ git commit -am "add social sharing buttons"
</pre>
<p>Merge the new code into the master branch and commit it:</p>
<pre>
$ git checkout master
$ git merge --squash social-share
$ git commit -am "add social sharing buttons"
</pre>
<p>You can delete the working branch when you’re done:</p>
<pre>
$ git branch -D social-share
</pre>
<h2>Test the App</h2>
<p>Run the integration test suite to see if everything works.</p>
<p>Be sure you’ve set up the database for testing before running Cucumber:</p>
<pre>
$ rake db:test:prepare
</pre>
<p>Then we can run our integration test suite with the following command:</p>
<pre>
$ cucumber
</pre>
<p>You can check that your app runs properly by entering the command</p>
<p><code>$ rails server</code></p>
<p>To see your application in action, open a browser window and navigate to <a href="http://localhost:3000">http://localhost:3000/</a>.</p>
<p>Sign in as the first user (the administrator) using:</p>
<ul>
<li>email: user@example.com</li>
<li>password: please</li>
</ul><p>You’ll see a navigation link for Admin. Clicking the link will display a page with a list of users at<br><a href="http://localhost:3000/users">http://localhost:3000/users</a>.</p>
<p>To sign in as the second user, use</p>
<ul>
<li>email: user2@example.com</li>
<li>password: please</li>
</ul><p>The second user will not see the Admin navigation link and will not be able to access the page at<br><a href="http://localhost:3000/users">http://localhost:3000/users</a>.</p>
<p>If you want to see what the admininstrative dashboard looks like with many users, you can add a line to the <strong>db/seeds.rb</strong> file to create a hundred bogus users:</p>
<pre>
100.times {|i| User.create! :name =&gt; "User #{i+3}", :email =&gt; "user#{i+3}@example.com", :password =&gt; 'please', :password_confirmation =&gt; 'please', :confirmed_at =&gt; (Time.now + i.day).utc, :created_at =&gt; (Time.now + i.day).utc }
</pre>
<p>You’ll have to modify the file <strong>app/models/user.rb</strong> to allow mass assignment of the <code>created_at</code> field:</p>
<pre>
attr_accessible :name, :email, :password, :password_confirmation, :remember_me, :confirmed_at, :opt_in, :created_at
</pre>
<p>Then run <code>$ rake db:reset</code> to recreate the database and visit the site again.</p>
<h2>Deploy to Heroku</h2>
<p>Heroku provides low cost, easily configured Rails application hosting. For your convenience, see <a href="http://railsapps.github.com/rails-heroku-tutorial.html">Tutorial: Rails on Heroku</a>.</p>
<h2>Conclusion</h2>
<p>This concludes the tutorial for the RailsApp <a href="https://github.com/RailsApps/rails-prelaunch-signup">rails-prelaunch-signup</a> example app.</p>
<h3>Did You Like the Tutorial? Here’s What You Can Do</h3>
<p>Was this useful to you? Follow <a href="http://twitter.com/rails_apps">rails_apps</a> on Twitter and tweet some praise. I’d love to know you were helped out by the tutorial.</p>
<p>Get some link juice! Add your website to the list of <a href="http://railsapps.github.com/rails-applications-from-examples.html">Rails Applications Built from the Examples</a>. I love to see what people have built with these examples.</p>
<p>Blog it! Share your discovery with the startup community. It’s up to you to get the word out and help fellow entrepreneurs. Links are the best way for people to find resources like this.</p>
<p>Please leave a comment to tell me how long it took for you to read the tutorial and complete the app.</p>
<p>Any issues? Please create an <a href="http://github.com/RailsApps/rails3-bootstrap-devise-cancan/issues">issue</a> on GitHub. Reporting (and patching!) issues helps everyone.</p>
<h3>Credits</h3>
<p>Daniel Kehoe implemented the application and wrote the tutorial.</p>
</div><!-- class="content" -->
<div class="comments">
<div class="content wikistyle gollum">
<h2>Comments and Issues</h2>
</div>
<p>Is this helpful? Please add a comment below. Your encouragement fuels the project.</p>
<p>Did you find an error? Or couldn't get something to work? For the example apps and tutorials, please create a GitHub issue in the repository for the example app. Creating a GitHub issue is the best way to make sure a problem is investigated and fixed.</p>
<div id="disqus_thread"></div>
<script type="text/javascript">
/* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
var disqus_shortname = 'railsapps'; // required: replace example with your forum shortname
/* * * DON'T EDIT BELOW THIS LINE * * */
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
</script>
<noscript>Please enable JavaScript to view the <a href="http://disqus.com/?ref_noscript">comments powered by Disqus.</a></noscript>
<a href="http://disqus.com" class="dsq-brlink">comments powered by <span class="logo-disqus">Disqus</span></a>
</div><!-- class="comments" -->
<div class="footer row">
<div class="span4">
<h3>Credits</h3>
<p><a href="http://danielkehoe.com/">Daniel Kehoe</a> initiated the <a href="http://railsapps.github.com/">RailsApps Project</a>. Thanks to all the users and contributors.</p>
</div>
<div class="span4">
<h3>Wiki</h3>
<p>Corrections? Additions? You can edit this page <a href="https://github.com/RailsApps/railsapps.github.com/wiki/_pages">on the wiki</a>.</p>
</div>
<div class="span4">
<h3>Last edit</h3>
<p>by <b>Daniel Kehoe</b>, 2012-07-20 23:08:42</p>
</div>
</div>
</div>
</body>
</html>