subdomainify 0.2.0
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/.gitignore +23 -0
- data/.rspec +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +24 -0
- data/README.md +77 -0
- data/Rakefile +9 -0
- data/example/.gitignore +16 -0
- data/example/Gemfile +3 -0
- data/example/README.md +9 -0
- data/example/Rakefile +6 -0
- data/example/app/controllers/application_controller.rb +5 -0
- data/example/app/controllers/articles_controller.rb +8 -0
- data/example/app/controllers/blogs_controller.rb +12 -0
- data/example/app/helpers/application_helper.rb +2 -0
- data/example/app/models/article.rb +29 -0
- data/example/app/models/blog.rb +31 -0
- data/example/app/views/articles/show.html.erb +5 -0
- data/example/app/views/blogs/index.html.erb +5 -0
- data/example/app/views/blogs/show.html.erb +7 -0
- data/example/app/views/layouts/application.html.erb +24 -0
- data/example/bin/bundle +3 -0
- data/example/bin/rails +4 -0
- data/example/config.ru +4 -0
- data/example/config/application.rb +30 -0
- data/example/config/boot.rb +4 -0
- data/example/config/environment.rb +5 -0
- data/example/config/environments/development.rb +25 -0
- data/example/config/initializers/cookies_serializer.rb +3 -0
- data/example/config/initializers/mime_types.rb +4 -0
- data/example/config/initializers/session_store.rb +3 -0
- data/example/config/initializers/wrap_parameters.rb +9 -0
- data/example/config/locales/en.yml +23 -0
- data/example/config/routes.rb +7 -0
- data/example/config/secrets.yml +22 -0
- data/lib/subdomainify.rb +3 -0
- data/lib/subdomainify/middleware.rb +52 -0
- data/lib/subdomainify/railtie.rb +32 -0
- data/lib/subdomainify/route_set.rb +71 -0
- data/lib/subdomainify/version.rb +3 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/subdomainify_spec.rb +224 -0
- data/subdomainify.gemspec +25 -0
- metadata +172 -0
data/.gitignore
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
log/
|
15
|
+
spec/reports
|
16
|
+
test/tmp
|
17
|
+
test/version_tmp
|
18
|
+
tmp
|
19
|
+
*.bundle
|
20
|
+
*.so
|
21
|
+
*.o
|
22
|
+
*.a
|
23
|
+
mkmf.log
|
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
Copyright (c) 2014, Oozou, Ltd. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without modification,
|
4
|
+
are permitted provided that the following conditions are met:
|
5
|
+
|
6
|
+
* Redistributions of source code must retain the above copyright notice, this
|
7
|
+
list of conditions and the following disclaimer.
|
8
|
+
* Redistributions in binary form must reproduce the above copyright notice, this
|
9
|
+
list of conditions and the following disclaimer in the documentation and/or
|
10
|
+
other materials provided with the distribution.
|
11
|
+
* Neither the name of the author nor the names of its contributors may be used
|
12
|
+
to endorse or promote products derived from this software without specific
|
13
|
+
prior written permission.
|
14
|
+
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
19
|
+
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
20
|
+
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
21
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
22
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
|
23
|
+
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
24
|
+
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# Subdomainify [](http://travis-ci.org/oozou/subdomainify)
|
2
|
+
|
3
|
+
Subdomainify is a subdomain rewriting middleware for your Rails 4 app.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
In a **Rails 4** app, add this to your Gemfile and run the `bundle` command:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem "subdomainify"
|
11
|
+
```
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
Simply mark any route in your `routes.rb` with `subdomainify: true`. For example:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
resources :blogs, subdomainify: true do
|
19
|
+
resources :articles
|
20
|
+
resources :comments
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
After marking a resource with `subdomainify`, a `url_for` call to that resource will automatically generate a subdomain route instead of a normal route. For example:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
@blog # => #<Blog id: 1, user_id: 1, name: "My Example Blog", slug: "foo">
|
28
|
+
@blog.to_param # => "foo"
|
29
|
+
blog_url(@blog) # => "http://foo.example.com/"
|
30
|
+
```
|
31
|
+
|
32
|
+
This also works for nested resources:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
@article # => #<Article id: 1, user_id: 1, title: "Lorem ipsum", slug: "lorem-ipsum", body: "Dolor sit amet">
|
36
|
+
@article.to_param # => "lorem-ipsum"
|
37
|
+
blog_article_url(@blog, @article) # => "http://foo.example.com/articles/lorem-ipsum"
|
38
|
+
```
|
39
|
+
|
40
|
+
### How it works
|
41
|
+
|
42
|
+
Subdomainify works by rewriting a subdomain URL to the specific route using a Rack middleware. In the above example, the resource URL for the `blogs` resource is located at `example.com/blogs/:id`. When users visit `foo.example.com`, Subdomainify will rewrite that request into `example.com/blogs/foo`. This includes everything else that was passed in as the path. For example, when users visit this URL:
|
43
|
+
|
44
|
+
```
|
45
|
+
http://foo.example.com/articles/hello-world
|
46
|
+
```
|
47
|
+
|
48
|
+
The middleware will rewrite `PATH_INFO` into:
|
49
|
+
|
50
|
+
```
|
51
|
+
http://foo.example.com/blogs/foo/articles/hello-world
|
52
|
+
```
|
53
|
+
|
54
|
+
Which means on the application side, you can treat subdomain routes like any other routes. Please note that even after rewriting, the subdomain is not discarded, allowing for its usage in constraints:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
constraints ->(req) { req.subdomain.present? } do
|
58
|
+
resources :blogs, subdomainify: true do
|
59
|
+
resources :articles
|
60
|
+
resources :comments
|
61
|
+
end
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
Doing so will make this route accessible only when visited from subdomain URL.
|
66
|
+
|
67
|
+
## License
|
68
|
+
|
69
|
+
Copyright (c) 2014, Oozou, Ltd. All rights reserved.
|
70
|
+
|
71
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
72
|
+
|
73
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
74
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
75
|
+
* Neither the name of the author nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
76
|
+
|
77
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/Rakefile
ADDED
data/example/.gitignore
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
|
2
|
+
#
|
3
|
+
# If you find yourself ignoring temporary files generated by your text editor
|
4
|
+
# or operating system, you probably want to add a global ignore instead:
|
5
|
+
# git config --global core.excludesfile '~/.gitignore_global'
|
6
|
+
|
7
|
+
# Ignore bundler config.
|
8
|
+
/.bundle
|
9
|
+
|
10
|
+
# Ignore the default SQLite database.
|
11
|
+
/db/*.sqlite3
|
12
|
+
/db/*.sqlite3-journal
|
13
|
+
|
14
|
+
# Ignore all logfiles and tempfiles.
|
15
|
+
/log/*.log
|
16
|
+
/tmp
|
data/example/Gemfile
ADDED
data/example/README.md
ADDED
data/example/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
class Article
|
2
|
+
extend ActiveModel::Naming
|
3
|
+
|
4
|
+
ARTICLES = [{ blog_slug: "foo", title: "Hello, world", slug: "hello-world" },
|
5
|
+
{ blog_slug: "foo", title: "Lorem", slug: "lorem" },
|
6
|
+
{ blog_slug: "bar", title: "Example!", slug: "example" }]
|
7
|
+
|
8
|
+
attr_accessor :blog_slug, :title, :slug
|
9
|
+
|
10
|
+
def self.find_by_blog_slug(slug)
|
11
|
+
ARTICLES.select { |article| article[:blog_slug] == slug }.map do |article|
|
12
|
+
Article.new(article)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.find_by_slug(slug)
|
17
|
+
Article.new(ARTICLES.find { |article| article[:slug] == slug })
|
18
|
+
end
|
19
|
+
|
20
|
+
def initialize(params)
|
21
|
+
self.blog_slug = params[:blog_slug]
|
22
|
+
self.title = params[:title]
|
23
|
+
self.slug = params[:slug]
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_param
|
27
|
+
self.slug
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Blog
|
2
|
+
extend ActiveModel::Naming
|
3
|
+
|
4
|
+
BLOGS = [{ name: "My Foo Blog", slug: "foo", },
|
5
|
+
{ name: "My Awesome Blog", slug: "bar", }]
|
6
|
+
|
7
|
+
attr_accessor :name, :slug
|
8
|
+
|
9
|
+
def self.all
|
10
|
+
BLOGS.map do |blog|
|
11
|
+
Blog.new(blog)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.find_by_slug(slug)
|
16
|
+
Blog.new(BLOGS.find { |blog| blog[:slug] == slug })
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(params)
|
20
|
+
self.name = params[:name]
|
21
|
+
self.slug = params[:slug]
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_param
|
25
|
+
self.slug
|
26
|
+
end
|
27
|
+
|
28
|
+
def articles
|
29
|
+
Article.find_by_blog_slug(self.slug)
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Example</title>
|
5
|
+
</head>
|
6
|
+
<body>
|
7
|
+
|
8
|
+
<h1><%= link_to "Example", root_path %></h1>
|
9
|
+
|
10
|
+
<hr>
|
11
|
+
|
12
|
+
<%= yield %>
|
13
|
+
|
14
|
+
<hr>
|
15
|
+
|
16
|
+
<em>Note, you may have to use <%= link_to "lvh.me", root_url(domain: "lvh.me") %> for this example to work.</em>
|
17
|
+
|
18
|
+
<pre>
|
19
|
+
params: <%= params.inspect %>
|
20
|
+
path: <%= request.fullpath %>
|
21
|
+
</pre>
|
22
|
+
|
23
|
+
</body>
|
24
|
+
</html>
|
data/example/bin/bundle
ADDED
data/example/bin/rails
ADDED
data/example/config.ru
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require File.expand_path('../boot', __FILE__)
|
2
|
+
|
3
|
+
# Pick the frameworks you want:
|
4
|
+
require "active_model/railtie"
|
5
|
+
# require "active_record/railtie"
|
6
|
+
require "action_controller/railtie"
|
7
|
+
require "action_mailer/railtie"
|
8
|
+
require "action_view/railtie"
|
9
|
+
# require "sprockets/railtie"
|
10
|
+
# require "rails/test_unit/railtie"
|
11
|
+
|
12
|
+
# Require the gems listed in Gemfile, including any gems
|
13
|
+
# you've limited to :test, :development, or :production.
|
14
|
+
Bundler.require(*Rails.groups)
|
15
|
+
|
16
|
+
module Example
|
17
|
+
class Application < Rails::Application
|
18
|
+
# Settings in config/environments/* take precedence over those specified here.
|
19
|
+
# Application configuration should go into files in config/initializers
|
20
|
+
# -- all .rb files in that directory are automatically loaded.
|
21
|
+
|
22
|
+
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
|
23
|
+
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
|
24
|
+
# config.time_zone = 'Central Time (US & Canada)'
|
25
|
+
|
26
|
+
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
27
|
+
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
28
|
+
# config.i18n.default_locale = :de
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
Rails.application.configure do
|
2
|
+
# Settings specified here will take precedence over those in config/application.rb.
|
3
|
+
|
4
|
+
# In the development environment your application's code is reloaded on
|
5
|
+
# every request. This slows down response time but is perfect for development
|
6
|
+
# since you don't have to restart the web server when you make code changes.
|
7
|
+
config.cache_classes = false
|
8
|
+
|
9
|
+
# Do not eager load code on boot.
|
10
|
+
config.eager_load = false
|
11
|
+
|
12
|
+
# Show full error reports and disable caching.
|
13
|
+
config.consider_all_requests_local = true
|
14
|
+
config.action_controller.perform_caching = false
|
15
|
+
|
16
|
+
# Don't care if the mailer can't send.
|
17
|
+
config.action_mailer.raise_delivery_errors = false
|
18
|
+
|
19
|
+
# Print deprecation notices to the Rails logger.
|
20
|
+
config.active_support.deprecation = :log
|
21
|
+
|
22
|
+
|
23
|
+
# Raises error for missing translations
|
24
|
+
# config.action_view.raise_on_missing_translations = true
|
25
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# This file contains settings for ActionController::ParamsWrapper which
|
4
|
+
# is enabled by default.
|
5
|
+
|
6
|
+
# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
|
7
|
+
ActiveSupport.on_load(:action_controller) do
|
8
|
+
wrap_parameters format: [:json] if respond_to?(:wrap_parameters)
|
9
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Files in the config/locales directory are used for internationalization
|
2
|
+
# and are automatically loaded by Rails. If you want to use locales other
|
3
|
+
# than English, add the necessary files in this directory.
|
4
|
+
#
|
5
|
+
# To use the locales, use `I18n.t`:
|
6
|
+
#
|
7
|
+
# I18n.t 'hello'
|
8
|
+
#
|
9
|
+
# In views, this is aliased to just `t`:
|
10
|
+
#
|
11
|
+
# <%= t('hello') %>
|
12
|
+
#
|
13
|
+
# To use a different locale, set it with `I18n.locale`:
|
14
|
+
#
|
15
|
+
# I18n.locale = :es
|
16
|
+
#
|
17
|
+
# This would use the information in config/locales/es.yml.
|
18
|
+
#
|
19
|
+
# To learn more, please read the Rails Internationalization guide
|
20
|
+
# available at http://guides.rubyonrails.org/i18n.html.
|
21
|
+
|
22
|
+
en:
|
23
|
+
hello: "Hello world"
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Be sure to restart your server when you modify this file.
|
2
|
+
|
3
|
+
# Your secret key is used for verifying the integrity of signed cookies.
|
4
|
+
# If you change this key, all old signed cookies will become invalid!
|
5
|
+
|
6
|
+
# Make sure the secret is at least 30 characters and all random,
|
7
|
+
# no regular words or you'll be exposed to dictionary attacks.
|
8
|
+
# You can use `rake secret` to generate a secure secret key.
|
9
|
+
|
10
|
+
# Make sure the secrets in this file are kept private
|
11
|
+
# if you're sharing your code publicly.
|
12
|
+
|
13
|
+
development:
|
14
|
+
secret_key_base: 6dbe86bbdd7449e38765d06459d0adbf49123bafe568f550401e7e02a2103da7fbeb783c34aed3c7b6e4449da030f92d89b841fa4abfc66ee6206a55a5a1f100
|
15
|
+
|
16
|
+
test:
|
17
|
+
secret_key_base: 2c60b440788ecccfa6da4ff5378cd41dd541cc202513a37f145a857b8b79287c7ea56211b3748fae6c836f2a3c92ad7c738654dcffa1c82db94d27bdb05c9e4d
|
18
|
+
|
19
|
+
# Do not keep production secrets in the repository,
|
20
|
+
# instead read values from the environment.
|
21
|
+
production:
|
22
|
+
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
|
data/lib/subdomainify.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Subdomainify
|
2
|
+
|
3
|
+
# Private: Middleware for rewriting PATH_INFO from subdomain.
|
4
|
+
#
|
5
|
+
# This is where URL rewriting magic takes place. It works by scanning
|
6
|
+
# routes for anything with :subdomainify option and take the route with
|
7
|
+
# highest precedence value (the "topmost" route) and use that as a
|
8
|
+
# base path.
|
9
|
+
#
|
10
|
+
# For example, if we have these lines in routes:
|
11
|
+
#
|
12
|
+
# resources :blogs, :subdomainify => true do
|
13
|
+
# resources :articles
|
14
|
+
# resources :comments
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# When user visited this URL:
|
18
|
+
#
|
19
|
+
# http://foo.example.com/articles/hello-world
|
20
|
+
#
|
21
|
+
# This middleware will rewrite `PATH_INFO` into:
|
22
|
+
#
|
23
|
+
# /blogs/foo/articles/hello-world/
|
24
|
+
#
|
25
|
+
class Middleware
|
26
|
+
|
27
|
+
# Private: Initialize Rack Middleware.
|
28
|
+
def initialize(app)
|
29
|
+
@app = app
|
30
|
+
end
|
31
|
+
|
32
|
+
# Private: Rewrite PATH_INFO if appropriate and calls Rack application.
|
33
|
+
def call(env)
|
34
|
+
request = ActionDispatch::Request.new(env)
|
35
|
+
routes = env['action_dispatch.routes']
|
36
|
+
|
37
|
+
if request.subdomain.present? && request.subdomain != 'www'
|
38
|
+
_route = routes.routes.select { |r| r.defaults[:subdomainify] }.last
|
39
|
+
if !request.path_info.start_with?('/assets/') && _route.present?
|
40
|
+
env['PATH_INFO'] = [
|
41
|
+
_route.format(id: request.subdomain),
|
42
|
+
request.path_info,
|
43
|
+
].select { |p| p.present? && p != '/' }.join('/').gsub(%r{//}, '/')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
@app.call(env)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'rails'
|
2
|
+
|
3
|
+
module Subdomainify
|
4
|
+
|
5
|
+
# Private: Provide an implement for Rails' hook point.
|
6
|
+
class Railtie < Rails::Railtie
|
7
|
+
railtie_name :subdomainify
|
8
|
+
|
9
|
+
initializer 'subdomainify.middleware' do |app|
|
10
|
+
app.middleware.use 'Subdomainify::Middleware'
|
11
|
+
end
|
12
|
+
|
13
|
+
initializer 'subdomainify.url_for' do |app|
|
14
|
+
routeset = ActionDispatch::Routing::RouteSet
|
15
|
+
routeset.send :include, Subdomainify::RouteSet
|
16
|
+
routeset.send :alias_method_chain, :url_for, :subdomain
|
17
|
+
|
18
|
+
# In Rails 4, Rails won't be using RouteSet#url_for in situation where
|
19
|
+
# nothing else but :controller and :action is present in route. This
|
20
|
+
# "optimized route" behavior breaks our url_for overrides.
|
21
|
+
#
|
22
|
+
# See also: https://github.com/rails/rails/issues/12420
|
23
|
+
# Also related: https://github.com/svenfuchs/routing-filter/issues/47
|
24
|
+
routeset::NamedRouteCollection::UrlHelper.class_eval do
|
25
|
+
def self.optimize_helper?(route)
|
26
|
+
false
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Subdomainify
|
2
|
+
|
3
|
+
# Private: Module for rewriting plain path into subdomain path.
|
4
|
+
module RouteSet
|
5
|
+
|
6
|
+
# Public: Rewrite normal route into route with subdomain if user links
|
7
|
+
# to or from subdomain enabled routes. In such situation, :only_path
|
8
|
+
# option will be ignored.
|
9
|
+
def url_for_with_subdomain(options)
|
10
|
+
options = default_url_options.merge(options || {})
|
11
|
+
|
12
|
+
if needs_subdomain?(options)
|
13
|
+
options[:only_path] = false
|
14
|
+
|
15
|
+
# Use route with highest precedence value (i.e. shortest route).
|
16
|
+
# TODO: Better ways to detect part name for nested resource?
|
17
|
+
subroute = @set.select { |route| route.defaults[:subdomainify] }.last
|
18
|
+
name = subroute.defaults[:controller].split('/').last.to_s.singularize
|
19
|
+
name = subroute.name if subroute.name.present?
|
20
|
+
subdomain_id = options[:"#{name}_id"] || options[:id]
|
21
|
+
|
22
|
+
# On realm transfer, when user links from subdomain route to
|
23
|
+
# bare route (i.e. :subdomainify is false) then we don't really
|
24
|
+
# need subdomain to be present even if subdomain id is present.
|
25
|
+
if options[:subdomainify] && subdomain_id
|
26
|
+
options[:subdomain] = subdomain_id.to_param
|
27
|
+
else
|
28
|
+
default_options = ActionController::Base.default_url_options
|
29
|
+
options[:subdomain] = default_options[:subdomain]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Turn /blog/foo/articles/ to just /articles/ using subroute prefix.
|
33
|
+
prefix = subroute.format(id: options[:subdomain])
|
34
|
+
url = URI.parse(url_for_without_subdomain(options))
|
35
|
+
url.path.gsub!(/^#{prefix}\/?/, '/')
|
36
|
+
url.to_s
|
37
|
+
else
|
38
|
+
url_for_without_subdomain(options)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
protected
|
43
|
+
|
44
|
+
# Private: Returns true if subdomain should be generated, such as when
|
45
|
+
# linking to subdomain path or when transferring realm (e.g. linking
|
46
|
+
# from subdomain path to non-subdomain path or vice versa.)
|
47
|
+
def needs_subdomain?(options)
|
48
|
+
options[:subdomainify].present? || # Presence.
|
49
|
+
get_realm(options) != get_realm(options[:_recall]) # Realm transfer.
|
50
|
+
end
|
51
|
+
|
52
|
+
# Private: Returns the realm of provided path options matched by
|
53
|
+
# controller and action name. Realm could be either :subdomain or :bare.
|
54
|
+
def get_realm(options)
|
55
|
+
return :bare if options.blank?
|
56
|
+
|
57
|
+
route = @set.select do |route|
|
58
|
+
route.defaults[:controller] == options[:controller] &&
|
59
|
+
route.defaults[:action] == options[:action]
|
60
|
+
end.last
|
61
|
+
|
62
|
+
if (route.try(:defaults) || {})[:subdomainify].present?
|
63
|
+
:subdomain
|
64
|
+
else
|
65
|
+
:bare
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
Bundler.require
|
4
|
+
|
5
|
+
require 'action_controller'
|
6
|
+
require 'subdomainify'
|
7
|
+
|
8
|
+
module MockApp
|
9
|
+
class Application < Rails::Application
|
10
|
+
config.eager_load = false
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
MockApp::Application.initialize!
|
15
|
+
|
16
|
+
$:.unshift File.expand_path('../support', __FILE__)
|
17
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
|
18
|
+
|
19
|
+
RSpec.configure do |config|
|
20
|
+
config.order = 'random'
|
21
|
+
end
|
@@ -0,0 +1,224 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Subdomainify do
|
4
|
+
let(:set) { ActionDispatch::Routing::RouteSet.new }
|
5
|
+
|
6
|
+
before do
|
7
|
+
set.draw do
|
8
|
+
resources :baz
|
9
|
+
resources :hoge
|
10
|
+
resources :foo, subdomainify: true do
|
11
|
+
resources :bar
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe Subdomainify::Middleware do
|
17
|
+
|
18
|
+
class MockRackApp
|
19
|
+
def call(env)
|
20
|
+
env
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
let(:app) { Subdomainify::Middleware.new(MockRackApp.new) }
|
25
|
+
|
26
|
+
def make_response(path, host, opts={})
|
27
|
+
app.call(opts.merge('action_dispatch.routes' => set,
|
28
|
+
'PATH_INFO' => path,
|
29
|
+
'HTTP_HOST' => host))
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should rewrite subdomain route" do
|
33
|
+
response = make_response('/', 'bar.example.com')
|
34
|
+
expect(response['PATH_INFO']).to eq '/foo/bar'
|
35
|
+
expect(response['HTTP_HOST']).to eq 'bar.example.com'
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should rewrite subdomain route with path" do
|
39
|
+
response = make_response('/bar/1', 'bar.example.com')
|
40
|
+
expect(response['PATH_INFO']).to eq '/foo/bar/bar/1'
|
41
|
+
expect(response['HTTP_HOST']).to eq 'bar.example.com'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should bypass route without subdomain" do
|
45
|
+
response = make_response('/foo', 'example.com')
|
46
|
+
expect(response['PATH_INFO']).to eq '/foo'
|
47
|
+
expect(response['HTTP_HOST']).to eq 'example.com'
|
48
|
+
end
|
49
|
+
|
50
|
+
it "should bypass route with www subdomain" do
|
51
|
+
response = make_response('/foo', 'www.example.com')
|
52
|
+
expect(response['PATH_INFO']).to eq '/foo'
|
53
|
+
expect(response['HTTP_HOST']).to eq 'www.example.com'
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should bypass route if subdomain route is not present" do
|
57
|
+
set.clear!
|
58
|
+
response = make_response('/', 'bar.example.com')
|
59
|
+
expect(response['PATH_INFO']).to eq '/'
|
60
|
+
expect(response['HTTP_HOST']).to eq 'bar.example.com'
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should bypass assets route" do
|
64
|
+
response = make_response('/assets/foo/bar', 'bar.example.com')
|
65
|
+
expect(response['PATH_INFO']).to eq '/assets/foo/bar'
|
66
|
+
expect(response['HTTP_HOST']).to eq 'bar.example.com'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe Subdomainify::Railtie do
|
71
|
+
it "should inject subdomain route utilities to action dispatch" do
|
72
|
+
expect(set).to respond_to :url_for_with_subdomain
|
73
|
+
expect(set).to respond_to :url_for_without_subdomain
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should disable optimized url helper" do
|
77
|
+
collection = ActionDispatch::Routing::RouteSet::NamedRouteCollection
|
78
|
+
set.routes.each do |route|
|
79
|
+
expect(collection::UrlHelper.optimize_helper?(route)).to be_falsey
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe Subdomainify::RouteSet do
|
85
|
+
|
86
|
+
class RouteSetMock
|
87
|
+
include Subdomainify::RouteSet
|
88
|
+
|
89
|
+
def default_url_options
|
90
|
+
{}
|
91
|
+
end
|
92
|
+
|
93
|
+
def initialize(set)
|
94
|
+
@route = set
|
95
|
+
@set = set.routes
|
96
|
+
end
|
97
|
+
|
98
|
+
def url_for_without_subdomain(options)
|
99
|
+
options = { only_path: true }.merge(options)
|
100
|
+
@route.url_for_without_subdomain(options)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
subject { RouteSetMock.new(set) }
|
105
|
+
|
106
|
+
describe "#url_for_with_subdomain" do
|
107
|
+
it "should construct subdomain url for linking to subdomain route" do
|
108
|
+
expect(subject.url_for_with_subdomain({
|
109
|
+
controller: "foo",
|
110
|
+
action: "show",
|
111
|
+
id: "foobar",
|
112
|
+
subdomainify: true,
|
113
|
+
host: "example.com",
|
114
|
+
_recall: { controller: "baz", action: "show" },
|
115
|
+
})).to eq "http://foobar.example.com/"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should construct subdomain url for nested resource" do
|
119
|
+
expect(subject.url_for_with_subdomain({
|
120
|
+
controller: "bar",
|
121
|
+
action: "show",
|
122
|
+
foo_id: "foobar",
|
123
|
+
id: "1",
|
124
|
+
subdomainify: true,
|
125
|
+
host: "example.com",
|
126
|
+
_recall: { controller: "baz", action: "show" },
|
127
|
+
})).to eq "http://foobar.example.com/bar/1"
|
128
|
+
end
|
129
|
+
|
130
|
+
it "should construct subdomain url for linking from subdomain route" do
|
131
|
+
expect(subject.url_for_with_subdomain({
|
132
|
+
controller: "baz",
|
133
|
+
action: "show",
|
134
|
+
id: "1",
|
135
|
+
host: "example.com",
|
136
|
+
_recall: { controller: "foo", action: "show", id: "foobar" },
|
137
|
+
})).to eq "http://example.com/baz/1"
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should construct normal url when linking from and to normal route" do
|
141
|
+
expect(subject.url_for_with_subdomain({
|
142
|
+
controller: "hoge",
|
143
|
+
action: "show",
|
144
|
+
id: "1",
|
145
|
+
host: "example.com",
|
146
|
+
_recall: { controller: "baz", action: "show", id: "1" },
|
147
|
+
})).to eq "/hoge/1"
|
148
|
+
end
|
149
|
+
|
150
|
+
it "should delegate default subdomain from action controller" do
|
151
|
+
klass = Class.new(ActionController::Base)
|
152
|
+
klass.default_url_options = { subdomain: 'www' }
|
153
|
+
stub_const("ActionController::Base", klass)
|
154
|
+
|
155
|
+
expect(subject.url_for_with_subdomain({
|
156
|
+
controller: "baz",
|
157
|
+
action: "show",
|
158
|
+
id: "1",
|
159
|
+
host: "example.com",
|
160
|
+
_recall: { controller: "foo", action: "show", id: "foobar" },
|
161
|
+
})).to eq "http://www.example.com/baz/1"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
describe "#needs_subdomain?" do
|
166
|
+
it "should be true for subdomain route" do
|
167
|
+
expect(subject.send(:needs_subdomain?, {
|
168
|
+
controller: "bar",
|
169
|
+
action: "show",
|
170
|
+
subdomainify: true,
|
171
|
+
_recall: { controller: "foo", action: "show" },
|
172
|
+
})).to be_truthy
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should be true for linking from subdomain realm to bare realm" do
|
176
|
+
expect(subject.send(:needs_subdomain?, {
|
177
|
+
controller: "baz",
|
178
|
+
action: "show",
|
179
|
+
_recall: { controller: "foo", action: "show" },
|
180
|
+
})).to be_truthy
|
181
|
+
end
|
182
|
+
|
183
|
+
it "should be true for linking to subdomain realm from bare realm" do
|
184
|
+
expect(subject.send(:needs_subdomain?, {
|
185
|
+
controller: "foo",
|
186
|
+
action: "show",
|
187
|
+
_recall: { controller: "baz", action: "show" },
|
188
|
+
})).to be_truthy
|
189
|
+
end
|
190
|
+
|
191
|
+
it "should be false for linking within same bare realm" do
|
192
|
+
expect(subject.send(:needs_subdomain?, {
|
193
|
+
controller: "baz",
|
194
|
+
action: "show",
|
195
|
+
_recall: { controller: "hoge", action: "show" },
|
196
|
+
})).to be_falsey
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
describe "#get_realm" do
|
201
|
+
it "should return bare for normal route" do
|
202
|
+
realm = subject.send(:get_realm, { controller: "baz", action: "show" })
|
203
|
+
expect(realm).to eq :bare
|
204
|
+
end
|
205
|
+
|
206
|
+
it "should return bare if route options is not given" do
|
207
|
+
realm = subject.send(:get_realm, {})
|
208
|
+
expect(realm).to eq :bare
|
209
|
+
end
|
210
|
+
|
211
|
+
it "should return subdomain for subdomain route" do
|
212
|
+
realm = subject.send(:get_realm, { controller: "foo", action: "show" })
|
213
|
+
expect(realm).to eq :subdomain
|
214
|
+
end
|
215
|
+
|
216
|
+
it "should return subdomain for subdomain subroute" do
|
217
|
+
realm = subject.send(:get_realm, { controller: "bar", action: "show" })
|
218
|
+
expect(realm).to eq :subdomain
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'subdomainify/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'subdomainify'
|
8
|
+
spec.version = Subdomainify::VERSION
|
9
|
+
spec.authors = ['Kridsada Thanabulpong']
|
10
|
+
spec.email = ['sirn@oozou.com']
|
11
|
+
spec.summary = %q{A subdomain rewriting middleware for your Rails 4 app}
|
12
|
+
spec.homepage = 'https://github.com/oozou/subdomainify'
|
13
|
+
spec.license = 'BSD'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ['lib']
|
19
|
+
|
20
|
+
spec.add_runtime_dependency 'railties', '>= 4.0.0'
|
21
|
+
spec.add_runtime_dependency 'actionpack', '>= 4.0.0'
|
22
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
23
|
+
spec.add_development_dependency 'rake'
|
24
|
+
spec.add_development_dependency 'rspec'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,172 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: subdomainify
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kridsada Thanabulpong
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-06-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: railties
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 4.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 4.0.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: actionpack
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 4.0.0
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 4.0.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.6'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.6'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: rake
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
description:
|
95
|
+
email:
|
96
|
+
- sirn@oozou.com
|
97
|
+
executables: []
|
98
|
+
extensions: []
|
99
|
+
extra_rdoc_files: []
|
100
|
+
files:
|
101
|
+
- .gitignore
|
102
|
+
- .rspec
|
103
|
+
- .travis.yml
|
104
|
+
- Gemfile
|
105
|
+
- LICENSE.txt
|
106
|
+
- README.md
|
107
|
+
- Rakefile
|
108
|
+
- example/.gitignore
|
109
|
+
- example/Gemfile
|
110
|
+
- example/README.md
|
111
|
+
- example/Rakefile
|
112
|
+
- example/app/controllers/application_controller.rb
|
113
|
+
- example/app/controllers/articles_controller.rb
|
114
|
+
- example/app/controllers/blogs_controller.rb
|
115
|
+
- example/app/helpers/application_helper.rb
|
116
|
+
- example/app/models/article.rb
|
117
|
+
- example/app/models/blog.rb
|
118
|
+
- example/app/views/articles/show.html.erb
|
119
|
+
- example/app/views/blogs/index.html.erb
|
120
|
+
- example/app/views/blogs/show.html.erb
|
121
|
+
- example/app/views/layouts/application.html.erb
|
122
|
+
- example/bin/bundle
|
123
|
+
- example/bin/rails
|
124
|
+
- example/config.ru
|
125
|
+
- example/config/application.rb
|
126
|
+
- example/config/boot.rb
|
127
|
+
- example/config/environment.rb
|
128
|
+
- example/config/environments/development.rb
|
129
|
+
- example/config/initializers/cookies_serializer.rb
|
130
|
+
- example/config/initializers/mime_types.rb
|
131
|
+
- example/config/initializers/session_store.rb
|
132
|
+
- example/config/initializers/wrap_parameters.rb
|
133
|
+
- example/config/locales/en.yml
|
134
|
+
- example/config/routes.rb
|
135
|
+
- example/config/secrets.yml
|
136
|
+
- lib/subdomainify.rb
|
137
|
+
- lib/subdomainify/middleware.rb
|
138
|
+
- lib/subdomainify/railtie.rb
|
139
|
+
- lib/subdomainify/route_set.rb
|
140
|
+
- lib/subdomainify/version.rb
|
141
|
+
- spec/spec_helper.rb
|
142
|
+
- spec/subdomainify_spec.rb
|
143
|
+
- subdomainify.gemspec
|
144
|
+
homepage: https://github.com/oozou/subdomainify
|
145
|
+
licenses:
|
146
|
+
- BSD
|
147
|
+
post_install_message:
|
148
|
+
rdoc_options: []
|
149
|
+
require_paths:
|
150
|
+
- lib
|
151
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
152
|
+
none: false
|
153
|
+
requirements:
|
154
|
+
- - ! '>='
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
158
|
+
none: false
|
159
|
+
requirements:
|
160
|
+
- - ! '>='
|
161
|
+
- !ruby/object:Gem::Version
|
162
|
+
version: '0'
|
163
|
+
requirements: []
|
164
|
+
rubyforge_project:
|
165
|
+
rubygems_version: 1.8.23
|
166
|
+
signing_key:
|
167
|
+
specification_version: 3
|
168
|
+
summary: A subdomain rewriting middleware for your Rails 4 app
|
169
|
+
test_files:
|
170
|
+
- spec/spec_helper.rb
|
171
|
+
- spec/subdomainify_spec.rb
|
172
|
+
has_rdoc:
|