acts_as_tenant 0.2.9 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ae0af3ec60e0f5aec616181f0cb84dbf819838d5
4
+ data.tar.gz: 24f836a129e42dbe7bf60aff0eb5cf3942eabc4d
5
+ SHA512:
6
+ metadata.gz: d5946028e86ba16bde4ed1ad232b2f89d75302111e44bcb765e144688e16f21c697d1951fae80160e5263c96c43871380e007a2ef813631278c932b2896f7e2c
7
+ data.tar.gz: 2a2f574538b2507d3837b1d6d3919acf409df8599afded9127c7d991baac5ba40f85e77f135ae1655bacce560ffe14672b462a43b909958c059ede1e17f4ca0a
@@ -1,3 +1,15 @@
1
+ 0.3.1
2
+ -----
3
+ * Added support for Rails 4
4
+
5
+ 0.3.0
6
+ -----
7
+ * You can now raise an exception if a query on a scope model is made without a tenant set. Adding an initializer that sets config.require_tenant to true will accomplish this. See readme for more details.
8
+ * `ActsAsTenant.with_tenant` will now return the value of the block it evaluates instead of the original tenant. The original tenant is restored automatically.
9
+ * acts_as_tenant now raises standard errors which can be caught individually.
10
+ * `set_current_tenant_to`, which was deprecated some versions ago and could lead to weird errors, has been removed.
11
+
12
+
1
13
  0.2.9
2
14
  -----
3
15
  * Added support for many-to-many associations (thx Nucleoid)
data/README.md CHANGED
@@ -65,14 +65,19 @@ This approach is useful when running background processes for a specified tenant
65
65
  any code in this block will be scoped to the current tenant. All methods that set the current tenant are thread safe.
66
66
 
67
67
  **note:** If the current tenant is not set by one of these methods, Acts_as_tenant will be unable to apply the proper scope to your models. So make sure you use one of the two methods to tell acts_as_tenant about the current tenant.
68
-
68
+
69
+ **Require tenant to be set always**
70
+
71
+ If you want to require the tenant to be set at all times, you can configure acts_as_tenant to raise an error when a query is made without a tenant available. See below under configuarion options.
72
+
69
73
  Scoping your models
70
74
  -------------------
71
75
  class Addaccounttousers < ActiveRecord::Migration
72
76
  def up
73
77
  add_column :users, :account_id, :integer
74
78
  end
75
-
79
+ end
80
+
76
81
  class User < ActiveRecord::Base
77
82
  acts_as_tenant(:account)
78
83
  end
@@ -110,6 +115,19 @@ If you need to validate for uniqueness, chances are that you want to scope this
110
115
 
111
116
  All options available to Rails' own `validates_uniqueness_of` are also available to this method.
112
117
 
118
+ Configuration options
119
+ ---------------------
120
+ An initializer can be created to control (currently one) option in ActsAsTenant. Defaults
121
+ are shown below with sample overrides following. In `config/initializer/acts_as_tenant.rb`:
122
+
123
+ ```ruby
124
+ ActsAsTenant.configure do |config|
125
+ config.require_tenant = false # true
126
+ end
127
+ ```
128
+
129
+ * `config.require_tenant` when set to true will raise an ActsAsTenant::NoTenant error whenever a query is made without a tenant set.
130
+
113
131
  Note on testing
114
132
  ---------------
115
133
  Whenever you set the `current_tenant` in your tests, either through integration tests or directly by calling `ActsAsTenant.current_tenant = some_tenant`, make sure to clean up the tenant after each test by calling `ActsAsTenant.current_tenant = nil`.
@@ -18,9 +18,13 @@ Gem::Specification.new do |s|
18
18
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
19
  s.require_paths = ["lib"]
20
20
 
21
+ #add_runtime_dependency("rails")
22
+ s.add_runtime_dependency('request_store', '>= 1.0.5')
21
23
  s.add_dependency('rails','>= 3.1')
24
+ #s.add_dependency('request_store', '>= 1.0.5')
22
25
 
23
26
  s.add_development_dependency('rspec')
