Site dispatcher in its core version works + tests are coming
This commit is contained in:
parent
3b8a6c67df
commit
8d1f6a24e1
7
app/controllers/home_controller.rb
Normal file
7
app/controllers/home_controller.rb
Normal file
@ -0,0 +1,7 @@
|
||||
class HomeController < ActionController::Base
|
||||
|
||||
def show; end
|
||||
|
||||
def unknown; end
|
||||
|
||||
end
|
9
app/controllers/pages_controller.rb
Normal file
9
app/controllers/pages_controller.rb
Normal file
@ -0,0 +1,9 @@
|
||||
class PagesController < ActionController::Base
|
||||
|
||||
include Locomotive::Routing::SiteDispatcher
|
||||
|
||||
before_filter :require_site
|
||||
|
||||
def show; end
|
||||
|
||||
end
|
@ -10,7 +10,7 @@ class Site
|
||||
## validations ##
|
||||
validates_presence_of :name, :subdomain
|
||||
validates_uniqueness_of :subdomain
|
||||
validates_exclusion_of :subdomain, :in => Locomotive::Configuration.forbidden_subdomains
|
||||
validates_exclusion_of :subdomain, :in => Locomotive.config.reserved_subdomains
|
||||
validates_format_of :subdomain, :with => Locomotive::Regexps::SUBDOMAIN, :allow_blank => true
|
||||
validate :domains_must_be_valid_and_unique
|
||||
|
||||
@ -28,11 +28,11 @@ class Site
|
||||
|
||||
def add_subdomain_to_domains
|
||||
self.domains ||= []
|
||||
(self.domains << "#{self.subdomain}.#{Locomotive::Configuration.default_domain}").uniq!
|
||||
(self.domains << "#{self.subdomain}.#{Locomotive.config.default_domain}").uniq!
|
||||
end
|
||||
|
||||
def domains_without_subdomain
|
||||
(self.domains || []) - ["#{self.subdomain}.#{Locomotive::Configuration.default_domain}"]
|
||||
(self.domains || []) - ["#{self.subdomain}.#{Locomotive.config.default_domain}"]
|
||||
end
|
||||
|
||||
protected
|
||||
|
3
app/views/home/show.html.erb
Normal file
3
app/views/home/show.html.erb
Normal file
@ -0,0 +1,3 @@
|
||||
<h1>Locomotive rocks !!!</h1>
|
||||
|
||||
<p><%= flash[:error] %></p>
|
8
app/views/pages/show.html.erb
Normal file
8
app/views/pages/show.html.erb
Normal file
@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title><%= current_site.name %></title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>...rendering liquid templates soon</h1>
|
||||
</body>
|
||||
</html>
|
@ -1,3 +1,5 @@
|
||||
Locomotive::Configuration.setup do |config|
|
||||
require 'lib/locomotive.rb'
|
||||
|
||||
Locomotive.configure do |config|
|
||||
config.default_domain = 'example.com'
|
||||
end
|
@ -1,60 +1,8 @@
|
||||
Locomotive::Application.routes.draw do |map|
|
||||
# de#vise_for :accounts
|
||||
|
||||
# The priority is based upon order of creation:
|
||||
# first created -> highest priority.
|
||||
|
||||
# Sample of regular route:
|
||||
# match 'products/:id' => 'catalog#view'
|
||||
# Keep in mind you can assign values other than :controller and :action
|
||||
|
||||
# Sample of named route:
|
||||
# match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase
|
||||
# This route can be invoked with purchase_url(:id => product.id)
|
||||
|
||||
# Sample resource route (maps HTTP verbs to controller actions automatically):
|
||||
# resources :products
|
||||
|
||||
# Sample resource route with options:
|
||||
# resources :products do
|
||||
# member do
|
||||
# get :short
|
||||
# post :toggle
|
||||
# end
|
||||
#
|
||||
# collection do
|
||||
# get :sold
|
||||
# end
|
||||
# end
|
||||
|
||||
# Sample resource route with sub-resources:
|
||||
# resources :products do
|
||||
# resources :comments, :sales
|
||||
# resource :seller
|
||||
# end
|
||||
|
||||
# Sample resource route with more complex sub-resources
|
||||
# resources :products do
|
||||
# resources :comments
|
||||
# resources :sales do
|
||||
# get :recent, :on => :collection
|
||||
# end
|
||||
# end
|
||||
|
||||
# Sample resource route within a namespace:
|
||||
# namespace :admin do
|
||||
# # Directs /admin/products/* to Admin::ProductsController
|
||||
# # (app/controllers/admin/products_controller.rb)
|
||||
# resources :products
|
||||
# end
|
||||
|
||||
# You can have the root of your site routed with "root"
|
||||
# just remember to delete public/index.html.
|
||||
# root :to => "welcome#index"
|
||||
|
||||
# See how all your routes lay out with "rake routes"
|
||||
|
||||
# This is a legacy wild controller route that's not recommended for RESTful applications.
|
||||
# Note: This route will make all actions in every controller accessible via GET requests.
|
||||
# match ':controller(/:action(/:id(.:format)))'
|
||||
|
||||
constraints(Locomotive::Routing::DefaultConstraint) do
|
||||
root :to => 'home#show'
|
||||
end
|
||||
|
||||
match '/' => 'pages#show'
|
||||
end
|
||||
|
@ -5,3 +5,6 @@
|
||||
#
|
||||
# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
|
||||
# Mayor.create(:name => 'Daley', :city => cities.first)
|
||||
|
||||
|
||||
Site.create! :name => 'Locomotive test website', :subdomain => 'test'
|
79
lib/locomotive.rb
Normal file
79
lib/locomotive.rb
Normal file
@ -0,0 +1,79 @@
|
||||
module Locomotive
|
||||
|
||||
class << self
|
||||
attr_accessor :config
|
||||
|
||||
def config
|
||||
self.config = Configuration.new unless @config
|
||||
@config
|
||||
end
|
||||
end
|
||||
|
||||
def self.configure
|
||||
self.config ||= Configuration.new
|
||||
yield(self.config)
|
||||
end
|
||||
|
||||
class Configuration
|
||||
|
||||
@@defaults = {
|
||||
:default_domain => 'rails.local.fr',
|
||||
:reserved_subdomains => %w{www admin email blog webmail mail support help site sites}
|
||||
}
|
||||
|
||||
cattr_accessor :settings
|
||||
|
||||
def initialize
|
||||
@@settings = self.class.get_from_hash(@@defaults)
|
||||
end
|
||||
|
||||
def self.settings
|
||||
@@settings
|
||||
end
|
||||
|
||||
def method_missing(name, *args, &block)
|
||||
self.settings.send(name, *args, &block)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# converts a hash map into a ConfigurationHash
|
||||
def self.get_from_hash(hash)
|
||||
config = ConfigurationHash.new
|
||||
|
||||
hash.each_pair do |key, value|
|
||||
config[key] = value.is_a?(Hash) ? self.get_from_hash(value) : value
|
||||
end
|
||||
|
||||
config
|
||||
end
|
||||
end
|
||||
|
||||
# specialized hash for storing configuration settings
|
||||
class ConfigurationHash < Hash
|
||||
# ensure that default entries always produce
|
||||
# instances of the ConfigurationHash class
|
||||
def default(key=nil)
|
||||
include?(key) ? self[key] : self[key] = self.class.new
|
||||
end
|
||||
|
||||
# retrieves the specified key and yields it
|
||||
# if a block is provided
|
||||
def [](key, &block)
|
||||
block_given? ? yield(super(key)) : super(key)
|
||||
end
|
||||
|
||||
# provides member-based access to keys
|
||||
# i.e. params.id === params[:id]
|
||||
# note: all keys are converted to symbols
|
||||
def method_missing(name, *args, &block)
|
||||
if name.to_s.ends_with? '='
|
||||
send :[]=, name.to_s.chomp('=').to_sym, *args
|
||||
else
|
||||
send(:[], name.to_sym, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
@ -1,100 +0,0 @@
|
||||
# require 'active_support'
|
||||
|
||||
# Custom configuration settings
|
||||
# code inspired by http://slateinfo.blogs.wvu.edu/
|
||||
#
|
||||
# Example:
|
||||
# Locomotive::Configuration.setup do |config|
|
||||
# config.title = "Hello world !!!"
|
||||
#
|
||||
# config.admin do |admin|
|
||||
# admin.per_page 10
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# Locomotive::Configuration.admin.per_page # => "10"
|
||||
#
|
||||
# # some alternate ways to access the settings
|
||||
# Locomotive::Configuration.config[:admin][:per_page] # => "10"
|
||||
|
||||
module Locomotive
|
||||
|
||||
class Configuration
|
||||
|
||||
DEFAULT_SETTINGS = {
|
||||
:default_domain => 'localhost',
|
||||
:forbidden_subdomains => %w{www admin email blog webmail mail support help site sites}
|
||||
}
|
||||
|
||||
cattr_accessor :settings
|
||||
|
||||
# creates a new configuration object if
|
||||
# necessary
|
||||
def self.settings # !> redefine settings
|
||||
if @@settings.nil?
|
||||
@@settings = self.get_from_hash(DEFAULT_SETTINGS)
|
||||
else
|
||||
@@settings
|
||||
end
|
||||
end
|
||||
|
||||
def self.setup
|
||||
block_given? ? yield(self.settings) : self.settings
|
||||
end
|
||||
|
||||
# reset settings
|
||||
def self.reset
|
||||
@@settings = nil
|
||||
end
|
||||
|
||||
# returns the current configuration
|
||||
# by passing a block you can easily edit the
|
||||
# configuration values
|
||||
def self.config
|
||||
block_given? ? yield(self.settings) : self.settings
|
||||
end
|
||||
|
||||
def self.method_missing(name, *args, &block)
|
||||
self.config.send(name, *args, &block)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# converts a hash map into a ConfigurationHash
|
||||
def self.get_from_hash(hash)
|
||||
config = ConfigurationHash.new
|
||||
|
||||
hash.each_pair do |key, value|
|
||||
config[key] = value.is_a?(Hash) ? self.get_from_hash(value) : value
|
||||
end
|
||||
|
||||
config
|
||||
end
|
||||
end
|
||||
|
||||
# specialized hash for storing configuration settings
|
||||
class ConfigurationHash < Hash
|
||||
# ensure that default entries always produce
|
||||
# instances of the ConfigurationHash class
|
||||
def default(key=nil)
|
||||
include?(key) ? self[key] : self[key] = self.class.new
|
||||
end
|
||||
|
||||
# retrieves the specified key and yields it
|
||||
# if a block is provided
|
||||
def [](key, &block)
|
||||
block_given? ? yield(super(key)) : super(key)
|
||||
end
|
||||
|
||||
# provides member-based access to keys
|
||||
# i.e. params.id === params[:id]
|
||||
# note: all keys are converted to symbols
|
||||
def method_missing(name, *args, &block)
|
||||
if name.to_s.ends_with? '='
|
||||
send :[]=, name.to_s.chomp('=').to_sym, *args
|
||||
else
|
||||
send(:[], name.to_sym, &block)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
45
lib/locomotive/routing/default_constraint.rb
Normal file
45
lib/locomotive/routing/default_constraint.rb
Normal file
@ -0,0 +1,45 @@
|
||||
module Locomotive
|
||||
|
||||
module Routing
|
||||
|
||||
class DefaultConstraint
|
||||
|
||||
def self.matches?(request)
|
||||
domain, subdomain = domain_and_subdomain(request)
|
||||
subdomain = 'www' if subdomain.blank?
|
||||
|
||||
# puts "domain = #{domain} / #{Locomotive.config.default_domain.inspect}"
|
||||
# puts "subdomain = #{subdomain} / #{Locomotive.config.reserved_subdomains.inspect}"
|
||||
|
||||
domain == Locomotive.config.default_domain && Locomotive.config.reserved_subdomains.include?(subdomain)
|
||||
end
|
||||
|
||||
# see actionpack/lib/action_dispatch/http/url.rb for more information
|
||||
def self.domain_and_subdomain(request)
|
||||
subdomain = extract_subdomain(request)
|
||||
[extract_domain(request), extract_subdomain(request)]
|
||||
end
|
||||
|
||||
def self.extract_domain(request, tld_length = 1)
|
||||
return nil unless named_host?(request.host)
|
||||
request.host.split('.').last(1 + tld_length).join('.')
|
||||
end
|
||||
|
||||
def self.extract_subdomain(request, tld_length = 1)
|
||||
subdomains(request, tld_length).join('.')
|
||||
end
|
||||
|
||||
def self.named_host?(host)
|
||||
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
|
||||
end
|
||||
|
||||
def self.subdomains(request, tld_length = 1)
|
||||
return [] unless named_host?(request.host)
|
||||
parts = request.host.split('.')
|
||||
parts[0..-(tld_length+2)]
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
43
lib/locomotive/routing/site_dispatcher.rb
Normal file
43
lib/locomotive/routing/site_dispatcher.rb
Normal file
@ -0,0 +1,43 @@
|
||||
module Locomotive
|
||||
|
||||
module Routing
|
||||
|
||||
module SiteDispatcher
|
||||
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
include Locomotive::Routing::SiteDispatcher::InstanceMethods
|
||||
|
||||
before_filter :fetch_site
|
||||
|
||||
helper_method :current_site
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
|
||||
protected
|
||||
|
||||
def fetch_site
|
||||
@site = Site.match_domain(request.host).first
|
||||
end
|
||||
|
||||
def current_site
|
||||
@site ||= fetch_site
|
||||
end
|
||||
|
||||
def require_site
|
||||
redirect_to application_root_url and return false if current_site.nil?
|
||||
end
|
||||
|
||||
def application_root_url
|
||||
root_url(:host => Locomotive.config.default_domain, :port => request.port)
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
@ -1,278 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Ruby on Rails: Welcome aboard</title>
|
||||
<style type="text/css" media="screen">
|
||||
body {
|
||||
margin: 0;
|
||||
margin-bottom: 25px;
|
||||
padding: 0;
|
||||
background-color: #f0f0f0;
|
||||
font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana";
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 28px;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
a {color: #03c}
|
||||
a:hover {
|
||||
background-color: #03c;
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
|
||||
#page {
|
||||
background-color: #f0f0f0;
|
||||
width: 750px;
|
||||
margin: 0;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
#content {
|
||||
float: left;
|
||||
background-color: white;
|
||||
border: 3px solid #aaa;
|
||||
border-top: none;
|
||||
padding: 25px;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
#sidebar {
|
||||
float: right;
|
||||
width: 175px;
|
||||
}
|
||||
|
||||
#footer {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
#header, #about, #getting-started {
|
||||
padding-left: 75px;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
|
||||
#header {
|
||||
background-image: url("images/rails.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: top left;
|
||||
height: 64px;
|
||||
}
|
||||
#header h1, #header h2 {margin: 0}
|
||||
#header h2 {
|
||||
color: #888;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
|
||||
#about h3 {
|
||||
margin: 0;
|
||||
margin-bottom: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#about-content {
|
||||
background-color: #ffd;
|
||||
border: 1px solid #fc0;
|
||||
margin-left: -11px;
|
||||
}
|
||||
#about-content table {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
font-size: 11px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
#about-content td {
|
||||
padding: 10px;
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
#about-content td.name {color: #555}
|
||||
#about-content td.value {color: #000}
|
||||
|
||||
#about-content ul {
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#about-content.failure {
|
||||
background-color: #fcc;
|
||||
border: 1px solid #f00;
|
||||
}
|
||||
#about-content.failure p {
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
|
||||
#getting-started {
|
||||
border-top: 1px solid #ccc;
|
||||
margin-top: 25px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
#getting-started h1 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
}
|
||||
#getting-started h2 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: normal;
|
||||
color: #333;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
#getting-started ol {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
#getting-started li {
|
||||
font-size: 18px;
|
||||
color: #888;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
#getting-started li h2 {
|
||||
margin: 0;
|
||||
font-weight: normal;
|
||||
font-size: 18px;
|
||||
color: #333;
|
||||
}
|
||||
#getting-started li p {
|
||||
color: #555;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
|
||||
#search {
|
||||
margin: 0;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
font-size: 11px;
|
||||
}
|
||||
#search input {
|
||||
font-size: 11px;
|
||||
margin: 2px;
|
||||
}
|
||||
#search-text {width: 170px}
|
||||
|
||||
|
||||
#sidebar ul {
|
||||
margin-left: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
#sidebar ul h3 {
|
||||
margin-top: 25px;
|
||||
font-size: 16px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 1px solid #ccc;
|
||||
}
|
||||
#sidebar li {
|
||||
list-style-type: none;
|
||||
}
|
||||
#sidebar ul.links li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
<script type="text/javascript" src="javascripts/prototype.js"></script>
|
||||
<script type="text/javascript" src="javascripts/effects.js"></script>
|
||||
<script type="text/javascript">
|
||||
function about() {
|
||||
if (Element.empty('about-content')) {
|
||||
new Ajax.Updater('about-content', 'rails/info/properties', {
|
||||
method: 'get',
|
||||
onFailure: function() {Element.classNames('about-content').add('failure')},
|
||||
onComplete: function() {new Effect.BlindDown('about-content', {duration: 0.25})}
|
||||
});
|
||||
} else {
|
||||
new Effect[Element.visible('about-content') ?
|
||||
'BlindUp' : 'BlindDown']('about-content', {duration: 0.25});
|
||||
}
|
||||
}
|
||||
|
||||
window.onload = function() {
|
||||
$('search-text').value = '';
|
||||
$('search').onsubmit = function() {
|
||||
$('search-text').value = 'site:rubyonrails.org ' + $F('search-text');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="page">
|
||||
<div id="sidebar">
|
||||
<ul id="sidebar-items">
|
||||
<li>
|
||||
<form id="search" action="http://www.google.com/search" method="get">
|
||||
<input type="hidden" name="hl" value="en" />
|
||||
<input type="text" id="search-text" name="q" value="site:rubyonrails.org " />
|
||||
<input type="submit" value="Search" /> the Rails site
|
||||
</form>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h3>Join the community</h3>
|
||||
<ul class="links">
|
||||
<li><a href="http://www.rubyonrails.org/">Ruby on Rails</a></li>
|
||||
<li><a href="http://weblog.rubyonrails.org/">Official weblog</a></li>
|
||||
<li><a href="http://wiki.rubyonrails.org/">Wiki</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h3>Browse the documentation</h3>
|
||||
<ul class="links">
|
||||
<li><a href="http://api.rubyonrails.org/">Rails API</a></li>
|
||||
<li><a href="http://stdlib.rubyonrails.org/">Ruby standard library</a></li>
|
||||
<li><a href="http://corelib.rubyonrails.org/">Ruby core</a></li>
|
||||
<li><a href="http://guides.rubyonrails.org/">Rails Guides</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<div id="header">
|
||||
<h1>Welcome aboard</h1>
|
||||
<h2>You’re riding Ruby on Rails!</h2>
|
||||
</div>
|
||||
|
||||
<div id="about">
|
||||
<h3><a href="rails/info/properties" onclick="about(); return false">About your application’s environment</a></h3>
|
||||
<div id="about-content" style="display: none"></div>
|
||||
</div>
|
||||
|
||||
<div id="getting-started">
|
||||
<h1>Getting started</h1>
|
||||
<h2>Here’s how to get rolling:</h2>
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
<h2>Use <code>rails generate</code> to create your models and controllers</h2>
|
||||
<p>To see all available options, run it without parameters.</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h2>Set up a default route and remove or rename this file</h2>
|
||||
<p>Routes are set up in config/routes.rb.</p>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<h2>Create your database</h2>
|
||||
<p>Run <code>rake db:migrate</code> to create your database. If you're not using SQLite (the default), edit <code>config/database.yml</code> with your username and password.</p>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="footer"> </div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user