mataki-subdomain_routes 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README.textile +421 -0
- data/Rakefile +58 -0
- data/VERSION.yml +4 -0
- data/lib/subdomain_routes.rb +13 -0
- data/lib/subdomain_routes/assertions.rb +68 -0
- data/lib/subdomain_routes/config.rb +5 -0
- data/lib/subdomain_routes/mapper.rb +43 -0
- data/lib/subdomain_routes/request.rb +11 -0
- data/lib/subdomain_routes/resources.rb +49 -0
- data/lib/subdomain_routes/routes.rb +97 -0
- data/lib/subdomain_routes/split_host.rb +32 -0
- data/lib/subdomain_routes/url_writer.rb +92 -0
- data/lib/subdomain_routes/validations.rb +42 -0
- data/rails/init.rb +1 -0
- data/spec/assertions_spec.rb +193 -0
- data/spec/extraction_spec.rb +73 -0
- data/spec/mapping_spec.rb +168 -0
- data/spec/recognition_spec.rb +78 -0
- data/spec/resources_spec.rb +33 -0
- data/spec/routes_spec.rb +13 -0
- data/spec/spec_helper.rb +77 -0
- data/spec/test_unit_matcher.rb +46 -0
- data/spec/url_writing_spec.rb +262 -0
- data/spec/validations_spec.rb +27 -0
- metadata +97 -0
data/VERSION.yml
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'active_support'
|
3
|
+
require 'action_controller'
|
4
|
+
|
5
|
+
require 'subdomain_routes/config'
|
6
|
+
require 'subdomain_routes/split_host'
|
7
|
+
require 'subdomain_routes/mapper'
|
8
|
+
require 'subdomain_routes/routes'
|
9
|
+
require 'subdomain_routes/resources'
|
10
|
+
require 'subdomain_routes/url_writer'
|
11
|
+
require 'subdomain_routes/request'
|
12
|
+
require 'subdomain_routes/validations'
|
13
|
+
require 'subdomain_routes/assertions'
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module SubdomainRoutes
|
2
|
+
module RoutingAssertions
|
3
|
+
include SubdomainRoutes::RewriteSubdomainOptions
|
4
|
+
|
5
|
+
def assert_recognizes_with_host(expected_options, path, extras={}, message=nil)
|
6
|
+
# copied from Rails source, with modification to set the the supplied host on the request
|
7
|
+
if path.is_a? Hash
|
8
|
+
request_method = path[:method]
|
9
|
+
host = path[:host]
|
10
|
+
path = path[:path]
|
11
|
+
else
|
12
|
+
request_method = nil
|
13
|
+
host = nil
|
14
|
+
end
|
15
|
+
clean_backtrace do
|
16
|
+
ActionController::Routing::Routes.reload if ActionController::Routing::Routes.empty?
|
17
|
+
request = recognized_request_for_with_host(path, host, request_method)
|
18
|
+
|
19
|
+
expected_options = expected_options.clone
|
20
|
+
extras.each_key { |key| expected_options.delete key } unless extras.nil?
|
21
|
+
|
22
|
+
expected_options.stringify_keys!
|
23
|
+
routing_diff = expected_options.diff(request.path_parameters)
|
24
|
+
|
25
|
+
msg = build_message(message, "The recognized options <?> did not match <?>, difference: <?>",
|
26
|
+
request.path_parameters, expected_options, expected_options.diff(request.path_parameters))
|
27
|
+
assert_block(msg) { request.path_parameters == expected_options }
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def assert_generates_with_host(expected_path, options, host, defaults={}, extras = {}, message=nil)
|
32
|
+
if expected_path.is_a? Hash
|
33
|
+
expected_host = expected_path[:host]
|
34
|
+
expected_path = expected_path[:path]
|
35
|
+
else
|
36
|
+
expected_host = nil
|
37
|
+
end
|
38
|
+
rewrite_subdomain_options(options, host)
|
39
|
+
if options.delete(:only_path) == false
|
40
|
+
generated_host = options.delete(:host)
|
41
|
+
msg = expected_host ?
|
42
|
+
build_message(message, "The route for <?> changed the host to <?> but this did not match expected host <?>", options, generated_host, expected_host) :
|
43
|
+
build_message(message, "The route for <?> changed the host to <?> but the host was not expected to change", options, generated_host)
|
44
|
+
assert_block(msg) { expected_host == generated_host }
|
45
|
+
else
|
46
|
+
msg = build_message(message, "The route for <?> was expected to change the host to <?> but did not change the host", options, expected_host)
|
47
|
+
assert_block(msg) { expected_host == nil }
|
48
|
+
end
|
49
|
+
assert_generates(expected_path, options, defaults, extras, message)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def recognized_request_for_with_host(path, host, request_method = nil)
|
55
|
+
path = "/#{path}" unless path.first == '/'
|
56
|
+
|
57
|
+
# Assume given controller
|
58
|
+
request = ActionController::TestRequest.new
|
59
|
+
request.env["REQUEST_METHOD"] = request_method.to_s.upcase if request_method
|
60
|
+
request.path = path
|
61
|
+
request.host = host if host
|
62
|
+
ActionController::Routing::Routes.recognize(request)
|
63
|
+
request
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
ActionController::Assertions::RoutingAssertions.send :include, SubdomainRoutes::RoutingAssertions
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SubdomainRoutes
|
2
|
+
module Routing
|
3
|
+
module RouteSet
|
4
|
+
module Mapper
|
5
|
+
def subdomain(*subdomains, &block)
|
6
|
+
options = subdomains.extract_options!.dup
|
7
|
+
if subdomains.empty?
|
8
|
+
if model = options.delete(:model)
|
9
|
+
raise ArgumentError, "Invalid model name" if model.blank?
|
10
|
+
models = model.to_s.downcase.pluralize
|
11
|
+
model = models.singularize
|
12
|
+
model_id = model.foreign_key.to_sym
|
13
|
+
subdomain_options = { :subdomains => model_id }
|
14
|
+
name = options.has_key?(:name) ? options.delete(:name) : model
|
15
|
+
else
|
16
|
+
raise ArgumentError, "Please specify at least one subdomain!"
|
17
|
+
end
|
18
|
+
else
|
19
|
+
subdomains.map!(&:to_s)
|
20
|
+
subdomains.map!(&:downcase)
|
21
|
+
subdomains.uniq!
|
22
|
+
subdomains.compact.each do |subdomain|
|
23
|
+
raise ArgumentError, "Illegal subdomain format: #{subdomain.inspect}" unless subdomain.blank? || SubdomainRoutes.valid_subdomain?(subdomain)
|
24
|
+
end
|
25
|
+
if subdomains.include? ""
|
26
|
+
raise ArgumentError, "Can't specify a nil subdomain unless you set Config.domain_length!" unless Config.domain_length
|
27
|
+
end
|
28
|
+
subdomain_options = { :subdomains => subdomains }
|
29
|
+
name = subdomains.reject(&:blank?).first
|
30
|
+
name = options.delete(:name) if options.has_key?(:name)
|
31
|
+
end
|
32
|
+
name = name.to_s.downcase.gsub(/[^(a-z0-9)]/, ' ').squeeze(' ').strip.gsub(' ', '_') unless name.blank?
|
33
|
+
subdomain_options.merge! :name_prefix => "#{name}_", :namespace => "#{name}/" unless name.blank?
|
34
|
+
with_options(subdomain_options.merge(options), &block)
|
35
|
+
end
|
36
|
+
alias_method :subdomains, :subdomain
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
ActionController::Routing::RouteSet::Mapper.send :include, SubdomainRoutes::Routing::RouteSet::Mapper
|
43
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SubdomainRoutes
|
2
|
+
module Resources
|
3
|
+
def self.included(base)
|
4
|
+
base.alias_method_chain :action_options_for, :subdomains
|
5
|
+
base::INHERITABLE_OPTIONS << :subdomains
|
6
|
+
end
|
7
|
+
|
8
|
+
def action_options_for_with_subdomains(action, resource, *args)
|
9
|
+
action_options_for_without_subdomains(action, resource, *args).merge(:subdomains => resource.options[:subdomains])
|
10
|
+
end
|
11
|
+
private :action_options_for_with_subdomains
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
ActionController::Resources.send :include, SubdomainRoutes::Resources
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
# #
|
21
|
+
# # This alternative also works. It duplicates code in add_route_with_subdomains (in routing.rb) so is not
|
22
|
+
# # so good that way, but has the benefit of not modifying ActionController::Resources#action_options_for,
|
23
|
+
# # which is a private method.
|
24
|
+
# #
|
25
|
+
# module SubdomainRoutes
|
26
|
+
# module Resources
|
27
|
+
# module Resource
|
28
|
+
# def self.included(base)
|
29
|
+
# base.alias_method_chain :conditions, :subdomains
|
30
|
+
# base.alias_method_chain :requirements, :subdomains
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def conditions_with_subdomains
|
34
|
+
# @options[:subdomains] ?
|
35
|
+
# conditions_without_subdomains.merge(:subdomains => @options[:subdomains]) :
|
36
|
+
# conditions_without_subdomains
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# def requirements_with_subdomains(with_id = false)
|
40
|
+
# @options[:subdomains] ?
|
41
|
+
# requirements_without_subdomains(with_id).merge(:subdomains => @options[:subdomains]) :
|
42
|
+
# requirements_without_subdomains(with_id)
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
# end
|
47
|
+
#
|
48
|
+
# ActionController::Resources::INHERITABLE_OPTIONS << :subdomains
|
49
|
+
# ActionController::Resources::Resource.send :include, SubdomainRoutes::Resources::Resource
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module SubdomainRoutes
|
2
|
+
module Routing
|
3
|
+
module RouteSet
|
4
|
+
include SplitHost
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
[ :extract_request_environment, :add_route, :raise_named_route_error ].each { |method| base.alias_method_chain method, :subdomains }
|
8
|
+
end
|
9
|
+
|
10
|
+
def extract_request_environment_with_subdomains(request)
|
11
|
+
extract_request_environment_without_subdomains(request).merge(:subdomain => subdomain_for_host(request.host))
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_route_with_subdomains(*args)
|
15
|
+
options = args.extract_options!
|
16
|
+
if subdomains = options.delete(:subdomains)
|
17
|
+
options[:conditions] ||= {}
|
18
|
+
options[:requirements] ||= {}
|
19
|
+
options[:conditions][:subdomains] = subdomains
|
20
|
+
options[:requirements][:subdomains] = subdomains
|
21
|
+
end
|
22
|
+
with_options(options) { |routes| routes.add_route_without_subdomains(*args) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def raise_named_route_error_with_subdomains(options, named_route, named_route_name)
|
26
|
+
unless named_route.conditions[:subdomains].is_a?(Symbol)
|
27
|
+
raise_named_route_error_without_subdomains(options, named_route, named_route_name)
|
28
|
+
else
|
29
|
+
begin
|
30
|
+
options.delete(named_route.conditions[:subdomains])
|
31
|
+
raise_named_route_error_without_subdomains(options, named_route, named_route_name)
|
32
|
+
rescue ActionController::RoutingError => e
|
33
|
+
e.message << " You may also need to specify #{named_route.conditions[:subdomains].inspect} for the subdomain."
|
34
|
+
raise e
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def reserved_subdomains
|
40
|
+
routes.map(&:reserved_subdomains).flatten.uniq
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
module Route
|
45
|
+
def self.included(base)
|
46
|
+
[ :recognition_conditions, :generation_extraction, :segment_keys, :significant_keys, :recognition_extraction ].each { |method| base.alias_method_chain method, :subdomains }
|
47
|
+
end
|
48
|
+
|
49
|
+
def recognition_conditions_with_subdomains
|
50
|
+
returning recognition_conditions_without_subdomains do |result|
|
51
|
+
case conditions[:subdomains]
|
52
|
+
when Array
|
53
|
+
result << "conditions[:subdomains].include?(env[:subdomain])"
|
54
|
+
when Symbol
|
55
|
+
result << "(subdomain = env[:subdomain] unless env[:subdomain].blank?)"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def generation_extraction_with_subdomains
|
61
|
+
results = [ generation_extraction_without_subdomains ]
|
62
|
+
if conditions[:subdomains].is_a?(Symbol)
|
63
|
+
results << "return [nil,nil] unless hash.delete(#{conditions[:subdomains].inspect})"
|
64
|
+
end
|
65
|
+
results.compact * "\n"
|
66
|
+
end
|
67
|
+
|
68
|
+
def segment_keys_with_subdomains
|
69
|
+
returning segment_keys_without_subdomains do |result|
|
70
|
+
result.unshift(conditions[:subdomains]) if conditions[:subdomains].is_a? Symbol
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def significant_keys_with_subdomains
|
75
|
+
returning significant_keys_without_subdomains do |result|
|
76
|
+
if conditions[:subdomains].is_a? Symbol
|
77
|
+
result << conditions[:subdomains]
|
78
|
+
result.uniq!
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def recognition_extraction_with_subdomains
|
84
|
+
returning recognition_extraction_without_subdomains do |result|
|
85
|
+
result.unshift "\nparams[#{conditions[:subdomains].inspect}] = subdomain\n" if conditions[:subdomains].is_a? Symbol
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def reserved_subdomains
|
90
|
+
conditions[:subdomains].is_a?(Array) ? conditions[:subdomains] : []
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
ActionController::Routing::RouteSet.send :include, SubdomainRoutes::Routing::RouteSet
|
97
|
+
ActionController::Routing::Route.send :include, SubdomainRoutes::Routing::Route
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module SubdomainRoutes
|
2
|
+
class TooManySubdomains < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
module SplitHost
|
6
|
+
private
|
7
|
+
|
8
|
+
def split_host(host)
|
9
|
+
raise HostNotSupplied, "No host supplied!" if host.blank?
|
10
|
+
raise HostNotSupplied, "Can't set subdomain for an IP address!" if host =~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
|
11
|
+
parts = host.split('.')
|
12
|
+
if Config.domain_length
|
13
|
+
domain_parts = [ ]
|
14
|
+
Config.domain_length.times { domain_parts.unshift parts.pop }
|
15
|
+
if parts.size > 1
|
16
|
+
raise TooManySubdomains, "Multiple subdomains found: #{parts.join('.')}. (Have you set SubdomainRoutes::Config.domain_length correctly?)"
|
17
|
+
end
|
18
|
+
[ parts.pop.to_s, domain_parts.join('.') ]
|
19
|
+
else
|
20
|
+
[ parts.shift.to_s, parts.join('.') ]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def domain_for_host(host)
|
25
|
+
split_host(host).last
|
26
|
+
end
|
27
|
+
|
28
|
+
def subdomain_for_host(host)
|
29
|
+
split_host(host).first
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module SubdomainRoutes
|
2
|
+
class HostNotSupplied < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
module RewriteSubdomainOptions
|
6
|
+
include SplitHost
|
7
|
+
|
8
|
+
def subdomain_procs
|
9
|
+
ActionController::Routing::Routes.subdomain_procs
|
10
|
+
end
|
11
|
+
|
12
|
+
def rewrite_subdomain_options(options, host, port = nil, protocol = nil)
|
13
|
+
if subdomains = options[:subdomains]
|
14
|
+
old_subdomain, domain = split_host(host)
|
15
|
+
options_subdomain = options.has_key?(:subdomain) ? options[:subdomain].to_param.to_s.downcase : nil
|
16
|
+
new_subdomain = options_subdomain || old_subdomain
|
17
|
+
begin
|
18
|
+
case subdomains
|
19
|
+
when Array
|
20
|
+
unless subdomains.include? new_subdomain
|
21
|
+
if subdomains.size > 1 || options_subdomain
|
22
|
+
raise ActionController::RoutingError, "expected subdomain in #{subdomains.inspect}, instead got subdomain #{new_subdomain.inspect}"
|
23
|
+
else
|
24
|
+
new_subdomain = subdomains.first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
when Symbol
|
28
|
+
new_subdomain = options[subdomains].to_param.to_s.downcase
|
29
|
+
unless new_subdomain.blank? || SubdomainRoutes.valid_subdomain?(new_subdomain)
|
30
|
+
raise ActionController::RoutingError, "subdomain #{new_subdomain.inspect} is invalid"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
rescue ActionController::RoutingError => e
|
34
|
+
raise ActionController::RoutingError, "Route for #{options.inspect} failed to generate (#{e.message})"
|
35
|
+
end
|
36
|
+
unless new_subdomain == old_subdomain
|
37
|
+
options[:only_path] = false
|
38
|
+
options[:host] = [ new_subdomain, domain ].reject(&:blank?).join('.')
|
39
|
+
options[:port] ||= port if port
|
40
|
+
options[:protocol] ||= protocol if protocol
|
41
|
+
end
|
42
|
+
options.delete(:subdomain)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
module UrlWriter
|
48
|
+
include RewriteSubdomainOptions
|
49
|
+
|
50
|
+
def self.included(base)
|
51
|
+
base.alias_method_chain :url_for, :subdomains
|
52
|
+
end
|
53
|
+
|
54
|
+
def url_for_with_subdomains(options)
|
55
|
+
host = options[:host] || default_url_options[:host]
|
56
|
+
port = options[:port] || default_url_options[:port]
|
57
|
+
protocol = options[:protocol] || default_url_options[:protocol]
|
58
|
+
if options[:subdomains] && host.blank?
|
59
|
+
raise HostNotSupplied, "Missing host to link to! Please provide :host parameter or set default_url_options[:host]"
|
60
|
+
end
|
61
|
+
rewrite_subdomain_options(options, host, port, protocol)
|
62
|
+
url_for_without_subdomains(options)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
module UrlRewriter
|
67
|
+
include RewriteSubdomainOptions
|
68
|
+
|
69
|
+
def self.included(base)
|
70
|
+
base.alias_method_chain :rewrite, :subdomains
|
71
|
+
base::RESERVED_OPTIONS << :subdomain # untested!
|
72
|
+
end
|
73
|
+
|
74
|
+
def rewrite_with_subdomains(options)
|
75
|
+
host = options[:host] || @request.host
|
76
|
+
protocol = options[:protocol] || (@request.ssl? ? @request.protocol : nil)
|
77
|
+
port = options[:port] || (@request.port_string =~ /:(\d+)$/ ? $1.to_i : nil)
|
78
|
+
if options[:subdomains] && host.blank?
|
79
|
+
raise HostNotSupplied, "Missing host to link to!"
|
80
|
+
end
|
81
|
+
rewrite_subdomain_options(options, host, port, protocol)
|
82
|
+
rewrite_without_subdomains(options)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
ActionController::UrlWriter.send :include, SubdomainRoutes::UrlWriter
|
88
|
+
ActionController::UrlRewriter.send :include, SubdomainRoutes::UrlRewriter
|
89
|
+
|
90
|
+
if defined? ActionMailer::Base
|
91
|
+
ActionMailer::Base.send :include, ActionController::UrlWriter
|
92
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module SubdomainRoutes
|
2
|
+
def self.valid_subdomain?(subdomain)
|
3
|
+
subdomain.to_s =~ /^([a-z]|[a-z][a-z0-9]|[a-z]([a-z0-9]|\-[a-z0-9])*)$/
|
4
|
+
end
|
5
|
+
|
6
|
+
# # Alternatively, we use URI::parse instead. This gives more lenient subdomains however:
|
7
|
+
# def self.valid_subdomain?(subdomain)
|
8
|
+
# URI.parse "http://#{subdomain}.example.com"
|
9
|
+
# rescue URI::InvalidURIError
|
10
|
+
# false
|
11
|
+
# end
|
12
|
+
|
13
|
+
module Validations
|
14
|
+
module ClassMethods
|
15
|
+
def validates_subdomain_format_of(*attr_names)
|
16
|
+
configuration = { :on => :save }
|
17
|
+
configuration.update(attr_names.extract_options!)
|
18
|
+
|
19
|
+
validates_each(attr_names, configuration) do |record, attr_name, value|
|
20
|
+
unless SubdomainRoutes.valid_subdomain?(value)
|
21
|
+
record.errors.add(attr_name, :not_a_valid_subdomain, :default => configuration[:message], :value => value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def validates_subdomain_not_reserved(*attr_names)
|
27
|
+
configuration = { :on => :save }
|
28
|
+
configuration.update(attr_names.extract_options!)
|
29
|
+
|
30
|
+
validates_each(attr_names, configuration) do |record, attr_name, value|
|
31
|
+
if ActionController::Routing::Routes.reserved_subdomains.include? value
|
32
|
+
record.errors.add(attr_name, :is_a_reserved_subdomain, :default => configuration[:message], :value => value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
if defined? ActiveRecord::Base
|
41
|
+
ActiveRecord::Base.send :extend, SubdomainRoutes::Validations::ClassMethods
|
42
|
+
end
|