24
27
  s.add_development_dependency('database_cleaner')
25
28
  s.add_development_dependency('sqlite3')
29
+ s.add_development_dependency('debugger')
26
30
  end
@@ -3,13 +3,15 @@
3
3
  require "active_record"
4
4
  require "action_controller"
5
5
  require "active_model"
6
+ require "request_store"
6
7
 
7
8
  #$LOAD_PATH.unshift(File.dirname(__FILE__))
8
9
 
9
- require "acts_as_tenant"
10
10
  require "acts_as_tenant/version"
11
- require "acts_as_tenant/controller_extensions.rb"
12
- require "acts_as_tenant/model_extensions.rb"
11
+ require "acts_as_tenant/errors"
12
+ require "acts_as_tenant/configuration"
13
+ require "acts_as_tenant/controller_extensions"
14
+ require "acts_as_tenant/model_extensions"
13
15
 
14
16
  #$LOAD_PATH.shift
15
17
 
@@ -18,5 +20,6 @@ if defined?(ActiveRecord::Base)
18
20
  ActionController::Base.extend ActsAsTenant::ControllerExtensions
19
21
  end
20
22
 
21
-
23
+ module ActsAsTenant
24
+ end
22
25
 
@@ -0,0 +1,26 @@
1
+ module ActsAsTenant
2
+ @@configuration = nil
3
+
4
+ def self.configure
5
+ @@configuration = Configuration.new
6
+
7
+ if block_given?
8
+ yield configuration
9
+ end
10
+
11
+ configuration
12
+ end
13
+
14
+ def self.configuration
15
+ @@configuration || configure
16
+ end
17
+
18
+ class Configuration
19
+ attr_writer :require_tenant
20
+
21
+ def require_tenant
22
+ @require_tenant ||= false
23
+ end
24
+
25
+ end
26
+ end
@@ -2,29 +2,25 @@ module ActsAsTenant
2
2
  module ControllerExtensions
3
3
 
4
4
  # this method allows setting the current_tenant by reading the subdomain and looking
5
- # it up in the tenant-model passed to the method (defaults to Account). The method will
6
- # look for the subdomain in a column referenced by the second argument (defaults to subdomain).
5
+ # it up in the tenant-model passed to the method. The method will look for the subdomain
6
+ # in a column referenced by the second argument.
7
7
  def set_current_tenant_by_subdomain(tenant = :account, column = :subdomain )
8
8
  self.class_eval do
9
9
  cattr_accessor :tenant_class, :tenant_column
10
- attr_accessor :current_tenant
11
10
  end
12
-
11
+
13
12
  self.tenant_class = tenant.to_s.camelcase.constantize
14
13
  self.tenant_column = column.to_sym
15
-
14
+
16
15
  self.class_eval do
17
16
  before_filter :find_tenant_by_subdomain
18
-
19
17
  helper_method :current_tenant
20
18
 
21
19
  private
22
20
  def find_tenant_by_subdomain
23
21
  ActsAsTenant.current_tenant = tenant_class.where(tenant_column => request.subdomains.first).first
24
- @current_tenant_instance = ActsAsTenant.current_tenant
25
22
  end
26
23
 
27
- # helper method to have the current_tenant available in the controller
28
24
  def current_tenant
29
25
  ActsAsTenant.current_tenant
30
26
  end
@@ -41,31 +37,7 @@ module ActsAsTenant
41
37
  ActsAsTenant.current_tenant = current_tenant_object
42
38
  end
43
39
 
44
- private
45
- # helper method to have the current_tenant available in the controller
46
- def current_tenant
47
- ActsAsTenant.current_tenant
48
- end
49
- end
50
- end
51
-
52
-
53
-
54
- # this method allows manual setting of the current_tenant by passing in a tenant object
55
- #
56
- def set_current_tenant_to(current_tenant_object)
57
- self.class_eval do
58
- cattr_accessor :tenant_class
59
- attr_accessor :current_tenant
60
- before_filter lambda {
61
- ActiveSupport::Deprecation.warn "set_current_tenant_to is deprecated and will be removed from Acts_as_tenant in a future releases, please use set_current_tenant_through_filter instead.", caller
62
- @current_tenant_instance = ActsAsTenant.current_tenant = current_tenant_object
63
- }
64
-
65
- helper_method :current_tenant
66
-
67
- private
68
- # helper method to have the current_tenant available in the controller
40
+ private
69
41
  def current_tenant
