Site dispatcher in its core version works + tests are coming

This commit is contained in:
dinedine 2010-04-12 01:59:18 +02:00
parent 3b8a6c67df
commit 8d1f6a24e1
13 changed files with 209 additions and 440 deletions

View File

@ -0,0 +1,7 @@
class HomeController < ActionController::Base
def show; end
def unknown; end
end

View File

@ -0,0 +1,9 @@
class PagesController < ActionController::Base
include Locomotive::Routing::SiteDispatcher
before_filter :require_site
def show; end
end

View File

@ -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

View File

@ -0,0 +1,3 @@
<h1>Locomotive rocks !!!</h1>
<p><%= flash[:error] %></p>

View File

@ -0,0 +1,8 @@
<html>
<head>
<title><%= current_site.name %></title>
</head>
<body>
<h1>...rendering liquid templates soon</h1>
</body>
</html>

View File

@ -1,3 +1,5 @@
Locomotive::Configuration.setup do |config|
require 'lib/locomotive.rb'
Locomotive.configure do |config|
config.default_domain = 'example.com'
end

View File

@ -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

View File

@ -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
View 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

View File

@ -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

View 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

View 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

View File

@ -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&rsquo;re riding Ruby on Rails!</h2>
</div>
<div id="about">
<h3><a href="rails/info/properties" onclick="about(); return false">About your application&rsquo;s environment</a></h3>
<div id="about-content" style="display: none"></div>
</div>
<div id="getting-started">
<h1>Getting started</h1>
<h2>Here&rsquo;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">&nbsp;</div>
</div>
</body>
</html>