projectdx-subdomain_routes 0.3.2

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/Rakefile ADDED
@@ -0,0 +1,59 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'yaml'
4
+
5
+ begin
6
+ require 'jeweler'
7
+ Jeweler::Tasks.new do |gem|
8
+ gem.name = "projectdx-subdomain_routes"
9
+ gem.summary = %Q{A Rails library for incorporating subdomains into route generation and recognition.}
10
+ gem.description = <<-EOF
11
+ SubdomainRoutes add subdomain conditions to the Rails routing system. Routes may be restricted to
12
+ one or many specified subdomains. An URL will be recognised only if the host subdomain matches the
13
+ subdomain specified in the route. Route generation is also enhanced, so that the subdomain of a
14
+ generated URL (or path) will be changed if the requested route has a different subdomain to that of
15
+ the current request. Model-based subdomain routes can also be defined.
16
+ EOF
17
+ gem.email = "devteam@projectdx.com"
18
+ gem.homepage = "http://github.com/projectdx/subdomain_routes"
19
+ gem.authors = ["Matthew Hollingworth", "ProjectDX"]
20
+ gem.add_dependency 'actionpack', ">= 2.3.9"
21
+ gem.has_rdoc = false
22
+
23
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
24
+ end
25
+ Jeweler::GemcutterTasks.new
26
+ rescue LoadError
27
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
28
+ end
29
+
30
+ require 'spec/rake/spectask'
31
+ Spec::Rake::SpecTask.new(:spec) do |spec|
32
+ spec.libs << 'lib' << 'spec'
33
+ spec.spec_files = FileList['spec/**/*_spec.rb']
34
+ end
35
+
36
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
37
+ spec.libs << 'lib' << 'spec'
38
+ spec.pattern = 'spec/**/*_spec.rb'
39
+ spec.rcov = true
40
+ end
41
+
42
+
43
+ task :default => :spec
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ if File.exist?('VERSION.yml')
48
+ config = YAML.load(File.read('VERSION.yml'))
49
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
50
+ else
51
+ version = ""
52
+ end
53
+
54
+ rdoc.rdoc_dir = 'rdoc'
55
+ rdoc.title = "subdomain_routes #{version}"
56
+ rdoc.rdoc_files.include('README*')
57
+ rdoc.rdoc_files.include('lib/**/*.rb')
58
+ end
59
+
data/VERSION.yml ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 3
4
+ :build:
5
+ :patch: 2
data/history.txt ADDED
@@ -0,0 +1,18 @@
1
+ SubdomainRoutes Revision History
2
+
3
+ Version 0.3.1
4
+ * Fixed bug whereby port number was not preserved when a subdomain route changed
5
+ the subdomain in the host (Matsumara Akihiro, Matthew Hollingworth).
6
+ * Similarly, added fixes to preserve https protocol when host is changed.
7
+
8
+ Version 0.3.0
9
+
10
+ * Added this revision history.
11
+ * Added #assert_recognizes_with_host and #assert_generates_with_host methods to
12
+ ActionController::Assertions::RoutingAssertions, allowing testing of subdomain routes
13
+ if you're into that sort of thing.
14
+ * Modified ActionController::Routing::Route#significant_keys to include the subdomain
15
+ option in the case of model-based subdomain routes.
16
+ * Added ability to override default namespace and name prefix for model-based subdomain
17
+ routes by passing :name option to the map.subdomain call.
18
+ * Updated README to describe how to test with subdomain routes.
@@ -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,5 @@
1
+ module SubdomainRoutes
2
+ module Config
3
+ mattr_accessor :domain_length
4
+ end
5
+ end
@@ -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,11 @@
1
+ module SubdomainRoutes
2
+ module Request
3
+ include SplitHost
4
+
5
+ def subdomain
6
+ subdomain_for_host(host)
7
+ end
8
+ end
9
+ end
10
+
11
+ ActionController::Request.send :include, SubdomainRoutes::Request
@@ -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