70
42
  ActsAsTenant.current_tenant
71
43
  end
@@ -0,0 +1,19 @@
1
+ module ActsAsTenant
2
+ class Error < StandardError
3
+ end
4
+
5
+ module Errors
6
+ class ModelNotScopedByTenant < ActsAsTenant::Error
7
+ end
8
+
9
+ class NoTenantSet < ActsAsTenant::Error
10
+ end
11
+
12
+ class ModelNotScopedByTenant < ActsAsTenant::Error
13
+ end
14
+
15
+ class TenantIsImmutable < ActsAsTenant::Error
16
+ end
17
+
18
+ end
19
+ end
@@ -1,117 +1,104 @@
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
+ module ActsAsTenant
2
+ @@tenant_klass = nil
3
+
4
+ def self.set_tenant_klass(klass)
5
+ @@tenant_klass = klass
6
+ end
7
+
8
+ def self.tenant_klass
9
+ @@tenant_klass
10
+ end
11
+
12
+ def self.fkey
13
+ "#{@@tenant_klass.to_s}_id"
14
+ end
15
+
16
+ def self.current_tenant=(tenant)
17
+ RequestStore.store[:current_tenant] = tenant
18
+ end
19
+
20
+ def self.current_tenant
21
+ RequestStore.store[:current_tenant]
22
+ end
23
+
24
+ def self.with_tenant(tenant, &block)
25
+ if block.nil?
26
+ raise ArgumentError, "block required"
27
+ end
28
+
29
+ old_tenant = self.current_tenant
30
+ self.current_tenant = tenant
31
+ value = block.call
32
+ self.current_tenant = old_tenant
33
+ return value
34
+ end
35
+
36
+ module ModelExtensions
37
+ def self.included(base)
38
+ base.extend(ClassMethods)
39
+ end
40
+
41
+ module ClassMethods
42
+ def acts_as_tenant(association = :account)
43
+ belongs_to association
44
+ ActsAsTenant.set_tenant_klass(association)
45
+
46
+ default_scope lambda {
47
+ if ActsAsTenant.configuration.require_tenant && ActsAsTenant.current_tenant.nil?
48
+ raise ActsAsTenant::Errors::NoTenantSet
49
+ end
50
+ where({ActsAsTenant.fkey => ActsAsTenant.current_tenant.id}) if ActsAsTenant.current_tenant
51
+ }
52
+
53
+ # Add the following validations to the receiving model:
54
+ # - new instances should have the tenant set
55
+ # - validate that associations belong to the tenant, currently only for belongs_to
56
+ #
57
+ before_validation Proc.new {|m|
58
+ if ActsAsTenant.current_tenant
59
+ m.send "#{association}_id=".to_sym, ActsAsTenant.current_tenant.id
60
+ end
61
+ }, :on => :create
62
+
63
+ reflect_on_all_associations.each do |a|
64
+ unless a == reflect_on_association(association) || a.macro != :belongs_to || a.options[:polymorphic]
65
+ association_class = a.options[:class_name].nil? ? a.name.to_s.classify.constantize : a.options[:class_name].constantize
66
+ validates_each a.foreign_key.to_sym do |record, attr, value|
67
+ record.errors.add attr, "association is invalid [ActsAsTenant]" unless value.nil? || association_class.where(:id => value).present?
68
+ end
69
+ end
70
+ end
71
+
72
+ # Dynamically generate the following methods:
73
+ # - Rewrite the accessors to make tenant immutable
74
+ # - Add a helper method to verify if a model has been scoped by AaT
75
+ #
76
+ define_method "#{ActsAsTenant.fkey}=" do |integer|
77
+ raise ActsAsTenant::Errors::TenantIsImmutable unless new_record?
78
+ write_attribute("#{ActsAsTenant.fkey}", integer)
79
+ end
80
+
81
+ define_method "#{ActsAsTenant.tenant_klass.to_s}=" do |model|
82
+ raise ActsAsTenant::Errors::TenantIsImmutable unless new_record?
83
+ super(model)
84
+ end
85
+
86
+ def scoped_by_tenant?
87
+ true
88
+ end
89
+ end
90
+
91
+ def validates_uniqueness_to_tenant(fields, args ={})
92
+ raise ActsAsTenant::Errors::ModelNotScopedByTenant unless respond_to?(:scoped_by_tenant?)
93
+ tenant_id = lambda { "#{ActsAsTenant.fkey}"}.call
94
+ if args[:scope]
95
+ args[:scope] = Array(args[:scope]) << tenant_id
96
+ else
97
+ args[:scope] = tenant_id
98
+ end
99
+
100
+ validates_uniqueness_of(fields, args)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -1,3 +1,3 @@
1
1
  module ActsAsTenant
