Rails subdomains with Pow, Capybara and Travis CI

Adding subdomains to a Rails application is an easy task today, but things can get a little trickier when testing is involved.

For starters, you may only need to handle subdomains at the routing level, which is quite easy. But after the easy step some troubles may be awaiting for you.

In our example we want to add an “admin” subdomain to a brand new app. We edit the file routes.rb as follows and we’re basically done:


  # myappname/config/routes.rb
  
  Rails.application.routes.draw do
    constraints subdomain: 'admin' do
      root to: 'admin#index', as: 'admin_root' # will match http://admin.myappname.dev/
    end
  end

Of course, to make the thing actually work you also need an AdminController with an index template.

Want to see the page in the browser? You can’t yet. If you start the server and go to admin.localhost:3000 you will only be able to see the default rails page. You have a few options in order to make this work, let’s discuss them one by one

Lvh.me

Just visit http://admin.lvh.me:3000/ and you’ll see the admin page. It works because lvh.me is an external website that points to localhost/127.0.0.1.

This solution is ok as long as you have an internet connection and you are willing to pay the time overhead of the necessary DNS search plus redirect.

Edit /etc/hosts

In order to avoid hitting an external website the easiest solution is to add to your /etc/hosts file an entry like this:


127.0.0.1 myappname.dev
127.0.0.1 admin.myappname.dev

and your app will be available at http://admin.myappname.dev:3000. Drawbacks are you need an entry for every single subdomain of each application and you need to manually edit/keep updated the file.

Pow

The most flexible solution is to use Pow or equivalents (if you’re on Linux you can use Prax). For increased ease of use you can manage pow with the gem powder. If you hacked the /etc/hosts file you should revert the changes before continuing.

Now that pow has taken control of your computer (!) if you visit http://myappname.dev (no port required, defaults to 80) you will see Pow page with the instructions. Just follow them (you’re just a symlink away) and you’re done.

Testing subdomains with Capybara

If good coders always test their code, then we’re no exception. You may want to write integration tests for your admin page, so the names Rspec and Capybara should be ‘nuff said. I’m assuming you already configured Rspec and Capybara in this app.

RackTest webdriver

I’m going to use a single spec to test the admin page, which will evolve through various steps according to increasing requirements.

Let’s add a very basic spec that uses Capybara’s default driver, rack_test, which is the fastest/easiest to work with:


# myappname/spec/features/admin_page_spec.rb

require 'rails_helper'

describe 'the admin page' do
  it 'can be seen' do
    visit admin_root_path
    expect(page).to have_content 'Admin#index'
  end
end

The spec won’t pass. You need to add as first line of the test the follwing code:

Capybara.default_host = 'http://admin.myappname-test.dev'

where myappname-test can actually be any name you want. Running specs again should result in a green message.

Selenium webdriver

I’m assuming you have already configured rspec/capybara to use Selenium in the app. To trigger Selenium in the previous spec we only need to add :js to the it description:


# myappname/spec/features/admin_page_spec.rb

require 'rails_helper'

describe 'the admin page', :js do
  it 'can be seen' do
    Capybara.default_host = 'http://admin.myappname-test.dev'
    visit admin_root_path
    expect(page).to have_content 'Admin#index'
  end
end

After the change the spec doesn’t pass anymore. We need to add some more configuration:


# myappname/spec/features/admin_page_spec.rb

require 'rails_helper'

describe 'the admin page', :js do
  it 'can be seen' do
    Capybara.server_port = 3030
    Capybara.default_host = 'http://myappname-test.dev'
    Capybara.app_host = 'http://admin.myappname-test.dev'
    visit admin_root_path
    expect(page).to have_content 'Admin#index'
  end
end

but we’re not done yet. We also need to add some more configuration for Pow. Let’s create a new file in ~/.pow named myappname-test that contains the text 3030:

 cd ~/.pow && echo "3030" > myappname-test

At this point rake spec should run without errors.

Travis integration

Here at Mikamai we use Travis for continuous integration, so the final step before calling a day it’s to push the code and see if the specs pass on the CI server as well.

First, the project must be configured to allow Selenium to work on Travis. This link explains the basics. You probably just need to add this to your .travis.yml file:


# myappname/.travis.yml

before_script:
  - "export DISPLAY=:99.0"
  - "sh -e /etc/init.d/xvfb start"
  - sleep 3 # give xvfb some time to start

At this point we need to instruct travis to use subdomains by adding the following configuration to the .travis.yml file:


# myappname/.travis.yml

addons:
  hosts:
    - admin.myappname-test.dev
    - myappname-test.dev

Again, let’s also add some further configuration to our rspec test to make it pass on Travis:


# myappname/spec/features/admin_page_spec.rb

require 'rails_helper'

describe 'the admin page', :js do
  it 'can be seen' do
    Capybara.server_port = 3030 unless ENV['CI'] # don't use port 3030 on travis
    Capybara.always_include_port = true # for travis  
    Capybara.default_host = 'http://myappname-test.dev'
    Capybara.app_host = 'http://admin.myappname-test.dev' # to change the subdomain
    visit admin_root_path # this route is under under "admin" subdomain
    expect(page).to have_content 'Admin#index'
  end
end

Now, when you push your code to Travis you should be rewarded with a green outcome… job done!

Of course there is one more step that should be done: move the Capybara port/domains initialization into rspec configuration:


# rspec/rails_helper.rb

Rspec.configure do |config|
  Capybara.server_port = 3030 unless ENV['CI'] # don't use port 3030 on travis
  Capybara.always_include_port = true # for travis 
  Capybara.default_host = 'http://myappname-test.dev'
end

Leave a Reply

wpDiscuz