Handcart
A rails gem for managing subdomains in Ruby on Rails.
Installation
RubyGems
gem 'handcart'
Github
gem 'handcart', git: 'https://github.com/mark-d-holmberg/handcart'
Copy Migrations
Handcart requires that you copy over its migrations in the following manner:
rake handcart:install:migrations
Configuration
Handcart can be configured via an initializer file in the following fashion:
# config/initializers/handcart.rb
Handcart.setup do |config|
# You shouldn't need to change this, but the option is available
config.subdomain_class = "Handcart::Subdomain"
# This MUST be set in order for Handcart to work properly.
# Set it to the value of the class which will call `acts_as_handcart`
config.handcart_class = "Company"
# This will allow you to prevent certain subdomains from being created
config.reserved_subdomains = ["www", "ftp", "ssh", "pop3", "staging", "master"]
# Set this to provide a custom route to the show action for the model which
# invokes the call to `acts_as_handcart`.
config.handcart_show_path = "companies"
# Set the strategy to use for IP Authorization
config. = :inclusion
# What Rails environments enable IP blocking and blacklisting
config.global_ip_blocking_enabled_environments = ["development", "production"]
# What Rails environments enable IP forwarding and forbidden redirects
config.global_ip_forwarding_enabled_environments = ["development", "staging", "production"]
end
Handcart Activation
If the thing which acts_as_handcart
responds to the method active?
, Handcart will detect that
and will not match the routing for the subdomain constraints for that handcart until that method returns true.
This allows your model to require activation before it will actually go live.
Extended Configuration
Handcart has support for default domain constraints using a JSON configuration file. Place the following
file inside the config/
folder of your application:
# config/handcart.json
{
"development" : { "domain_constraint" : "dummy.dev" },
"test" : { "domain_constraint" : "dummy.test" },
"staging" : { "domain_constraint" : "dummy.dev" },
"production" : { "domain_constraint" : "dummy.dev" }
}
You can then use the default constraint provided by this JSON configuration file by doing the following inside your routes
# config/routes.rb
constraints Handcart::DomainConstraint.default_constraint do
# My Routes here...
end
Advanced Routing Constraints
Handcart provides the ability to configure advanced routing constraints. This will allow you to match routes only if certain conditions are met. See the following example:
constraints(Handcart::Subdomain) do
resources :custom_constraints, only: [:index], constraints: Handcart::SettingConstraint.new(:enables?, :custom_constraints)
match '/', to: 'subdomain#index', via: [:get], as: :subdomain_root
end
The advanced routing constraint show in this example will only match the route /custom_constraints
if the Handcart (Company) can call
enables?(:custom_constraints)
and returns true. This allows you to check to ensure that routes are match on a per Handcart basis.
Mount the Engine
Handcart needs to be mounted inside the routes file. Add the following line where appropriate:
mount Handcart::Engine => "/handcart"
Setup Handcart Model
Handcart needs to know what model is the thing that is being subdomained, i.e. a School or a Company.
# app/models/company.rb
class Company < ActiveRecord::Base
acts_as_handcart
end
Provided Helpers
Handcart gives you a few helper methods which can be used in the controller or views
- current_subdomain
This helper will allow you to fetch the current subdomain (assuming you're on one).
- current_handcart
This helper will allow you to fetch the current thing that is being subdomained. In the case of the company model above, current_handcart would return the company.
- handcart_show_path(handcart)
This is a special route generated by Handcart which will allow the backend to link to the
show page for the thing which acts_as_handcart
.
If in the configuration for Handcart the handcart_show_path
was set to "companies", it
would result in the following URL:
/companies/:id
Where ID would be set to the id of the company. If this configuration option is not set, it will
try to infer it based on the handcart_class
that is specified.
Routing Helpers
Handcart provides a way to apply domain/subdomain constraints to your routes. The following is an example which uses both Domain Constraints and Subdomain Constraints.
# config/routes.rb
Rails.application.routes.draw do
# Allows for a Reserved Subdomain (Master Backend Interface)
constraints(subdomain: /master/) do
scope module: 'master' do
match '/', to: 'dashboard#index', via: [:get], as: :master_root
end
end
# Allows for Subdomains created through Handcart (Franchisee Interface)
constraints(Handcart::Subdomain) do
match '/', to: 'subdomain#index', via: [:get], as: :subdomain_root
end
# Allows for Domain Constraints (Public Interface)
constraints Handcart::DomainConstraint.default_constraint do
match '/', to: 'public#index', via: [:get], as: :public_root
end
end
IP Authorization
Handcart provides support for foreign IP authorization using various built-in strategies. The IP authorization module provides the following strategies for use when trying to authenticate foreign IP addresses:
None
If the config.ip_authorization_strategy
configuration variable is set to :none
, then no foreign IP
authorization will occur. This means that any IP address from the outside world will be permitted to
login to the franchisee interface.
Containment
Setting config.ip_authorization_strategy
to :containment
will require that the foreign IP address
is simple included in the list of IP addresses which are allowed for the franchisee.
Inclusion
Setting config.ip_authorization_strategy
to :inclusion
will require that the foreign IP address
is included in the list of IP addresses which are allowed for the franchisee AND that the subnet of
the foreign IP address is also in the same subnet as any which are in the whitelist.
IP Forwarding
If your application has a public interface, Handcart can be configured to automatically forward authorized foreign IP addresses to a controller action of your choosing. In addition, if they are not authorized, it will forward them to a public controller action of your choosing. To invoke IP forwarding the following settings must be configured in the initializer:
# config/initializers/handcart.rb
# What Rails environments enable IP forwarding and forbidden redirects
config.global_ip_forwarding_enabled_environments = ["development", "staging", "production"]
Make sure that the environments in which you want IP forwarding to occur in are listed in the variable above.
Public Controller Invocation
To have Handcart enable IP forwarding, the following method must be invoked in the PublicController
:
# app/controllers/public_controller.rb
enable_forwarding("subdomain#index", "public#forbidden", only: [:index])
The first argument indicates what controller and action should be invoked inside the franchisee namespace. The second
argument is the controller and action on the PublicController
which should be invoked when the foreign IP is NOT authorized.
IP Blocking
If you want Handcart to enable the IP blocking feature, you'll need to have the following settings in the initializer:
# What Rails environments enable IP blocking and blacklisting
config.global_ip_blocking_enabled_environments = ["development", "production"]
Franchisee Controller Invocation
To have Handcart enable IP blocking, the following method must be invoked in a controller which is namespaced under the franchisee:
# app/controllers/sessions_controller.rb
enable_blocking("public#blocked")
The first argument indicates the controller and action which will be redirected to should the IP authorization strategy indicate
that the foreign IP address is NOT authorized. This method can also accept the standard options such as only: [:index]
. The use
case for this method is designed to be the sessions controller for the franchisee. The idea is that it will not let them login to
the franchise if they're not authorized.