tenancy 0.2.0 → 1.0.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.
- checksums.yaml +4 -4
- data/.coveralls.yml +2 -0
- data/.rspec +2 -1
- data/.rvmrc +1 -1
- data/.travis.yml +5 -1
- data/CHANGELOG.md +24 -0
- data/Gemfile +13 -3
- data/README.md +57 -37
- data/Rakefile +4 -4
- data/gemfiles/active_record_32.gemfile +10 -4
- data/gemfiles/active_record_40.gemfile +10 -4
- data/gemfiles/mongoid_3.gemfile +12 -0
- data/gemfiles/mongoid_4.gemfile +12 -0
- data/lib/tenancy.rb +4 -2
- data/lib/tenancy/matchers.rb +29 -12
- data/lib/tenancy/resource.rb +2 -2
- data/lib/tenancy/resource_scope.rb +14 -51
- data/lib/tenancy/scoping.rb +13 -0
- data/lib/tenancy/scoping/active_record.rb +52 -0
- data/lib/tenancy/scoping/mongoid.rb +79 -0
- data/lib/tenancy/tenant.rb +13 -0
- data/lib/tenancy/version.rb +1 -1
- data/spec/lib/resource_spec.rb +82 -38
- data/spec/lib/scoping/active_record_spec.rb +135 -0
- data/spec/lib/scoping/mongoid_spec.rb +179 -0
- data/spec/lib/shoulda_matchers_spec.rb +39 -15
- data/spec/spec_helper.rb +57 -6
- data/spec/support/{models.rb → active_record/models.rb} +5 -5
- data/spec/support/{schema.rb → active_record/schema.rb} +3 -4
- data/spec/support/mongoid/connection.rb +3 -0
- data/spec/support/mongoid/models.rb +44 -0
- data/spec/support/mongoid/mongoid.yml +6 -0
- data/tenancy.gemspec +10 -13
- metadata +43 -97
- data/spec/lib/resource_scope_spec.rb +0 -125
@@ -0,0 +1,179 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
if defined?(Mongoid)
|
4
|
+
describe "Tenancy::Scoping::Mongoid", mongoid: true do
|
5
|
+
let(:camyp) { Mongo::Portal.create(domain_name: "yp.com.kh") }
|
6
|
+
let(:panpages) { Mongo::Portal.create(domain_name: "panpages.com") }
|
7
|
+
let(:listing) { Mongo::Listing.create(name: "Listing 1", portal_id: camyp.id) }
|
8
|
+
|
9
|
+
describe Mongo::Listing do
|
10
|
+
it { should belong_to(:portal).of_type(Mongo::Portal) }
|
11
|
+
|
12
|
+
it { should validate_presence_of(:portal) }
|
13
|
+
|
14
|
+
it { should validate_uniqueness_of(:name).scoped_to(:portal_id).case_insensitive }
|
15
|
+
|
16
|
+
it "have default_scope with :portal_id field" do
|
17
|
+
Mongo::Portal.current = camyp
|
18
|
+
|
19
|
+
expect(Mongo::Listing.where(nil).selector).to eq({"is_active"=>true, "portal_id"=>Mongo::Portal.current_id})
|
20
|
+
end
|
21
|
+
|
22
|
+
it "doesn't have default_scope when it doesn't have current portal" do
|
23
|
+
Mongo::Portal.current = nil
|
24
|
+
|
25
|
+
expect(Mongo::Listing.where(nil).selector).to eq({"is_active"=>true})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe Mongo::Communication do
|
30
|
+
it { should belong_to(:portal) }
|
31
|
+
|
32
|
+
it { should validate_presence_of(:portal) }
|
33
|
+
|
34
|
+
it { should belong_to(:listing) }
|
35
|
+
|
36
|
+
it { should validate_presence_of(:listing) }
|
37
|
+
|
38
|
+
it { should validate_uniqueness_of(:value).scoped_to(:portal_id, :listing_id) }
|
39
|
+
|
40
|
+
it "have default_scope with :portal_id field" do
|
41
|
+
Mongo::Portal.current = camyp
|
42
|
+
Mongo::Listing.current = listing
|
43
|
+
|
44
|
+
expect(Mongo::Communication.where(nil).selector).to eq({"is_active"=>true, "portal_id"=>Mongo::Portal.current_id, "listing_id"=>Mongo::Listing.current_id})
|
45
|
+
end
|
46
|
+
|
47
|
+
it "doesn't have default_scope when it doesn't have current portal and listing" do
|
48
|
+
Mongo::Portal.current = nil
|
49
|
+
Mongo::Listing.current = nil
|
50
|
+
|
51
|
+
expect(Mongo::Communication.where(nil).selector).to eq({"is_active"=>true})
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe Mongo::ExtraCommunication do
|
56
|
+
it { should belong_to(:portal).of_type(Mongo::Portal) }
|
57
|
+
|
58
|
+
it { should belong_to(:listing).of_type(Mongo::Listing) }
|
59
|
+
|
60
|
+
it "raise exception when passing two resources and options" do
|
61
|
+
expect { Mongo::ExtraCommunication.scope_to(:portal, :listing, class_name: "Mongo::Listing") }.to raise_error(ArgumentError)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "uses the correct scope" do
|
65
|
+
listing2 = Mongo::Listing.create(name: "Name 2", portal: camyp)
|
66
|
+
|
67
|
+
Mongo::Portal.current = camyp
|
68
|
+
Mongo::Listing.current = listing2
|
69
|
+
|
70
|
+
extra_communication = Mongo::ExtraCommunication.new
|
71
|
+
expect(extra_communication.listing_id).to eq(listing2.id)
|
72
|
+
expect(extra_communication.portal_id).to eq(camyp.id)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "belongs_to method override" do
|
77
|
+
before(:each) { Mongo::Portal.current = camyp }
|
78
|
+
|
79
|
+
it "reload belongs_to when passes true" do
|
80
|
+
listing.portal.domain_name = "abc.com"
|
81
|
+
|
82
|
+
expect(listing.portal(true).object_id).not_to eq(Mongo::Portal.current.object_id)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "doesn't reload belongs_to" do
|
86
|
+
listing.portal.domain_name = "abc.com"
|
87
|
+
|
88
|
+
expect(listing.portal.object_id).to eq(Mongo::Portal.current.object_id)
|
89
|
+
end
|
90
|
+
|
91
|
+
it "returns different object" do
|
92
|
+
listing.portal_id = panpages.id
|
93
|
+
|
94
|
+
expect(listing.portal.object_id).not_to eq(Mongo::Portal.current.object_id)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "doesn't touch db" do
|
98
|
+
current_listing = listing
|
99
|
+
|
100
|
+
Mongo::Portal.store_in session: ""
|
101
|
+
expect(current_listing.portal.object_id).to eq(Mongo::Portal.current.object_id)
|
102
|
+
Mongo::Portal.store_in session: "default"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "#shard_key_selector override" do
|
107
|
+
before(:each) { Mongo::Portal.current = camyp }
|
108
|
+
|
109
|
+
it "returns with current_portal" do
|
110
|
+
expect(Mongo::Communication.new.shard_key_selector).to eq({"portal_id"=>camyp.id})
|
111
|
+
end
|
112
|
+
|
113
|
+
it "returns with current_listing" do
|
114
|
+
Mongo::Listing.current = listing
|
115
|
+
|
116
|
+
expect(Mongo::Communication.new.shard_key_selector).to eq({"portal_id"=>camyp.id, "listing_id"=>listing.id})
|
117
|
+
end
|
118
|
+
|
119
|
+
it "returns without :current_portal and :current_listing" do
|
120
|
+
Mongo::Portal.current = nil
|
121
|
+
Mongo::Listing.current = nil
|
122
|
+
|
123
|
+
expect(Mongo::Communication.new.shard_key_selector).to eq({})
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
describe "#tenant_scope" do
|
128
|
+
before(:each) { Mongo::Portal.current = camyp }
|
129
|
+
|
130
|
+
it "scopes only :current_portal" do
|
131
|
+
Mongo::Listing.current = listing
|
132
|
+
|
133
|
+
expect(Mongo::Communication.tenant_scope(:portal).selector).to eq({"is_active"=>true, "portal_id"=>Mongo::Portal.current_id})
|
134
|
+
end
|
135
|
+
|
136
|
+
it "scopes only :current_listing" do
|
137
|
+
Mongo::Listing.current = listing
|
138
|
+
|
139
|
+
expect(Mongo::Communication.tenant_scope(:listing).selector).to eq({"is_active"=>true, "listing_id"=>Mongo::Listing.current_id})
|
140
|
+
end
|
141
|
+
|
142
|
+
it "scopes only :current_listing and :current_portal" do
|
143
|
+
Mongo::Listing.current = listing
|
144
|
+
|
145
|
+
expect(Mongo::Communication.tenant_scope(:listing, :portal).selector).to eq(Mongo::Communication.where(nil).selector)
|
146
|
+
end
|
147
|
+
|
148
|
+
it "scopes nothing" do
|
149
|
+
Mongo::Listing.current = listing
|
150
|
+
|
151
|
+
expect(Mongo::Communication.tenant_scope(nil).selector).to eq({"is_active"=>true})
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
if ::Mongoid::VERSION.start_with?("3.1.")
|
156
|
+
describe "getter method override" do
|
157
|
+
before(:each) { Mongo::Portal.current = camyp }
|
158
|
+
|
159
|
+
it "returns the current portal_id" do
|
160
|
+
expect(Mongo::Listing.new.portal_id).to eq(camyp.id)
|
161
|
+
end
|
162
|
+
|
163
|
+
it "#portal_id returns nil" do
|
164
|
+
Mongo::Portal.current = nil
|
165
|
+
|
166
|
+
expect(Mongo::Listing.new.portal_id).to eq(nil)
|
167
|
+
end
|
168
|
+
|
169
|
+
it "returns its value for existing" do
|
170
|
+
listing = Mongo::Listing.create(name: "abcdef")
|
171
|
+
expect(listing.portal_id).to eq(camyp.id)
|
172
|
+
|
173
|
+
Mongo::Portal.current = nil
|
174
|
+
expect(listing.portal_id).to eq(camyp.id)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -1,20 +1,44 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "spec_helper"
|
2
|
+
require "tenancy/matchers"
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
if defined?(ActiveRecord)
|
5
|
+
describe Portal do
|
6
|
+
it { should be_a_tenant }
|
7
|
+
end
|
7
8
|
|
8
|
-
describe Listing do
|
9
|
-
|
10
|
-
end
|
9
|
+
describe Listing do
|
10
|
+
it { should be_a_tenant }
|
11
|
+
end
|
11
12
|
|
12
|
-
describe ExtraCommunication do
|
13
|
-
|
14
|
-
|
13
|
+
describe ExtraCommunication do
|
14
|
+
let(:camyp) { Portal.create(domain_name: "yp.com.kh") }
|
15
|
+
before { Portal.current = camyp }
|
15
16
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
it { should have_scope_to(:portal) }
|
18
|
+
it { should have_scope_to(:portal).class_name("Portal") }
|
19
|
+
it { should have_scope_to(:listing) }
|
20
|
+
it { should have_scope_to(:listing).class_name("Listing") }
|
21
|
+
end
|
20
22
|
end
|
23
|
+
|
24
|
+
if defined?(Mongoid)
|
25
|
+
module Mongo
|
26
|
+
describe Portal do
|
27
|
+
it { should be_a_tenant }
|
28
|
+
end
|
29
|
+
|
30
|
+
describe Listing do
|
31
|
+
it { should be_a_tenant }
|
32
|
+
end
|
33
|
+
|
34
|
+
describe ExtraCommunication do
|
35
|
+
let(:camyp) { Portal.create(domain_name: "yp.com.kh") }
|
36
|
+
before { Portal.current = camyp }
|
37
|
+
|
38
|
+
it { should have_scope_to(:portal) }
|
39
|
+
it { should have_scope_to(:portal).of_type(Mongo::Portal) }
|
40
|
+
it { should have_scope_to(:listing) }
|
41
|
+
it { should have_scope_to(:listing).of_type(Mongo::Listing) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,17 +1,68 @@
|
|
1
|
+
require "simplecov"
|
2
|
+
require "coveralls"
|
1
3
|
require "codeclimate-test-reporter"
|
2
|
-
CodeClimate::TestReporter.start
|
3
4
|
|
4
|
-
|
5
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
6
|
+
Coveralls::SimpleCov::Formatter,
|
7
|
+
SimpleCov::Formatter::HTMLFormatter,
|
8
|
+
CodeClimate::TestReporter::Formatter
|
9
|
+
]
|
10
|
+
|
11
|
+
SimpleCov.start do
|
12
|
+
add_filter "/spec/"
|
13
|
+
end
|
14
|
+
|
15
|
+
require "pry"
|
16
|
+
require "database_cleaner"
|
17
|
+
require "logger"
|
18
|
+
require "tenancy"
|
5
19
|
|
6
20
|
# active_record
|
7
|
-
|
8
|
-
load File.dirname(__FILE__) +
|
21
|
+
if Gem.loaded_specs["activerecord"]
|
22
|
+
load File.dirname(__FILE__) + "/support/active_record/schema.rb"
|
23
|
+
load File.dirname(__FILE__) + "/support/active_record/models.rb"
|
24
|
+
require "shoulda-matchers"
|
25
|
+
end
|
9
26
|
|
10
|
-
|
11
|
-
|
27
|
+
# mongoid
|
28
|
+
if Gem.loaded_specs["mongoid"]
|
29
|
+
load File.dirname(__FILE__) + "/support/mongoid/connection.rb"
|
30
|
+
load File.dirname(__FILE__) + "/support/mongoid/models.rb"
|
31
|
+
require "mongoid-rspec"
|
32
|
+
end
|
12
33
|
|
13
34
|
RSpec.configure do |config|
|
14
35
|
config.filter_run focus: true
|
15
36
|
config.run_all_when_everything_filtered = true
|
16
37
|
config.treat_symbols_as_metadata_keys_with_true_values = true
|
38
|
+
|
39
|
+
config.before(:suite) do
|
40
|
+
DatabaseCleaner.strategy = :truncation
|
41
|
+
end
|
42
|
+
config.include Mongoid::Matchers, mongoid: true if defined?(Mongoid)
|
43
|
+
|
44
|
+
config.around(:each) do |example|
|
45
|
+
if example.metadata[:log]
|
46
|
+
if defined?(ActiveRecord)
|
47
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
48
|
+
end
|
49
|
+
if defined?(Mongoid)
|
50
|
+
Mongoid.logger.level = Logger::DEBUG
|
51
|
+
Moped.logger.level = Logger::DEBUG
|
52
|
+
end
|
53
|
+
end
|
54
|
+
DatabaseCleaner.start
|
55
|
+
RequestStore.clear!
|
56
|
+
|
57
|
+
example.run
|
58
|
+
|
59
|
+
if defined?(ActiveRecord)
|
60
|
+
ActiveRecord::Base.logger = nil
|
61
|
+
end
|
62
|
+
if defined?(Mongoid)
|
63
|
+
Mongoid.logger.level = Logger::INFO
|
64
|
+
Moped.logger.level = Logger::INFO
|
65
|
+
end
|
66
|
+
DatabaseCleaner.clean
|
67
|
+
end
|
17
68
|
end
|
@@ -7,7 +7,7 @@ class Listing < ActiveRecord::Base
|
|
7
7
|
include Tenancy::ResourceScope
|
8
8
|
|
9
9
|
default_scope -> { where(is_active: true) }
|
10
|
-
scope_to
|
10
|
+
scope_to :portal
|
11
11
|
validates_uniqueness_in_scope :name, case_sensitive: false
|
12
12
|
end
|
13
13
|
|
@@ -15,13 +15,13 @@ class Communication < ActiveRecord::Base
|
|
15
15
|
include Tenancy::ResourceScope
|
16
16
|
|
17
17
|
default_scope -> { where(is_active: true) }
|
18
|
-
scope_to
|
18
|
+
scope_to :portal, :listing
|
19
19
|
validates_uniqueness_in_scope :value
|
20
20
|
end
|
21
21
|
|
22
22
|
class ExtraCommunication < ActiveRecord::Base
|
23
23
|
include Tenancy::ResourceScope
|
24
24
|
|
25
|
-
scope_to :portal, class_name:
|
26
|
-
scope_to :listing, class_name:
|
27
|
-
end
|
25
|
+
scope_to :portal, class_name: "Portal"
|
26
|
+
scope_to :listing, class_name: "Listing"
|
27
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
|
-
require
|
2
|
-
require 'logger'
|
1
|
+
require "active_record"
|
3
2
|
|
4
|
-
ActiveRecord::Base.establish_connection(adapter:
|
3
|
+
ActiveRecord::Base.establish_connection(adapter: "sqlite3", database: "spec/test.sqlite3")
|
5
4
|
ActiveRecord::Migration.verbose = false
|
6
5
|
|
7
6
|
ActiveRecord::Schema.define do
|
@@ -35,4 +34,4 @@ ActiveRecord::Schema.define do
|
|
35
34
|
t.references :portal
|
36
35
|
t.timestamps
|
37
36
|
end
|
38
|
-
end
|
37
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Mongo
|
2
|
+
class Portal
|
3
|
+
include Mongoid::Document
|
4
|
+
include Tenancy::Resource
|
5
|
+
|
6
|
+
field :domain_name, type: String
|
7
|
+
end
|
8
|
+
|
9
|
+
class Listing
|
10
|
+
include Mongoid::Document
|
11
|
+
include Tenancy::Resource
|
12
|
+
include Tenancy::ResourceScope
|
13
|
+
|
14
|
+
field :name, type: String
|
15
|
+
|
16
|
+
default_scope -> { where(is_active: true) }
|
17
|
+
scope_to :portal, class_name: "Mongo::Portal"
|
18
|
+
validates_uniqueness_in_scope \
|
19
|
+
:name, case_sensitive: false
|
20
|
+
end
|
21
|
+
|
22
|
+
class Communication
|
23
|
+
include Mongoid::Document
|
24
|
+
include Tenancy::ResourceScope
|
25
|
+
|
26
|
+
field :value, type: String
|
27
|
+
|
28
|
+
default_scope -> { where(is_active: true) }
|
29
|
+
scope_to :portal, class_name: "Mongo::Portal"
|
30
|
+
scope_to :listing, class_name: "Mongo::Listing"
|
31
|
+
validates_uniqueness_in_scope \
|
32
|
+
:value
|
33
|
+
end
|
34
|
+
|
35
|
+
class ExtraCommunication
|
36
|
+
include Mongoid::Document
|
37
|
+
include Tenancy::ResourceScope
|
38
|
+
|
39
|
+
field :value, type: String
|
40
|
+
|
41
|
+
scope_to :portal, class_name: "Mongo::Portal"
|
42
|
+
scope_to :listing, class_name: "Mongo::Listing"
|
43
|
+
end
|
44
|
+
end
|
data/tenancy.gemspec
CHANGED
@@ -1,29 +1,26 @@
|
|
1
1
|
# coding: utf-8
|
2
|
-
lib = File.expand_path(
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
3
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require
|
4
|
+
require "tenancy/version"
|
5
5
|
|
6
6
|
Gem::Specification.new do |spec|
|
7
7
|
spec.name = "tenancy"
|
8
8
|
spec.version = Tenancy::VERSION
|
9
|
-
spec.authors = ["
|
9
|
+
spec.authors = ["Chamnap Chhorn"]
|
10
10
|
spec.email = ["chamnapchhorn@gmail.com"]
|
11
|
-
spec.description = %q{A simple multitenancy with activerecord through scoping}
|
12
|
-
spec.summary = %q{A simple multitenancy with activerecord through scoping}
|
11
|
+
spec.description = %q{A simple multitenancy with activerecord/mongoid through scoping}
|
12
|
+
spec.summary = %q{A simple multitenancy with activerecord/mongoid through scoping}
|
13
13
|
spec.homepage = "https://github.com/yoolk/tenancy"
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
16
|
+
spec.required_ruby_version = ">= 1.9.3"
|
17
|
+
spec.required_rubygems_version = ">= 1.8.11"
|
18
|
+
|
16
19
|
spec.files = `git ls-files`.split($/)
|
17
20
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
21
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
22
|
spec.require_paths = ["lib"]
|
20
23
|
|
21
|
-
spec.
|
22
|
-
spec.add_development_dependency "shoulda", "~> 3.5.0"
|
23
|
-
spec.add_development_dependency "pry", "~> 0.9.12"
|
24
|
-
spec.add_development_dependency "sqlite3", "~> 1.3.7"
|
25
|
-
spec.add_development_dependency "rake"
|
26
|
-
|
27
|
-
spec.add_dependency "activerecord", ">= 3.2.13"
|
24
|
+
spec.add_dependency "activesupport", ">= 3.2"
|
28
25
|
spec.add_dependency "request_store", "~> 1.0.5"
|
29
|
-
end
|
26
|
+
end
|