projectdx-subdomain_routes 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
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