tenancy 0.2.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
@@ -3,65 +3,28 @@ module Tenancy
|
|
3
3
|
extend ActiveSupport::Concern
|
4
4
|
|
5
5
|
module ClassMethods
|
6
|
-
attr_reader :scope_fields
|
7
|
-
|
8
|
-
def scope_fields
|
9
|
-
@scope_fields ||= []
|
10
|
-
end
|
11
6
|
|
12
7
|
def scope_to(*resources)
|
13
|
-
|
14
|
-
raise ArgumentError, 'options should be blank if there are multiple resources' if resources.count > 1 and options.present?
|
15
|
-
|
16
|
-
resources.each do |resource|
|
17
|
-
resource = resource.to_sym
|
18
|
-
resource_class_name ||= (options[:class_name].to_s.presence || resource.to_s).classify
|
19
|
-
resource_class = resource_class_name.constantize
|
20
|
-
|
21
|
-
# validates and belongs_to
|
22
|
-
validates resource, presence: true
|
23
|
-
belongs_to resource, options
|
24
|
-
|
25
|
-
# default_scope
|
26
|
-
resource_foreign_key = reflect_on_association(resource).foreign_key
|
27
|
-
scope_fields << resource_foreign_key
|
28
|
-
default_scope { where(:"#{resource_foreign_key}" => resource_class.current_id) if resource_class.current_id }
|
29
|
-
|
30
|
-
# override to return current resource instance
|
31
|
-
# so that it doesn't touch db
|
32
|
-
define_method(resource) do |reload=false|
|
33
|
-
return super(reload) if reload
|
34
|
-
return resource_class.current if send(resource_foreign_key) == resource_class.current_id
|
35
|
-
super(reload)
|
36
|
-
end
|
37
|
-
end
|
8
|
+
tenancy_scoping.scope_to(resources)
|
38
9
|
end
|
39
10
|
|
40
|
-
|
41
|
-
|
42
|
-
scope = where(nil).with_default_scope
|
43
|
-
resources.each do |resource|
|
44
|
-
resource = resource.to_sym
|
45
|
-
reflection = reflect_on_association(resource)
|
46
|
-
next if reflection.nil?
|
47
|
-
|
48
|
-
resource_scope_sql = where(nil).table[reflection.foreign_key].eq(reflection.klass.current_id).to_sql
|
49
|
-
|
50
|
-
scope.where_values.delete_if { |query| query.to_sql == resource_scope_sql }
|
51
|
-
end
|
52
|
-
|
53
|
-
scope
|
11
|
+
def tenant_scope(*resources)
|
12
|
+
tenancy_scoping.tenant_scope(resources)
|
54
13
|
end
|
55
14
|
|
56
15
|
def validates_uniqueness_in_scope(fields, args={})
|
57
|
-
|
58
|
-
args[:scope] = Array.wrap(args[:scope]) << scope_fields
|
59
|
-
else
|
60
|
-
args[:scope] = scope_fields
|
61
|
-
end
|
62
|
-
|
63
|
-
validates_uniqueness_of(fields, args)
|
16
|
+
tenancy_scoping.validates_uniqueness_in_scope(fields, args)
|
64
17
|
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def tenancy_scoping
|
22
|
+
@tenancy_scoping ||= if defined?(::ActiveRecord) && ancestors.include?(::ActiveRecord::Base)
|
23
|
+
Scoping::ActiveRecord.new(self)
|
24
|
+
elsif defined?(Mongoid) && ancestors.include?(Mongoid::Document)
|
25
|
+
Scoping::Mongoid.new(self)
|
26
|
+
end
|
27
|
+
end
|
65
28
|
end
|
66
29
|
end
|
67
30
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module Tenancy
|
2
|
+
class Scoping::ActiveRecord < Scoping
|
3
|
+
|
4
|
+
def scope_to(tenant_names)
|
5
|
+
options = tenant_names.extract_options!.dup
|
6
|
+
raise ArgumentError, "options should be blank if there are multiple tenants" if tenant_names.count > 1 and options.present?
|
7
|
+
|
8
|
+
tenant_names.each do |tenant_name|
|
9
|
+
# validates and belongs_to
|
10
|
+
klass.validates tenant_name, presence: true
|
11
|
+
klass.belongs_to tenant_name, options
|
12
|
+
|
13
|
+
tenant = Tenant.new(tenant_name, options[:class_name], klass)
|
14
|
+
self.tenants << tenant
|
15
|
+
|
16
|
+
# default_scope
|
17
|
+
klass.send(:default_scope, lambda { klass.where(:"#{tenant.foreign_key}" => tenant.klass.current_id) if tenant.klass.current_id })
|
18
|
+
|
19
|
+
# override to return current tenant instance
|
20
|
+
# so that it doesn"t touch db
|
21
|
+
klass.send(:define_method, tenant_name, lambda { |reload=false|
|
22
|
+
return super(reload) if reload
|
23
|
+
return tenant.klass.current if send(tenant.foreign_key) == tenant.klass.current_id
|
24
|
+
super(reload)
|
25
|
+
})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def tenant_scope(tenant_names)
|
30
|
+
scope = klass.where(nil).with_default_scope
|
31
|
+
tenants.each do |tenant|
|
32
|
+
next if tenant_names.include?(tenant.name.to_sym)
|
33
|
+
|
34
|
+
tenant_scope_sql = klass.where(nil).table[tenant.foreign_key].eq(tenant.klass.current_id).to_sql
|
35
|
+
scope.where_values.delete_if { |query| query.to_sql == tenant_scope_sql }
|
36
|
+
end
|
37
|
+
|
38
|
+
scope
|
39
|
+
end
|
40
|
+
|
41
|
+
def validates_uniqueness_in_scope(fields, args={})
|
42
|
+
foreign_keys = tenants.map(&:foreign_key)
|
43
|
+
if args[:scope]
|
44
|
+
args[:scope] = Array.wrap(args[:scope]) << foreign_keys
|
45
|
+
else
|
46
|
+
args[:scope] = foreign_keys
|
47
|
+
end
|
48
|
+
|
49
|
+
klass.validates_uniqueness_of(fields, args)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Tenancy
|
2
|
+
class Scoping::Mongoid < Scoping
|
3
|
+
|
4
|
+
def scope_to(tenant_names)
|
5
|
+
options = tenant_names.extract_options!.dup
|
6
|
+
raise ArgumentError, "options should be blank if there are multiple tenants" if tenant_names.count > 1 and options.present?
|
7
|
+
|
8
|
+
tenant_names.each do |tenant_name|
|
9
|
+
# validates and belongs_to
|
10
|
+
klass.validates tenant_name, presence: true
|
11
|
+
klass.belongs_to tenant_name, options
|
12
|
+
|
13
|
+
tenant = Tenant.new(tenant_name, options[:class_name], klass)
|
14
|
+
self.tenants << tenant
|
15
|
+
|
16
|
+
# default_scope
|
17
|
+
klass.default_scope lambda {
|
18
|
+
if tenant.klass.current_id
|
19
|
+
klass.where(:"#{tenant.foreign_key}" => tenant.klass.current_id)
|
20
|
+
else
|
21
|
+
klass.where(nil)
|
22
|
+
end
|
23
|
+
}
|
24
|
+
|
25
|
+
# override to return current tenant_name instance
|
26
|
+
# so that it doesn't touch db
|
27
|
+
klass.send(:define_method, :"#{tenant_name}_with_tenant", lambda { |reload=false|
|
28
|
+
return send(:"#{tenant_name}_without_tenant", reload) if reload
|
29
|
+
return tenant.klass.current if send(tenant.foreign_key) == tenant.klass.current_id
|
30
|
+
send(:"#{tenant_name}_without_tenant", reload)
|
31
|
+
})
|
32
|
+
klass.alias_method_chain :"#{tenant_name}", :tenant
|
33
|
+
|
34
|
+
# override getter for mongoid 3.1
|
35
|
+
if ::Mongoid::VERSION.start_with?("3.1.")
|
36
|
+
klass.send(:define_method, tenant.foreign_key, lambda {
|
37
|
+
value = super()
|
38
|
+
if value.nil? && new_record?
|
39
|
+
self[tenant.foreign_key] = tenant.klass.current_id
|
40
|
+
end
|
41
|
+
self[tenant.foreign_key]
|
42
|
+
})
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# tenants variable is for lambda
|
47
|
+
tenants = self.tenants
|
48
|
+
klass.send(:define_method, :shard_key_selector, lambda {
|
49
|
+
selector = super()
|
50
|
+
tenants.each do |tenant|
|
51
|
+
selector[tenant.foreign_key.to_s] = send(tenant.foreign_key) if tenant.klass.current_id
|
52
|
+
end
|
53
|
+
selector
|
54
|
+
})
|
55
|
+
end
|
56
|
+
|
57
|
+
def tenant_scope(tenant_names)
|
58
|
+
scope = klass.where(nil)
|
59
|
+
tenants.each do |tenant|
|
60
|
+
next if tenant_names.include?(tenant.name.to_sym)
|
61
|
+
|
62
|
+
scope.selector.delete(tenant.foreign_key.to_s)
|
63
|
+
end
|
64
|
+
|
65
|
+
scope
|
66
|
+
end
|
67
|
+
|
68
|
+
def validates_uniqueness_in_scope(fields, args={})
|
69
|
+
foreign_keys = tenants.map(&:foreign_key)
|
70
|
+
if args[:scope]
|
71
|
+
args[:scope] = Array.wrap(args[:scope]) << foreign_keys
|
72
|
+
else
|
73
|
+
args[:scope] = foreign_keys
|
74
|
+
end
|
75
|
+
|
76
|
+
klass.validates_uniqueness_of(fields, args)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Tenancy
|
2
|
+
class Tenant
|
3
|
+
attr_accessor :name, :klass, :klass_name, :foreign_key
|
4
|
+
|
5
|
+
def initialize(name, klass_name, host_klass)
|
6
|
+
@name = name.to_sym
|
7
|
+
@klass_name = (klass_name.to_s.presence || name.to_s).classify
|
8
|
+
@klass = @klass_name.constantize
|
9
|
+
@foreign_key = host_klass.reflect_on_association(@name).foreign_key.to_sym
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
data/lib/tenancy/version.rb
CHANGED
data/spec/lib/resource_spec.rb
CHANGED
@@ -1,57 +1,101 @@
|
|
1
|
-
require
|
1
|
+
require "spec_helper"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
end
|
3
|
+
if defined?(ActiveRecord)
|
4
|
+
describe "Tenancy::Resource" do
|
5
|
+
let(:camyp) { Portal.create(id: 1, domain_name: "yp.com.kh") }
|
6
|
+
let(:panpage) { Portal.create(id: 2, domain_name: "panpages.my") }
|
7
|
+
let(:yoolk) { Portal.create(id: 3, domain_name: "yoolk.com") }
|
9
8
|
|
10
|
-
|
11
|
-
|
12
|
-
end
|
9
|
+
it "set current with instance" do
|
10
|
+
Portal.current = camyp
|
13
11
|
|
14
|
-
|
12
|
+
Portal.current.should == camyp
|
13
|
+
RequestStore.store[:"Portal.current"].should == camyp
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
it "set current with id" do
|
17
|
+
Portal.current = panpage.id
|
18
18
|
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
Portal.current.should == panpage
|
20
|
+
RequestStore.store[:"Portal.current"].should == panpage
|
21
|
+
end
|
22
22
|
|
23
|
-
|
24
|
-
|
23
|
+
it "set current with nil" do
|
24
|
+
Portal.current = panpage
|
25
|
+
Portal.current = nil
|
25
26
|
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
Portal.current.should == nil
|
28
|
+
RequestStore.store[:"Portal.current"].should == nil
|
29
|
+
end
|
29
30
|
|
30
|
-
|
31
|
-
|
32
|
-
Portal.current = nil
|
31
|
+
it "#current_id" do
|
32
|
+
Portal.current = yoolk
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
end
|
34
|
+
Portal.current_id.should == yoolk.id
|
35
|
+
end
|
37
36
|
|
38
|
-
|
39
|
-
|
37
|
+
it "#with_scope with block" do
|
38
|
+
Portal.current.should == nil
|
40
39
|
|
41
|
-
|
40
|
+
Portal.with_tenant(yoolk) do
|
41
|
+
Portal.current.should == yoolk
|
42
|
+
end
|
43
|
+
|
44
|
+
Portal.current.should == nil
|
45
|
+
end
|
46
|
+
|
47
|
+
it "#with_scope without block" do
|
48
|
+
expect { Portal.with_tenant(yoolk) }.to raise_error(ArgumentError)
|
49
|
+
end
|
42
50
|
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if defined?(Mongoid)
|
54
|
+
describe "Tenancy::Resource" do
|
55
|
+
let(:camyp) { Mongo::Portal.create(domain_name: "yp.com.kh") }
|
56
|
+
let(:panpage) { Mongo::Portal.create(domain_name: "panpages.my") }
|
57
|
+
let(:yoolk) { Mongo::Portal.create(domain_name: "yoolk.com") }
|
43
58
|
|
44
|
-
|
45
|
-
|
59
|
+
it "set current with instance" do
|
60
|
+
Mongo::Portal.current = camyp
|
46
61
|
|
47
|
-
|
48
|
-
Portal.current.should ==
|
62
|
+
Mongo::Portal.current.should == camyp
|
63
|
+
RequestStore.store[:"Mongo::Portal.current"].should == camyp
|
49
64
|
end
|
50
65
|
|
51
|
-
|
52
|
-
|
66
|
+
it "set current with id" do
|
67
|
+
Mongo::Portal.current = panpage.id
|
68
|
+
|
69
|
+
Mongo::Portal.current.should == panpage
|
70
|
+
RequestStore.store[:"Mongo::Portal.current"].should == panpage
|
71
|
+
end
|
72
|
+
|
73
|
+
it "set current with nil" do
|
74
|
+
Mongo::Portal.current = panpage
|
75
|
+
Mongo::Portal.current = nil
|
76
|
+
|
77
|
+
Mongo::Portal.current.should == nil
|
78
|
+
RequestStore.store[:"Mongo::Portal.current"].should == nil
|
79
|
+
end
|
80
|
+
|
81
|
+
it "#current_id" do
|
82
|
+
Mongo::Portal.current = yoolk
|
53
83
|
|
54
|
-
|
55
|
-
|
84
|
+
Mongo::Portal.current_id.should == yoolk.id
|
85
|
+
end
|
86
|
+
|
87
|
+
it "#with_scope with block" do
|
88
|
+
Mongo::Portal.current.should == nil
|
89
|
+
|
90
|
+
Mongo::Portal.with_tenant(yoolk) do
|
91
|
+
Mongo::Portal.current.should == yoolk
|
92
|
+
end
|
93
|
+
|
94
|
+
Mongo::Portal.current.should == nil
|
95
|
+
end
|
96
|
+
|
97
|
+
it "#with_scope without block" do
|
98
|
+
expect { Mongo::Portal.with_tenant(yoolk) }.to raise_error(ArgumentError)
|
99
|
+
end
|
56
100
|
end
|
57
101
|
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
if defined?(ActiveRecord)
|
4
|
+
describe "Tenancy::Scoping::ActiveRecord" do
|
5
|
+
let(:camyp) { Portal.create(domain_name: "yp.com.kh") }
|
6
|
+
let(:panpages) { Portal.create(domain_name: "panpages.com") }
|
7
|
+
let(:listing) { Listing.create(name: "Listing 1", portal_id: camyp.id) }
|
8
|
+
|
9
|
+
describe Listing do
|
10
|
+
it { should belong_to(: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
|
+
Portal.current = camyp
|
18
|
+
|
19
|
+
expect(Listing.where(nil).to_sql).to eq(Listing.where(portal_id: Portal.current_id).to_sql)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "doesn't have default_scope when it doesn't have current portal" do
|
23
|
+
Portal.current = nil
|
24
|
+
|
25
|
+
expect(Listing.where(nil).to_sql).not_to include(%{"listings"."portal_id" = #{Portal.current_id}})
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe 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 and :listing_id" do
|
41
|
+
Portal.current = camyp
|
42
|
+
Listing.current = listing
|
43
|
+
|
44
|
+
Communication.where(nil).to_sql.should == Communication.where(portal_id: Portal.current_id, listing_id: Listing.current_id).to_sql
|
45
|
+
end
|
46
|
+
|
47
|
+
it "doesn't have default_scope when it doesn't have current portal and listing" do
|
48
|
+
Portal.current = nil
|
49
|
+
Listing.current = nil
|
50
|
+
|
51
|
+
expect(Communication.where(nil).to_sql).not_to include(%{"communications"."portal_id" = #{Portal.current_id}})
|
52
|
+
expect(Communication.where(nil).to_sql).not_to include(%{"communications"."listing_id" = #{Listing.current_id}})
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe ExtraCommunication do
|
57
|
+
it { should belong_to(:portal) }
|
58
|
+
|
59
|
+
it { should belong_to(:listing) }
|
60
|
+
|
61
|
+
it "raise exception when passing two resources and options" do
|
62
|
+
expect { ExtraCommunication.scope_to(:portal, :listing, class_name: "Listing") }.to raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
|
65
|
+
it "uses the correct scope" do
|
66
|
+
listing2 = Listing.create(name: "Name 2", portal: camyp)
|
67
|
+
|
68
|
+
Portal.current = camyp
|
69
|
+
Listing.current = listing2
|
70
|
+
|
71
|
+
extra_communication = ExtraCommunication.new
|
72
|
+
expect(extra_communication.listing_id).to eq(listing2.id)
|
73
|
+
expect(extra_communication.portal_id).to eq(camyp.id)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "belongs_to method override" do
|
78
|
+
before(:each) { Portal.current = camyp }
|
79
|
+
|
80
|
+
it "reload belongs_to when passes true" do
|
81
|
+
listing.portal.domain_name = "abc.com"
|
82
|
+
expect(listing.portal(true).object_id).not_to eq(Portal.current.object_id)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "doesn't reload belongs_to" do
|
86
|
+
listing.portal.domain_name = "abc.com"
|
87
|
+
expect(listing.portal.object_id).to eq(Portal.current.object_id)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "returns different object" do
|
91
|
+
listing.portal_id = panpages.id
|
92
|
+
expect(listing.portal.object_id).not_to eq(Portal.current.object_id)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "doesn't touch db" do
|
96
|
+
current_listing = listing
|
97
|
+
|
98
|
+
Portal.establish_connection(adapter: "sqlite3", database: "spec/invalid.sqlite3")
|
99
|
+
expect(current_listing.portal.object_id).to eq(Portal.current.object_id)
|
100
|
+
|
101
|
+
Portal.establish_connection(ActiveRecord::Base.connection_config)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe "#tenant_scope" do
|
106
|
+
before(:each) { Portal.current = camyp }
|
107
|
+
|
108
|
+
it "scopes only :current_portal" do
|
109
|
+
Listing.current = listing
|
110
|
+
|
111
|
+
expect(Communication.tenant_scope(:portal).to_sql).not_to include(%{"communications"."listing_id" = #{Listing.current_id}})
|
112
|
+
expect(Communication.tenant_scope(:portal).to_sql).to eq(%{SELECT "communications".* FROM "communications" WHERE "communications"."is_active" = 't' AND "communications"."portal_id" = #{Portal.current_id}})
|
113
|
+
end
|
114
|
+
|
115
|
+
it "scopes only :current_listing" do
|
116
|
+
Listing.current = listing
|
117
|
+
|
118
|
+
expect(Communication.tenant_scope(:listing).to_sql).not_to include(%{"communications"."portal_id" = #{Portal.current_id}})
|
119
|
+
expect(Communication.tenant_scope(:listing).to_sql).to eq(%{SELECT "communications".* FROM "communications" WHERE "communications"."is_active" = 't' AND "communications"."listing_id" = #{Listing.current_id}})
|
120
|
+
end
|
121
|
+
|
122
|
+
it "scopes only :current_listing and :current_portal" do
|
123
|
+
Listing.current = listing
|
124
|
+
|
125
|
+
expect(Communication.tenant_scope(:listing, :portal).to_sql).to eq(Communication.where(nil).to_sql)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "scopes nothing" do
|
129
|
+
Listing.current = listing
|
130
|
+
|
131
|
+
expect(Communication.tenant_scope(nil).to_sql).to eq(%{SELECT "communications".* FROM "communications" WHERE "communications"."is_active" = 't'})
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|