acts_as_tenant 0.2.8 → 0.2.9
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/CHANGELOG.md +4 -0
- data/lib/acts_as_tenant/model_extensions.rb +117 -117
- data/lib/acts_as_tenant/version.rb +1 -1
- data/spec/model_extensions_spec.rb +298 -250
- metadata +9 -9
data/CHANGELOG.md
CHANGED
@@ -1,117 +1,117 @@
|
|
1
|
-
# ActsAsTenant
|
2
|
-
|
3
|
-
|
4
|
-
module ActsAsTenant
|
5
|
-
|
6
|
-
class << self
|
7
|
-
cattr_accessor :tenant_class
|
8
|
-
|
9
|
-
# This will also work whithin Fibers:
|
10
|
-
# http://devblog.avdi.org/2012/02/02/ruby-thread-locals-are-also-fiber-local/
|
11
|
-
def current_tenant=(tenant)
|
12
|
-
Thread.current[:current_tenant] = tenant
|
13
|
-
end
|
14
|
-
|
15
|
-
def current_tenant
|
16
|
-
Thread.current[:current_tenant]
|
17
|
-
end
|
18
|
-
|
19
|
-
# Sets the current_tenant within the given block
|
20
|
-
def with_tenant(tenant, &block)
|
21
|
-
if block.nil?
|
22
|
-
raise ArgumentError, "block required"
|
23
|
-
end
|
24
|
-
|
25
|
-
old_tenant = self.current_tenant
|
26
|
-
self.current_tenant = tenant
|
27
|
-
|
28
|
-
block.call
|
29
|
-
|
30
|
-
self.current_tenant= old_tenant
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
module ModelExtensions
|
35
|
-
extend ActiveSupport::Concern
|
36
|
-
|
37
|
-
# Alias the v_uniqueness_of method so we can scope it to the current tenant when relevant
|
38
|
-
|
39
|
-
module ClassMethods
|
40
|
-
|
41
|
-
def acts_as_tenant(association = :account)
|
42
|
-
|
43
|
-
# Method that enables checking if a class is scoped by tenant
|
44
|
-
def self.is_scoped_by_tenant?
|
45
|
-
true
|
46
|
-
end
|
47
|
-
|
48
|
-
ActsAsTenant.tenant_class ||= association
|
49
|
-
|
50
|
-
# Setup the association between the class and the tenant class
|
51
|
-
belongs_to association
|
52
|
-
|
53
|
-
# get the tenant model and its foreign key
|
54
|
-
reflection = reflect_on_association association
|
55
|
-
|
56
|
-
# As the "foreign_key" method changed name in 3.1 we check for backward compatibility
|
57
|
-
if reflection.respond_to?(:foreign_key)
|
58
|
-
fkey = reflection.foreign_key
|
59
|
-
else
|
60
|
-
fkey = reflection.association_foreign_key
|
61
|
-
end
|
62
|
-
|
63
|
-
# set the current_tenant on newly created objects
|
64
|
-
before_validation Proc.new {|m|
|
65
|
-
return unless ActsAsTenant.current_tenant
|
66
|
-
m.send "#{association}_id=".to_sym, ActsAsTenant.current_tenant.id
|
67
|
-
}, :on => :create
|
68
|
-
|
69
|
-
# set the default_scope to scope to current tenant
|
70
|
-
default_scope lambda {
|
71
|
-
where({fkey => ActsAsTenant.current_tenant.id}) if ActsAsTenant.current_tenant
|
72
|
-
}
|
73
|
-
|
74
|
-
# Rewrite accessors to make tenant foreign_key/association immutable
|
75
|
-
define_method "#{fkey}=" do |integer|
|
76
|
-
if new_record?
|
77
|
-
write_attribute(fkey, integer)
|
78
|
-
else
|
79
|
-
raise "#{fkey} is immutable! [ActsAsTenant]"
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
define_method "#{association}=" do |model|
|
84
|
-
if new_record?
|
85
|
-
super(model)
|
86
|
-
else
|
87
|
-
raise "#{association} is immutable! [ActsAsTenant]"
|
88
|
-
end
|
89
|
-
end
|
90
|
-
|
91
|
-
# add validation of associations against tenant scope
|
92
|
-
# we can't do this for polymorphic associations so we
|
93
|
-
# exempt them
|
94
|
-
reflect_on_all_associations.each do |a|
|
95
|
-
unless a == reflection || a.macro == :has_many || a.macro == :has_one || a.options[:polymorphic]
|
96
|
-
# check if the association is aliasing another class, if so
|
97
|
-
# find the unaliased class name
|
98
|
-
association_class = a.options[:class_name].nil? ? a.name.to_s.classify.constantize : a.options[:class_name].constantize
|
99
|
-
validates_each a.foreign_key.to_sym do |record, attr, value|
|
100
|
-
# Invalidate the association unless the parent is known to the tenant or no association has
|
101
|
-
# been set.
|
102
|
-
record.errors.add attr, "is invalid [ActsAsTenant]" unless value.nil? || association_class.where(:id => value).present?
|
103
|
-
end
|
104
|
-
end
|
105
|
-
end
|
106
|
-
end
|
107
|
-
|
108
|
-
def validates_uniqueness_to_tenant(fields, args ={})
|
109
|
-
raise "[ActsAsTenant] validates_uniqueness_to_tenant: no current tenant" unless respond_to?(:is_scoped_by_tenant?)
|
110
|
-
tenant_id = lambda { "#{ActsAsTenant.tenant_class.to_s.downcase}_id"}.call
|
111
|
-
args[:scope].nil? ? args[:scope] = tenant_id : args[:scope] << tenant_id
|
112
|
-
validates_uniqueness_of(fields, args)
|
113
|
-
end
|
114
|
-
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
1
|
+
# ActsAsTenant
|
2
|
+
|
3
|
+
|
4
|
+
module ActsAsTenant
|
5
|
+
|
6
|
+
class << self
|
7
|
+
cattr_accessor :tenant_class
|
8
|
+
|
9
|
+
# This will also work whithin Fibers:
|
10
|
+
# http://devblog.avdi.org/2012/02/02/ruby-thread-locals-are-also-fiber-local/
|
11
|
+
def current_tenant=(tenant)
|
12
|
+
Thread.current[:current_tenant] = tenant
|
13
|
+
end
|
14
|
+
|
15
|
+
def current_tenant
|
16
|
+
Thread.current[:current_tenant]
|
17
|
+
end
|
18
|
+
|
19
|
+
# Sets the current_tenant within the given block
|
20
|
+
def with_tenant(tenant, &block)
|
21
|
+
if block.nil?
|
22
|
+
raise ArgumentError, "block required"
|
23
|
+
end
|
24
|
+
|
25
|
+
old_tenant = self.current_tenant
|
26
|
+
self.current_tenant = tenant
|
27
|
+
|
28
|
+
block.call
|
29
|
+
|
30
|
+
self.current_tenant= old_tenant
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module ModelExtensions
|
35
|
+
extend ActiveSupport::Concern
|
36
|
+
|
37
|
+
# Alias the v_uniqueness_of method so we can scope it to the current tenant when relevant
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
|
41
|
+
def acts_as_tenant(association = :account)
|
42
|
+
|
43
|
+
# Method that enables checking if a class is scoped by tenant
|
44
|
+
def self.is_scoped_by_tenant?
|
45
|
+
true
|
46
|
+
end
|
47
|
+
|
48
|
+
ActsAsTenant.tenant_class ||= association
|
49
|
+
|
50
|
+
# Setup the association between the class and the tenant class
|
51
|
+
belongs_to association
|
52
|
+
|
53
|
+
# get the tenant model and its foreign key
|
54
|
+
reflection = reflect_on_association association
|
55
|
+
|
56
|
+
# As the "foreign_key" method changed name in 3.1 we check for backward compatibility
|
57
|
+
if reflection.respond_to?(:foreign_key)
|
58
|
+
fkey = reflection.foreign_key
|
59
|
+
else
|
60
|
+
fkey = reflection.association_foreign_key
|
61
|
+
end
|
62
|
+
|
63
|
+
# set the current_tenant on newly created objects
|
64
|
+
before_validation Proc.new {|m|
|
65
|
+
return unless ActsAsTenant.current_tenant
|
66
|
+
m.send "#{association}_id=".to_sym, ActsAsTenant.current_tenant.id
|
67
|
+
}, :on => :create
|
68
|
+
|
69
|
+
# set the default_scope to scope to current tenant
|
70
|
+
default_scope lambda {
|
71
|
+
where({fkey => ActsAsTenant.current_tenant.id}) if ActsAsTenant.current_tenant
|
72
|
+
}
|
73
|
+
|
74
|
+
# Rewrite accessors to make tenant foreign_key/association immutable
|
75
|
+
define_method "#{fkey}=" do |integer|
|
76
|
+
if new_record?
|
77
|
+
write_attribute(fkey, integer)
|
78
|
+
else
|
79
|
+
raise "#{fkey} is immutable! [ActsAsTenant]"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
define_method "#{association}=" do |model|
|
84
|
+
if new_record?
|
85
|
+
super(model)
|
86
|
+
else
|
87
|
+
raise "#{association} is immutable! [ActsAsTenant]"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# add validation of associations against tenant scope
|
92
|
+
# we can't do this for polymorphic associations so we
|
93
|
+
# exempt them
|
94
|
+
reflect_on_all_associations.each do |a|
|
95
|
+
unless a == reflection || a.macro == :has_many || a.macro == :has_one || a.macro == :has_and_belongs_to_many || a.options[:polymorphic]
|
96
|
+
# check if the association is aliasing another class, if so
|
97
|
+
# find the unaliased class name
|
98
|
+
association_class = a.options[:class_name].nil? ? a.name.to_s.classify.constantize : a.options[:class_name].constantize
|
99
|
+
validates_each a.foreign_key.to_sym do |record, attr, value|
|
100
|
+
# Invalidate the association unless the parent is known to the tenant or no association has
|
101
|
+
# been set.
|
102
|
+
record.errors.add attr, "is invalid [ActsAsTenant]" unless value.nil? || association_class.where(:id => value).present?
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def validates_uniqueness_to_tenant(fields, args ={})
|
109
|
+
raise "[ActsAsTenant] validates_uniqueness_to_tenant: no current tenant" unless respond_to?(:is_scoped_by_tenant?)
|
110
|
+
tenant_id = lambda { "#{ActsAsTenant.tenant_class.to_s.downcase}_id"}.call
|
111
|
+
args[:scope].nil? ? args[:scope] = tenant_id : args[:scope] << tenant_id
|
112
|
+
validates_uniqueness_of(fields, args)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -1,250 +1,298 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
|
3
|
-
# Setup the db
|
4
|
-
ActiveRecord::Schema.define(:version =>
|
5
|
-
create_table :accounts, :force => true do |t|
|
6
|
-
t.column :name, :string
|
7
|
-
end
|
8
|
-
|
9
|
-
create_table :projects, :force => true do |t|
|
10
|
-
t.column :name, :string
|
11
|
-
t.column :account_id, :integer
|
12
|
-
end
|
13
|
-
|
14
|
-
create_table :managers, :force => true do |t|
|
15
|
-
t.column :name, :string
|
16
|
-
t.column :project_id, :integer
|
17
|
-
t.column :account_id, :integer
|
18
|
-
end
|
19
|
-
|
20
|
-
create_table :tasks, :force => true do |t|
|
21
|
-
t.column :name, :string
|
22
|
-
t.column :account_id, :integer
|
23
|
-
t.column :project_id, :integer
|
24
|
-
t.column :completed, :boolean
|
25
|
-
end
|
26
|
-
|
27
|
-
create_table :countries, :force => true do |t|
|
28
|
-
t.column :name, :string
|
29
|
-
end
|
30
|
-
|
31
|
-
create_table :cities, :force => true do |t|
|
32
|
-
t.column :name, :string
|
33
|
-
end
|
34
|
-
|
35
|
-
create_table :sub_tasks, :force => true do |t|
|
36
|
-
t.column :name, :string
|
37
|
-
t.column :something_else, :integer
|
38
|
-
end
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
class
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
@
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
@
|
128
|
-
|
129
|
-
|
130
|
-
@
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
@
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
@
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
@
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
@
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
@
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
end
|
204
|
-
|
205
|
-
describe 'When using
|
206
|
-
before do
|
207
|
-
@
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
@
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
ActsAsTenant.current_tenant = @
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
# Setup the db
|
4
|
+
ActiveRecord::Schema.define(:version => 2) do
|
5
|
+
create_table :accounts, :force => true do |t|
|
6
|
+
t.column :name, :string
|
7
|
+
end
|
8
|
+
|
9
|
+
create_table :projects, :force => true do |t|
|
10
|
+
t.column :name, :string
|
11
|
+
t.column :account_id, :integer
|
12
|
+
end
|
13
|
+
|
14
|
+
create_table :managers, :force => true do |t|
|
15
|
+
t.column :name, :string
|
16
|
+
t.column :project_id, :integer
|
17
|
+
t.column :account_id, :integer
|
18
|
+
end
|
19
|
+
|
20
|
+
create_table :tasks, :force => true do |t|
|
21
|
+
t.column :name, :string
|
22
|
+
t.column :account_id, :integer
|
23
|
+
t.column :project_id, :integer
|
24
|
+
t.column :completed, :boolean
|
25
|
+
end
|
26
|
+
|
27
|
+
create_table :countries, :force => true do |t|
|
28
|
+
t.column :name, :string
|
29
|
+
end
|
30
|
+
|
31
|
+
create_table :cities, :force => true do |t|
|
32
|
+
t.column :name, :string
|
33
|
+
end
|
34
|
+
|
35
|
+
create_table :sub_tasks, :force => true do |t|
|
36
|
+
t.column :name, :string
|
37
|
+
t.column :something_else, :integer
|
38
|
+
end
|
39
|
+
|
40
|
+
create_table :tools, :force => true do |t|
|
41
|
+
t.column :name, :string
|
42
|
+
t.column :account_id, :integer
|
43
|
+
end
|
44
|
+
|
45
|
+
create_table :managers_tools, {:force => true, id: false} do |t|
|
46
|
+
t.integer :manager_id
|
47
|
+
t.integer :tool_id
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
# Setup the models
|
53
|
+
class Account < ActiveRecord::Base
|
54
|
+
has_many :projects
|
55
|
+
end
|
56
|
+
|
57
|
+
class Project < ActiveRecord::Base
|
58
|
+
has_one :manager
|
59
|
+
has_many :tasks
|
60
|
+
acts_as_tenant :account
|
61
|
+
|
62
|
+
validates_uniqueness_to_tenant :name
|
63
|
+
end
|
64
|
+
|
65
|
+
class Manager < ActiveRecord::Base
|
66
|
+
belongs_to :project
|
67
|
+
has_and_belongs_to_many :tools
|
68
|
+
|
69
|
+
acts_as_tenant :account
|
70
|
+
end
|
71
|
+
|
72
|
+
class Task < ActiveRecord::Base
|
73
|
+
belongs_to :project
|
74
|
+
default_scope :conditions => { :completed => nil }, :order => "name"
|
75
|
+
|
76
|
+
acts_as_tenant :account
|
77
|
+
validates_uniqueness_of :name
|
78
|
+
end
|
79
|
+
|
80
|
+
class City < ActiveRecord::Base
|
81
|
+
validates_uniqueness_of :name
|
82
|
+
end
|
83
|
+
|
84
|
+
class SubTask < ActiveRecord::Base
|
85
|
+
acts_as_tenant :account
|
86
|
+
belongs_to :something_else, :class_name => "Project"
|
87
|
+
end
|
88
|
+
|
89
|
+
class Tool < ActiveRecord::Base
|
90
|
+
has_and_belongs_to_many :managers
|
91
|
+
|
92
|
+
acts_as_tenant :account
|
93
|
+
end
|
94
|
+
|
95
|
+
# Start testing!
|
96
|
+
describe ActsAsTenant do
|
97
|
+
after { ActsAsTenant.current_tenant = nil }
|
98
|
+
|
99
|
+
describe 'Setting the current tenant' do
|
100
|
+
before { ActsAsTenant.current_tenant = :foo }
|
101
|
+
it { ActsAsTenant.current_tenant == :foo }
|
102
|
+
end
|
103
|
+
|
104
|
+
describe 'is_scoped_as_tenant should return the correct value' do
|
105
|
+
it {Project.respond_to?(:is_scoped_by_tenant?).should == true}
|
106
|
+
end
|
107
|
+
|
108
|
+
describe 'Project.all should be scoped to the current tenant if set' do
|
109
|
+
before do
|
110
|
+
@account1 = Account.create!(:name => 'foo')
|
111
|
+
@account2 = Account.create!(:name => 'bar')
|
112
|
+
|
113
|
+
@project1 = @account1.projects.create!(:name => 'foobar')
|
114
|
+
@project2 = @account2.projects.create!(:name => 'baz')
|
115
|
+
|
116
|
+
ActsAsTenant.current_tenant= @account1
|
117
|
+
@projects = Project.all
|
118
|
+
end
|
119
|
+
|
120
|
+
it { @projects.length.should == 1 }
|
121
|
+
it { @projects.should == [@project1] }
|
122
|
+
end
|
123
|
+
|
124
|
+
describe 'Project.unscoped.all should return the unscoped value' do
|
125
|
+
before do
|
126
|
+
@account1 = Account.create!(:name => 'foo')
|
127
|
+
@account2 = Account.create!(:name => 'bar')
|
128
|
+
|
129
|
+
@project1 = @account1.projects.create!(:name => 'foobar')
|
130
|
+
@project2 = @account2.projects.create!(:name => 'baz')
|
131
|
+
|
132
|
+
ActsAsTenant.current_tenant= @account1
|
133
|
+
@projects = Project.unscoped.all
|
134
|
+
end
|
135
|
+
|
136
|
+
it { @projects.length.should == 2 }
|
137
|
+
end
|
138
|
+
|
139
|
+
describe 'Associations should be correctly scoped by current tenant' do
|
140
|
+
before do
|
141
|
+
@account = Account.create!(:name => 'foo')
|
142
|
+
@project = @account.projects.create!(:name => 'foobar', :account_id => @account.id )
|
143
|
+
# the next line would normally be nearly impossible: a task assigned to a tenant project,
|
144
|
+
# but the task has no tenant assigned
|
145
|
+
@task1 = Task.create!(:name => 'no_tenant', :project => @project)
|
146
|
+
|
147
|
+
ActsAsTenant.current_tenant = @account
|
148
|
+
@task2 = @project.tasks.create!(:name => 'baz')
|
149
|
+
@tasks = @project.tasks
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'should correctly set the tenant on the task created with current_tenant set' do
|
153
|
+
@task2.account.should == @account
|
154
|
+
end
|
155
|
+
|
156
|
+
it 'should filter out the non-tenant task from the project' do
|
157
|
+
@tasks.length.should == 1
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
describe 'When dealing with a user defined default_scope' do
|
162
|
+
before do
|
163
|
+
@account = Account.create!(:name => 'foo')
|
164
|
+
@project1 = Project.create!(:name => 'inaccessible')
|
165
|
+
@task1 = Task.create!(:name => 'no_tenant', :project => @project1)
|
166
|
+
|
167
|
+
ActsAsTenant.current_tenant = @account
|
168
|
+
@project2 = Project.create!(:name => 'accessible')
|
169
|
+
@task2 = @project2.tasks.create!(:name => 'bar')
|
170
|
+
@task3 = @project2.tasks.create!(:name => 'baz')
|
171
|
+
@task4 = @project2.tasks.create!(:name => 'foo')
|
172
|
+
@task5 = @project2.tasks.create!(:name => 'foobar', :completed => true )
|
173
|
+
|
174
|
+
@tasks= Task.all
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'should apply both the tenant scope and the user defined default_scope, including :order' do
|
178
|
+
@tasks.length.should == 3
|
179
|
+
@tasks.should == [@task2, @task3, @task4]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
describe 'tenant_id should be immutable' do
|
184
|
+
before do
|
185
|
+
@account = Account.create!(:name => 'foo')
|
186
|
+
@project = @account.projects.create!(:name => 'bar')
|
187
|
+
end
|
188
|
+
|
189
|
+
it { lambda {@project.account_id = @account.id + 1}.should raise_error }
|
190
|
+
end
|
191
|
+
|
192
|
+
describe 'Associations can only be made with in-scope objects' do
|
193
|
+
before do
|
194
|
+
@account = Account.create!(:name => 'foo')
|
195
|
+
@project1 = Project.create!(:name => 'inaccessible_project', :account_id => @account.id + 1)
|
196
|
+
|
197
|
+
ActsAsTenant.current_tenant = @account
|
198
|
+
@project2 = Project.create!(:name => 'accessible_project')
|
199
|
+
@task = @project2.tasks.create!(:name => 'bar')
|
200
|
+
end
|
201
|
+
|
202
|
+
it { @task.update_attributes(:project_id => @project1.id).should == false }
|
203
|
+
end
|
204
|
+
|
205
|
+
describe 'When using validates_uniqueness_to_tenant in a aat model' do
|
206
|
+
before do
|
207
|
+
@account = Account.create!(:name => 'foo')
|
208
|
+
ActsAsTenant.current_tenant = @account
|
209
|
+
@project1 = Project.create!(:name => 'bar')
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'should not be possible to create a duplicate within the same tenant' do
|
213
|
+
@project2 = Project.create(:name => 'bar').valid?.should == false
|
214
|
+
end
|
215
|
+
|
216
|
+
it 'should be possible to create a duplicate outside the tenant scope' do
|
217
|
+
@account = Account.create!(:name => 'baz')
|
218
|
+
ActsAsTenant.current_tenant = @account
|
219
|
+
@project2 = Project.create(:name => 'bar').valid?.should == true
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
describe 'When using validates_uniqueness_of in a NON-aat model' do
|
224
|
+
before do
|
225
|
+
@city1 = City.create!(:name => 'foo')
|
226
|
+
end
|
227
|
+
it 'should not be possible to create duplicates' do
|
228
|
+
@city2 = City.create(:name => 'foo').valid?.should == false
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe "It should be possible to use aliased associations" do
|
233
|
+
it { @sub_task = SubTask.create(:name => 'foo').valid?.should == true }
|
234
|
+
end
|
235
|
+
|
236
|
+
describe "It should be possible to create and save an AaT-enabled child without it having a parent" do
|
237
|
+
@account = Account.create!(:name => 'baz')
|
238
|
+
ActsAsTenant.current_tenant = @account
|
239
|
+
Task.create(:name => 'bar').valid?.should == true
|
240
|
+
end
|
241
|
+
|
242
|
+
describe "It should be possible to use direct many-to-many associations" do
|
243
|
+
@manager = Manager.create!(:name => 'fool')
|
244
|
+
@manager.tools.new(:name => 'golden hammer')
|
245
|
+
@manager.save.should == true
|
246
|
+
end
|
247
|
+
|
248
|
+
describe "It should be possible to use direct many-to-many associations" do
|
249
|
+
@manager = Manager.create!(:name => 'fool')
|
250
|
+
@manager.tools.new(:name => 'golden hammer')
|
251
|
+
@manager.save.should == true
|
252
|
+
end
|
253
|
+
|
254
|
+
describe "When using direct many-to-many associations they are correctly scoped to the tenant" do
|
255
|
+
before do
|
256
|
+
@account1 = Account.create!(:name => 'foo')
|
257
|
+
@account2 = Account.create!(:name => 'bar')
|
258
|
+
|
259
|
+
ActsAsTenant.current_tenant= @account1
|
260
|
+
@manager1 = Manager.create!(:name => 'fool')
|
261
|
+
@tool1 = @manager1.tools.create!(:name => 'golden hammer')
|
262
|
+
|
263
|
+
ActsAsTenant.current_tenant= @account2
|
264
|
+
@manager2 = Manager.create!(:name => 'pitty')
|
265
|
+
@tool2 = @manager2.tools.create!(:name => 'golden saw')
|
266
|
+
|
267
|
+
@tools = Tool.all
|
268
|
+
end
|
269
|
+
it { @tools.should == [@tool2] }
|
270
|
+
end
|
271
|
+
|
272
|
+
describe "::with_tenant" do
|
273
|
+
it "should set current_tenant to the specified tenant inside the block" do
|
274
|
+
@account = Account.create!(:name => 'baz')
|
275
|
+
|
276
|
+
ActsAsTenant.with_tenant(@account) do
|
277
|
+
ActsAsTenant.current_tenant.should eq(@account)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
|
282
|
+
it "should return current_tenant to the previous tenant once exiting the block" do
|
283
|
+
@account1 = Account.create!(:name => 'foo')
|
284
|
+
@account2 = Account.create!(:name => 'bar')
|
285
|
+
|
286
|
+
ActsAsTenant.current_tenant = @account1
|
287
|
+
ActsAsTenant.with_tenant @account2 do
|
288
|
+
|
289
|
+
end
|
290
|
+
|
291
|
+
ActsAsTenant.current_tenant.should eq(@account1)
|
292
|
+
end
|
293
|
+
|
294
|
+
it "should raise an error when no block is provided" do
|
295
|
+
expect { ActsAsTenant.with_tenant(nil) }.to raise_error(ArgumentError, /block required/)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: acts_as_tenant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2012-11-07 00:00:00.000000000 Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
16
|
-
requirement: &
|
16
|
+
requirement: &70124434956940 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '3.1'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70124434956940
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: rspec
|
27
|
-
requirement: &
|
27
|
+
requirement: &70124434955660 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70124434955660
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: database_cleaner
|
38
|
-
requirement: &
|
38
|
+
requirement: &70124434955020 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70124434955020
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: sqlite3
|
49
|
-
requirement: &
|
49
|
+
requirement: &70124434954400 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,7 +54,7 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70124434954400
|
58
58
|
description: Integrates multi-tenancy into a Rails application in a convenient and
|
59
59
|
out-of-your way manner
|
60
60
|
email:
|