2
- VERSION = "0.2.9"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActsAsTenant::Configuration do
4
+ describe 'no configuration given' do
5
+ before do
6
+ ActsAsTenant.configure
7
+ end
8
+
9
+ it 'provides defaults' do
10
+ ActsAsTenant.configuration.require_tenant.should_not be_true
11
+ end
12
+ end
13
+
14
+ describe 'with config block' do
15
+ after do
16
+ ActsAsTenant.configure
17
+ end
18
+
19
+ it 'stores config' do
20
+ ActsAsTenant.configure do |config|
21
+ config.require_tenant = true
22
+ end
23
+
24
+ ActsAsTenant.configuration.require_tenant.should be_true
25
+ end
26
+
27
+ end
28
+ end
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  # Setup the db
4
- ActiveRecord::Schema.define(:version => 2) do
4
+ ActiveRecord::Schema.define(:version => 1) do
5
5
  create_table :accounts, :force => true do |t|
6
6
  t.column :name, :string
7
7
  end
@@ -10,43 +10,35 @@ ActiveRecord::Schema.define(:version => 2) do
10
10
  t.column :name, :string
11
11
  t.column :account_id, :integer
12
12
  end
13
-
13
+
14
14
  create_table :managers, :force => true do |t|
15
15
  t.column :name, :string
16
16
  t.column :project_id, :integer
17
17
  t.column :account_id, :integer
18
18
  end
19
-
19
+
20
20
  create_table :tasks, :force => true do |t|
21
21
  t.column :name, :string
22
22
  t.column :account_id, :integer
23
23
  t.column :project_id, :integer
24
24
  t.column :completed, :boolean
25
25
  end
26
-
26
+
27
27
  create_table :countries, :force => true do |t|
28
28
  t.column :name, :string
29
29
  end
30
-
30
+
31
31
  create_table :cities, :force => true do |t|
32
32
  t.column :name, :string
33
33
  end
34
-
34
+
35
35
  create_table :sub_tasks, :force => true do |t|
36
36
  t.column :name, :string
37
+ t.column :attribute2, :string
37
38
  t.column :something_else, :integer
38
- end
39
-
40
- create_table :tools, :force => true do |t|
41
- t.column :name, :string
42
39
  t.column :account_id, :integer
43
40
  end
44
41
 
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
42
  end
51
43
 
52
44
  # Setup the models
@@ -58,21 +50,19 @@ class Project < ActiveRecord::Base
58
50
  has_one :manager
59
51
  has_many :tasks
60
52
  acts_as_tenant :account
61
-
53
+
62
54
  validates_uniqueness_to_tenant :name
63
55
  end
64
56
 
65
57
  class Manager < ActiveRecord::Base
66
58
  belongs_to :project
67
- has_and_belongs_to_many :tools
68
-
69
59
  acts_as_tenant :account
70
60
  end
71
61
 
72
62
  class Task < ActiveRecord::Base
73
63
  belongs_to :project
