rack-multitenant 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8668da451467bac3c7fd343928512230aa796c3a
4
+ data.tar.gz: 93aa7f15e84a84509103edb89555bae8a877eb64
5
+ SHA512:
6
+ metadata.gz: 996ae7d0d9c6aaa00cbe5044b5955005e8c6f8a3a5f4c62af49573a86b04d6c5dbf537d48bcef60eacd39d5a4649bd7d5998db6e9f5ccf1eab6b96bffd092f05
7
+ data.tar.gz: bf5fe47655d2546955fa1c0136898146545e8b820857972aa4cf65b36d4f28ccb96fc67382f6bcaebd916c7e0bc8760a6d7d7adffa333fa69327f479a431f426
data/.gitignore ADDED
@@ -0,0 +1,19 @@
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
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.swp
19
+ .DS_Store
data/Gemfile ADDED
@@ -0,0 +1,19 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in rack-multitenant.gemspec
4
+ gemspec
5
+
6
+ group :development, :test do
7
+ gem "rake", "~> 10.0.3"
8
+ gem "pry", "~> 0.9.11"
9
+
10
+ gem "activerecord"
11
+ gem "sinatra"
12
+ gem "haml"
13
+ gem "sqlite3"
14
+ end
15
+
16
+ group :test do
17
+ gem "rdoctest", "~> 0.0.2"
18
+ gem "rack-test", "~> 0.6.2"
19
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Andrew O'Brien
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # Rack::Multitenant
2
+
3
+ ## What?
4
+
5
+ Many web-applications want to provide a sandboxed instance for each customer, but know that this would be impractical and inefficient. To present the illusion of this, they often provide a subdomain, url prefix, different domain entirely, or something more exotic. Many of these can hamper development or make it hard to reproduce production problems.
6
+
7
+ Rack::Multitenant provides an interface to allow the application to decide which customer (or _tenant_) the request the for. It will assign the tenant object to the request's environment hash, meaning that all Rack compliant applications will be able to use it.
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'rack-multitenant'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install rack-multitenant
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rdoctest/task'
3
+ require 'rake/testtask'
4
+
5
+ namespace :test do
6
+ Rdoctest::Task.new :unit do |t|
7
+ #t.libs << 'lib' # The 'lib' directory is loaded by default,
8
+ t.pattern = 'lib/rack/multitenant/**/*.rb'
9
+ end
10
+ Rake::TestTask.new :integration do |t|
11
+ t.libs << "lib"
12
+ t.test_files = FileList['examples/*_spec.rb']
13
+ #t.verbose = true
14
+ end
15
+ end
16
+
17
+ desc "Run unit and integration tests"
18
+ task :test => %w{test:unit test:integration}
@@ -0,0 +1,9 @@
1
+ require "rack/test"
2
+ require "minitest/spec"
3
+ require "minitest/autorun"
4
+ require "pry"
5
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
6
+
7
+ class MiniTest::Unit::TestCase
8
+ include Rack::Test::Methods
9
+ end
@@ -0,0 +1,38 @@
1
+ require "rack"
2
+ require "rack/builder"
3
+ require "rack/auth/basic"
4
+ require "rack/multitenant"
5
+
6
+ ACCOUNTS = {
7
+ andrew: {
8
+ foo: :user
9
+ },
10
+ vince: {
11
+ foo: :admin,
12
+ bar: :user
13
+ },
14
+ lou: {}
15
+ }
16
+
17
+ Get_identity = Rack::Builder.new do
18
+ use Rack::MultiTenant::GetCurrentTenant do |request|
19
+ # TODO: subdomain strategy for getting current tenant.
20
+ request.url =~ /foo/ ? :foo : :bar
21
+ end
22
+ # TODO: replace with Warden
23
+ use Rack::Auth::Basic do |username, password|
24
+ username if password == "password" && accounts = ACCOUNTS[username.to_sym]
25
+ end
26
+ use Rack::MultiTenant::GetIdentity do |username, tenant|
27
+ # TODO: DB lookup
28
+ if identities = ACCOUNTS[username.to_sym] and identity = identities[tenant]
29
+ ->(app) { app.pass identity } # Or just return identity.
30
+ else
31
+ ->(app) { app.forbid! }
32
+ # -> (app) { app.create_with SignUpWithExisting, identities } or something like this...
33
+ end
34
+ end
35
+ run lambda {|env|
36
+ [200, {}, "Your identity: #{env['rack.multitenant.identity']}"]
37
+ }
38
+ end
@@ -0,0 +1,34 @@
1
+ require_relative "example_helper"
2
+
3
+ describe "Simple example" do
4
+ def app
5
+ @app = Rack::Builder.parse_file('./examples/get_identity.rb').first
6
+ end
7
+
8
+ it "gets the identity for an authorized user of the given tenant" do
9
+ authorize "andrew", "password"
10
+ get "/foo"
11
+ assert last_response.ok?
12
+ assert_equal("Your identity: user", last_response.body)
13
+ end
14
+
15
+ it "does not allow unauthenticated users" do
16
+ get "/foo"
17
+ assert_equal 401, last_response.status
18
+
19
+ authorize "andrew", "fail"
20
+ get "/foo"
21
+ assert_equal 401, last_response.status
22
+ end
23
+
24
+ it "does not allow in users without an identity for the tenant" do
25
+ authorize "lou", "password"
26
+ get "/foo"
27
+ assert_equal 403, last_response.status
28
+
29
+ authorize "andrew", "password"
30
+ get "/bar"
31
+ assert_equal 403, last_response.status
32
+ end
33
+ end
34
+
@@ -0,0 +1,31 @@
1
+ require "rack"
2
+ require "rack/builder"
3
+ require "rack/auth/basic"
4
+ require "rack/multitenant"
5
+
6
+ Getter_strategies = Rack::Builder.new do
7
+ # Builds a piece of middleware that:
8
+ # 1. First tries to get the tenant from a subdomain
9
+ # 2. If the RACK_ENV is development or test, it uses a hash to
10
+ # look up by port.
11
+ # 3. Last, if a tenant still hasn't been found, it defaults to a
12
+ # hard-coded value.
13
+ #
14
+ # The return value of the block will be saved as the value of
15
+ # <tt>env['rack.multitenant.current_tenant']</tt>.
16
+ use *Rack::MultiTenant::GetCurrentTenant.build {|builder|
17
+ builder.use :Subdomain, "example.com"
18
+ #builder.use :PathPrefix Not implemented. Anyone need this?
19
+ if %w{development test}.include?(ENV["RACK_ENV"])
20
+ builder.use :Port, {
21
+ 3000 => :foo,
22
+ 3001 => :bar
23
+ }
24
+ #builder.use :EnvVariable
25
+ end
26
+ builder.use :Default, :bar
27
+ }
28
+ run lambda {|env|
29
+ [200, {}, "Current tenant: #{env['rack.multitenant.current_tenant']}"]
30
+ }
31
+ end
@@ -0,0 +1,22 @@
1
+ require_relative "example_helper"
2
+
3
+ describe "Getter strategies example" do
4
+ def app
5
+ @app = Rack::Builder.parse_file('./examples/getter_strategies.rb').first
6
+ end
7
+
8
+ it "gets default tenant" do
9
+ get "/"
10
+
11
+ assert last_response.ok?
12
+ assert_match /bar/, last_response.body
13
+ end
14
+
15
+ it "gets tenant by subdomain" do
16
+ get "/", {}, "HTTP_HOST" => "foo.example.com"
17
+
18
+ assert last_response.ok?
19
+ assert_match /foo/, last_response.body
20
+ end
21
+ end
22
+
@@ -0,0 +1,169 @@
1
+ # Run with: bundle exec rackup examples/multitenant_sinatra.rb
2
+
3
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
4
+ require "rubygems"
5
+ require "active_record"
6
+ require "sqlite3"
7
+ require "sinatra"
8
+ require "haml"
9
+ require "rack/builder"
10
+ require "rack/auth/basic"
11
+ require "rack/multitenant"
12
+ require "pry"
13
+
14
+ def connect_to_db!
15
+ ActiveRecord::Base.establish_connection(
16
+ adapter: "sqlite3",
17
+ database: File.dirname(__FILE__) + "/../tmp/multitenant_sinatra.sqlite3"
18
+ )
19
+ end
20
+ connect_to_db!
21
+
22
+ ActiveRecord::Migration.create_table :items, force: true do |t|
23
+ t.text :text
24
+ t.integer :tenant_id
25
+ t.integer :created_by_id
26
+ t.integer :finished_by_id
27
+ t.timestamps
28
+ end
29
+
30
+ ActiveRecord::Migration.create_table :identities, force: true do |t|
31
+ t.integer :tenant_id
32
+ t.integer :account_id
33
+ end
34
+
35
+ ActiveRecord::Migration.create_table :accounts, force: true do |t|
36
+ t.string :username
37
+ end
38
+
39
+ ActiveRecord::Migration.create_table :tenants, force: true do |t|
40
+ t.string :key
41
+ end
42
+
43
+ class Item < ActiveRecord::Base
44
+ belongs_to :created_by, class_name: "Identity"
45
+ belongs_to :finished_by, class_name: "Identity"
46
+ belongs_to :tenant
47
+
48
+ validates :tenant, presence: true
49
+
50
+ def self.add(attrs, creator)
51
+ create(attrs.merge(created_by: creator))
52
+ end
53
+
54
+ def finish!(finisher)
55
+ update_attributes(finished_by: finisher)
56
+ end
57
+
58
+ def finished?
59
+ finished_by.present?
60
+ end
61
+ end
62
+
63
+ class Tenant < ActiveRecord::Base
64
+ has_many :todos, class_name: "Item"
65
+ end
66
+
67
+ class Identity < ActiveRecord::Base
68
+ belongs_to :account
69
+ belongs_to :tenant
70
+ end
71
+
72
+ class Account < ActiveRecord::Base
73
+ has_many :identities
74
+ end
75
+
76
+ foo_corp = Tenant.create!(key: "foo")
77
+ bar_inc = Tenant.create!(key: "bar")
78
+
79
+ andrew = Account.create!(username: "andrew")
80
+ vince = Account.create!(username: "vince")
81
+ lou = Account.create!(username: "lou")
82
+
83
+ andrew.identities.create!(tenant: foo_corp)
84
+ andrew.identities.create!(tenant: bar_inc)
85
+ vince.identities.create!(tenant: bar_inc)
86
+
87
+ class HostedTodos < Sinatra::Base
88
+ get "/" do
89
+ redirect "/items"
90
+ end
91
+
92
+ post "/items" do
93
+ tenant.todos.add(params[:item], current_user)
94
+ redirect "/items"
95
+ end
96
+
97
+ get "/items" do
98
+ @items = tenant.todos.all
99
+ haml :items
100
+ end
101
+
102
+ post "/items/:id/finish" do
103
+ tenant.todos.find(params[:id]).finish!(current_user)
104
+ redirect "/items"
105
+ end
106
+
107
+ private
108
+ def tenant
109
+ env["rack.multitenant.current_tenant"]
110
+ end
111
+
112
+ def current_user
113
+ env['rack.multitenant.identity']
114
+ end
115
+
116
+ enable :inline_templates
117
+ end
118
+
119
+ Multitenant_sinatra = Rack::Builder.new do
120
+ connect_to_db!
121
+ # Liking this *build method a little less... Probably want to reconsider before releasing.
122
+ # Just an aesthetic problem--not worth holding things up.
123
+ use *Rack::MultiTenant::GetCurrentTenant.build {|builder|
124
+ builder.use :Subdomain, "example.com" do |key|
125
+ Tenant.find_by_key(key)
126
+ end
127
+ builder.use :Default, bar_inc
128
+ }
129
+ use Rack::Auth::Basic do |username, password|
130
+ # In a real example, we could do a couple of things:
131
+ #
132
+ # * Multi-identity, single password: store the auth credentials on the Account
133
+ # * Multi-identity, multi-auth strategy: Move the credentials to the Identity.
134
+ # Scope your find to the current tenant.
135
+ password == "password" && account = Account.find_by_username(username.to_sym)
136
+ end
137
+ use Rack::MultiTenant::GetIdentity do |account_name, tenant|
138
+ # Rack::Basic only keeps the username. In a real app, we'd use something that only
139
+ # made us hit the Account table once per request (like Warden).
140
+ account = Account.find_by_username(account_name)
141
+ Identity.where(account_id: account.id, tenant_id: tenant.id).first
142
+ end
143
+ run HostedTodos
144
+ end
145
+
146
+ __END__
147
+
148
+ @@ layout
149
+ %html
150
+ = yield
151
+
152
+ @@ items
153
+ %form(action="/items" method="POST")
154
+ %p
155
+ %label(for="text") Text:
156
+ %input#text(type="text" name="item[text]")
157
+ %p
158
+ %input(type="submit" value="Create")
159
+
160
+ .items
161
+ - @items.each do |item|
162
+ %p
163
+ - if item.finished?
164
+ %del
165
+ = item.text
166
+ - else
167
+ = item.text
168
+ %form{:action => "/items/#{item.id}/finish", :method => "POST"}
169
+ %input(type="submit" value="Finish")
@@ -0,0 +1,3 @@
1
+ require "rack-multitenant/version"
2
+
3
+ require "rack/multitenant"
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ module Multitenant
3
+ VERSION = "1.0.0"
4
+ end
5
+ end
@@ -0,0 +1,13 @@
1
+ module Rack
2
+ module MultiTenant
3
+ autoload :GetCurrentTenant, "rack/multitenant/get_current_tenant"
4
+ autoload :GetIdentity, "rack/multitenant/get_identity"
5
+
6
+ module TenantStrategies
7
+ autoload :Subdomain, "rack/multitenant/tenant_strategies/subdomain"
8
+ autoload :Port, "rack/multitenant/tenant_strategies/port"
9
+ autoload :EnvVariable, "rack/multitenant/tenant_strategies/env_variable"
10
+ autoload :Default, "rack/multitenant/tenant_strategies/default"
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,98 @@
1
+ require_relative "../multitenant"
2
+ require "rack/request"
3
+
4
+ module Rack::MultiTenant
5
+ class GetCurrentTenant
6
+ # Convenience method for initializing the GetCurrentTenant middleware
7
+ # with a stack of strategies.
8
+ #
9
+ # >> GetCurrentTenant = Rack::MultiTenant::GetCurrentTenant
10
+ # A custom getter that will be instantiated once and called.
11
+ # >> class MyCustomGetter
12
+ # >> def initialize(host)
13
+ # >> @host = host
14
+ # >> end
15
+ # >> def call(req)
16
+ # >> req.host == @host ? :custom : nil
17
+ # >> end
18
+ # >> end
19
+ #
20
+ # >> # Stub the rest of the Rack stack to return the env value we're
21
+ # >> # interested in.
22
+ # >> next_app = lambda {|env| env["rack.multitenant.current_tenant"]}
23
+ #
24
+ # >> # Build the middleware and instantiate it.
25
+ # >> app = GetCurrentTenant.new(next_app, GetCurrentTenant.build do |get|
26
+ # >> get.use :Subdomain, "example.com", &:to_sym
27
+ # >> get.use MyCustomGetter, "custom"
28
+ # >> get.use lambda {|req| req.host == "lambda" ? :lambda : nil }
29
+ # >> get.use :Default, :default_tenant
30
+ # >> end[1])
31
+ #
32
+ # >> subdomain_req_env = {"HTTP_HOST" => "foo.example.com"}
33
+ # >> app.call(subdomain_req_env)
34
+ # => :foo
35
+ #
36
+ # >> custom_req_env = {"HTTP_HOST" => "custom"}
37
+ # >> app.call(custom_req_env)
38
+ # => :custom
39
+ #
40
+ # >> lambda_req_env = {"HTTP_HOST" => "lambda"}
41
+ # >> app.call(lambda_req_env)
42
+ # => :lambda
43
+ #
44
+ # >> default_req_env = {"HTTP_HOST" => "kwjibo"}
45
+ # >> app.call(default_req_env)
46
+ # => :default_tenant
47
+ def self.build
48
+ [self, (Builder.new.tap {|b| yield b}).to_proc]
49
+ end
50
+
51
+ def initialize(app, foo = nil, &getter)
52
+ @app, @getter = app, foo || getter
53
+ end
54
+
55
+ def call(env)
56
+ if tenant = @getter.call(Rack::Request.new(env))
57
+ env["rack.multitenant.current_tenant"] = tenant
58
+ end
59
+ @app.call(env)
60
+ end
61
+
62
+ class Builder
63
+ def initialize
64
+ @stack = []
65
+ end
66
+
67
+ def use(name, *args, &blk)
68
+ @stack << case strategy = resolve(name)
69
+ when Class
70
+ strategy.new(*args, &blk)
71
+ else
72
+ strategy
73
+ end
74
+ end
75
+ alias_method :with, :use
76
+
77
+ def to_proc
78
+ _stack = @stack.compact
79
+ lambda {|request|
80
+ tenant = nil
81
+ _stack.find {|strategy| tenant = strategy.call(request)}
82
+ tenant
83
+ }
84
+ end
85
+
86
+ private
87
+ def resolve(name)
88
+ case name
89
+ when Symbol
90
+ Rack::MultiTenant::TenantStrategies.const_get(name)
91
+ else
92
+ name
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
98
+
@@ -0,0 +1,45 @@
1
+ module Rack::MultiTenant
2
+ class GetIdentity
3
+ def initialize(app, user_key = "REMOTE_USER", &getter)
4
+ @app, @user_key, @getter = app, user_key, getter
5
+ end
6
+
7
+ def call(env)
8
+ user, tenant = env.values_at(@user_key, "rack.multitenant.current_tenant")
9
+ Next.new(@app, env).call(@getter.call(user, tenant))
10
+ end
11
+
12
+ class Next
13
+ def initialize(app, env)
14
+ @app, @env = app, env
15
+ end
16
+
17
+ def call(result)
18
+ case result
19
+ when Proc
20
+ result.call(self)
21
+ when nil
22
+ forbid!
23
+ when false
24
+ forbid!
25
+ else
26
+ pass result
27
+ end
28
+ end
29
+
30
+ def pass(identity)
31
+ @env["rack.multitenant.identity"] = identity
32
+ @app.call(@env)
33
+ end
34
+
35
+ def forbid!(forbidden_app = nil)
36
+ [403, {"Content-Type" => "text/html"},
37
+ "No identity found for #{@env['rack.multitenant.current_tenant']}"]
38
+ end
39
+
40
+ def create_identity_with(subapp)
41
+ # TODO: calls subapp that either creates or gets the user on the path to creating a new identity.
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ require "rack/multitenant"
2
+
3
+ module Rack::MultiTenant::TenantStrategies
4
+ # Always returns the intial value for tenant.
5
+ #
6
+ # >> default = Rack::MultiTenant::TenantStrategies::Default.new(:foo)
7
+ # >> default.call(:stub_request)
8
+ # => :foo
9
+ class Default
10
+ def initialize(default)
11
+ @default = default
12
+ end
13
+
14
+ def call(_)
15
+ @default
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,22 @@
1
+ module Rack::MultiTenant::TenantStrategies
2
+ class EnvVariable
3
+ # env: Environment variable containing the tenant or tenant key.
4
+ # &getter: optional block. If specified, the env variable will be passed in.
5
+ #
6
+ # >> ENV["TENANT"] = "foo"
7
+ # >> s = Rack::MultiTenant::TenantStrategies::EnvVariable.new
8
+ # >> s.call(:stub_request)
9
+ # => "foo"
10
+ #
11
+ # >> s2 = Rack::MultiTenant::TenantStrategies::EnvVariable.new(&:to_sym)
12
+ # >> s2.call(:stub_request)
13
+ # => :foo
14
+ def initialize(env = ENV["TENANT"], &getter)
15
+ @env = (getter || lambda {|k| k}).call(env)
16
+ end
17
+
18
+ def call(_)
19
+ @env
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Rack::MultiTenant::TenantStrategies
2
+ class Port
3
+ # port_map: A hash of ports to tenants
4
+ #
5
+ # >> require "ostruct"
6
+ # >> ports = {3000 => :foo, 3001 => :bar}
7
+ # >> req = OpenStruct.new(port: 3001)
8
+ # >> s = Rack::MultiTenant::TenantStrategies::Port.new(ports)
9
+ # >> s.call(req)
10
+ # => :bar
11
+ def initialize(port_map)
12
+ @port_map = port_map
13
+ end
14
+
15
+ def call(request)
16
+ tenant = @port_map[request.port]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,40 @@
1
+ module Rack::MultiTenant::TenantStrategies
2
+ class Subdomain
3
+ # Gets a tenant by subdomain.
4
+ #
5
+ # Optionally takes a block that transforms the tenant string into a
6
+ # proper tenant.
7
+ #
8
+ # >> Sub = Rack::MultiTenant::TenantStrategies::Subdomain
9
+ # >> require "ostruct"
10
+ #
11
+ # >> foo_req = OpenStruct.new(host: "foo.example.com")
12
+ # >> strat = Sub.new(".example.com")
13
+ # >> strat.call(foo_req)
14
+ # => "foo"
15
+ #
16
+ # >> strat_with_getter = Sub.new("example.com", &:to_sym)
17
+ # >> strat_with_getter.call(foo_req)
18
+ # => :foo
19
+ #
20
+ # >> wrong_host_req = OpenStruct.new(host: "localhost")
21
+ # >> strat.call(wrong_host_req)
22
+ # => nil
23
+ def initialize(domain, &getter)
24
+ @domain, @getter = domain, getter || lambda {|k| k}
25
+ end
26
+
27
+ def call(request)
28
+ if subdomain = subdomain(request.host, @domain)
29
+ @getter.call(subdomain)
30
+ end
31
+ end
32
+
33
+ private
34
+ def subdomain(host, domain)
35
+ if loc = host.rindex(domain)
36
+ host[0, loc].chomp(".")
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'rack-multitenant/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "rack-multitenant"
8
+ gem.version = Rack::Multitenant::VERSION
9
+ gem.authors = ["Andrew O'Brien"]
10
+ gem.email = ["andrew@econify.com"]
11
+ gem.description = %q{Rack middleware for scoping requests and sharing accounts.}
12
+ gem.summary = %q{For multitenant applications with shared accounts but different data. Scope requests to the tenant when before it enters the app.}
13
+ gem.homepage = "https://github.com/Econify/rack-multitenant"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(examples|test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency "rack", "~> 1.4.0"
21
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-multitenant
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Andrew O'Brien
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2013-04-22 00:00:00 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ prerelease: false
17
+ requirement: &id001 !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.4.0
22
+ type: :runtime
23
+ version_requirements: *id001
24
+ description: Rack middleware for scoping requests and sharing accounts.
25
+ email:
26
+ - andrew@econify.com
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files: []
32
+
33
+ files:
34
+ - .gitignore
35
+ - Gemfile
36
+ - LICENSE.txt
37
+ - README.md
38
+ - Rakefile
39
+ - examples/example_helper.rb
40
+ - examples/get_identity.rb
41
+ - examples/get_identity_spec.rb
42
+ - examples/getter_strategies.rb
43
+ - examples/getter_strategies_spec.rb
44
+ - examples/multitenant_sinatra.rb
45
+ - lib/rack-multitenant.rb
46
+ - lib/rack-multitenant/version.rb
47
+ - lib/rack/multitenant.rb
48
+ - lib/rack/multitenant/get_current_tenant.rb
49
+ - lib/rack/multitenant/get_identity.rb
50
+ - lib/rack/multitenant/tenant_strategies/default.rb
51
+ - lib/rack/multitenant/tenant_strategies/env_variable.rb
52
+ - lib/rack/multitenant/tenant_strategies/port.rb
53
+ - lib/rack/multitenant/tenant_strategies/subdomain.rb
54
+ - rack-multitenant.gemspec
55
+ homepage: https://github.com/Econify/rack-multitenant
56
+ licenses: []
57
+
58
+ metadata: {}
59
+
60
+ post_install_message:
61
+ rdoc_options: []
62
+
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - &id002
68
+ - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: "0"
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - *id002
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 2.0.3
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: For multitenant applications with shared accounts but different data. Scope requests to the tenant when before it enters the app.
81
+ test_files:
82
+ - examples/example_helper.rb
83
+ - examples/get_identity.rb
84
+ - examples/get_identity_spec.rb
85
+ - examples/getter_strategies.rb
86
+ - examples/getter_strategies_spec.rb
87
+ - examples/multitenant_sinatra.rb
88
+ has_rdoc: