Yo dawg, I heard you like…
Purely theoretical (obviously) case: you have existing php app and some legacy rails code that you refreshed a little bit and now someone asks you one simple question - why can’t you just put that code into that code and run it as one app? Sometimes you can reason, sometimes not. But let’s do it - I mean seriously - let’s create some kind of hybrid that will work transparently and silently for the user. Unleash your imagination and get ready for breaking every programming paradigm; treat it like a sport and get ready :).
Let’s ever get a little bit further, let’s create one rails app that will distribute its content for two different domains. Where do we start?
I assume you have some kind of working application already, in the end we want to have:
- domain.com - our main application written in php
- domain.com/superapp1 - rails app that is serving some part of content
- domain.com/superapp2 - same rails app that is serving another part of content
Moreover we want to create this whole structure in a way that it can be ran as separate apps in the future just in case.
First - get familiar with mod proxy as I’ll be using Apache here with with phusion passanger. I think the most reasonable approach is to handle two different sub-domains for serving content (plus admin namespace for backend).
So looking at our application it would look like:
- superapp1.rails.app - for serving content for ‘app1’. This url will be not directly accessible on production.
- superapp2.rails.app - same as above, for ‘app2’
- rails.app/admin - handling backend with restricted access
Be aware that I can’t provide where working example as solutions may vary quite a lot depending on structure of your existing applications and need, but I will give you some ideas if you will need to (theoretically) create that kind of hybrid someday.
Configure phussion passanger for your rails app, don’t forget to set PassengerFriendlyErrorPages on
and RailsEnv "development"
for now.
Now it’s time to start some proxy-action in your php app, setup in your virtual host config:
ProxyPass /superapp1 http://superapp1.rails.app
ProxyPassReverse /superapp1 http://superapp1.rails.app
ProxyPass /superapp1 http://superapp2.rails.app
ProxyPassReverse /superapp1 http://superapp2.rails.app
In your rails routes on the other hand you have to handle those sub-domains, after reading/watching this railscasts I ended up with:
# config/routes.rb
...
constraints(Subdomain) do
# some resources and stuff
end
...
namespace :admin do
# and here some controllers for handling backend
end
end
and:
# app/lib/subdomain.rb
class Subdomain
def self.matches?(request)
%w(superapp1 superapp2).each do |subdomain|
return true if request.env['SERVER_NAME'] =~ /#{subdomain}/
end
false
end
end
After reloading apache cofiguration (service apache2 reload
) loading address /superapp1 from php app should bring on your brand new ror app.
Its not especially exciting and you may noticed some serious problems here:
you ended up with broken urls when trying to move deeper inside your proxy-ed app - as you need to provide proper urls
your assets are broken because you are working really on subdomain, but that can be easily fixed by adding to your apache conf:
ProxyPass /assets http://rails.app/assets
how can you serve diffirent part of content with same rails application depeding on those domains?
what about log in feature for your users?
Lets stard from fixing those urls, one way is to overload url_for
method from UrlHelper. I ended up with something like that:
# app/helpers/url_helper.rb
module UrlHelper
def url_for(options = nil)
options[:host] = "#{request.domain}/#{subdomain_from_server}" if options.kind_of?(Hash) && !subdomain_from_server.empty?
super
end
end
# app/controllers/application_controllers.rb
class ApplicationController < ActionController::Base
include UrlHelper
helper_method :subdomain_from_server # allow with method for views (UrlHelper)
protected
def subdomain_from_server
%w(superapp1 superapp2).each do |subdomain|
return subdomain if request.env['SERVER_NAME'] =~ /#{subdomain}/ # as it's proxied we have to get subdomain from server
end
''
end
end
Now when you call eg. articles_path or whatever you should end up with pretty proxy-ready url (that will be broken when you run your app as-it obviously).
I decided to filter content just by using CanCan (once again thank to Ryan) and setup access depending on current sub-domain in ApplicationController
(you can use can
and cannot
on current_ability
).
What’s left is authentication - how to setup backend access, log-in curret users - and that’s whole different subject. I will point three things here:
- you can work your way with php-memcached, dalli and cookies that are shared across your applications
- you can share existing database by setting up additional adapter in config/database.yml (then use establish_connection in your model and build up proper relation(s) onto existing database structure)
in the end you probably would want to lock your domains they are accessible only from internal network(s), rewrite below should do it
RewriteEngine On RewriteCond %{HTTP_HOST} ^superapp1\.rails\.app$ RewriteCond %{REMOTE_ADDR} !^((1\.2\.3\.4)|(127\.0\.0\.1))$ # allowed address(es) RewriteRule ^.*$ http://domain.com [R]
Well, that’s it - very generall idea how it can be done, hope you liked this little piece of dark hackery ;-).