74
- default_scope :conditions => { :completed => nil }, :order => "name"
75
-
64
+ default_scope -> { where(:completed => nil).order("name") }
65
+
76
66
  acts_as_tenant :account
77
67
  validates_uniqueness_of :name
78
68
  end
@@ -84,12 +74,7 @@ end
84
74
  class SubTask < ActiveRecord::Base
85
75
  acts_as_tenant :account
86
76
  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
77
+ validates_uniqueness_to_tenant :name, scope: :attribute2
93
78
  end
94
79
 
95
80
  # Start testing!
@@ -100,11 +85,11 @@ describe ActsAsTenant do
100
85
  before { ActsAsTenant.current_tenant = :foo }
101
86
  it { ActsAsTenant.current_tenant == :foo }
102
87
  end
103
-
88
+
104
89
  describe 'is_scoped_as_tenant should return the correct value' do
105
- it {Project.respond_to?(:is_scoped_by_tenant?).should == true}
90
+ it {Project.respond_to?(:scoped_by_tenant?).should == true}
106
91
  end
107
-
92
+
108
93
  describe 'Project.all should be scoped to the current tenant if set' do
109
94
  before do
110
95
  @account1 = Account.create!(:name => 'foo')
@@ -112,15 +97,15 @@ describe ActsAsTenant do
112
97
 
113
98
  @project1 = @account1.projects.create!(:name => 'foobar')
114
99
  @project2 = @account2.projects.create!(:name => 'baz')
115
-
100
+
116
101
  ActsAsTenant.current_tenant= @account1
117
102
  @projects = Project.all
118
103
  end
119
-
104
+
120
105
  it { @projects.length.should == 1 }
121
106
  it { @projects.should == [@project1] }
122
107
  end
123
-
108
+
124
109
  describe 'Project.unscoped.all should return the unscoped value' do
125
110
  before do
126
111
  @account1 = Account.create!(:name => 'foo')
@@ -128,147 +113,123 @@ describe ActsAsTenant do
128
113
 
129
114
  @project1 = @account1.projects.create!(:name => 'foobar')
130
115
  @project2 = @account2.projects.create!(:name => 'baz')
131
-
116
+
132
117
  ActsAsTenant.current_tenant= @account1
133
- @projects = Project.unscoped.all
118
+ @projects = Project.unscoped
134
119
  end
135
-
136
- it { @projects.length.should == 2 }
120
+
121
+ it { @projects.count.should == 2 }
137
122
  end
138
-
123
+
139
124
  describe 'Associations should be correctly scoped by current tenant' do
140
125
  before do
141
126
  @account = Account.create!(:name => 'foo')
142
127
  @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,
128
+ # the next line would normally be nearly impossible: a task assigned to a tenant project,
144
129
  # but the task has no tenant assigned
145
- @task1 = Task.create!(:name => 'no_tenant', :project => @project)
146
-
130
+ @task1 = Task.create!(:name => 'no_tenant', :project => @project)
131
+
147
132
  ActsAsTenant.current_tenant = @account
148
133
  @task2 = @project.tasks.create!(:name => 'baz')
149
134
  @tasks = @project.tasks
150
135
  end
151
-
136
+
152
137
  it 'should correctly set the tenant on the task created with current_tenant set' do
153
138
  @task2.account.should == @account
154
139
  end
155
-
140
+
156
141
  it 'should filter out the non-tenant task from the project' do
157
142
  @tasks.length.should == 1
158
143
  end
159
144
  end
160
-
145
+
161
146
  describe 'When dealing with a user defined default_scope' do
162
147
  before do
163
148
  @account = Account.create!(:name => 'foo')
164
149
  @project1 = Project.create!(:name => 'inaccessible')
165
150
  @task1 = Task.create!(:name => 'no_tenant', :project => @project1)
166
-
151
+
167
152
  ActsAsTenant.current_tenant = @account
168
153
  @project2 = Project.create!(:name => 'accessible')
169
154
  @task2 = @project2.tasks.create!(:name => 'bar')
170
155
  @task3 = @project2.tasks.create!(:name => 'baz')
