-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathtutorial-rails-prelaunch-signup.html
2003 lines (1883 loc) · 115 KB
/
tutorial-rails-prelaunch-signup.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Rails Tutorial for a Startup Prelaunch Signup Site</title>
<link href="https://plus.google.com/u/0/b/117374718581973393536/117374718581973393536/posts/" rel="publisher" />
<link rel="stylesheet" href="/css/bootstrap.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="/css/screen.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="/css/gollum.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="/css/site.css" type="text/css" charset="utf-8" />
<link rel="stylesheet" href="/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="/javascript/jquery.text_selection-1.0.0.min.js" type="text/javascript"></script>
<script src="/javascript/jquery.previewable_comment_form.js" type="text/javascript"></script>
<script src="/javascript/jquery.tabs.js" type="text/javascript"></script>
<script src="/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="/" 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="page-header">
<h1>Rails Tutorial for a Startup Prelaunch Signup Site</h1>
</div>
<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 19 May 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, the app provides an administrative interface so you can select users to receive an invitation that instructs them to confirm their email address and set a password.</p>
<p>The <a href="https://github.com/plataformatec/devise/wiki">Devise wiki</a> and discussion on <a href="http://stackoverflow.com/questions/tagged/devise">Stack Overflow</a> can help you if your needs are different.</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 and explains how to integrate 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>
</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>Company Email</h3>
<p>For better or worse, most of us are using Gmail for our personal accounts. You can set up a single Gmail account to receive and <a href="http://support.google.com/mail/bin/answer.py?hl=en&ctx=mail&answer=22370">send email from a different address</a>. More likely, you’ll want several email addresses for your company mail. For that, you can use <a href="http://www.google.com/enterprise/apps/business/index.html">Google Apps for Business</a>.</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 (called “transactional email”), you will need a hosted <span class="caps">SMTP</span> relay service.</p>
<p>Don’t attempt to manage your own <span class="caps">SMTP</span> servers. The required sysadmin is time-consuming. More importantly, 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">SMTP</span> relay service to increase reliability of delivery. The best services offer tracking data to 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&systpl=turbo-smtp&currency=3">turboSMTP</a></li>
</ul><p>Instructions are provided 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> to add the Mandrill service. Make sure you <a href="http://help.mandrill.com/customer/portal/articles/464811-verify-a-sending-domain">Verify a Sending Domain</a> and 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 may 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.</p>
<p>For a more full-featured service 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.</p>
<h3>Domain Registration</h3>
<p>You’ll need a domain name before you set up an <span class="caps">SMTP</span> relay service (which you’ll need if you’ll be sending more than a few email messages). <a href="http://www.namecheap.com/">NameCheap</a> is a popular alternative to the much-maligned GoDaddy.</p>
<h3>GitHub</h3>
<p>You’ll need an account on GitHub. Get a <a href="https://github.com/signup/free">free GitHub account</a> if you don’t already have one. 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>We’ll soon offer an application template to generate a new Rails app with code that closely matches the tutorial. It’s not available yet.</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 code it up. However, for the purposes of this tutorial, I want to show the practices that establish a good software development process. You may find it beneficial to use the same process when you develop more complex applications.</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 be prepared to build more complex applications.</p>
<h3>Write Your User Stories</h3>
<p><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 <role>, I want <goal> so that <benefit>
</pre>
<p>As an example, here are four user stories we will implement for this application:</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>If you have ideas for additional features for this application, simply create a <a href="http://github.com/RailsApps/rails-prelaunch-signup/issues">GitHub issue</a> describing the feature you’d like to see.</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>The <code>$</code> character indicates a shell prompt; don’t include it when you run the command.</p>
<p>For a starter app using ActiveRecord and a <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.</p>
<p>If you want to use the MongoDB datastore instead of ActiveRecord and a <span class="caps">SQL</span> database use the <a href="https://github.com/RailsApps/rails3-mongoid-devise">rails3-mongoid-devise</a> starter app:</p>
<pre>
$ rails new rails-prelaunch-signup -m https://github.com/RailsApps/rails3-application-templates/raw/master/rails3-mongoid-devise-template.rb -T -O
</pre>
<p>Use the <code>-T -O</code> flags to skip Test::Unit files and Active Record files.</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> & <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>yes</strong>
</li>
<li>Would you like to add ‘will_paginate’ for pagination? <strong>no</strong>
</li>
</ul><p>Choose the <strong>Mandrill by MailChimp</strong> option for email if you’ve set up a Mandrill account for future deployment. If you are just experimenting, you can choose <strong>Gmail</strong> as a temporary solution.</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 & 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>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>
<h3>Edit the <span class="caps">README</span>
</h3>
<p>If your version of the app will be visible on GitHub, please edit the <span class="caps">README</span> file to add a description of the app and your contact info. Changing the <span class="caps">README</span> is important if you’re using a clone of the example app. I’ve been mistaken (and contacted) as the author of apps that are copied from my example.</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>
<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.3'
gem 'sqlite3'
group :assets do
gem 'sass-rails', '~> 3.2.3'
gem 'coffee-rails', '~> 3.2.1'
gem 'uglifier', '>= 1.0.3'
end
gem 'jquery-rails'
gem "haml", ">= 3.1.4"
gem "haml-rails", ">= 0.3.4", :group => :development
gem "rspec-rails", ">= 2.9.0.rc2", :group => [:development, :test]
gem "factory_girl_rails", ">= 3.2.0", :group => [:development, :test]
gem "email_spec", ">= 1.2.1", :group => :test
gem "cucumber-rails", ">= 1.3.0", :group => :test
gem "capybara", ">= 1.1.2", :group => :test
gem "database_cleaner", ">= 0.7.2", :group => :test
gem "launchy", ">= 2.1.0", :group => :test
gem "devise", ">= 2.1.0.rc"
gem "devise_invitable", ">= 1.0.1"
gem "cancan", ">= 1.6.7"
gem "rolify", ">= 3.1.0"
gem "bootstrap-sass", ">= 2.0.1"
gem "simple_form"
gem "hominid"
</pre>
<p>Check for the <a href="http://rubygems.org/gems/rails">current version of Rails</a> and replace <code>gem 'rails', '3.2.3'</code> accordingly.</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">adding Haml to 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>
$ bundle exec cucumber features/visitors/request_invitation.feature
</pre>
<p>The starter app script sets up the <strong>config/cucumber.yml</strong> file so it is not necessary to add <code>--require features</code> to the command to run a single Cucumber feature.</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 => 'localhost:3000' }
config.action_mailer.delivery_method = :smtp
# change to false to prevent email from being sent during development
config.action_mailer.perform_deliveries = false
config.action_mailer.raise_delivery_errors = true
config.action_mailer.default :charset => "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 => '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 => "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 => '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>If you want to use a Gmail account to send email, 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>If you want to use a Mandrill account to send email, 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.mandrillapp.com",
:port => 25,
:user_name => ENV["MANDRILL_USERNAME"],
:password => 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>Set Environment Variables for Email</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="[email protected]"
export GMAIL_PASSWORD="secret"
</pre>
<p>or for Mandrill:</p>
<pre>
export MANDRILL_USERNAME="myname"
export MANDRILL_API_KEY="secret"
</pre>
<p>If you deploy to Heroku, you will need to set the username and password as Heroku environment variables:</p>
<pre>
$ heroku config:add [email protected] GMAIL_PASSWORD=secret
</pre>
<p>or for Mandrill:</p>
<pre>
$ heroku config:add MANDRILL_USERNAME=myname MANDRILL_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 => 'First User', :email => '[email protected]', :password => 'please', :password_confirmation => 'please', :confirmed_at => Time.now.utc
puts 'New user created: ' << user.name
user2 = User.create! :name => 'Second User', :email => '[email protected]', :password => 'please', :password_confirmation => 'please', :confirmed_at => Time.now.utc
puts 'New user created: ' << user2.name
user.add_role :admin
</pre>
<p>You can change the values for name, email, and password as you wish.</p>
<h3>Seed the Database</h3>
<p>The starter app script has already set up the database and added the default user.</p>
<p>If you change the default user’s name, email, or password you’ll need to reset the database:</p>
<pre>
$ bundle exec rake db:reset
</pre>
<p>If you need to, you can run <code>$ bundle exec rake db:reset</code> whenever you need to recreate the database.</p>
<h2>Test the Starter App</h2>
<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>
<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 < ApplicationController
end
</pre>
<h2>Create an Initializer File</h2>
<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>
<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 get our git workflow set up 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 to establish management 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 => "[email protected]",
:password => "please", :password_confirmation => "please" }
end
def invitation_request user
visit '/users/sign_up'
fill_in "Email", :with => 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("[email protected]")
test_user.should respond_to(:email)
end
Then /^my account should be unconfirmed$/ do
test_user = User.find_by_email("[email protected]")
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 => "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>
$ bundle exec rake db:test:prepare
</pre>
<p>Then we can run our integration test with the following command:</p>
<pre>
$ bundle exec cucumber features/visitors/request_invitation.feature
</pre>
<p>The test should fail indicating that the home page has no button “Request Invitation.”</p>
<h3>Implement “Request Invitation” Form</h3>
<p>We begin implementing the actual application here.</p>
<p>The application’s home page doesn’t contain a “Request Invitation” form. Should we add a sign-up form to the home page? We could but 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 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 => resource_name, :url => registration_path(resource_name)) do |f|
= devise_error_messages!
= f.input :email, :placeholder => '[email protected]'
= f.submit "Request Invitation"
</pre>
<h3>Override Password Validation</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>Modify the file <strong>app/models/user.rb</strong> to override methods supplied by Devise:</p>
<pre>
class User < 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
# 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>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 => @visitor[:email]
click_button "Request Invitation"
find_user
end
</pre>
<p>We no longer ask the visitor to set a password 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 no longer ask the visitor to supply a name when the account is created.</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 => 'home#index'
end
root :to => "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 => "home#index"</code> so you see this:</p>
<pre>
authenticated :user do
root :to => 'home#index'
end
devise_scope :user do
root :to => "devise/registrations#new"
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, 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>
<h1>Thank You</h1>
</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 < Devise::RegistrationsController
protected
def after_inactive_sign_up_path_for(resource)
'/thankyou.html'
end
def after_sign_up_path_for(resource)
'/thankyou.html'
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 the thank you 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 => { :registrations => "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 < 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
# 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 add send an email welcome message using a new ActionMailer method when the account is created.</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: "[email protected]")
@email = UserMailer.welcome_email(@user).deliver
end
it "should be delivered to the email address provided" do
@email.should deliver_to("[email protected]")
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 < ActionMailer::Base
default :from => "[email protected]"
def welcome_email(user)
mail(:to => user.email, :subject => "Invitation Request Received")
headers[‘X-MC-Track’] = "opens, clicks"
headers[‘X-MC-GoogleAnalytics’] = "example.com"
headers[‘X-MC-Tags’] = "welcome"
end
end
</pre>
<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>
<!DOCTYPE html>
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Type" />
</head>
<body>
<h1>Welcome</h1>
<p>
We have received your request for an invitation to example.com.
</p>
<p>
We'll contact you when we launch.
</p>
</body>
</html>
</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.</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 < 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
after_create :send_welcome_email
# 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
UserMailer.welcome_email(self).deliver
end
end
</pre>
<p>Now the visitor will get a welcome email when they request an invitation.</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>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 “unconfirmed account message” step definition:</p>
<pre>
Then /^I see an unconfirmed account message$/ do
page.should have_content "Your account is not active."
end
</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 create a new “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>Create a file <strong>app/views/devise/shared/_links.html.haml</strong>:</p>
<pre>
- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks'
= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name)
%br/
- if devise_mapping.omniauthable?
- resource_class.omniauth_providers.each do |provider|
= link_to "Sign in with #{provider.to_s.titleize}", omniauth_authorize_path(resource_name, provider)
%br/
- if devise_mapping.confirmable? && controller_name != 'confirmations' && Rails.configuration.launched == true
= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name)
%br/
- if devise_mapping.recoverable? && controller_name != 'passwords' && Rails.configuration.launched == true
= link_to "Forgot your password?", new_password_path(resource_name)
%br/
</pre>
<p>Now our feature is complete.</p>
<h3>Test the Implemention of the “Request Invitation” Feature</h3>
<p>Run the integration test with the following command:</p>
<pre>
$ bundle exec 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>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 work-in-progress
</pre>
<p>The command creates a new branch named “work-in-progress” 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 => "display: none;"}
= simple_form_for resource, :as => resource_name, :url => registration_path(resource_name) , :html => {:class => 'form-horizontal' } do |f|
.modal-header
%a.close{"data-dismiss" => "modal"} ×
%h3 Request Invitation
.modal-body
= f.error_notification
= f.hidden_field :password, :value => 'please'
= f.input :email, :placeholder => '[email protected]'
.modal-footer
= f.submit "Request Invitation", :class => "btn.btn-success", :id => 'invitation_button'
%a.btn{"data-dismiss" => "modal", :href => "#"} Close
#romance-copy{:style => "text-align: center; margin-top: 80px"}
%h2 Want in?
#call-to-action{:style => "text-align: center; margin-top: 80px"}
%a.btn.btn-primary.btn-large{"data-toggle" => "modal", :href => "#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. 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 => "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>