acts_as_tenant 0.2.9 → 0.3.1

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.
@@ -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