171
156
  @task4 = @project2.tasks.create!(:name => 'foo')
172
157
  @task5 = @project2.tasks.create!(:name => 'foobar', :completed => true )
173
-
158
+
174
159
  @tasks= Task.all
175
160
  end
176
-
161
+
177
162
  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]
163
+ @tasks.length.should == 3
164
+ @tasks.should == [@task2, @task3, @task4]
180
165
  end
181
166
  end
182
-
167
+
183
168
  describe 'tenant_id should be immutable' do
184
169
  before do
185
170
  @account = Account.create!(:name => 'foo')
186
171
  @project = @account.projects.create!(:name => 'bar')
187
172
  end
188
-
173
+
189
174
  it { lambda {@project.account_id = @account.id + 1}.should raise_error }
190
175
  end
191
-
176
+
192
177
  describe 'Associations can only be made with in-scope objects' do
193
178
  before do
194
179
  @account = Account.create!(:name => 'foo')
195
180
  @project1 = Project.create!(:name => 'inaccessible_project', :account_id => @account.id + 1)
196
-
181
+
197
182
  ActsAsTenant.current_tenant = @account
198
183
  @project2 = Project.create!(:name => 'accessible_project')
199
184
  @task = @project2.tasks.create!(:name => 'bar')
200
185
  end
201
-
186
+
202
187
  it { @task.update_attributes(:project_id => @project1.id).should == false }
203
188
  end
204
-
189
+
205
190
  describe 'When using validates_uniqueness_to_tenant in a aat model' do
206
191
  before do
207
192
  @account = Account.create!(:name => 'foo')
208
193
  ActsAsTenant.current_tenant = @account
209
194
  @project1 = Project.create!(:name => 'bar')
210
195
  end
211
-
196
+
212
197
  it 'should not be possible to create a duplicate within the same tenant' do
213
- @project2 = Project.create(:name => 'bar').valid?.should == false
198
+ Project.create(:name => 'bar').valid?.should == false
214
199
  end
215
-
200
+
216
201
  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
202
+ account = Account.create!(:name => 'baz')
203
+ ActsAsTenant.current_tenant = account
204
+ Project.create(:name => 'bar').valid?.should == true
205
+ end
206
+
207
+ it 'applies additional scopes' do
208
+ subtask1 = SubTask.create!(:name => 'foo', :attribute2 => 'unique_scope')
209
+ SubTask.create(:name => 'foo', :attribute2 => 'another_scope').should be_valid
210
+ SubTask.create(:name => 'foo', :attribute2 => 'unique_scope').should_not be_valid
220
211
  end
221
212
  end
222
-
213
+
223
214
  describe 'When using validates_uniqueness_of in a NON-aat model' do
224
215
  before do
225
216
  @city1 = City.create!(:name => 'foo')
226
217
  end
227
218
  it 'should not be possible to create duplicates' do
228
- @city2 = City.create(:name => 'foo').valid?.should == false
219
+ City.create(:name => 'foo').valid?.should == false
229
220
  end
230
221
  end
231
-
222
+
232
223
  describe "It should be possible to use aliased associations" do
233
- it { @sub_task = SubTask.create(:name => 'foo').valid?.should == true }
224
+ it { SubTask.create(:name => 'foo').valid?.should == true }
234
225
  end
235
-
226
+
236
227
  describe "It should be possible to create and save an AaT-enabled child without it having a parent" do
237
228
  @account = Account.create!(:name => 'baz')
238
229
  ActsAsTenant.current_tenant = @account
239
230
  Task.create(:name => 'bar').valid?.should == true
240
231
  end
241
232
 
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
233
  describe "::with_tenant" do
273
234
  it "should set current_tenant to the specified tenant inside the block" do
274
235
  @account = Account.create!(:name => 'baz')
@@ -278,11 +239,10 @@ describe ActsAsTenant do
278
239
  end
279
240
  end
280
241
 
281
-
282
- it "should return current_tenant to the previous tenant once exiting the block" do
242
+ it "should reset current_tenant to the previous tenant once exiting the block" do
283
243
  @account1 = Account.create!(:name => 'foo')
