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.
Files changed (44) hide show
  1. data/.gitignore +23 -0
  2. data/.rspec +1 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +24 -0
  6. data/README.md +77 -0
  7. data/Rakefile +9 -0
  8. data/example/.gitignore +16 -0
  9. data/example/Gemfile +3 -0
  10. data/example/README.md +9 -0
  11. data/example/Rakefile +6 -0
  12. data/example/app/controllers/application_controller.rb +5 -0
  13. data/example/app/controllers/articles_controller.rb +8 -0
  14. data/example/app/controllers/blogs_controller.rb +12 -0
  15. data/example/app/helpers/application_helper.rb +2 -0
  16. data/example/app/models/article.rb +29 -0
  17. data/example/app/models/blog.rb +31 -0
  18. data/example/app/views/articles/show.html.erb +5 -0
  19. data/example/app/views/blogs/index.html.erb +5 -0
  20. data/example/app/views/blogs/show.html.erb +7 -0
  21. data/example/app/views/layouts/application.html.erb +24 -0
  22. data/example/bin/bundle +3 -0
  23. data/example/bin/rails +4 -0
  24. data/example/config.ru +4 -0
  25. data/example/config/application.rb +30 -0
  26. data/example/config/boot.rb +4 -0
  27. data/example/config/environment.rb +5 -0
  28. data/example/config/environments/development.rb +25 -0
  29. data/example/config/initializers/cookies_serializer.rb +3 -0
  30. data/example/config/initializers/mime_types.rb +4 -0
  31. data/example/config/initializers/session_store.rb +3 -0
  32. data/example/config/initializers/wrap_parameters.rb +9 -0
  33. data/example/config/locales/en.yml +23 -0
  34. data/example/config/routes.rb +7 -0
  35. data/example/config/secrets.yml +22 -0
  36. data/lib/subdomainify.rb +3 -0
  37. data/lib/subdomainify/middleware.rb +52 -0
  38. data/lib/subdomainify/railtie.rb +32 -0
  39. data/lib/subdomainify/route_set.rb +71 -0
  40. data/lib/subdomainify/version.rb +3 -0
  41. data/spec/spec_helper.rb +21 -0
  42. data/spec/subdomainify_spec.rb +224 -0
  43. data/subdomainify.gemspec +25 -0
  44. metadata +172 -0
@@ -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
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ script: bundle exec rake
3
+ rvm:
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1.1
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in subdomainify.gemspec
4
+ gemspec
@@ -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.
@@ -0,0 +1,77 @@
1
+ # Subdomainify [![Build Status](https://travis-ci.org/oozou/subdomainify.svg?branch=master)](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.
@@ -0,0 +1,9 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc 'Run RSpec'
5
+ RSpec::Core::RakeTask.new do |t|
6
+ t.verbose = false
7
+ end
8
+
9
+ task default: :spec
@@ -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
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+ gem 'rails', '4.1.1'
3
+ gem 'subdomainify', path: '..'
@@ -0,0 +1,9 @@
1
+ # Example
2
+
3
+ Demonstrating basic usage of the *subdomainify* gem.
4
+
5
+ ## Usage
6
+
7
+ 1. Clone the subdomainify repo.
8
+ 2. `cd example/ && bundle install && bin/rails s`
9
+ 3. Visit [http://lvh.me:3000/](http://lvh.me:3000/).
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,5 @@
1
+ class ApplicationController < ActionController::Base
2
+ # Prevent CSRF attacks by raising an exception.
3
+ # For APIs, you may want to use :null_session instead.
4
+ protect_from_forgery with: :exception
5
+ end
@@ -0,0 +1,8 @@
1
+ class ArticlesController < ApplicationController
2
+
3
+ def show
4
+ @article = Article.find_by_slug(params[:id])
5
+ @blog = Blog.find_by_slug(params[:blog_id])
6
+ end
7
+
8
+ end
@@ -0,0 +1,12 @@
1
+ class BlogsController < ApplicationController
2
+
3
+ def index
4
+ @blogs = Blog.all
5
+ end
6
+
7
+ def show
8
+ @blog = Blog.find_by_slug(params[:id])
9
+ @articles = @blog.articles
10
+ end
11
+
12
+ end
@@ -0,0 +1,2 @@
1
+ module ApplicationHelper
2
+ end
@@ -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,5 @@
1
+ <h2><%= link_to @blog.name, blog_path(@blog) %></h2>
2
+
3
+ <h3><%= @article.title %></h3>
4
+
5
+ <p>Unfortunately, we don't have any real content here. :'(</p>
@@ -0,0 +1,5 @@
1
+ <ul>
2
+ <% @blogs.each do |blog| %>
3
+ <li><%= link_to blog.name, blog_path(blog) %></li>
4
+ <% end %>
5
+ </ul>
@@ -0,0 +1,7 @@
1
+ <h2><%= link_to @blog.name, blog_path(@blog) %></h2>
2
+
3
+ <ul>
4
+ <% @articles.each do |article| %>
5
+ <li><%= link_to article.title, blog_article_path(@blog, article) %></li>
6
+ <% end %>
7
+ </ul>
@@ -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>
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env ruby
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ load Gem.bin_path('bundler', 'bundle')
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ APP_PATH = File.expand_path('../../config/application', __FILE__)
3
+ require_relative '../config/boot'
4
+ require 'rails/commands'
@@ -0,0 +1,4 @@
1
+ # This file is used by Rack-based servers to start the application.
2
+
3
+ require ::File.expand_path('../config/environment', __FILE__)
4
+ run Rails.application
@@ -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,4 @@
1
+ # Set up gems listed in the Gemfile.
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+
4
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
@@ -0,0 +1,5 @@
1
+ # Load the Rails application.
2
+ require File.expand_path('../application', __FILE__)
3
+
4
+ # Initialize the Rails application.
5
+ Rails.application.initialize!
@@ -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,3 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ Rails.application.config.action_dispatch.cookies_serializer = :json
@@ -0,0 +1,4 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ # Add new mime types for use in respond_to blocks:
4
+ # Mime::Type.register "text/richtext", :rtf
@@ -0,0 +1,3 @@
1
+ # Be sure to restart your server when you modify this file.
2
+
3
+ Rails.application.config.session_store :cookie_store, key: '_example_session'
@@ -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,7 @@
1
+ Rails.application.routes.draw do
2
+ resources :blogs, only: [:show], subdomainify: true do
3
+ resources :articles, only: [:show]
4
+ end
5
+
6
+ root to: "blogs#index"
7
+ end
@@ -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"] %>
@@ -0,0 +1,3 @@
1
+ require 'subdomainify/middleware'
2
+ require 'subdomainify/railtie'
3
+ require 'subdomainify/route_set'
@@ -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
@@ -0,0 +1,3 @@
1
+ module Subdomainify
2
+ VERSION = "0.2.0"
3
+ end
@@ -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: