-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathtutorial-rails-subdomains.html
More file actions
559 lines (555 loc) · 42 KB
/
tutorial-rails-subdomains.html
File metadata and controls
559 lines (555 loc) · 42 KB
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
<!DOCTYPE html>
<!--[if IE 9]><html class="lt-ie10" lang="en" > <![endif]-->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Rails Tutorial for Subdomains with Devise · RailsApps</title>
<meta name="viewport" content="width=device-width">
<link href="https://plus.google.com/117374718581973393536" rel="publisher">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/5.2.2/css/normalize.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundation/5.2.2/css/foundation.min.css">
<link rel="stylesheet" href="http://railsapps.github.io/css/railsapps.css" />
<link rel="stylesheet" href="http://railsapps.github.io/css/syntax.css" />
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.6.2/modernizr.min.js"></script>
<script>!function(d,s,id){var js,fjs=d.getElementsByTagName(s)[0];if(!d.getElementById(id)){js=d.createElement(s);js.id=id;js.src="http://platform.twitter.com/widgets.js";fjs.parentNode.insertBefore(js,fjs);}}(document,"script","twitter-wjs");</script>
<script type="text/javascript">
window.analytics=window.analytics||[],window.analytics.methods=["identify","group","track","page","pageview","alias","ready","on","once","off","trackLink","trackForm","trackClick","trackSubmit"],window.analytics.factory=function(t){return function(){var a=Array.prototype.slice.call(arguments);return a.unshift(t),window.analytics.push(a),window.analytics}};for(var i=0;i<window.analytics.methods.length;i++){var key=window.analytics.methods[i];window.analytics[key]=window.analytics.factory(key)}window.analytics.load=function(t){if(!document.getElementById("analytics-js")){var a=document.createElement("script");a.type="text/javascript",a.id="analytics-js",a.async=!0,a.src=("https:"===document.location.protocol?"https://":"http://")+"cdn.segment.io/analytics.js/v1/"+t+"/analytics.min.js";var n=document.getElementsByTagName("script")[0];n.parentNode.insertBefore(a,n)}},window.analytics.SNIPPET_VERSION="2.0.9",
window.analytics.load("l1stqfqqbf");
window.analytics.page();
window.analytics.ready(function () {
ga('require', 'linker');
ga('linker:autoLink', ['railscomposer.com','learn-rails.com','blog.railsapps.org','tutorials.railsapps.org']);
});
</script>
</head>
<body>
<div class="fixed">
<nav class="top-bar" data-topbar>
<ul class="title-area">
<li class="name">
<a href="http://railsapps.github.io/" class="brand">RailsApps Project</a>
</li>
<li class="toggle-topbar menu-icon"><a href="#"><span>Menu</span></a></li>
</ul>
<section class="top-bar-section">
<ul class="right">
<li><a href="https://tutorials.railsapps.org/" class="google">Tutorials</a></li>
<li><a href="http://twitter.com/rails_apps" class="twitter">Twitter</a></li>
<li><a href="http://blog.railsapps.org/" class="twitter">Blog</a></li>
<li><a href="https://github.com/RailsApps" class="github">GitHub Repository</a></li>
</ul>
</section>
</nav>
</div>
<div class="row">
<div class="large-12 columns">
<div class="content wikistyle gollum textile">
<h1>Rails Tutorial for Subdomains with Devise</h1>
<h4>by Daniel Kehoe</h4>
<p><em>Last updated 20 August 2012</em></p>
<p>Ruby on Rails tutorial showing how to create a Rails 3.2 application with subdomains and authentication using Devise.</p>
<h4>What Is Implemented</h4>
<p>The example app implements a common use of subdomains, often called “Basecamp-style subdomains in Rails.” Visitors to the main site create a user account which is hosted at a subdomain that matches their user name. Each user has only one subdomain and when they log in, all their activity is confined to their subdomain.</p>
<h4>What Is Not Implemented</h4>
<p>Another common use of subdomains can be called “blog-style subdomains.” This approach is familiar to users of sites such as wordpress.com where each user can create multiple blogs and each is hosted on its own subdomain. For example, a user with the email address “user@example.com@” can own more than one blog, for example, “puppyphotos.wordpress.com” and “kittenphotos.wordpress.com”. The “blog-style subdomains” approach is not implemented in this example application.</p>
<h4>Based on the Rails3-Mongoid-Devise Example App</h4>
<p>This app extends the <a href="https://github.com/RailsApps/rails3-mongoid-devise">Rails 3 + Mongoid + Devise</a> example app. You can see a <a href="http://railsapps.github.io/tutorial-rails-mongoid-devise.html">tutorial for the Rails 3 + Mongoid + Devise</a>. <a href="http://mongoid.org/">Mongoid</a> gives access to a MongoDB datastore for quick development without schemas or migrations. <a href="https://github.com/plataformatec/devise">Devise</a> gives you ready-made authentication.</p>
<h4>Similar Examples and Tutorials</h4>
<p>See a list of additional <a href="http://railsapps.github.io/rails-examples-tutorials.html">Rails examples, tutorials, and starter apps</a>.</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>
<img src="http://railsapps.github.io/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, no 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 recommendations for a <a href="https://tutorials.railsapps.org/rails-tutorial">Rails tutorial</a> and resources for getting started with <a href="http://railsapps.github.io/rails.html">Rails</a>.</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="https://github.com/RailsApps/rails3-subdomains/issues">GitHub issue</a>.</p>
<p>To improve this tutorial, please leave comments below.</p>
<h2>Installing MongoDB</h2>
<p>If you don’t have MongoDB installed on your computer, you’ll need to install it and set it up to be always running on your computer (run at launch). On Mac OS X, the easiest way to install MongoDB is to install <a href="http://mxcl.github.com/homebrew/">Homebrew</a> and then run the following:</p>
<pre>
brew install mongodb
</pre>
<p>Homebrew will provide post-installation instructions to get MongoDB running. The last line of the installation output shows you the MongoDB install location (for example, <strong>/usr/local/Cellar/mongodb/1.8.0-x86_64</strong>). You’ll find the MongoDB configuration file there. After an installation using Homebrew, the default data directory will be <strong>/usr/local/var/mongodb</strong>.</p>
<h2>Creating the Application</h2>
<p>You have several options for getting the code. You can <em>copy from the tutorial</em>, <em>fork</em>, <em>clone</em>, or <em>generate</em>.</p>
<h3>Copy from the Tutorial</h3>
<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>Fork</h3>
<p>If you’d like to add features (or bug fixes) to improve the example application, you can fork the GitHub repo and <a href="http://help.github.com/send-pull-requests/">make pull requests</a>. Your code contributions are welcome!</p>
<h3>Clone</h3>
<p>If you want to copy and customize the app with changes that are only useful for your own project, you can download or clone the GitHub repo. You’ll need to search-and-replace the project name throughout the application. You probably should generate the app instead (see below).</p>
<h3>Generate</h3>
<p>You can use the <a href="http://railsapps.github.io/rails-composer/">Rails Composer</a> tool to generate a new version of the example app. You’ll be able to give it your own project name when you generate the app. Generating the application gives you additional options.</p>
<p>To build the example application, run the command:</p>
<pre>
$ rails new rails3-subdomains -m https://raw.github.com/RailsApps/rails-composer/master/composer-Rails3_2.rb -T -O
</pre>
<p>Use the <code>-T -O</code> flags to skip Test::Unit files and Active Record 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>rails3-subdomains</code> on your computer. You can use a different name if you wish.</p>
<p>You’ll see a prompt:</p>
<pre>
question Install an example application?
1) I want to build my own application
2) rails3-bootstrap-devise-cancan
3) rails3-devise-rspec-cucumber
4) rails3-mongoid-devise
5) rails3-mongoid-omniauth
6) rails3-subdomains
</pre>
<p>Choose <strong>6) rails3-subdomains</strong>.</p>
<p>The application generator template will ask you for additional preferences.</p>
<p>For this tutorial, choose “WEBrick” for your development webserver.</p>
<p>You can choose to set a robots.txt file to ban spiders and keep your site out of Google search results.</p>
<p>If you are a Linux user and you haven’t installed node.js, you’ll need to answer “yes” to install the ‘therubyracer’ JavaScript runtime.</p>
<p>If you have installed <a href="https://rvm.io/">rvm</a>, the Ruby Version Manager, I recommend you answer “yes” to the prompt, “Use or create a project-specific rvm gemset?” See the article <a href="http://railsapps.github.io/installing-rails.html">Installing Rails</a> to learn about the benefits of using rvm.</p>
<p>Finally, the program will ask if you want to create a GitHub repository.</p>
<p>If you get an error “You have already activated (…) but your Gemfile requires (…)”, try deleting the rails3-subdomains folder and running the command again.</p>
<h2>Assumptions</h2>
<p>Before beginning this tutorial, you need to install</p>
<ul>
<li>The Ruby language (version 1.9.3 or newer)</li>
<li>Rails 3.2</li>
</ul>
<p>Check that appropriate versions of Ruby and Rails are installed in your development environment:</p>
<p><code>$ ruby -v</code><br>
<code>$ rails -v</code></p>
<p>Be sure to read <a href="http://railsapps.github.io/installing-rails.html">Installing Rails</a> for detailed instructions and advice.</p>
<h2>Create the Rails Application</h2>
<p>Before you write any code, you’ll start by generating a starter 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 the <a href="http://railsapps.github.io/rails-composer/">Rails Composer</a> tool (“like the <code>rails new</code> command on steroids”) to create a more complex “starter” app. The starter app simplifies the tutorial. If you want a tutorial that shows you the steps to build the starter app, see the <a href="http://railsapps.github.io/tutorial-rails3-mongoid-devise.html">rails3-mongoid-devise</a> tutorial.</p>
<p>For the starter app we need, use the command:</p>
<pre>
$ rails new rails3-subdomains -m https://raw.github.com/RailsApps/rails-composer/master/composer-Rails3_2.rb -T -O
</pre>
<p>Use the <code>-T -O</code> flags to skip Test::Unit files and Active Record 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>rails3-subdomains</code> on your computer. You can use a different name if you wish.</p>
<p>You’ll see a prompt:</p>
<pre>
question Install an example application?
1) I want to build my own application
2) rails3-bootstrap-devise-cancan
3) rails3-devise-rspec-cucumber
4) rails3-mongoid-devise
5) rails3-mongoid-omniauth
6) rails3-subdomains
</pre>
<p>Choose <strong>4) rails3-mongoid-devise</strong>.</p>
<p>Wait! Why are we choosing “rails3-mongoid-devise” when we want to build a “rails3-subdomains” app? We could select the “rails3-subdomains” app and get a complete application. If you want to work through each step in the tutorial, select the “rails3-mongoid-devise” app and we’ll modify it.</p>
<p>The application generator template will ask you for additional preferences.</p>
<p>For this tutorial, choose “WEBrick” for your development webserver.</p>
<p>You can choose to set a robots.txt file to ban spiders. Choosing “no” makes sense if you want your prelaunch site to show up in Google search results.</p>
<p>If you are a Linux user and you haven’t installed node.js, you’ll need to answer “yes” to install the ‘therubyracer’ JavaScript runtime.</p>
<p>If you have installed <a href="https://rvm.io/">rvm</a>, the Ruby Version Manager, I recommend you answer “yes” to the prompt, “Use or create a project-specific rvm gemset?” See the article <a href="http://railsapps.github.io/installing-rails.html">Installing Rails</a> to learn about the benefits of using rvm.</p>
<p>Finally, the program will ask if you want to create a GitHub repository.</p>
<p>If you get an error “You have already activated (…) but your Gemfile requires (…)”, try deleting the rails3-subdomains 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 rails3-subdomains</code></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.io/rails-git.html">Git and 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>The Rails Composer program sets up your Gemfile and (if you are using rvm) creates a project-specific gemset.</p>
<p>Open your <strong>Gemfile</strong> and you should see the following. Gem version numbers may differ:</p>
<pre>
source 'https://rubygems.org'
gem 'rails', '3.2.13'
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 "mongoid", ">= 3.0.3"
gem "rspec-rails", ">= 2.11.0", :group => [:development, :test]
gem "capybara", ">= 1.1.2", :group => :test
gem "database_cleaner", ">= 0.8.0", :group => :test
gem "mongoid-rspec", ">= 1.4.6", :group => :test
gem "email_spec", ">= 1.2.1", :group => :test
gem "cucumber-rails", ">= 1.3.0", :group => :test, :require => false
gem "launchy", ">= 2.1.2", :group => :test
gem "factory_girl_rails", ">= 4.0.0", :group => [:development, :test]
gem "devise", ">= 2.1.2"
</pre>
<p>Check for the <a href="http://rubygems.org/gems/rails">current version of Rails</a> and replace <code>gem 'rails', '3.2.13'</code> accordingly.</p>
<p><em>Note:</em> Rails Composer templates are 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. Run:</p>
<pre>
bundle install
</pre>
<p>You can check which gems are installed on your computer with:</p>
<pre>
$ gem list
</pre>
<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>
<h4>Haml</h4>
<p>In this tutorial, we’ll use the default “<span class="caps">ERB</span>” Rails template engine. Optionally, you can use another template engine, such as Haml. See instructions for <a href="http://railsapps.github.io/rails-haml.html">Haml and Rails</a>.</p>
<h4>RSpec</h4>
<p>The <a href="https://github.com/RailsApps/rails3-mongoid-devise">Rails 3 + Mongoid + Devise</a> example app uses 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.</p>
<h4>Cucumber</h4>
<p>The <a href="https://github.com/RailsApps/rails3-mongoid-devise">Rails 3 + Mongoid + Devise</a> example app sets up Cucumber for specifications and acceptance testing. 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.</p>
<h4>Mongoid</h4>
<p>The <a href="https://github.com/RailsApps/rails3-mongoid-devise">Rails 3 + Mongoid + Devise</a> example app uses Mongoid for access to a a MongoDB datastore. The datastore is initialized with a default user in the the file <strong>db/seeds.rb</strong>.</p>
<p>If you need to, you can run <code>$ rake db:reset</code> to drop and then recreate the database using your seeds.rb file.</p>
<h2>Test the App</h2>
<h4>Seed the Database</h4>
<p>Add the default user to the MongoDB database by running the command:</p>
<p><code>$ rake db:seed</code></p>
<h4>Test the App</h4>
<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>. 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>To sign in as the default user, (unless you’ve changed it) use</p>
<ul>
<li>email: user@example.com</li>
<li>password: changeme</li>
</ul>
<p>You should delete or change the pre-configured logins before you deploy your application.</p>
<p>Stop the server with Control-C.</p>
<h2>One Subdomain Per User</h2>
<h4>Use Case</h4>
<p>Each user will have access to their own profile page with a unique <span class="caps">URL</span> that includes a custom subdomain. When a user creates a new account, they will be given an opportunity to specify a user ID that will be used as the subdomain. For example, when I sign up for an account, I might specify my User ID as “kehoe” and my profile page will be available at <code>kehoe.example.com</code>.</p>
<h4>User Name as a Subdomain</h4>
<p>The rails3-mongoid-devise example app has a field in the User model named “name.” We’ll use this for the user-specific subdomain.</p>
<p>If you wish, you could rename the “name” field as “subdomain” or “account” or use another label that better matches the requirements of your application. You could also add fields to the User model such as “firstname” and “lastname.” If you do so, you’ll need to change the forms in the <strong>app/views/devise/registrations</strong> directory as well as <strong>app/models/user.rb</strong>. For simplicity, we’ll just use the “name” field to set the subdomain.</p>
<p>In its current form, the User model allows any string to be stored as the user name. To use it as a subdomain, we’ll need to make sure it only contains alphanumeric characters (with an optional underscore). We’ll also limit the name to a maximum of 32 characters. Finally, we don’t want users to choose names such as “www” that will result in URLs such as “http://www.myapp.com”.</p>
<p>Modify <strong>app/models/user.rb</strong> to add the following validations:</p>
<pre>
validates_format_of :name, with: /^[a-z0-9_]+$/, message: "must be lowercase alphanumerics only"
validates_length_of :name, maximum: 32, message: "exceeds maximum of 32 characters"
validates_exclusion_of :name, in: ['www', 'mail', 'ftp'], message: "is not available"
</pre>
<p>We’ll modify the <strong>db/seeds.rb</strong> file to supply an appropriate user name when we initialize the datastore:</p>
<pre>
# user = User.create! :name => 'First User', :email => 'user@example.com', :password => 'please', :password_confirmation => 'please'
user = User.create! :name => 'myname', :email => 'user@example.com', :password => 'please', :password_confirmation => 'please'
</pre>
<h2>User’s Profile Page</h2>
<h4>Use Case</h4>
<p>We’ll display a profile page for a user when anyone enters a <span class="caps">URL</span> with a subdomain that matches an existing user. For example, visiting <code>kehoe.example.com</code> will display kehoe’s profile page.</p>
<h4>Controller and Views for the Profile Page</h4>
<p>Create a controller for profiles:</p>
<pre>
$ rails generate controller Profiles show
</pre>
<p>Before we display a profile, we’ll try to find the user with a name that matches the subdomain attribute of the request object. If the user is not found, we’ll raise an exception and display an error message.</p>
<p><strong>app/controllers/profiles_controller.rb</strong></p>
<pre>
class ProfilesController < ApplicationController
def show
@user = User.where(:name => request.subdomain).first || not_found
end
def not_found
raise ActionController::RoutingError.new('User Not Found')
end
end
</pre>
<p>Next, modify the view to display a user’s profile. Add the following code:</p>
<p><strong>app/views/profile/show.html.erb</strong></p>
<pre>
<h1>Profile</h1>
<h3><%= @user.name %></h3>
<h3><%= @user.email %></h3>
</pre>
<p>If you prefer Haml, it looks likes this:</p>
<p><strong>app/views/profile/show.html.haml</strong></p>
<pre>
%h1 Profile
%h4= @user.name
%h4= @user.email
</pre>
<h2>Remove Access Control for the Users Page</h2>
<p>As implemented in rails3-mongoid-devise example app, no visitor can view the user detail page (user/show) if they are not logged in. That serves to demonstrate use of authentication implemented as a filter in the Users controller. We’ll remove this feature by commenting out <code>before_filter :authenticate_user!</code> to simplify our demonstration of subdomains.</p>
<p><strong>app/controllers/users_controller.rb</strong></p>
<pre>
class UsersController < ApplicationController
# before_filter :authenticate_user!
def show
@user = User.find(params[:id])
end
end
</pre>
<p>Skip this step if you want to retain access control to prevent display of the user detail page.</p>
<p>You also might want to limit access so only an administrator can see the user detail page. For implementation suggestions, seethe Devise wiki for <a href="https://github.com/plataformatec/devise/wiki/How-To:-Add-an-Admin-role">How to add an admin role</a>.</p>
<h2>Implement Routing for Subdomains</h2>
<p>Rails 3.0 introduced support for routing constrained by subdomains.</p>
<p>A subdomain can be specified explicitly, like this:</p>
<pre>
match '/' => 'home#index', :constraints => { :subdomain => 'www' }
</pre>
<p>Or a set of subdomains can be matched using a regular expression:</p>
<pre>
match '/' => 'profiles#show', :constraints => { :subdomain => /.+/ }
</pre>
<p>Finally, for greatest flexibility, router constraints can also take objects, allowing custom code.</p>
<p>Create a class like this:</p>
<p><strong>lib/subdomain.rb</strong></p>
<pre>
class Subdomain
def self.matches?(request)
case request.subdomain
when 'www', '', nil
false
else
true
end
end
end
</pre>
<p>This class allows use of a route when a subdomain is present in the request object. If the subdomain is “www,” the class will respond as if a subdomain is not present.</p>
<p>Make sure the class is autoloaded when the application starts. You can <code>require 'subdomain'</code> at the top of the <strong>config/routes.rb</strong> file. Or you can modify the file <strong>config/application.rb</strong> (recommended):</p>
<pre>
# config.autoload_paths += %W(#{config.root}/extras)
config.autoload_paths += %W(#{config.root}/lib)
</pre>
<p>Use this class when you create routes in the file <strong>config/routes.rb</strong>:</p>
<pre>
devise_for :users
resources :users, :only => :show
constraints(Subdomain) do
match '/' => 'profiles#show'
end
root :to => "home#index"
</pre>
<p>A match from a “/” <span class="caps">URL</span> (such as http://myname.myapp.com) will route to the <code>show</code> action of the <code>Profiles</code> controller only when a subdomain is present. If a subdomain is not present (or is “www”), a route with less priority will be applied (in this case, a route to the <code>index</code> action of the <code>Home</code> controller).</p>
<p>Be sure to comment out (or remove) the route that was added by the Rails generator when we created the controller:</p>
<pre>
#get "profiles/show"
</pre>
<h2>
<span class="caps">URL</span> Helpers With Subdomains</h2>
<p>Applications that do not use subdomains use routing helpers to generate links that either include the site’s hostname (for example, <code>users_url</code> generates <code>http://mysite.com/users</code>) or links that only contain a relative path (for example, <code>users_path</code> generates <code>/users</code>). To provide navigation between sites hosted on the subdomains and the main site, you must use <span class="caps">URL</span> helpers (“users_url”) not path helpers (“users_path”) because path helpers do not include a hostname.</p>
<p>Rails 3.1 provides a way to include a subdomain as part of the hostname when generating links.</p>
<p>You can specify a hostname when creating a link, with the syntax:</p>
<p><code>root_url(:subdomain => @subdomain)</code></p>
<p>If you need a link to the main site (a <span class="caps">URL</span> without a subdomain), you can force the <span class="caps">URL</span> helper to drop the subdomain:</p>
<p><code>root_url(:host => request.domain)</code></p>
<p>It should be possible to use <code>root_url(:subdomain => false)</code> but it seems there is an unresolved issue in Rails 3.1 (see <a href="https://github.com/rails/rails/issues/2025">https://github.com/rails/rails/issues/2025</a>).</p>
<h4>Home Page</h4>
<p>The rails3-mongoid-devise example app provides a home page that lists all registered users. We’ll modify the home page to add a link to each user’s profile page, using a <span class="caps">URL</span> with a custom subdomain.</p>
<p><strong>app/views/home/index.html.erb</strong></p>
<pre>
<h3>Home</h3>
<% @users.each do |user| %>
<p><%= user.name %> <%= link_to root_url(:subdomain => user.name), root_url(:subdomain => user.name) %></p>
<% end %>
</pre>
<p>If you prefer Haml, it looks likes this:</p>
<p><strong>app/views/home/index.html.haml</strong></p>
<pre>
%h3 Home
- @users.each do |user|
%p
= user.name
= link_to root_url(:subdomain => user.name), root_url(:subdomain => user.name)
</pre>
<h4>Modify the Users Page</h4>
<p>We’ll add a link to the user’s profile page on the page that displays a list of all registered users.</p>
<p><strong>app/views/users/show.html.erb</strong></p>
<pre>
<p>User: <%= @user.name %></p>
<p>Email: <%= @user.email if @user.email %></p>
<p>Profile: <%= link_to root_url(:subdomain => @user.name), root_url(:subdomain => @user.name) %></p>
</pre>
<p>If you prefer Haml, it looks likes this:</p>
<p><strong>app/views/users/show.html.haml</strong></p>
<pre>
%p
User: #{@user.name}
%p
Email: #{@user.email if @user.email}
%p
Profile: #{link_to root_url(:subdomain => @user.name), root_url(:subdomain => @user.name)}
</pre>
<h4>Navigation Links</h4>
<p>We want a visitor to sign up on the main site but only log in on their subdomain-hosted site.</p>
<p>We’ll change the navigation links to show “Sign up” on the main site and “Login” on the subdomain sites.</p>
<p><strong>app/views/layouts/_navigation.html.erb</strong></p>
<pre>
<%= link_to "Rails3 Subdomains", root_path, :class => 'brand' %>
<ul class="nav">
<li>
<%= link_to 'Main', root_url(:host => request.domain) %>
</li>
<% if request.subdomain.present? && request.subdomain != "www" %>
<% if user_signed_in? %>
<li>
<%= link_to 'Edit account', edit_user_registration_url %>
</li>
<li>
<%= link_to 'Logout', destroy_user_session_url, :method=>'delete' %>
</li>
<% else %>
<li>
<%= link_to 'Login', new_user_session_url %>
</li>
<% end %>
<% else %>
<li>
<%= link_to 'Sign up', new_user_registration_url(:host => request.domain) %>
</li>
<% if user_signed_in? %>
<li>
<%= link_to 'Logout', destroy_user_session_url, :method=>'delete' %>
</li>
<% end %>
<% end %>
</ul>
</pre>
<p>If you prefer Haml, it looks likes this:</p>
<p><strong>app/views/layouts/_navigation.html.haml</strong></p>
<pre>
%li
= link_to 'Main', root_url(:host => request.domain)
- if request.subdomain.present? && request.subdomain != "www"
- if user_signed_in?
%li
= link_to('Edit account', edit_user_registration_url)
%li
= link_to('Logout', destroy_user_session_url, :method=>'delete')
- else
%li
= link_to('Login', new_user_session_url)
- else
%li
= link_to('Sign up', new_user_registration_url(:host => request.domain))
- if user_signed_in?
%li
= link_to('Logout', destroy_user_session_url, :method=>'delete')
</pre>
<h2>Test the Application With Subdomains</h2>
<p>If you launch the application, it will be running at <a href="http://localhost:3000/">http://localhost:3000/</a> or <a href="http://0.0.0.0:3000/">http://0.0.0.0:3000/</a>. However, unless you’ve made some configuration changes to your computer, you won’t be able to resolve an address that uses a subdomain, such as <a href="http://foo.localhost:3000/">http://foo.localhost:3000/</a>.</p>
<h4>Some Options</h4>
<p>There are several complex solutions to this problem. You could set up your own domain name server on your localhost and create an A entry to catch all subdomains. You could modify your <strong>/etc/hosts</strong> file (but it won’t accommodate dynamically created subdomains). You can create a <a href="http://en.wikipedia.org/wiki/Proxy_auto-config" title="PAC">proxy auto-config</a> file and set it up as the proxy in your web browser preferences.</p>
<h4>Use lvh.me</h4>
<p>There’s a far simpler solution that does not require reconfiguring your computer or web browser preferences. The developer Levi Cook registered a domain, <a href="http://lvh.me:3000/">lvh.me</a> (short for: local virtual host me), that resolves to the localhost IP address 127.0.0.1 and supports wildcards (accommodating dynamically created subdomains).</p>
<h4>Seed the Datastore</h4>
<p>You’ve changed the name of the default user in the the <strong>db/seeds.rb</strong> file.</p>
<p>Re-initialize the MongoDB datastore by running the command:</p>
<p><code>$ rake db:seed</code></p>
<h4>Test the App</h4>
<p>Start the app by entering the command</p>
<p><code>$ rails server</code></p>
<p>To test the application, visit <a href="http://lvh.me:3000/">http://lvh.me:3000/</a>. You should see a list of registered users with links to their profile pages.</p>
<p>Try <a href="http://myname.lvh.me:3000/">http://myname.lvh.me:3000/</a>. You should see the profile page for the default user “myname.”</p>
<p>Try <a href="http://foo.lvh.me:3000/">http://foo.lvh.me:3000/</a>. You should see the “user not found” error message.</p>
<p>Try <a href="http://www.lvh.me:3000/">http://www.lvh.me:3000/</a>. You should be redirected to the application home page at <a href="http://lvh.me:3000/">http://lvh.me:3000/</a>.</p>
<h2>Optional: Allow Sessions To Be Shared Across Subdomains</h2>
<p>The application allows a user to log in to either the main site (<a href="http://lvh.me:3000/">http://lvh.me:3000/</a>) or a user site (<a href="http://myname.lvh.me:3000/">http://myname.lvh.me:3000/</a>). Logging in creates a user session. In its current form, the application maintains separate and independent user sessions for the main site and each subdomain. That’s because session data for each visitor is managed by browser-based cookies and cookies are not shared across domains (and, by default, not shared across subdomains). Not only is the user login not maintained between the main site and subdomains, but flash messages (used to communicate between actions) are lost because they are stored in sessions.</p>
<p>For the example application, we want a visitor to sign up on the main site but only log in on their subdomain-hosted site. As implemented, we don’t want sessions to be shared across subdomains.</p>
<p>However, your requirements may differ. If you wish to maintain sessions between the main site and subdomain-hosted sites, modify the configuration file to add the parameter <code>:domain => :all</code>:</p>
<p><strong>config/initializers/session_store.rb</strong></p>
<pre>
Rails3Subdomains::Application.config.session_store :cookie_store, :key => '_rails3-subdomains_session', :domain => :all
</pre>
<h2>Deploy to Heroku</h2>
<p>For your convenience, here are <a href="http://railsapps.github.io/rails-heroku-tutorial.html">instructions for deploying your app to Heroku</a>. Heroku provides low cost, easily configured Rails application hosting.</p>
<h2>Conclusion</h2>
<p>This concludes the tutorial for creating a Rails 3 web application that uses subdomains and provides user management and authentication using Devise.</p>
<h4>Credits</h4>
<p>Thank you to <a href="http://bcardarella.com/">Brian Cardarella</a> for a helpful blog post on <a href="http://bcardarella.com/post/716951242/custom-subdomains-in-rails-3">Custom Subdomains in Rails 3</a>.</p>
<p>Daniel Kehoe (<a href="http://danielkehoe.com/">http://danielkehoe.com/</a>) implemented the application and wrote the tutorial.</p>
<p>Was this useful to you? Follow me on Twitter:<br>
<a href="http://twitter.com/rails_apps">rails_apps</a><br>
and tweet some praise. I’d love to know you were helped out by the tutorial.</p>
<p>Any issues? Please create an <a href="https://github.com/RailsApps/rails3-subdomains/issues">issue</a> on GitHub.</p>
</div>
<div class="comments" id="comments">
<div class="content wikistyle gollum">
<h2>Comments</h2>
</div>
<p>Is this helpful? Your encouragement fuels the project. Please tweet or add a comment. Couldn't get something to work? For the example apps and tutorials, it's best to open an issue on GitHub so we can help you.</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="columns" -->
</div><!-- class="row" -->
<footer class="row">
<div class="large-12 columns">
<div class="row">
<div class="medium-4 large-4 columns">
<dl class="footer_nav">
<dt>RailsApps · Getting Started</dt>
<dd><a href="http://railsapps.github.io/ruby-and-rails.html">Ruby on Rails</a></dd>
<dd><a href="http://railsapps.github.io/what-is-ruby-rails.html">What is Ruby on Rails?</a></dd>
<dd><a href="http://learn-rails.com/learn-ruby-on-rails.html">Learn Ruby on Rails</a></dd>
<dd><a href="https://tutorials.railsapps.org/rails-tutorial">Rails Tutorial</a></dd>
<dd><a href="http://learn-rails.com/ruby-on-rails-tutorial-for-beginners">Ruby on Rails Tutorial for Beginners</a></dd>
<dd><a href="http://railsapps.github.io/installing-rails.html">Install Ruby on Rails</a></dd>
<dd><a href="http://railsapps.github.io/installrubyonrails-mac.html">Install Ruby on Rails - Mac OS X</a></dd>
<dd><a href="http://railsapps.github.io/installrubyonrails-ubuntu.html">Install Ruby on Rails - Ubuntu</a></dd>
<dd><a href="http://railsapps.github.io/rubyonrails-nitrous-io.html">Ruby on Rails - Nitrous.io</a></dd>
<dd><a href="http://railsapps.github.io/updating-rails.html">Update Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-composer/">Rails Composer</a></dd>
<dd><a href="http://railsapps.github.io/">Rails Examples</a></dd>
<dd><a href="http://railsapps.github.io/rails-examples-tutorials.html">Rails Starter Apps</a></dd>
</dl>
</div>
<div class="medium-4 large-4 columns">
<dl class="footer_nav">
<dt>RailsApps · Articles</dt>
<dd><a href="http://railsapps.github.io/rails-authorization.html">Rails Authorization</a></dd>
<dd><a href="http://railsapps.github.io/rails-google-analytics.html">Analytics for Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-heroku-tutorial.html">Heroku and Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-javascript-include-external.html">JavaScript and Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-environment-variables.html">Rails Environment Variables</a></dd>
<dd><a href="http://railsapps.github.io/rails-git.html">Git and Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-github.html">Rails GitHub</a></dd>
<dd><a href="http://railsapps.github.io/rails-send-email.html">Send Email with Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-haml.html">Haml and Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-default-application-layout.html">Rails Application Layout</a></dd>
<dd><a href="http://railsapps.github.io/rails-html5-boilerplate.html">HTML5 Boilerplate for Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-3-2-example-gemfile.html">Example Gemfiles for Rails</a></dd>
<dd><a href="http://railsapps.github.io/rails-application-templates.html">Rails Application Templates</a></dd>
<dd><a href="http://railsapps.github.io/rails-product-planning.html">Rails Product Planning</a></dd>
<dd><a href="http://railsapps.github.io/rails-project-management.html">Rails Project Management</a></dd>
</dl>
</div>
<div class="medium-4 large-4 columns">
<dl class="footer_nav">
<dt>RailsApps · Tutorials</dt>
<dd><a href="http://railsapps.github.io/twitter-bootstrap-rails.html">Rails Bootstrap</a></dd>
<dd><a href="http://railsapps.github.io/rails-foundation.html">Rails Foundation</a></dd>
<dd><a href="http://railsapps.github.io/rails-omniauth/">OmniAuth Tutorial</a></dd>
<dd><a href="http://railsapps.github.io/tutorial-rails-devise.html">Rails Devise Tutorial</a></dd>
<dd><a href="http://railsapps.github.io/tutorial-rails-devise-rspec-cucumber.html">Devise RSpec</a></dd>
<dd><a href="http://railsapps.github.io/tutorial-rails-bootstrap-devise-cancan.html">Devise Bootstrap</a></dd>
<dd><a href="http://railsapps.github.io/rails-devise-roles">Role-Based Authorization</a></dd>
<dd><a href="http://railsapps.github.io/rails-devise-pundit">Rails Authorization with Pundit</a></dd>
<dd><a href="https://tutorials.railsapps.org/rails-stripe-membership-saas">Rails Membership Site with Stripe</a></dd>
<dd><a href="https://tutorials.railsapps.org/rails-recurly-subscription-saas">Rails Subscription Site with Recurly</a></dd>
<dd><a href="https://tutorials.railsapps.org/rails-prelaunch-signup">Startup Prelaunch Signup Application</a></dd>
</dl>
<dl class="footer_nav">
<dt>RailsApps Profile</dt>
<dd><a href="https://plus.google.com/108039160165742774777?rel=author">Google</a></dd>
<dd><a href="https://plus.google.com/117374718581973393536" rel="publisher">Find us on Google+</a></dd>
</dl>
</div>
</div>
</div>
</footer>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/foundation/5.2.2/js/foundation.min.js"></script>
<script>
$(document).foundation();
</script>
</body>
</html>