284
244
  @account2 = Account.create!(:name => 'bar')
285
-
245
+
286
246
  ActsAsTenant.current_tenant = @account1
287
247
  ActsAsTenant.with_tenant @account2 do
288
248
 
@@ -291,8 +251,47 @@ describe ActsAsTenant do
291
251
  ActsAsTenant.current_tenant.should eq(@account1)
292
252
  end
293
253
 
254
+ it "should return the value of the block" do
255
+ @account1 = Account.create!(:name => 'foo')
256
+ @account2 = Account.create!(:name => 'bar')
257
+
258
+ ActsAsTenant.current_tenant = @account1
259
+ value = ActsAsTenant.with_tenant @account2 do
260
+ "something"
261
+ end
262
+
263
+ value.should eq "something"
264
+ end
265
+
294
266
  it "should raise an error when no block is provided" do
295
267
  expect { ActsAsTenant.with_tenant(nil) }.to raise_error(ArgumentError, /block required/)
296
268
  end
297
269
  end
270
+
271
+ context "tenant required" do
272
+ describe "raises exception if no tenant specified" do
273
+ before do
274
+ @account1 = Account.create!(:name => 'foo')
275
+ @project1 = @account1.projects.create!(:name => 'foobar')
276
+ ActsAsTenant.configuration.stub(require_tenant: true)
277
+ end
278
+
279
+ it "should raise an error when no tenant is provided" do
280
+ expect { Project.all.load }.to raise_error(ActsAsTenant::Errors::NoTenantSet)
281
+ end
282
+ end
283
+ end
284
+
285
+ context "no tenant required" do
286
+ describe "does not raise exception if no tenant specified" do
287
+ before do
288
+ @account1 = Account.create!(:name => 'foo')
289
+ @project1 = @account1.projects.create!(:name => 'foobar')
290
+ end
291
+
292
+ it "should not raise an error when no tenant is provided" do
293
+ expect { Project.all }.to_not raise_error
294
+ end
295
+ end
296
+ end
298
297
  end
@@ -1,30 +1,14 @@
1
1
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
2
  $LOAD_PATH.unshift(File.dirname(__FILE__))
3
- require 'rspec'
4
- require 'active_record'
5
- require 'action_controller'
6
- require 'logger'
7
- require 'database_cleaner'
8
-
9
- require 'acts_as_tenant/model_extensions'
10
- require 'acts_as_tenant/controller_extensions'
11
3
 
12
- ActiveRecord::Base.send(:include, ActsAsTenant::ModelExtensions)
13
- ActionController::Base.extend ActsAsTenant::ControllerExtensions
4
+ require 'database_cleaner'
5
+ require 'acts_as_tenant'
14
6
 
15
7
  config = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'database.yml')))
16
8
  ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
17
9
  ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
18
10
 
19
- # Requires supporting files with custom matchers and macros, etc,
20
- # in ./support/ and its subdirectories.
21
- #Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
22
-
23
- #RSpec.configure do |config|
24
- #end
25
-
26
11
  RSpec.configure do |config|
27
-
28
12
  config.before(:suite) do
29
13
  DatabaseCleaner.strategy = :transaction
30
14
  DatabaseCleaner.clean_with(:truncation)
@@ -36,6 +20,7 @@ RSpec.configure do |config|
36
20
 
37
21
  config.after(:each) do
38
22
  DatabaseCleaner.clean
23
+ ActsAsTenant.current_tenant = nil
39
24
  end
40
25
 
41
26
  end
metadata CHANGED
@@ -1,60 +1,99 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acts_as_tenant
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.9
5
- prerelease:
4
+ version: 0.3.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Erwin Matthijssen
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-11-07 00:00:00.000000000 Z
11
+ date: 2013-06-24 00:00:00.000000000 Z
13
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: request_store
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.0.5
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.0.5
14
27
  - !ruby/object:Gem::Dependency
15
28
  name: rails
16
- requirement: &70124434956940 !ruby/object:Gem::Requirement
17
- none: false
29
+ requirement: !ruby/object:Gem::Requirement
18
30
  requirements:
