Multiple domains on Rails 4 app + POW + RSpec/Capybara

I am working on a ROR project that is going to run 2 domains (not sub-domains). This is how I did it.

1) Routes

Add constraints in the routes file. Credits to this thread on Stack Overflow ^^

# lib/domain_constraint.rb
class DomainConstraint
def initialize(domain)
@domains = domain
end
def matches?(request)
@domains.include? request.host
end
end
# config/routes.rb
Rails.application.routes.draw do
get '/', :to => 'pages#home', as: nil

constraints DomainConstraint.new(ENV['DOMAIN1']) do
get '/i-am-going-to-become-the-king-of-pirate', to: 'controller1#someAction'
# namespace, scope, resources etc. routes under domain1 here
end

constraints DomainConstraint.new(ENV['DOMAIN2']) do
get '/i-am-going-to-become-the-world-s-greatest-swordsman', to: 'controller2#someAction'
# namespace, scope, resources etc. routes under domain2 here
end
end
  • I am using bkeepers’ dotenv gem to hold the env variables DOMAIN1 and DOMAIN2 so that I can deploy to production right away. It is not compulsory. You can replace it with pure ruby string.
  • Note that there is no root path. It causes complications since there are now 2 domains. The workaround I have is at the bottom. Read on :)

2) POW

Install pow so that during development, your rails app can run under the different domains that are specified in routes.rb.

Suppose you want DOMAIN1 as www.monkey.d.luffy.com and DOMAIN2 as www.roronoa.zoro.com. Simply symlink the root directory of your project into the .pow folder twice, each under the respective domains.

# for example, if your rails project resides in ~/projects/my_multiple_domain_app, execute these commands in the terminal.
cd ~/.pow
ln -s ~/projects/my_multiple_domain_app www.monkey.d.luffy.com
ln -s ~/projects/my_multiple_domain_app www.roronoa.zoro.com

Alas, you will need to create .powenv file in the root directory of your project. Go ahead and create that file.

cd ~/projects/my_multiple_domain_app && touch .powenv

I am using RVM and I had to throw in some chunk of codes into the .powenv file as stated on the RVM site. In case it disappears, I’ll just add the snippet here.

# .powenv
# detect `$rvm_path`
if [ -z "${rvm_path:-}" ] && [ -x "${HOME:-}/.rvm/bin/rvm" ]
then rvm_path="${HOME:-}/.rvm"
fi
if [ -z "${rvm_path:-}" ] && [ -x "/usr/local/rvm/bin/rvm" ]
then rvm_path="/usr/local/rvm"
fi
# load environment of current project ruby
if
[ -n "${rvm_path:-}" ] &&
[ -x "${rvm_path:-}/bin/rvm" ] &&
rvm_project_environment=`"${rvm_path:-}/bin/rvm" . do rvm env --path 2>/dev/null` &&
[ -n "${rvm_project_environment:-}" ] &&
[ -s "${rvm_project_environment:-}" ]
then
echo "RVM loading: ${rvm_project_environment:-}"
\. "${rvm_project_environment:-}"
else
echo "RVM project not found at: $PWD"
fi

One more thing…add this line to the end of the .powenv file; it is required when we are going to do tests.

export RAILS_ENV=development

3) Developing

Now you are able to access your app under the 2 different domains that you specified. NOTE that POW appends a .dev to the symlinked urls.

Load your server as you usually do with rails server command in the terminal.

Key in www.monkey.d.luffy.com.dev and www.roronoa.zoro.com.dev in your browser. Both urls should bring you to the view handled by the home action of the PagesController as the routes file indicated. Things should also work if you go to www.monkey.d.luffy.com.dev/i-am-going-to-become-the-king-of-pirate and www.roronoa.zoro.com.dev/i-am-going-to-become-the-world-s-greatest-swordsman.

But if you navigate to www.roronoa.zoro.com.dev/i-am-going-to-become-the-king-of-pirate and www.monkey.d.luffy.com.dev/i-am-going-to-become-the-world-s-greatest-swordsman, you should get 404 errors since you are accessing routes that do not exist in the corresponding domains. (Sorry for the long urls. There is a difference with the urls in this paragraph and those in the previous ones if you look closely :) If you are a One Piece fan like me, then you should be able to spot it rather subconsciously).

The terminal where you ran the rails server command should not be showing any logs. That kind of suck. Here’s the workaround. Execute this command in a new terminal to tail the development logs.

tail -f ~/projects/my_multiple_domain_app/log/development.log

4) Debugging

I use the pry gem to do the awesome Rails’ way of debugging, but it would not work for this setup. You will need his aniki for the job. As a brief description of how to use it, replace binding.pry with binding.remote_pry at your desired breakpoints during debugging. Then execute the command below in a new terminal to start the debugging session.

pry-remote

Its a lot more work but this is the only way I found :(

5) Testing

I use RSpec/Capybara for testing. You will need to change the environment as stated in the last line of the .powenv file. And then you have to change it back after the test.

This sounds like trouble, but here’s a solution(TODO find the link) to the rescue. Below’s the snippet of the code to be placed in the rails_helper or spec_helper.rb file, whichever is used to define the RSpec configurations.

The gist of it is, before the test starts, copy the .powenv file and name it anything you want (.pow.original in this case). Then, change the environment in the last line of the .powenv file from development to test. After the test, remove .powenv, then copy .powenv.original to .powenv and rm the former. The environment in the new .powenv file is now reset back to development.

# spec/spec_helper.rb
RSpec.configure do |config|
  config.before :suite do
FileUtils.cp "#{Rails.root}/.powenv", "#{Rails.root}/.powenv.original"
file_name = "#{Rails.root}/.powenv"
text = File.read(file_name)
new_contents = text.gsub(/export RAILS_ENV=development/, "export RAILS_ENV=test")
File.open(file_name, "w") {|file| file.puts new_contents }
...
end

config.after :suite do
FileUtils.rm "#{Rails.root}/.powenv"
FileUtils.cp "#{Rails.root}/.powenv.original", "#{Rails.root}/.powenv"
FileUtils.rm "#{Rails.root}/.powenv.original"
...
end
...
end

In each test case, you will also need to set domain that the test will run. In my feature tests (I am using Selenium-webdriver), I have this snippet in a before block to ensure the test case is run in the correct domain.

feature "some feature test", type: :feature, js: true do
before do
Capybara.server_port = 3000
Capybara.app_host = "http://" + ENV['DOMAIN1'] + ":3000"
end
...
end

NOTE remember to have the ‘http://’ scheme for the app_host attribute.

6) Notes

Do not define root path in your routes. Somehow it does not work and I forgot y :( The workaround I have is to define root_path in the ApplicationHelper and include it in the appropriate places. You may need to add the helper to the RSpec configurations too if you are using route helpers in your tests.

module ApplicationHelper
def root_path
'/'
end
end

Feel free to define root_url in there too.