is_multitenant 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.rspec +1 -0
- data/Gemfile +19 -0
- data/Gemfile.lock +96 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +20 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/is_multitenant.gemspec +82 -0
- data/lib/is_multitenant.rb +168 -0
- data/spec/database.yml +17 -0
- data/spec/is_multitenant_spec.rb +253 -0
- data/spec/spec_helper.rb +79 -0
- data/spec/support/models.rb +50 -0
- data/spec/support/schema.rb +58 -0
- metadata +201 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2462d924c58beb5fd2f45a4f83787b650e493d89
|
4
|
+
data.tar.gz: b7a67bf5f21d258ce07d95b52880be39798c46c0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e8930b41dd1b3150af8d19fa700b5eb3772a7f34ba4c5cbd249154ff123c1084a20212f036db3fd389c41bda92777067bcae49692e583a04abd62bbcefea4bce
|
7
|
+
data.tar.gz: de0efb6513e84bb7c3313172fd27b7a2ee0b518e950b2cd67f9f99787c0007724bdbac43ec6302fbe86d82dcf926b85dc537547c372cd7b7dc3719111d783c42
|
data/.document
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem "activerecord", "~>3.2"
|
4
|
+
gem "activesupport", "~>3.2"
|
5
|
+
|
6
|
+
gem 'scope_injector', '~>0'
|
7
|
+
|
8
|
+
|
9
|
+
# Add dependencies to develop your gem here.
|
10
|
+
# Include everything needed to run rake, tests, features, etc.
|
11
|
+
group :development do
|
12
|
+
gem "rspec", "~> 2.14"
|
13
|
+
gem "rdoc", "~> 3.12"
|
14
|
+
gem "bundler", "~> 1"
|
15
|
+
gem "jeweler", "~> 2.0"
|
16
|
+
gem "simplecov", "~> 0"
|
17
|
+
gem "sqlite3", "~> 1"
|
18
|
+
gem 'database_cleaner', '~>1'
|
19
|
+
end
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.2.17)
|
5
|
+
activesupport (= 3.2.17)
|
6
|
+
builder (~> 3.0.0)
|
7
|
+
activerecord (3.2.17)
|
8
|
+
activemodel (= 3.2.17)
|
9
|
+
activesupport (= 3.2.17)
|
10
|
+
arel (~> 3.0.2)
|
11
|
+
tzinfo (~> 0.3.29)
|
12
|
+
activesupport (3.2.17)
|
13
|
+
i18n (~> 0.6, >= 0.6.4)
|
14
|
+
multi_json (~> 1.0)
|
15
|
+
addressable (2.3.5)
|
16
|
+
arel (3.0.3)
|
17
|
+
builder (3.0.4)
|
18
|
+
database_cleaner (1.2.0)
|
19
|
+
descendants_tracker (0.0.3)
|
20
|
+
diff-lcs (1.2.5)
|
21
|
+
docile (1.1.3)
|
22
|
+
faraday (0.9.0)
|
23
|
+
multipart-post (>= 1.2, < 3)
|
24
|
+
git (1.2.6)
|
25
|
+
github_api (0.11.3)
|
26
|
+
addressable (~> 2.3)
|
27
|
+
descendants_tracker (~> 0.0.1)
|
28
|
+
faraday (~> 0.8, < 0.10)
|
29
|
+
hashie (>= 1.2)
|
30
|
+
multi_json (>= 1.7.5, < 2.0)
|
31
|
+
nokogiri (~> 1.6.0)
|
32
|
+
oauth2
|
33
|
+
hashie (2.0.5)
|
34
|
+
highline (1.6.21)
|
35
|
+
i18n (0.6.9)
|
36
|
+
jeweler (2.0.1)
|
37
|
+
builder
|
38
|
+
bundler (>= 1.0)
|
39
|
+
git (>= 1.2.5)
|
40
|
+
github_api
|
41
|
+
highline (>= 1.6.15)
|
42
|
+
nokogiri (>= 1.5.10)
|
43
|
+
rake
|
44
|
+
rdoc
|
45
|
+
json (1.8.1)
|
46
|
+
jwt (0.1.11)
|
47
|
+
multi_json (>= 1.5)
|
48
|
+
mini_portile (0.5.2)
|
49
|
+
multi_json (1.9.0)
|
50
|
+
multi_xml (0.5.5)
|
51
|
+
multipart-post (2.0.0)
|
52
|
+
nokogiri (1.6.1)
|
53
|
+
mini_portile (~> 0.5.0)
|
54
|
+
oauth2 (0.9.3)
|
55
|
+
faraday (>= 0.8, < 0.10)
|
56
|
+
jwt (~> 0.1.8)
|
57
|
+
multi_json (~> 1.3)
|
58
|
+
multi_xml (~> 0.5)
|
59
|
+
rack (~> 1.2)
|
60
|
+
rack (1.5.2)
|
61
|
+
rake (10.1.1)
|
62
|
+
rdoc (3.12.2)
|
63
|
+
json (~> 1.4)
|
64
|
+
rspec (2.14.1)
|
65
|
+
rspec-core (~> 2.14.0)
|
66
|
+
rspec-expectations (~> 2.14.0)
|
67
|
+
rspec-mocks (~> 2.14.0)
|
68
|
+
rspec-core (2.14.7)
|
69
|
+
rspec-expectations (2.14.5)
|
70
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
71
|
+
rspec-mocks (2.14.6)
|
72
|
+
scope_injector (0.3.1)
|
73
|
+
activerecord (~> 3.2)
|
74
|
+
activesupport (~> 3.2)
|
75
|
+
simplecov (0.8.2)
|
76
|
+
docile (~> 1.1.0)
|
77
|
+
multi_json
|
78
|
+
simplecov-html (~> 0.8.0)
|
79
|
+
simplecov-html (0.8.0)
|
80
|
+
sqlite3 (1.3.9)
|
81
|
+
tzinfo (0.3.39)
|
82
|
+
|
83
|
+
PLATFORMS
|
84
|
+
ruby
|
85
|
+
|
86
|
+
DEPENDENCIES
|
87
|
+
activerecord (~> 3.2)
|
88
|
+
activesupport (~> 3.2)
|
89
|
+
bundler (~> 1)
|
90
|
+
database_cleaner (~> 1)
|
91
|
+
jeweler (~> 2.0)
|
92
|
+
rdoc (~> 3.12)
|
93
|
+
rspec (~> 2.14)
|
94
|
+
scope_injector (~> 0)
|
95
|
+
simplecov (~> 0)
|
96
|
+
sqlite3 (~> 1)
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2014 Tom Smith
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
= is_multitenant
|
2
|
+
|
3
|
+
<Description goes here...>
|
4
|
+
|
5
|
+
|
6
|
+
== Contributing to is_multitenant
|
7
|
+
|
8
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
9
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
10
|
+
* Fork the project.
|
11
|
+
* Start a feature/bugfix branch.
|
12
|
+
* Commit and push until you are happy with your contribution.
|
13
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
14
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
15
|
+
|
16
|
+
== Copyright
|
17
|
+
|
18
|
+
Copyright (c) 2014 Tom Smith. See LICENSE.txt for
|
19
|
+
further details.
|
20
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://guides.rubygems.org/specification-reference/ for more options
|
17
|
+
gem.name = "is_multitenant"
|
18
|
+
gem.homepage = "http://github.com/rtomsmith/is_multitenant"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Rails ActiveRecord extension for enforcing multi-tenancy in a shared database}
|
21
|
+
gem.description = %Q{Rails ActiveRecord extension for enforcing multi-tenancy in a shared database. Does NOT use default_scope, instead reyling on the scope_injector gem.}
|
22
|
+
gem.email = "tsmith@landfall.com"
|
23
|
+
gem.authors = ["Tom Smith"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rspec/core'
|
29
|
+
require 'rspec/core/rake_task'
|
30
|
+
RSpec::Core::RakeTask.new(:spec) do |spec|
|
31
|
+
spec.pattern = FileList['spec/**/*_spec.rb']
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Code coverage detail"
|
35
|
+
task :simplecov do
|
36
|
+
ENV['COVERAGE'] = "true"
|
37
|
+
Rake::Task['spec'].execute
|
38
|
+
end
|
39
|
+
|
40
|
+
task :default => :spec
|
41
|
+
|
42
|
+
require 'rdoc/task'
|
43
|
+
Rake::RDocTask.new do |rdoc|
|
44
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
45
|
+
|
46
|
+
rdoc.rdoc_dir = 'rdoc'
|
47
|
+
rdoc.title = "is_multitenant #{version}"
|
48
|
+
rdoc.rdoc_files.include('README*')
|
49
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
50
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.5.0
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
# stub: is_multitenant 0.5.0 ruby lib
|
6
|
+
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "is_multitenant"
|
9
|
+
s.version = "0.5.0"
|
10
|
+
|
11
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
12
|
+
s.require_paths = ["lib"]
|
13
|
+
s.authors = ["Tom Smith"]
|
14
|
+
s.date = "2014-03-11"
|
15
|
+
s.description = "Rails ActiveRecord extension for enforcing multi-tenancy in a shared database. Does NOT use default_scope, instead reyling on the scope_injector gem."
|
16
|
+
s.email = "tsmith@landfall.com"
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE.txt",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".rspec",
|
24
|
+
"Gemfile",
|
25
|
+
"Gemfile.lock",
|
26
|
+
"LICENSE.txt",
|
27
|
+
"README.rdoc",
|
28
|
+
"Rakefile",
|
29
|
+
"VERSION",
|
30
|
+
"is_multitenant.gemspec",
|
31
|
+
"lib/is_multitenant.rb",
|
32
|
+
"spec/database.yml",
|
33
|
+
"spec/is_multitenant_spec.rb",
|
34
|
+
"spec/spec_helper.rb",
|
35
|
+
"spec/support/models.rb",
|
36
|
+
"spec/support/schema.rb"
|
37
|
+
]
|
38
|
+
s.homepage = "http://github.com/rtomsmith/is_multitenant"
|
39
|
+
s.licenses = ["MIT"]
|
40
|
+
s.rubygems_version = "2.2.2"
|
41
|
+
s.summary = "Rails ActiveRecord extension for enforcing multi-tenancy in a shared database"
|
42
|
+
|
43
|
+
if s.respond_to? :specification_version then
|
44
|
+
s.specification_version = 4
|
45
|
+
|
46
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
47
|
+
s.add_runtime_dependency(%q<activerecord>, ["~> 3.2"])
|
48
|
+
s.add_runtime_dependency(%q<activesupport>, ["~> 3.2"])
|
49
|
+
s.add_runtime_dependency(%q<scope_injector>, ["~> 0"])
|
50
|
+
s.add_development_dependency(%q<rspec>, ["~> 2.14"])
|
51
|
+
s.add_development_dependency(%q<rdoc>, ["~> 3.12"])
|
52
|
+
s.add_development_dependency(%q<bundler>, ["~> 1"])
|
53
|
+
s.add_development_dependency(%q<jeweler>, ["~> 2.0"])
|
54
|
+
s.add_development_dependency(%q<simplecov>, ["~> 0"])
|
55
|
+
s.add_development_dependency(%q<sqlite3>, ["~> 1"])
|
56
|
+
s.add_development_dependency(%q<database_cleaner>, ["~> 1"])
|
57
|
+
else
|
58
|
+
s.add_dependency(%q<activerecord>, ["~> 3.2"])
|
59
|
+
s.add_dependency(%q<activesupport>, ["~> 3.2"])
|
60
|
+
s.add_dependency(%q<scope_injector>, ["~> 0"])
|
61
|
+
s.add_dependency(%q<rspec>, ["~> 2.14"])
|
62
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
63
|
+
s.add_dependency(%q<bundler>, ["~> 1"])
|
64
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0"])
|
65
|
+
s.add_dependency(%q<simplecov>, ["~> 0"])
|
66
|
+
s.add_dependency(%q<sqlite3>, ["~> 1"])
|
67
|
+
s.add_dependency(%q<database_cleaner>, ["~> 1"])
|
68
|
+
end
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<activerecord>, ["~> 3.2"])
|
71
|
+
s.add_dependency(%q<activesupport>, ["~> 3.2"])
|
72
|
+
s.add_dependency(%q<scope_injector>, ["~> 0"])
|
73
|
+
s.add_dependency(%q<rspec>, ["~> 2.14"])
|
74
|
+
s.add_dependency(%q<rdoc>, ["~> 3.12"])
|
75
|
+
s.add_dependency(%q<bundler>, ["~> 1"])
|
76
|
+
s.add_dependency(%q<jeweler>, ["~> 2.0"])
|
77
|
+
s.add_dependency(%q<simplecov>, ["~> 0"])
|
78
|
+
s.add_dependency(%q<sqlite3>, ["~> 1"])
|
79
|
+
s.add_dependency(%q<database_cleaner>, ["~> 1"])
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require 'scope_injector'
|
2
|
+
|
3
|
+
module Multitenant
|
4
|
+
|
5
|
+
# Use this method to set the current tenant id. Typically this would be called from
|
6
|
+
# a controller. For example:
|
7
|
+
#
|
8
|
+
# class ApplicationController < ActionController::Base
|
9
|
+
# before_filter :set_the_current_tenant
|
10
|
+
#
|
11
|
+
# def set_the_current_tenant
|
12
|
+
# Multitenant.current_tenant_id = current_user.account_id
|
13
|
+
# end
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
def self.current_tenant_id=(tenant_id)
|
17
|
+
Thread.current[:current_tenant_id] = tenant_id
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the currently set tenant id. Used by models to scope operations.
|
21
|
+
def self.current_tenant_id
|
22
|
+
Thread.current[:current_tenant_id] ||= (defined?(Rails::Console) ? Account.first.id : nil)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Sometimes you need to execute code in the scope of a tenant is different
|
26
|
+
# than the current tenant. This can be useful for admin tasks in Rake, or
|
27
|
+
# in tests. For example:
|
28
|
+
#
|
29
|
+
# Multitenant.with_tenant_id(override_tenant_id) do
|
30
|
+
# ...do some AR stuff...
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# forces the specified tenant id to be used to scope all the ActiveRecord
|
34
|
+
# operations inside the block.
|
35
|
+
#
|
36
|
+
def self.with_tenant_id(tenant_id)
|
37
|
+
begin
|
38
|
+
old_tenant, self.current_tenant_id = self.current_tenant_id, tenant_id
|
39
|
+
yield
|
40
|
+
ensure
|
41
|
+
self.current_tenant_id = old_tenant
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Disables the multitenant scoping for all model operations within the block. The
|
46
|
+
# scoping is temporarily disabled for ALL model classes involved.
|
47
|
+
def self.without_multitenant_scope(&blk)
|
48
|
+
ScopeInjector.without_injected_scope(:multitenant, &blk)
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
# Add support to ActiveRecord::Base for the +is_mulitenant+ macro
|
53
|
+
module IsMultitenant
|
54
|
+
|
55
|
+
def self.included(base)
|
56
|
+
base.extend ClassMethods
|
57
|
+
end
|
58
|
+
|
59
|
+
module ClassMethods
|
60
|
+
# When invoked from an ActiveRecord model class, +is_multitenant+ specifies
|
61
|
+
# that the model's database operations always be scoped to the application
|
62
|
+
# request's current tenant. Usage is as follows:
|
63
|
+
#
|
64
|
+
# class Contact < ActiveRecord::Base
|
65
|
+
# is_multitenant :with_attribute => :account_id
|
66
|
+
# -OR-
|
67
|
+
# is_multitenant :class => :account
|
68
|
+
# -OR-
|
69
|
+
# is_multitenant :class => :account, :with_attribute => :account_key
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
def is_multitenant(options = {})
|
73
|
+
raise 'Cannot use is_multitenant more than once per model class' if is_multitenant?
|
74
|
+
|
75
|
+
cattr_accessor :tenant_class_name
|
76
|
+
cattr_accessor :tenant_attribute
|
77
|
+
|
78
|
+
self.tenant_attribute = options[:with_attribute]
|
79
|
+
raise ArgumentError, "Must specify :class and/or :with_attribute" if options[:class].blank? && self.tenant_attribute.blank?
|
80
|
+
class_name = self.tenant_attribute.to_s.ends_with?('_id') ? self.tenant_attribute.to_s.sub('_id','').underscore.to_sym : nil
|
81
|
+
self.tenant_class_name = (options[:class] || class_name).to_s.classify
|
82
|
+
self.tenant_attribute = self.tenant_class_name.foreign_key.to_sym if self.tenant_attribute.nil?
|
83
|
+
|
84
|
+
extend IsMultitenant::SingletonMethods
|
85
|
+
include IsMultitenant::InstanceMethods
|
86
|
+
|
87
|
+
inject_scope :multitenant, :for_current_tenant, :apply_to => :all
|
88
|
+
|
89
|
+
scope :for_current_tenant, ->() { where(tenant_condition) }
|
90
|
+
scope :for_tenant, ->(tenant_id) { where(tenant_condition(tenant_id)) }
|
91
|
+
|
92
|
+
validate :associations_have_same_tenant
|
93
|
+
|
94
|
+
before_validation :force_current_tenant_id
|
95
|
+
before_save :force_current_tenant_id
|
96
|
+
|
97
|
+
define_tenant_id_writer(tenant_attribute)
|
98
|
+
end
|
99
|
+
|
100
|
+
def is_multitenant?
|
101
|
+
false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# These methods are available as class level methods to the models that
|
106
|
+
# invoke is_multitenant, and only to those models.
|
107
|
+
module SingletonMethods
|
108
|
+
|
109
|
+
public
|
110
|
+
|
111
|
+
def is_multitenant?
|
112
|
+
true
|
113
|
+
end
|
114
|
+
|
115
|
+
def tenant_condition(tenant_id = nil)
|
116
|
+
raise 'ERROR: the current tenant id has not been set' unless tenant_id || current_tenant_id
|
117
|
+
{self.tenant_attribute => tenant_id || current_tenant_id}
|
118
|
+
end
|
119
|
+
|
120
|
+
def current_tenant_id
|
121
|
+
Multitenant.current_tenant_id
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def define_tenant_id_writer(tenant_attribute)
|
127
|
+
define_method("#{tenant_attribute}=") do |value|
|
128
|
+
if self.class.without_multitenant_scope? || new_record? || send(tenant_attribute).nil?
|
129
|
+
write_attribute(:"#{tenant_attribute}", value)
|
130
|
+
else
|
131
|
+
raise "Unauthorized assignment to :#{tenant_attribute}. This field is protected by is_multitenant and is set automatically."
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
end
|
137
|
+
|
138
|
+
# All instances of is_multitenant models have access to the following
|
139
|
+
# methods.
|
140
|
+
module InstanceMethods
|
141
|
+
def self.included(base)
|
142
|
+
|
143
|
+
protected
|
144
|
+
|
145
|
+
def set_tenant_id(tenant_id = nil)
|
146
|
+
write_attribute(self.class.tenant_attribute, tenant_id || self.class.current_tenant_id)
|
147
|
+
end
|
148
|
+
|
149
|
+
def force_current_tenant_id
|
150
|
+
set_tenant_id unless self.class.without_multitenant_scope?
|
151
|
+
end
|
152
|
+
|
153
|
+
def associations_have_same_tenant
|
154
|
+
self.class.reflect_on_all_associations(:belongs_to).each do |assoc|
|
155
|
+
if assoc.klass.is_multitenant? && assoc.class_name != self.tenant_class_name
|
156
|
+
value = send(assoc.foreign_key)
|
157
|
+
errors.add assoc.foreign_key, "multitenant association #{assoc.name} has different tenant" unless value.nil? || assoc.klass.where(:id => value).exists?
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
ActiveRecord::Base.send :include, Multitenant::IsMultitenant
|
data/spec/database.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
sqlite3:
|
2
|
+
adapter: sqlite3
|
3
|
+
database: ':memory:'
|
4
|
+
|
5
|
+
mysql:
|
6
|
+
adapter: mysql
|
7
|
+
hostname: localhost
|
8
|
+
username: root
|
9
|
+
password:
|
10
|
+
database: delineate_test
|
11
|
+
|
12
|
+
postgresql:
|
13
|
+
adapter: postgresql
|
14
|
+
hostname: localhost
|
15
|
+
username: postgres
|
16
|
+
password:
|
17
|
+
database: delineate_test
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe Multitenant do
|
4
|
+
before { Multitenant.current_tenant_id = 1 }
|
5
|
+
after { Multitenant.current_tenant_id = nil }
|
6
|
+
|
7
|
+
# Setting and getting
|
8
|
+
describe 'setting the current tenant id' do
|
9
|
+
before { Multitenant.current_tenant_id = 9876 }
|
10
|
+
it { expect(Multitenant.current_tenant_id).to eq(9876) }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'models should report support for multi-tenancy' do
|
14
|
+
it {Project.is_multitenant?.should be_true}
|
15
|
+
it {Project.respond_to?(:inject_scope_multitenant?).should be_true}
|
16
|
+
it {UnscopedModel.is_multitenant?.should_not be_true}
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "tenant required" do
|
20
|
+
describe "raises exception if no tenant specified" do
|
21
|
+
before do
|
22
|
+
@account1 = Account.create!(:name => 'foo')
|
23
|
+
Multitenant.current_tenant_id = @account1.id
|
24
|
+
@project1 = @account1.projects.create!(:name => 'foobar')
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should raise an error when no tenant is provided" do
|
28
|
+
Multitenant.current_tenant_id = nil
|
29
|
+
expect { Project.all.to_a }.to raise_error(RuntimeError, /current tenant/)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'tenant_id should be immutable, if already set' do
|
35
|
+
before do
|
36
|
+
@account = Account.create!(:name => 'Test account')
|
37
|
+
Multitenant.current_tenant_id = @account.id
|
38
|
+
@project = @account.projects.create!(:name => 'Test project')
|
39
|
+
end
|
40
|
+
|
41
|
+
it { running {@project.account_id = @account.id + 1}.should raise_error }
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'tenant_id should be mutable, if not already set' do
|
45
|
+
before do
|
46
|
+
@account = Account.create!(:name => 'foo')
|
47
|
+
Multitenant.current_tenant_id = @account.id
|
48
|
+
@project = Project.create!(:name => 'bar')
|
49
|
+
@project.update_column('account_id', nil)
|
50
|
+
end
|
51
|
+
|
52
|
+
it { @project.account_id.should be_nil }
|
53
|
+
it { running { @project.account_id = @account.id }.should_not raise_error }
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'allows custom foreign_key on is_multitenant' do
|
57
|
+
before do
|
58
|
+
@account = Account.create!(:name => 'foo')
|
59
|
+
Multitenant.current_tenant_id = @account.id
|
60
|
+
@custom_foreign_key_task = CustomForeignKeyTask.create!(:name => 'foo')
|
61
|
+
end
|
62
|
+
|
63
|
+
it { @custom_foreign_key_task.accountID.should == @account.id }
|
64
|
+
end
|
65
|
+
|
66
|
+
# Scoping models
|
67
|
+
describe 'Project.all should be scoped to the current tenant if set' do
|
68
|
+
before do
|
69
|
+
@account1 = Account.create!(:name => 'foo')
|
70
|
+
@account2 = Account.create!(:name => 'bar')
|
71
|
+
|
72
|
+
Multitenant.current_tenant_id = @account1.id
|
73
|
+
@project1 = @account1.projects.create!(:name => 'foobar')
|
74
|
+
Multitenant.current_tenant_id = @account2.id
|
75
|
+
@project2 = @account2.projects.create!(:name => 'baz')
|
76
|
+
|
77
|
+
Multitenant.current_tenant_id = @account1.id
|
78
|
+
@projects = Project.all
|
79
|
+
end
|
80
|
+
|
81
|
+
it { @projects.length.should == 1 }
|
82
|
+
it { @projects.should == [@project1] }
|
83
|
+
end
|
84
|
+
|
85
|
+
describe 'unscoping operations should work' do
|
86
|
+
before do
|
87
|
+
@account1 = Account.create!(:name => 'foo')
|
88
|
+
@account2 = Account.create!(:name => 'bar')
|
89
|
+
|
90
|
+
Multitenant.current_tenant_id = @account1.id
|
91
|
+
@project1 = @account1.projects.create!(:name => 'foobar')
|
92
|
+
Multitenant.current_tenant_id = @account2.id
|
93
|
+
@project2 = @account2.projects.create!(:name => 'baz')
|
94
|
+
|
95
|
+
Multitenant.current_tenant_id = @account1.id
|
96
|
+
@projects_unscoped = Project.unscoped.all
|
97
|
+
@projects_without_multitenant_scope = Project.without_multitenant_scope{Project.all}
|
98
|
+
end
|
99
|
+
|
100
|
+
it { @projects_unscoped.count.should == 1 } # unscoping default_scope should have no affect
|
101
|
+
it { @projects_without_multitenant_scope.count.should == 2 }
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'Associations should be correctly scoped by current tenant' do
|
105
|
+
before do
|
106
|
+
@account = Account.create!(:name => 'foo')
|
107
|
+
Multitenant.current_tenant_id = @account.id
|
108
|
+
@project = Project.create!(:name => 'foobar', :account_id => @account.id )
|
109
|
+
|
110
|
+
@task1 = Task.create!(:name => 'no_tenant', :project => @project)
|
111
|
+
@task1.update_column('account_id', nil)
|
112
|
+
|
113
|
+
@task2 = @project.tasks.create!(:name => 'baz')
|
114
|
+
@tasks = @project.tasks
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should correctly set the tenant on the task created with current_tenant set' do
|
118
|
+
@task2.account_id.should == @account.id
|
119
|
+
end
|
120
|
+
|
121
|
+
it 'should filter out the non-tenant task from the project' do
|
122
|
+
@tasks.length.should == 1
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
describe 'Associations can only be made with in-scope objects' do
|
127
|
+
before do
|
128
|
+
@account = Account.create!(:name => 'foo')
|
129
|
+
Multitenant.current_tenant_id = @account.id+1
|
130
|
+
@project1 = Project.create!(:name => 'inaccessible_project')
|
131
|
+
|
132
|
+
Multitenant.current_tenant_id = @account.id
|
133
|
+
@project2 = Project.create!(:name => 'accessible_project')
|
134
|
+
@task = @project2.tasks.create!(:name => 'bar')
|
135
|
+
end
|
136
|
+
|
137
|
+
it do
|
138
|
+
@task.update_attributes(:project_id => @project1.id).should be_false
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
describe 'Create and save a multitenant child without it having a parent' do
|
144
|
+
@account = Account.create!(:name => 'baz')
|
145
|
+
Multitenant.current_tenant_id = @account.id
|
146
|
+
|
147
|
+
it {Task.create(:name => 'bar').valid?.should == true}
|
148
|
+
end
|
149
|
+
|
150
|
+
describe 'It should be possible to use aliased associations' do
|
151
|
+
it { AliasedTask.create(:name => 'foo', :project_alias => @project2).valid?.should == true }
|
152
|
+
end
|
153
|
+
|
154
|
+
# Additional default_scopes
|
155
|
+
describe 'When dealing with a user defined default_scope' do
|
156
|
+
before do
|
157
|
+
@account = Account.create!(:name => 'foo')
|
158
|
+
Multitenant.current_tenant_id = @account.id+1
|
159
|
+
@project1 = Project.create!(:name => 'inaccessible')
|
160
|
+
@task1 = Task.create!(:name => 'no_tenant', :project => @project1)
|
161
|
+
|
162
|
+
Multitenant.current_tenant_id = @account.id
|
163
|
+
@project2 = Project.create!(:name => 'accessible')
|
164
|
+
@task2 = @project2.tasks.create!(:name => 'bar')
|
165
|
+
@task3 = @project2.tasks.create!(:name => 'baz')
|
166
|
+
@task4 = @project2.tasks.create!(:name => 'foo')
|
167
|
+
@task5 = @project2.tasks.create!(:name => 'foobar', :completed => true )
|
168
|
+
|
169
|
+
@tasks = Task.all
|
170
|
+
end
|
171
|
+
|
172
|
+
it 'should apply both the tenant scope and the user defined default_scope, including :order' do
|
173
|
+
@tasks.length.should == 3
|
174
|
+
@tasks.should == [@task2, @task3, @task4]
|
175
|
+
@tasks = Task.unscoped.all
|
176
|
+
@tasks.length.should == 4
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
# Validates_uniqueness
|
181
|
+
describe 'When using validates_uniqueness_of in a multitenant model' do
|
182
|
+
before do
|
183
|
+
account = Account.create!(:name => 'foo')
|
184
|
+
Multitenant.current_tenant_id = account.id
|
185
|
+
Project.create!(:name => 'existing_name')
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'should not be possible to create a duplicate within the same tenant' do
|
189
|
+
Project.create(:name => 'existing_name').valid?.should == false
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'should be possible to create a duplicate outside the tenant scope' do
|
193
|
+
account = Account.create!(:name => 'baz')
|
194
|
+
Multitenant.current_tenant_id = account.id
|
195
|
+
Project.create(:name => 'existing_name').valid?.should == true
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
describe 'Handles user defined scopes' do
|
200
|
+
before do
|
201
|
+
UniqueTask.create!(:name => 'foo', :user_defined_scope => 'unique_scope')
|
202
|
+
end
|
203
|
+
|
204
|
+
it { UniqueTask.create(:name => 'foo', :user_defined_scope => 'another_scope').should be_valid }
|
205
|
+
it { UniqueTask.create(:name => 'foo', :user_defined_scope => 'unique_scope').should_not be_valid }
|
206
|
+
end
|
207
|
+
|
208
|
+
describe 'When using validates_uniqueness_of in a NON-aat model' do
|
209
|
+
before do
|
210
|
+
UnscopedModel.create!(:name => 'foo')
|
211
|
+
end
|
212
|
+
it 'should not be possible to create duplicates' do
|
213
|
+
UnscopedModel.create(:name => 'foo').valid?.should == false
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# with_tenant_id
|
218
|
+
describe "Multitenant.with_tenant_id" do
|
219
|
+
it "should set current_tenant to the specified tenant inside the block" do
|
220
|
+
@account = Account.create!(:name => 'baz')
|
221
|
+
|
222
|
+
Multitenant.with_tenant_id(@account.id) do
|
223
|
+
Multitenant.current_tenant_id.should eq(@account.id)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
it "should reset current_tenant to the previous tenant once exiting the block" do
|
228
|
+
@account1 = Account.create!(:name => 'foo')
|
229
|
+
@account2 = Account.create!(:name => 'bar')
|
230
|
+
|
231
|
+
Multitenant.current_tenant_id = @account1.id
|
232
|
+
Multitenant.with_tenant_id @account2.id do
|
233
|
+
end
|
234
|
+
|
235
|
+
Multitenant.current_tenant_id.should eq(@account1.id)
|
236
|
+
end
|
237
|
+
|
238
|
+
it "should return the value of the block" do
|
239
|
+
@account1 = Account.create!(:name => 'foo')
|
240
|
+
@account2 = Account.create!(:name => 'bar')
|
241
|
+
|
242
|
+
Multitenant.current_tenant_id = @account1.id
|
243
|
+
Multitenant.with_tenant_id(@account2.id) do
|
244
|
+
:foo
|
245
|
+
end.should eq :foo
|
246
|
+
end
|
247
|
+
|
248
|
+
it "should raise an error when no block is provided" do
|
249
|
+
expect { Multitenant.with_tenant_id(1) }.to raise_error(LocalJumpError, /no block given/)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
#require 'simplecov'
|
7
|
+
#SimpleCov.start
|
8
|
+
|
9
|
+
ENV['RAILS_ENV'] = 'test'
|
10
|
+
|
11
|
+
require 'active_support'
|
12
|
+
require 'active_support/core_ext/logger'
|
13
|
+
require 'active_record'
|
14
|
+
|
15
|
+
require 'rspec'
|
16
|
+
require 'database_cleaner'
|
17
|
+
|
18
|
+
require 'is_multitenant'
|
19
|
+
|
20
|
+
|
21
|
+
# Requires supporting files with custom matchers and macros, etc,
|
22
|
+
# in ./support/ and its subdirectories.
|
23
|
+
#Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
24
|
+
|
25
|
+
RSpec.configure do |config|
|
26
|
+
config.before(:suite) do
|
27
|
+
DatabaseCleaner.strategy = :transaction
|
28
|
+
DatabaseCleaner.clean_with(:truncation)
|
29
|
+
end
|
30
|
+
|
31
|
+
config.before(:each) do
|
32
|
+
DatabaseCleaner.start
|
33
|
+
end
|
34
|
+
|
35
|
+
config.after(:each) do
|
36
|
+
DatabaseCleaner.clean
|
37
|
+
end
|
38
|
+
|
39
|
+
# Run specs in random order to surface order dependencies. If you find an
|
40
|
+
# order dependency and want to debug it, you can fix the order by providing
|
41
|
+
# the seed, which is printed after each run.
|
42
|
+
# --seed 1234
|
43
|
+
config.order = "random"
|
44
|
+
|
45
|
+
# Log entry each test to aid in debugging
|
46
|
+
config.before(:each) do |x|
|
47
|
+
full_example_description = "#{x.example.metadata[:full_description]}"
|
48
|
+
ActiveRecord::Base.logger.info("\n#{full_example_description}\n#{'-' * (full_example_description.length)}")
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# a little sugar for checking results of a section of code
|
53
|
+
alias :running :lambda
|
54
|
+
|
55
|
+
|
56
|
+
# Set the DB var if testing with other than sqlite
|
57
|
+
ENV['DB'] ||= 'sqlite3'
|
58
|
+
|
59
|
+
database_yml = File.expand_path('../database.yml', __FILE__)
|
60
|
+
if File.exists?(database_yml)
|
61
|
+
active_record_configuration = YAML.load_file(database_yml)[ENV['DB']]
|
62
|
+
|
63
|
+
ActiveRecord::Base.establish_connection(active_record_configuration)
|
64
|
+
|
65
|
+
ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "test.log"))
|
66
|
+
#ActiveRecord::Base.logger = Logger.new(STDOUT)
|
67
|
+
|
68
|
+
ActiveRecord::Base.silence do
|
69
|
+
ActiveRecord::Migration.verbose = false
|
70
|
+
|
71
|
+
load(File.dirname(__FILE__) + '/support/schema.rb')
|
72
|
+
create_schema!
|
73
|
+
|
74
|
+
load(File.dirname(__FILE__) + '/support/models.rb')
|
75
|
+
clean_database!
|
76
|
+
end
|
77
|
+
else
|
78
|
+
raise "Create #{database_yml} first to configure your database. Take a look at: #{database_yml}.sample"
|
79
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
class Account < ActiveRecord::Base
|
3
|
+
has_many :projects
|
4
|
+
end
|
5
|
+
|
6
|
+
class Project < ActiveRecord::Base
|
7
|
+
is_multitenant :with_attribute => :account_id
|
8
|
+
|
9
|
+
has_one :manager
|
10
|
+
has_many :tasks
|
11
|
+
|
12
|
+
validates_uniqueness_of :name
|
13
|
+
end
|
14
|
+
|
15
|
+
class Manager < ActiveRecord::Base
|
16
|
+
is_multitenant :with_attribute => :account_id
|
17
|
+
belongs_to :project
|
18
|
+
end
|
19
|
+
|
20
|
+
class Task < ActiveRecord::Base
|
21
|
+
is_multitenant :with_attribute => :account_id
|
22
|
+
|
23
|
+
belongs_to :project
|
24
|
+
default_scope -> { where(:completed => nil).order("name") }
|
25
|
+
|
26
|
+
validates_uniqueness_of :name
|
27
|
+
end
|
28
|
+
|
29
|
+
class UnscopedModel < ActiveRecord::Base
|
30
|
+
validates_uniqueness_of :name
|
31
|
+
end
|
32
|
+
|
33
|
+
class AliasedTask < ActiveRecord::Base
|
34
|
+
is_multitenant :with_attribute => :account_id
|
35
|
+
|
36
|
+
belongs_to :project_alias, :class_name => "Project"
|
37
|
+
end
|
38
|
+
|
39
|
+
class UniqueTask < ActiveRecord::Base
|
40
|
+
is_multitenant :with_attribute => :account_id
|
41
|
+
|
42
|
+
belongs_to :project
|
43
|
+
validates_uniqueness_of :name, scope: :user_defined_scope
|
44
|
+
end
|
45
|
+
|
46
|
+
class CustomForeignKeyTask < ActiveRecord::Base
|
47
|
+
is_multitenant :with_attribute => :accountID
|
48
|
+
validates_uniqueness_of :name
|
49
|
+
end
|
50
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# Support for creating the test schema and seed data
|
2
|
+
|
3
|
+
def create_schema!
|
4
|
+
ActiveRecord::Schema.define(:version => 1) do
|
5
|
+
create_table :accounts, :force => true do |t|
|
6
|
+
t.column :name, :string
|
7
|
+
t.column :subdomain, :string
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table :projects, :force => true do |t|
|
11
|
+
t.column :name, :string
|
12
|
+
t.column :account_id, :integer
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :managers, :force => true do |t|
|
16
|
+
t.column :name, :string
|
17
|
+
t.column :project_id, :integer
|
18
|
+
t.column :account_id, :integer
|
19
|
+
end
|
20
|
+
|
21
|
+
create_table :tasks, :force => true do |t|
|
22
|
+
t.column :name, :string
|
23
|
+
t.column :account_id, :integer
|
24
|
+
t.column :project_id, :integer
|
25
|
+
t.column :completed, :boolean
|
26
|
+
end
|
27
|
+
|
28
|
+
create_table :countries, :force => true do |t|
|
29
|
+
t.column :name, :string
|
30
|
+
end
|
31
|
+
|
32
|
+
create_table :unscoped_models, :force => true do |t|
|
33
|
+
t.column :name, :string
|
34
|
+
end
|
35
|
+
|
36
|
+
create_table :aliased_tasks, :force => true do |t|
|
37
|
+
t.column :name, :string
|
38
|
+
t.column :project_alias_id, :integer
|
39
|
+
t.column :account_id, :integer
|
40
|
+
end
|
41
|
+
|
42
|
+
create_table :unique_tasks, :force => true do |t|
|
43
|
+
t.column :name, :string
|
44
|
+
t.column :user_defined_scope, :string
|
45
|
+
t.column :project_id, :integer
|
46
|
+
t.column :account_id, :integer
|
47
|
+
end
|
48
|
+
|
49
|
+
create_table :custom_foreign_key_tasks, :force => true do |t|
|
50
|
+
t.column :name, :string
|
51
|
+
t.column :accountID, :integer
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def clean_database!
|
57
|
+
end
|
58
|
+
|
metadata
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: is_multitenant
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tom Smith
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-03-11 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
version_requirements: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ~>
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '3.2'
|
19
|
+
prerelease: false
|
20
|
+
name: activerecord
|
21
|
+
requirement: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ~>
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '3.2'
|
26
|
+
type: :runtime
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.2'
|
33
|
+
prerelease: false
|
34
|
+
name: activesupport
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.2'
|
40
|
+
type: :runtime
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
prerelease: false
|
48
|
+
name: scope_injector
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '2.14'
|
61
|
+
prerelease: false
|
62
|
+
name: rspec
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ~>
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '2.14'
|
68
|
+
type: :development
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ~>
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '3.12'
|
75
|
+
prerelease: false
|
76
|
+
name: rdoc
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ~>
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '3.12'
|
82
|
+
type: :development
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ~>
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '1'
|
89
|
+
prerelease: false
|
90
|
+
name: bundler
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ~>
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1'
|
96
|
+
type: :development
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - ~>
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '2.0'
|
103
|
+
prerelease: false
|
104
|
+
name: jeweler
|
105
|
+
requirement: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ~>
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '2.0'
|
110
|
+
type: :development
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
version_requirements: !ruby/object:Gem::Requirement
|
113
|
+
requirements:
|
114
|
+
- - ~>
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '0'
|
117
|
+
prerelease: false
|
118
|
+
name: simplecov
|
119
|
+
requirement: !ruby/object:Gem::Requirement
|
120
|
+
requirements:
|
121
|
+
- - ~>
|
122
|
+
- !ruby/object:Gem::Version
|
123
|
+
version: '0'
|
124
|
+
type: :development
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
version_requirements: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - ~>
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '1'
|
131
|
+
prerelease: false
|
132
|
+
name: sqlite3
|
133
|
+
requirement: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ~>
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '1'
|
138
|
+
type: :development
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
version_requirements: !ruby/object:Gem::Requirement
|
141
|
+
requirements:
|
142
|
+
- - ~>
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
version: '1'
|
145
|
+
prerelease: false
|
146
|
+
name: database_cleaner
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ~>
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '1'
|
152
|
+
type: :development
|
153
|
+
description: Rails ActiveRecord extension for enforcing multi-tenancy in a shared
|
154
|
+
database. Does NOT use default_scope, instead reyling on the scope_injector gem.
|
155
|
+
email: tsmith@landfall.com
|
156
|
+
executables: []
|
157
|
+
extensions: []
|
158
|
+
extra_rdoc_files:
|
159
|
+
- LICENSE.txt
|
160
|
+
- README.rdoc
|
161
|
+
files:
|
162
|
+
- .document
|
163
|
+
- .rspec
|
164
|
+
- Gemfile
|
165
|
+
- Gemfile.lock
|
166
|
+
- LICENSE.txt
|
167
|
+
- README.rdoc
|
168
|
+
- Rakefile
|
169
|
+
- VERSION
|
170
|
+
- is_multitenant.gemspec
|
171
|
+
- lib/is_multitenant.rb
|
172
|
+
- spec/database.yml
|
173
|
+
- spec/is_multitenant_spec.rb
|
174
|
+
- spec/spec_helper.rb
|
175
|
+
- spec/support/models.rb
|
176
|
+
- spec/support/schema.rb
|
177
|
+
homepage: http://github.com/rtomsmith/is_multitenant
|
178
|
+
licenses:
|
179
|
+
- MIT
|
180
|
+
metadata: {}
|
181
|
+
post_install_message:
|
182
|
+
rdoc_options: []
|
183
|
+
require_paths:
|
184
|
+
- lib
|
185
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
186
|
+
requirements:
|
187
|
+
- - '>='
|
188
|
+
- !ruby/object:Gem::Version
|
189
|
+
version: '0'
|
190
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - '>='
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
requirements: []
|
196
|
+
rubyforge_project:
|
197
|
+
rubygems_version: 2.2.2
|
198
|
+
signing_key:
|
199
|
+
specification_version: 4
|
200
|
+
summary: Rails ActiveRecord extension for enforcing multi-tenancy in a shared database
|
201
|
+
test_files: []
|