19
- - - ! '>='
31
+ - - '>='
20
32
  - !ruby/object:Gem::Version
21
33
  version: '3.1'
22
34
  type: :runtime
23
35
  prerelease: false
24
- version_requirements: *70124434956940
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '3.1'
25
41
  - !ruby/object:Gem::Dependency
26
42
  name: rspec
27
- requirement: &70124434955660 !ruby/object:Gem::Requirement
28
- none: false
43
+ requirement: !ruby/object:Gem::Requirement
29
44
  requirements:
30
- - - ! '>='
45
+ - - '>='
31
46
  - !ruby/object:Gem::Version
32
47
  version: '0'
33
48
  type: :development
34
49
  prerelease: false
35
- version_requirements: *70124434955660
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
36
55
  - !ruby/object:Gem::Dependency
37
56
  name: database_cleaner
38
- requirement: &70124434955020 !ruby/object:Gem::Requirement
39
- none: false
57
+ requirement: !ruby/object:Gem::Requirement
40
58
  requirements:
41
- - - ! '>='
59
+ - - '>='
42
60
  - !ruby/object:Gem::Version
43
61
  version: '0'
44
62
  type: :development
45
63
  prerelease: false
46
- version_requirements: *70124434955020
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
47
69
  - !ruby/object:Gem::Dependency
48
70
  name: sqlite3
49
- requirement: &70124434954400 !ruby/object:Gem::Requirement
50
- none: false
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
51
79
  requirements:
52
- - - ! '>='
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: debugger
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
53
88
  - !ruby/object:Gem::Version
54
89
  version: '0'
55
90
  type: :development
56
91
  prerelease: false
57
- version_requirements: *70124434954400
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
58
97
  description: Integrates multi-tenancy into a Rails application in a convenient and
59
98
  out-of-your way manner
60
99
  email:
@@ -71,38 +110,41 @@ files:
71
110
  - Rakefile
72
111
  - acts_as_tenant.gemspec
73
112
  - lib/acts_as_tenant.rb
113
+ - lib/acts_as_tenant/configuration.rb
74
114
  - lib/acts_as_tenant/controller_extensions.rb
115
+ - lib/acts_as_tenant/errors.rb
75
116
  - lib/acts_as_tenant/model_extensions.rb
76
117
  - lib/acts_as_tenant/version.rb
77
118
  - rails/init.rb
119
+ - spec/acts_as_tenant/configuration_spec.rb
120
+ - spec/acts_as_tenant/model_extensions_spec.rb
78
121
  - spec/database.yml
79
- - spec/model_extensions_spec.rb
80
122
  - spec/spec_helper.rb
81
123
  homepage: http://www.rollcallapp.com/blog
82
124
  licenses: []
125
+ metadata: {}
83
126
  post_install_message:
84
127
  rdoc_options: []
85
128
  require_paths:
86
129
  - lib
87
130
  required_ruby_version: !ruby/object:Gem::Requirement
88
- none: false
89
131
  requirements:
90
- - - ! '>='
132
+ - - '>='
91
133
  - !ruby/object:Gem::Version
92
134
  version: '0'
93
135
  required_rubygems_version: !ruby/object:Gem::Requirement
94
- none: false
95
136
  requirements:
96
- - - ! '>='
137
+ - - '>='
97
138
  - !ruby/object:Gem::Version
98
139
  version: '0'
99
140
  requirements: []
100
141
  rubyforge_project: acts_as_tenant
101
- rubygems_version: 1.8.15
142
+ rubygems_version: 2.0.3
102
143
  signing_key:
103
- specification_version: 3
144
+ specification_version: 4
104
145
  summary: Add multi-tenancy to Rails applications using a shared db strategy
105
146
  test_files:
147
+ - spec/acts_as_tenant/configuration_spec.rb
148
+ - spec/acts_as_tenant/model_extensions_spec.rb
106
149
  - spec/database.yml
107
- - spec/model_extensions_spec.rb
108
150
  - spec/spec_helper.rb