multitenancy 0.0.4

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 ADDED
@@ -0,0 +1,17 @@
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
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in multitenancy.gemspec
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'test-unit', '2.4.8'
8
+ gem 'rack-test', :require => "rack/test"
9
+ gem 'mocha'
10
+ gem 'shoulda'
11
+ gem 'shoulda-matchers'
12
+ gem 'shoulda-context'
13
+ gem 'activerecord'
14
+ gem 'database_cleaner'
15
+ gem 'sqlite3'
16
+ gem 'rest-client'
17
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 ganeshs
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,43 @@
1
+ # Multitenancy
2
+
3
+ Multitenancy gem nicely plugs in to activerecord to provide multitenant support within a single schema. It allows multitenancy at two levels, tenant and sub-tenant. For instance you can have SAAS application where the primary tenant could be an organization and sub-tenant will be users in that organization.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'multitenancy'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install multitenancy
18
+
19
+ ## Usage
20
+
21
+ This gem expects the tenant and sub-tenant values passed in the request header. But you can enhace this to support other logic as well.
22
+
23
+ You use it in your padrino/sinatra application, add the below lines to the config.ru
24
+
25
+ Multitenancy.init(:tenant_header => 'X_COMPANY_ID', :sub_tenant_header => 'X_USER_ID')
26
+ Padrino.use Multitenancy::Filter
27
+
28
+ You can also outside of a filter or in a standalone application,
29
+
30
+ tenant = Multitenancy::Tenant.new('flipkart', 'ganeshs')
31
+ Multitenancy.with_tenant(tenant) do
32
+ # Your code here
33
+ end
34
+
35
+ Any active record query executed within the tenant block, will be tenant/sub-tenant scoped. New records will persist the tenant and sub-tenant ids, find queries will be scoped to teanant and sub-tenant ids. If the sub-tenant id is not specified, it will be de-scoped.
36
+
37
+ ## Contributing
38
+
39
+ 1. Fork it
40
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
41
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
42
+ 4. Push to the branch (`git push origin my-new-feature`)
43
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,75 @@
1
+ module Multitenancy
2
+
3
+ class << self
4
+ attr_accessor :tenant_field, :sub_tenant_field
5
+ end
6
+
7
+ module ModelExtensions
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+
12
+ def acts_as_tenant(tenant_id, sub_tenant_id=nil)
13
+ raise "tenant_id can't be nil! [Multitenancy]" unless tenant_id
14
+
15
+ def self.is_scoped_by_tenant?
16
+ true
17
+ end
18
+
19
+ Multitenancy.tenant_field = tenant_id
20
+ Multitenancy.sub_tenant_field = sub_tenant_id
21
+
22
+ # set the current_tenant on newly created objects
23
+ before_validation Proc.new {|m|
24
+ tenant = Multitenancy.current_tenant
25
+ return unless tenant && tenant.tenant_id
26
+ m.send "#{tenant_id}=".to_sym, tenant.tenant_id
27
+ m.send "#{sub_tenant_id}=".to_sym, tenant.sub_tenant_id
28
+ }, :on => :create
29
+
30
+ # set the default_scope to scope to current tenant
31
+ default_scope lambda {
32
+ tenant = Multitenancy.current_tenant
33
+ if tenant && tenant.tenant_id
34
+ conditions = {}
35
+ conditions[tenant_id] = tenant.tenant_id
36
+ conditions[sub_tenant_id] = tenant.sub_tenant_id if sub_tenant_id && tenant.sub_tenant_id
37
+ where(conditions)
38
+ end
39
+ }
40
+
41
+ # Rewrite accessors to make tenantimmutable
42
+ define_method "#{tenant_id}=" do |value|
43
+ if new_record?
44
+ write_attribute(tenant_id, value)
45
+ else
46
+ raise "#{tenant_id} is immutable! [Multitenancy]"
47
+ end
48
+ end
49
+
50
+ # Rewrite accessors to make sub_tenant immutable
51
+ define_method "#{sub_tenant_id}=" do |value|
52
+ if new_record?
53
+ write_attribute(sub_tenant_id, value)
54
+ else
55
+ raise "#{sub_tenant_id} is immutable! [Multitenancy]"
56
+ end
57
+ end
58
+ end
59
+
60
+ def validates_uniqueness_to_tenant(fields, args ={})
61
+ raise "[Multitenancy] validates_uniqueness_to_tenant: no current tenant" unless respond_to?(:is_scoped_by_tenant?)
62
+ tenant_id = lambda {Multitenancy.tenant_field.downcase}.call
63
+ sub_tenant_id = Multitenancy.sub_tenant_field ? lambda {Multitenancy.sub_tenant_field.downcase}.call : nil
64
+
65
+ if args[:scope].nil?
66
+ args[:scope] = [tenant_id]
67
+ else
68
+ args[:scope] << tenant_id
69
+ end
70
+ args[:scope] = sub_tenant_id if sub_tenant_id
71
+ validates_uniqueness_of(fields, args)
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,27 @@
1
+ module Multitenancy
2
+
3
+ class Filter
4
+
5
+ def initialize(app)
6
+ @app = app
7
+ end
8
+
9
+ def call(env)
10
+ fix_headers!(env)
11
+ tenant = Tenant.new env[Multitenancy.tenant_header], env[Multitenancy.sub_tenant_header]
12
+ Multitenancy.with_tenant tenant do
13
+ @app.call env
14
+ end
15
+ end
16
+
17
+ private
18
+ # rack converts X_FOO to HTTP_X_FOO, so strip "HTTP_"
19
+ def fix_headers!(env)
20
+ env.keys.select { |k| k =~ /^HTTP_X_/ }.each do |k|
21
+ env[k.gsub("HTTP_", "")] = env[k]
22
+ env.delete(k)
23
+ end
24
+ env
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ module RestClient
2
+ class Request
3
+
4
+ class << self
5
+
6
+ def execute_with_tenant_headers(args, &block)
7
+ args[:headers] = append_tenant_headers(args[:headers]) if Multitenancy.current_tenant && Multitenancy.append_headers_to_rest_calls?
8
+ return execute_without_tenant_headers(args, &block)
9
+ end
10
+
11
+ alias_method :execute_without_tenant_headers, :execute
12
+ alias_method :execute, :execute_with_tenant_headers
13
+
14
+ private
15
+ def append_tenant_headers(headers)
16
+ headers ||= {}
17
+ if !headers[Multitenancy.tenant_header] && Multitenancy.current_tenant
18
+ headers[Multitenancy.tenant_header] = Multitenancy.current_tenant.tenant_id
19
+ end
20
+
21
+ if !headers[Multitenancy.sub_tenant_header] && Multitenancy.current_tenant
22
+ headers[Multitenancy.sub_tenant_header] = Multitenancy.current_tenant.sub_tenant_id
23
+ end
24
+
25
+ headers
26
+ end
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ module Multitenancy
2
+
3
+ class Tenant
4
+ attr_reader :tenant_id, :sub_tenant_id
5
+
6
+ def initialize(tenant_id, sub_tenant_id=nil)
7
+ @tenant_id = tenant_id
8
+ @sub_tenant_id = sub_tenant_id
9
+ end
10
+
11
+ def headers
12
+ {Multitenancy.tenant_header => tenant_id, Multitenancy.sub_tenant_header => sub_tenant_id}
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module Multitenancy
2
+ VERSION = "0.0.4"
3
+ end
@@ -0,0 +1,67 @@
1
+ require "active_record"
2
+ require "active_model"
3
+ require "multitenancy/version"
4
+ require "multitenancy/tenant"
5
+ require "multitenancy/rack/filter"
6
+ require "multitenancy/model_extensions"
7
+ require "multitenancy/rest_client/rest_client.rb"
8
+
9
+ module Multitenancy
10
+
11
+ @@tenant_header = 'X_TENANT_ID'
12
+ @@sub_tenant_header = 'X_SUB_TENANT_ID'
13
+ @@append_headers_to_rest_calls = true
14
+ @@logger = (logger rescue nil) || Logger.new(STDOUT)
15
+
16
+ class << self
17
+ def init(config)
18
+ @@tenant_header = config[:tenant_header]
19
+ @@sub_tenant_header = config[:sub_tenant_header]
20
+ @@logger = config[:logger] if config[:logger]
21
+ @@append_headers_to_rest_calls = config[:append_headers_to_rest_calls] unless config[:append_headers_to_rest_calls].nil?
22
+ end
23
+
24
+ def logger
25
+ @@logger
26
+ end
27
+
28
+ def tenant_header
29
+ @@tenant_header
30
+ end
31
+
32
+ def sub_tenant_header
33
+ @@sub_tenant_header
34
+ end
35
+
36
+ def append_headers_to_rest_calls?
37
+ @@append_headers_to_rest_calls
38
+ end
39
+
40
+ def with_tenant(tenant, &block)
41
+ self.logger.debug "Executing the block with the tenant - #{tenant}"
42
+ if block.nil?
43
+ raise ArgumentError, "block required"
44
+ end
45
+ old_tenant = self.current_tenant
46
+ self.current_tenant = tenant
47
+ begin
48
+ return block.call
49
+ ensure
50
+ self.current_tenant = old_tenant
51
+ end
52
+ end
53
+
54
+ def current_tenant=(tenant)
55
+ self.logger.debug "Setting the current tenant to - #{tenant}"
56
+ Thread.current[:tenant] = tenant
57
+ end
58
+
59
+ def current_tenant
60
+ Thread.current[:tenant]
61
+ end
62
+ end
63
+ end
64
+
65
+ if defined?(ActiveRecord::Base)
66
+ ActiveRecord::Base.send(:include, Multitenancy::ModelExtensions)
67
+ end
@@ -0,0 +1,17 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/multitenancy/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["ganeshs"]
6
+ gem.email = ["ganeshs@flipkart.com"]
7
+ gem.description = 'Support multitennacy with active record'
8
+ gem.summary = 'Support multitennacy with active record at tenant and sub-tenant level'
9
+ gem.homepage = "https://github.com/Flipkart/multitenancy"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "multitenancy"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Multitenancy::VERSION
17
+ end
data/test/database.yml ADDED
@@ -0,0 +1,3 @@
1
+ sqlite:
2
+ adapter: sqlite3
3
+ database: test/multitenancy.sqlite3
Binary file
@@ -0,0 +1,31 @@
1
+ require 'bundler/setup'
2
+ require 'test/unit'
3
+ require 'pp'
4
+ require 'logger'
5
+ require 'mocha'
6
+ require 'shoulda'
7
+ require 'active_record'
8
+ require 'active_model'
9
+ require 'restclient'
10
+ require 'multitenancy'
11
+ require 'database_cleaner'
12
+
13
+ ROOT = File.expand_path("../..", __FILE__)
14
+ $:.unshift(ROOT + "/lib")
15
+
16
+ config = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'database.yml')))
17
+ ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
18
+ ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
19
+
20
+ class ActiveSupport::TestCase
21
+
22
+ setup do
23
+ DatabaseCleaner.strategy = :transaction
24
+ DatabaseCleaner.clean_with(:truncation)
25
+ DatabaseCleaner.start
26
+ end
27
+
28
+ teardown do
29
+ DatabaseCleaner.clean
30
+ end
31
+ end
@@ -0,0 +1,20 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_config.rb')
2
+
3
+ module Multitenancy
4
+ class FilterTest < Test::Unit::TestCase
5
+
6
+ setup do
7
+ @env = {
8
+ 'X_TENANT_ID' => 'tenant_id',
9
+ 'X_SUB_TENANT_ID' => 'seller_id'
10
+ }
11
+ @app = mock
12
+ @app.stubs(:call).with(@env).returns([:status, :headers, :body])
13
+ end
14
+
15
+ should "call app" do
16
+ filter = Multitenancy::Filter.new(@app)
17
+ assert_equal [:status, :headers, :body], filter.call(@env)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,223 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_config.rb')
2
+
3
+ # Setup the db
4
+ ActiveRecord::Schema.define(:version => 1) do
5
+ create_table :model_with_tenant_ids, :force => true do |t|
6
+ t.column :org_id, :string
7
+ t.column :name, :string
8
+ end
9
+
10
+ create_table :model_with_sub_tenant_ids, :force => true do |t|
11
+ t.column :seller_id, :string
12
+ t.column :name, :string
13
+ end
14
+
15
+ create_table :model_with_tenant_id_and_sub_tenant_ids, :force => true do |t|
16
+ t.column :org_id, :string
17
+ t.column :seller_id, :string
18
+ t.column :name, :string
19
+ end
20
+
21
+ create_table :model_without_tenant_id_and_sub_tenant_ids, :force => true do |t|
22
+ t.column :name, :string
23
+ end
24
+ end
25
+
26
+ class ModelWithTenantId < ActiveRecord::Base
27
+ acts_as_tenant :org_id
28
+ end
29
+
30
+ class ModelWithoutTenantIdAndSubTenantId < ActiveRecord::Base
31
+ end
32
+
33
+ class ModelWithTenantIdAndSubTenantId < ActiveRecord::Base
34
+ acts_as_tenant :org_id, :seller_id
35
+ end
36
+
37
+ class ModelWithSubTenantId < ActiveRecord::Base
38
+ # acts_as_tenant nil, :seller_id
39
+ end
40
+
41
+ class ModelExtensionsTest < ActiveSupport::TestCase
42
+
43
+ context "create model with tenant id and sub tenant id" do
44
+ should "populate tenant and sub tenant id when set in context" do
45
+ Multitenancy.with_tenant Multitenancy::Tenant.new('Flipkart', 'Poorvika') do
46
+ model = ModelWithTenantIdAndSubTenantId.new({:name => "test name"})
47
+ model.save!
48
+ assert_equal 'Flipkart', model.org_id
49
+ assert_equal 'Poorvika', model.seller_id
50
+ end
51
+ end
52
+
53
+ should "populate only tenant when sub tenant is not set in context" do
54
+ Multitenancy.with_tenant Multitenancy::Tenant.new('Flipkart') do
55
+ model = ModelWithTenantIdAndSubTenantId.new({:name => "test name"})
56
+ model.save!
57
+ assert_equal 'Flipkart', model.org_id
58
+ assert_nil model.seller_id
59
+ end
60
+ end
61
+
62
+ should "not populate tenant and sub tenant when tenant id is not set in context" do
63
+ Multitenancy.with_tenant Multitenancy::Tenant.new(nil, 'Poorvika') do
64
+ model = ModelWithTenantIdAndSubTenantId.new({:name => "test name"})
65
+ model.save!
66
+ assert_nil model.org_id
67
+ assert_nil model.seller_id
68
+ end
69
+ end
70
+
71
+ should "not populate tenant and sub tenant when context is not set" do
72
+ model = ModelWithTenantIdAndSubTenantId.new({:name => "test name"})
73
+ model.save!
74
+ assert_nil model.org_id
75
+ assert_nil model.seller_id
76
+ end
77
+ end
78
+
79
+ context "create model without tenant id and sub tenant id" do
80
+ should "not populate tenant and sub tenant id when set in context" do
81
+ Multitenancy.with_tenant Multitenancy::Tenant.new('Flipkart', 'Poorvika') do
82
+ model = ModelWithoutTenantIdAndSubTenantId.new({:name => "test name"})
83
+ model.save!
84
+ assert_raises NoMethodError do
85
+ model.org_id
86
+ end
87
+ assert_raises NoMethodError do
88
+ model.seller_id
89
+ end
90
+ end
91
+ end
92
+
93
+ should "not populate tenant and sub tenant when context is not set" do
94
+ model = ModelWithoutTenantIdAndSubTenantId.new({:name => "test name"})
95
+ model.save!
96
+ assert_raises NoMethodError do
97
+ model.org_id
98
+ end
99
+ assert_raises NoMethodError do
100
+ model.seller_id
101
+ end
102
+ end
103
+ end
104
+
105
+ context "create model only with tenant id" do
106
+ should "populate tenant when set in context" do
107
+ Multitenancy.with_tenant Multitenancy::Tenant.new('Flipkart') do
108
+ model = ModelWithTenantId.new({:name => "test name"})
109
+ model.save!
110
+ assert_equal 'Flipkart', model.org_id
111
+ end
112
+ end
113
+
114
+ should "not populate tenant when tenant id is not set in context" do
115
+ Multitenancy.with_tenant Multitenancy::Tenant.new(nil) do
116
+ model = ModelWithTenantId.new({:name => "test name"})
117
+ model.save!
118
+ assert_nil model.org_id
119
+ end
120
+ end
121
+
122
+ should "not populate tenant when context is not set" do
123
+ model = ModelWithTenantId.new({:name => "test name"})
124
+ model.save!
125
+ assert_nil model.org_id
126
+ end
127
+ end
128
+
129
+ context "create model only with sub tenant id" do
130
+ should "raise error when tenant id is nil" do
131
+ model = ModelWithSubTenantId.new
132
+ assert_raises RuntimeError do
133
+ ModelWithSubTenantId.acts_as_tenant nil
134
+ end
135
+ end
136
+ end
137
+
138
+ context "find by scope for model with tenant and sub tenant" do
139
+ setup do
140
+ Multitenancy.with_tenant Multitenancy::Tenant.new('Flipkart', 'Poorvika') do
141
+ model = ModelWithTenantIdAndSubTenantId.new({:name => "test name"})
142
+ model.save!
143
+ end
144
+
145
+ Multitenancy.with_tenant Multitenancy::Tenant.new('ebay', 'Poorvika') do
146
+ model = ModelWithTenantIdAndSubTenantId.new({:name => "test name"})
147
+ model.save!
148
+ end
149
+
150
+ Multitenancy.with_tenant Multitenancy::Tenant.new('ebay', 'Univercell') do
151
+ model = ModelWithTenantIdAndSubTenantId.new({:name => "test name"})
152
+ model.save!
153
+ end
154
+
155
+ Multitenancy.with_tenant Multitenancy::Tenant.new('Flipkart', nil) do
156
+ model = ModelWithTenantIdAndSubTenantId.new({:name => "test name"})
157
+ model.save!
158
+ end
159
+
160
+ model = ModelWithTenantIdAndSubTenantId.new({:name => "test name1"})
161
+ model.save!
162
+ end
163
+
164
+ should "return by tenant id and sub tenant id when set in context" do
165
+ Multitenancy.with_tenant Multitenancy::Tenant.new('Flipkart', 'Poorvika') do
166
+ assert_equal 1, ModelWithTenantIdAndSubTenantId.all.count
167
+ assert_equal 'Flipkart', ModelWithTenantIdAndSubTenantId.find_by_name('test name').org_id
168
+ end
169
+
170
+ Multitenancy.with_tenant Multitenancy::Tenant.new('ebay', 'Univercell') do
171
+ assert_equal 1, ModelWithTenantIdAndSubTenantId.all.count
172
+ end
173
+ end
174
+
175
+ should "return by tenant id when sub tenant is not set in context" do
176
+ Multitenancy.with_tenant Multitenancy::Tenant.new('Flipkart') do
177
+ assert_equal 2, ModelWithTenantIdAndSubTenantId.all.count
178
+ end
179
+
180
+ Multitenancy.with_tenant Multitenancy::Tenant.new('ebay') do
181
+ assert_equal 2, ModelWithTenantIdAndSubTenantId.all.count
182
+ end
183
+ end
184
+
185
+ should "return all when tenant id is not set in context" do
186
+ Multitenancy.with_tenant Multitenancy::Tenant.new(nil, 'Poorvika') do
187
+ assert_equal 5, ModelWithTenantIdAndSubTenantId.all.count
188
+ assert_equal 4, ModelWithTenantIdAndSubTenantId.where(:name => 'test name').count
189
+ end
190
+ end
191
+ end
192
+
193
+ context "find by scope for model with tenant" do
194
+ setup do
195
+ Multitenancy.with_tenant Multitenancy::Tenant.new('Flipkart') do
196
+ model = ModelWithTenantIdAndSubTenantId.new({:name => "test name"})
197
+ model.save!
198
+ end
199
+
200
+ Multitenancy.with_tenant Multitenancy::Tenant.new('ebay') do
201
+ model = ModelWithTenantIdAndSubTenantId.new({:name => "test name1"})
202
+ model.save!
203
+ end
204
+ end
205
+
206
+ should "return by tenant id when set in context" do
207
+ Multitenancy.with_tenant Multitenancy::Tenant.new('Flipkart') do
208
+ assert_equal 1, ModelWithTenantIdAndSubTenantId.all.count
209
+ end
210
+
211
+ Multitenancy.with_tenant Multitenancy::Tenant.new('ebay') do
212
+ assert_equal 1, ModelWithTenantIdAndSubTenantId.all.count
213
+ end
214
+ end
215
+
216
+ should "return all when tenant id is not set in context" do
217
+ Multitenancy.with_tenant Multitenancy::Tenant.new(nil) do
218
+ assert_equal 2, ModelWithTenantIdAndSubTenantId.all.count
219
+ assert_equal 1, ModelWithTenantIdAndSubTenantId.where(:name => 'test name').count
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_config.rb')
2
+
3
+ module Multitenancy
4
+ class MultiTenancyTest < Test::Unit::TestCase
5
+
6
+ should "configure headers" do
7
+ Multitenancy.init(:tenant_header => 'tenant_id', :sub_tenant_header => 'seller_id')
8
+ assert_equal 'tenant_id', Multitenancy.tenant_header
9
+ end
10
+
11
+ should "return current tenant" do
12
+ tenant = Tenant.new('tenant_id', 'seller_id')
13
+ Multitenancy.current_tenant = tenant
14
+ assert_equal tenant, Multitenancy.current_tenant
15
+ end
16
+
17
+ context "block within context" do
18
+ setup do
19
+ tenant = Tenant.new('tenant_id', 'seller_id')
20
+ Multitenancy.current_tenant = tenant
21
+ end
22
+
23
+ should "call block with right tenant and subtenant ids" do
24
+ Multitenancy.with_tenant(Tenant.new('dummy_tenant_id', 'dummy_seller_id')) do
25
+ assert_equal Multitenancy.current_tenant.tenant_id, 'dummy_tenant_id'
26
+ assert_equal Multitenancy.current_tenant.sub_tenant_id, 'dummy_seller_id'
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_config.rb')
2
+
3
+ class RestClientTest < Test::Unit::TestCase
4
+
5
+ setup do
6
+ Multitenancy.init({:tenant_header => 'X_COMPANY_ID', :sub_tenant_header => 'X_USER_ID'})
7
+ end
8
+
9
+ should "append tenant headers to rest calls within a tenant scope" do
10
+ RestClient::Request.any_instance.expects(:make_headers).with() {|headers| headers[Multitenancy.tenant_header] == 'Flipkart' && headers[Multitenancy.sub_tenant_header] == 'ganeshs'}
11
+ RestClient::Request.any_instance.expects(:execute)
12
+ Multitenancy.with_tenant(Multitenancy::Tenant.new('Flipkart', 'ganeshs')) do
13
+ RestClient.get('http://some/resource')
14
+ end
15
+ end
16
+
17
+ should "not append tenant headers to rest calls outside of a tenant scope" do
18
+ RestClient::Request.any_instance.expects(:make_headers).with() {|headers| headers[Multitenancy.tenant_header].nil? && headers[Multitenancy.sub_tenant_header].nil? }
19
+ RestClient::Request.any_instance.expects(:execute)
20
+ RestClient.get('http://some/resource')
21
+ end
22
+
23
+ context "no tenant headers in rest calls" do
24
+ setup do
25
+ Multitenancy.init({:tenant_header => 'X_COMPANY_ID', :sub_tenant_header => 'X_USER_ID', :append_headers_to_rest_calls => false})
26
+ end
27
+
28
+ should "not append tenant headers to rest calls within tenant scope" do
29
+ RestClient::Request.any_instance.expects(:make_headers).with() {|headers| headers[Multitenancy.tenant_header].nil? && headers[Multitenancy.sub_tenant_header].nil? }
30
+ RestClient::Request.any_instance.expects(:execute)
31
+ Multitenancy.with_tenant(Multitenancy::Tenant.new('Flipkart', 'ganeshs')) do
32
+ RestClient.get('http://some/resource')
33
+ end
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,27 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../test_config.rb')
2
+
3
+ module Multitenancy
4
+ class TenantTest < Test::Unit::TestCase
5
+
6
+ should "create tenant" do
7
+ tenant = Tenant.new('tenant_id', 'seller_id')
8
+ assert_equal tenant.tenant_id, 'tenant_id'
9
+ assert_equal tenant.sub_tenant_id, 'seller_id'
10
+ end
11
+
12
+ should "get headers" do
13
+ tenant = Tenant.new('tenant_id', 'seller_id')
14
+ assert_equal tenant.headers, {'X_TENANT_ID' => 'tenant_id', 'X_SUB_TENANT_ID' => 'seller_id'}
15
+ end
16
+
17
+ should "not allow setting tenant and sub tenant id" do
18
+ tenant = Tenant.new('tenant_id', 'seller_id')
19
+ assert_raises NoMethodError do
20
+ tenant.tenant_id = 'test'
21
+ end
22
+ assert_raises NoMethodError do
23
+ tenant.sub_tenant_id = 'test'
24
+ end
25
+ end
26
+ end
27
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: multitenancy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.4
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - ganeshs
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-12-11 00:00:00.000000000Z
13
+ dependencies: []
14
+ description: Support multitennacy with active record
15
+ email:
16
+ - ganeshs@flipkart.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - Gemfile
23
+ - LICENSE
24
+ - README.md
25
+ - Rakefile
26
+ - lib/multitenancy.rb
27
+ - lib/multitenancy/model_extensions.rb
28
+ - lib/multitenancy/rack/filter.rb
29
+ - lib/multitenancy/rest_client/rest_client.rb
30
+ - lib/multitenancy/tenant.rb
31
+ - lib/multitenancy/version.rb
32
+ - multitenancy.gemspec
33
+ - test/database.yml
34
+ - test/multitenancy.sqlite3
35
+ - test/test_config.rb
36
+ - test/unit/filter_test.rb
37
+ - test/unit/model_extensions_test.rb
38
+ - test/unit/multitenancy_test.rb
39
+ - test/unit/rest_client_test.rb
40
+ - test/unit/tenant_test.rb
41
+ homepage: https://github.com/Flipkart/multitenancy
42
+ licenses: []
43
+ post_install_message:
44
+ rdoc_options: []
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ! '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ! '>='
57
+ - !ruby/object:Gem::Version
58
+ version: '0'
59
+ requirements: []
60
+ rubyforge_project:
61
+ rubygems_version: 1.8.10
62
+ signing_key:
63
+ specification_version: 3
64
+ summary: Support multitennacy with active record at tenant and sub-tenant level
65
+ test_files:
66
+ - test/database.yml
67
+ - test/multitenancy.sqlite3
68
+ - test/test_config.rb
69
+ - test/unit/filter_test.rb
70
+ - test/unit/model_extensions_test.rb
71
+ - test/unit/multitenancy_test.rb
72
+ - test/unit/rest_client_test.rb
73
+ - test/unit/tenant_test.rb
74
+ has_rdoc: