acts_as_tenant 0.4.4 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (111) hide show
  1. checksums.yaml +5 -5
  2. data/README.md +86 -25
  3. data/Rakefile +5 -3
  4. data/lib/acts_as_tenant.rb +112 -14
  5. data/lib/acts_as_tenant/configuration.rb +6 -19
  6. data/lib/acts_as_tenant/controller_extensions.rb +10 -59
  7. data/lib/acts_as_tenant/controller_extensions/filter.rb +13 -0
  8. data/lib/acts_as_tenant/controller_extensions/subdomain.rb +20 -0
  9. data/lib/acts_as_tenant/controller_extensions/subdomain_or_domain.rb +20 -0
  10. data/lib/acts_as_tenant/errors.rb +3 -4
  11. data/lib/acts_as_tenant/model_extensions.rb +33 -136
  12. data/lib/acts_as_tenant/sidekiq.rb +11 -7
  13. data/lib/acts_as_tenant/tenant_helper.rb +7 -0
  14. data/lib/acts_as_tenant/test_tenant_middleware.rb +15 -0
  15. data/lib/acts_as_tenant/version.rb +1 -1
  16. data/spec/acts_as_tenant/configuration_spec.rb +8 -19
  17. data/spec/acts_as_tenant/sidekiq_spec.rb +14 -19
  18. data/spec/{acts_as_tenant/tenant_by_filter_spec.rb → controllers/filter_spec.rb} +7 -12
  19. data/spec/controllers/subdomain_or_domain_spec.rb +55 -0
  20. data/spec/controllers/subdomain_spec.rb +49 -0
  21. data/spec/dummy/.ruby-version +1 -0
  22. data/spec/dummy/Rakefile +6 -0
  23. data/spec/dummy/app/assets/config/manifest.js +2 -0
  24. data/spec/dummy/app/assets/images/.keep +0 -0
  25. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  26. data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
  27. data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
  28. data/spec/dummy/app/controllers/application_controller.rb +2 -0
  29. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  30. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  31. data/spec/dummy/app/javascript/packs/application.js +15 -0
  32. data/spec/dummy/app/jobs/application_job.rb +7 -0
  33. data/spec/dummy/app/mailers/application_mailer.rb +4 -0
  34. data/spec/dummy/app/mailers/user_mailer.rb +5 -0
  35. data/spec/dummy/app/models/account.rb +4 -0
  36. data/spec/dummy/app/models/aliased_task.rb +4 -0
  37. data/spec/dummy/app/models/article.rb +3 -0
  38. data/spec/dummy/app/models/comment.rb +5 -0
  39. data/spec/dummy/app/models/concerns/.keep +0 -0
  40. data/spec/dummy/app/models/custom_counter_cache_task.rb +4 -0
  41. data/spec/dummy/app/models/custom_foreign_key_task.rb +4 -0
  42. data/spec/dummy/app/models/custom_primary_key_task.rb +4 -0
  43. data/spec/dummy/app/models/global_project.rb +6 -0
  44. data/spec/dummy/app/models/manager.rb +4 -0
  45. data/spec/dummy/app/models/polymorphic_tenant_comment.rb +5 -0
  46. data/spec/dummy/app/models/project.rb +8 -0
  47. data/spec/dummy/app/models/task.rb +7 -0
  48. data/spec/dummy/app/models/unique_task.rb +5 -0
  49. data/spec/dummy/app/models/unscoped_model.rb +3 -0
  50. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  51. data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
  52. data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
  53. data/spec/dummy/bin/rails +4 -0
  54. data/spec/dummy/bin/rake +4 -0
  55. data/spec/dummy/bin/setup +33 -0
  56. data/spec/dummy/config.ru +5 -0
  57. data/spec/dummy/config/application.rb +19 -0
  58. data/spec/dummy/config/boot.rb +5 -0
  59. data/spec/dummy/config/cable.yml +10 -0
  60. data/spec/dummy/config/database.yml +19 -0
  61. data/spec/dummy/config/environment.rb +5 -0
  62. data/spec/dummy/config/environments/development.rb +62 -0
  63. data/spec/dummy/config/environments/production.rb +112 -0
  64. data/spec/dummy/config/environments/test.rb +49 -0
  65. data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
  66. data/spec/dummy/config/initializers/assets.rb +12 -0
  67. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  68. data/spec/dummy/config/initializers/content_security_policy.rb +28 -0
  69. data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
  70. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  71. data/spec/dummy/config/initializers/inflections.rb +16 -0
  72. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  73. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  74. data/spec/dummy/config/locales/en.yml +40 -0
  75. data/spec/dummy/config/puma.rb +38 -0
  76. data/spec/dummy/config/routes.rb +4 -0
  77. data/spec/dummy/config/spring.rb +6 -0
  78. data/spec/dummy/config/storage.yml +34 -0
  79. data/spec/dummy/db/schema.rb +84 -0
  80. data/spec/dummy/lib/assets/.keep +0 -0
  81. data/spec/dummy/log/.keep +0 -0
  82. data/spec/dummy/public/404.html +67 -0
  83. data/spec/dummy/public/422.html +67 -0
  84. data/spec/dummy/public/500.html +66 -0
  85. data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
  86. data/spec/dummy/public/apple-touch-icon.png +0 -0
  87. data/spec/dummy/public/favicon.ico +0 -0
  88. data/spec/dummy/tmp/development_secret.txt +1 -0
  89. data/spec/fixtures/accounts.yml +10 -0
  90. data/spec/fixtures/custom_primary_key_tasks.yml +2 -0
  91. data/spec/fixtures/global_projects.yml +13 -0
  92. data/spec/fixtures/projects.yml +10 -0
  93. data/spec/helpers/tenant_helper_spec.rb +16 -0
  94. data/spec/middlewares/test_tenant_middleware_spec.rb +86 -0
  95. data/spec/models/model_extensions_spec.rb +383 -0
  96. data/spec/spec_helper.rb +9 -13
  97. metadata +201 -41
  98. data/.gitignore +0 -7
  99. data/.travis.yml +0 -4
  100. data/CHANGELOG.md +0 -119
  101. data/Gemfile +0 -4
  102. data/_config.yml +0 -1
  103. data/acts_as_tenant.gemspec +0 -32
  104. data/docs/blog_post.md +0 -67
  105. data/rails/init.rb +0 -2
  106. data/spec/active_record_helper.rb +0 -22
  107. data/spec/active_record_models.rb +0 -143
  108. data/spec/acts_as_tenant/model_extensions_spec.rb +0 -476
  109. data/spec/acts_as_tenant/tenant_by_subdomain_or_domain.rb +0 -46
  110. data/spec/acts_as_tenant/tenant_by_subdomain_spec.rb +0 -32
  111. data/spec/database.yml +0 -3
data/.gitignore DELETED
@@ -1,7 +0,0 @@
1
- .rvmrc
2
- *.gem
3
- .bundle
4
- Gemfile.lock
5
- pkg/*
6
- spec/*.sqlite3
7
- spec/*.log
@@ -1,4 +0,0 @@
1
- language: ruby
2
- before_install:
3
- - gem update bundler
4
- script: bundle exec rake spec
@@ -1,119 +0,0 @@
1
- 0.4.4
2
- -----
3
- * Implement support for polymorphic tenant
4
- * Ability to use acts_as_tenant with only ActiveRecord (no Rails)
5
- * Allow setting of custom primary key
6
- * Bug fixes
7
-
8
- 0.4.3
9
- -----
10
- * allow 'optional' relations
11
- * Sidekiq fixes
12
- * Replace all `before_filter` with `before_action` for Rails 5.1 compatibility
13
-
14
- 0.4.1
15
- ------
16
- * Removed (stale, no longer working) MongoDB support; moved code to separate branch
17
- * Added without_tenant option (see readme, thx duboff)
18
-
19
- 0.4.0
20
- ------
21
- * (Sub)domain lookup is no longer case insensitive
22
- * Added ability to use inverse_of (thx lowjoel)
23
- * Added ability to disable tenant checking for a block (thx duboff)
24
- * Allow for validation that associations belong to the tenant to reflect on associations which return an Array from `where` (thx ludamillion)
25
-
26
- 0.3.9
27
- -----
28
- * Added ability to configure a default tenant for testing purposes. (thx iangreenleaf)
29
- * AaT will now accept a string for a tenant_id (thx calebthompson)
30
- * Improvements to readme (thx stgeneral)
31
-
32
- 0.3.8
33
- -----
34
- * Added Mongoid compatibility [thx iangreenleaf]
35
-
36
- 0.3.7
37
- -----
38
- * Fix for proper handling of polymorphic associations (thx sol1dus)
39
- * Fix fefault scope to generate correct sql when using database prefix (thx IgorDobryn)
40
- * Added ability to specify a custom Primary Key (thx matiasdim)
41
- * Sidekiq 3.2.2+ no longer supports Ruby 1.9. Locking Sidekiq in gemspec at 3.2.1.
42
- * Update RSPEC to 3.0. Convert all specs (thx petergoldstein)
43
- * support sidekiq 3 interface (thx davekaro)
44
-
45
- 0.3.6
46
- -----
47
- * Added method `set_current_tenant_by_subdomain_or_domain` (thx preth00nker)
48
-
49
- 0.3.5
50
- -----
51
- * Fix to degredation introduced after 3.1 that prevented tenant_id from being set during initialization (thx jorgevaldivia)
52
-
53
- 0.3.4
54
- -----
55
- * Fix to a bug introduced in 0.3.2
56
-
57
- 0.3.3
58
- -----
59
- * Support user defined foreign keys on scoped models
60
-
61
- 0.3.2
62
- -----
63
- * correctly support nested models with has_many :through (thx dexion)
64
- * Support 'www.subdomain.example.com' (thx wtfiwtz)
65
- * Support setting `tenant_id` on scoped models if the `tenant_id` is nil (thx Matt Wilson)
66
-
67
- 0.3.1
68
- -----
69
- * Added support for Rails 4
70
-
71
- 0.3.0
72
- -----
73
- * 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.
74
- * `ActsAsTenant.with_tenant` will now return the value of the block it evaluates instead of the original tenant. The original tenant is restored automatically.
75
- * acts_as_tenant now raises standard errors which can be caught individually.
76
- * `set_current_tenant_to`, which was deprecated some versions ago and could lead to weird errors, has been removed.
77
-
78
-
79
- 0.2.9
80
- -----
81
- * Added support for many-to-many associations (thx Nucleoid)
82
-
83
- 0.2.8
84
- -----
85
- * Added dependencies to gemspec (thx aaronrenner)
86
- * Added the `ActsAsTenant.with_tenant` block method (see readme) (thx aaronrenner)
87
- * Acts_as_Tenant is now thread safe (thx davide)
88
-
89
- 0.2.7
90
- -----
91
- * Changed the interface for passing in the current_tenant manually in the controller. `set_current_tenant_to` has been deprecated and replaced by `set_current_tenant_through_filter` declaration and the `set_current_tenant` method. See readme for details.
92
-
93
- 0.2.6
94
- -----
95
- * Fixed a bug with resolving the tenant model name (thx devton!)
96
- * Added support for using relations: User.create(:account => Account.first) now works, while it wouldn't before (thx bnmrrs)
97
-
98
- 0.2.5
99
- -----
100
- * Added Rails 3.2 compatibility (thx nickveys!)
101
-
102
- 0.2.4
103
- -----
104
- * Added correct handling of child models that do not have their parent set (foreign key == nil)
105
-
106
-
107
- 0.2.3
108
- -----
109
- * Added support for models that declare a has_one relationships, these would error out in the previous versions.
110
-
111
-
112
- 0.2.2
113
- -----
114
- * Enhancements
115
- * Added support for aliased associations ( belongs_to :something, :class_name => 'SomethingElse'). In previous version these would raise an 'uninitialized constant' error.
116
-
117
- 0.2.1
118
- -----
119
- * Initial release
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source "http://rubygems.org"
2
-
3
- # Specify your gem's dependencies in acts_as_tenant.gemspec
4
- gemspec
@@ -1 +0,0 @@
1
- theme: jekyll-theme-tactile
@@ -1,32 +0,0 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "acts_as_tenant/version"
4
-
5
- Gem::Specification.new do |s|
6
- s.name = "acts_as_tenant"
7
- s.version = ActsAsTenant::VERSION
8
- s.authors = ["Erwin Matthijssen"]
9
- s.email = ["erwin.matthijssen@gmail.com"]
10
- s.homepage = "http://www.rollcallapp.com/blog"
11
- s.summary = %q{Add multi-tenancy to Rails applications using a shared db strategy}
12
- s.description = %q{Integrates multi-tenancy into a Rails application in a convenient and out-of-your way manner}
13
-
14
- s.rubyforge_project = "acts_as_tenant"
15
-
16
- s.files = `git ls-files`.split("\n")
17
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
- s.require_paths = ["lib"]
20
-
21
- s.add_runtime_dependency('request_store', '>= 1.0.5')
22
- s.add_dependency('rails','>= 4.0')
23
- #s.add_dependency('request_store', '>= 1.0.5')
24
-
25
- s.add_development_dependency('rspec', '>=3.0')
26
- s.add_development_dependency('rspec-rails')
27
- s.add_development_dependency('database_cleaner', '~> 1.7')
28
- s.add_development_dependency('sqlite3')
29
- #s.add_development_dependency('mongoid', '~> 4.0')
30
-
31
- s.add_development_dependency('sidekiq', '3.2.1')
32
- end
@@ -1,67 +0,0 @@
1
- <h2> Adding multi-tenancy to your Rails app: acts_as_tenant </h2>
2
- Roll Call is implemented as a multi-tenant application: each user gets their own instance of the app, content is strictly scoped to a user&#8217;s instance. In Rails, this can be achieved in various ways. Guy Naor did a great job of diving into the pros and cons of each option in his <a href="http://confreaks.net/videos/111-aac2009-writing-multi-tenant-applications-in-rails">2009 Acts As Conference talk</a>. If you are doing multi-tenancy in Rails, you should watch his video.</p>
3
- <p>With a multi-db or multi-schema approach, you only deal with the multi-tenancy-aspect in a few specific spots in your app (Jerod Santo recently wrote an excellent post on implementing a <a href="http://blog.jerodsanto.net/2011/07/building-multi-tenant-rails-apps-with-postgresql-schemas/">multi-schema strategy</a>). Compared to the previous two strategies, a <strong>shared database</strong> strategy has the downside that the &#8216;multi-tenancy&#8217;-logic is something you need to actively be aware of and manage in almost every part of your app.</p>
4
- <h4>Using a Shared Database strategy is alot of work!</h4>
5
- <p>For various other reasons we opted for a <strong>shared database</strong> strategy. However, for us the prospect of dealing with the multi-tenancy-logic throughout our app, was not appealing. Worse, we run the risk of accidently exposing content of one tenant to another one, if we mismanage this logic. While researching this topic I noticed there are no real ready made solutions available that get you on your way, <a href="http://github.com/wireframe/multitenant">Ryan Sonnek</a> wrote his &#8216;multitenant&#8217; gem and <a href="http://github.com/mconnell/multi_tenant">Mark Connel</a> did the same. Neither of these solution seemed &#8220;finished&#8221; to us. So, we wrote our own implementation.</p>
6
- <h4>First, how does multi-tenancy with a shared database strategy work</h4>
7
- <p>A shared database strategy manages the multi-tenancy-logic through Rails associations. A tenant is represented by an object, for example an <code>Account</code>. All other objects are associated with a tenant: <code>belongs_to :account</code>. Each request starts with finding the <code>@current_account</code>. After that, each find is scoped through the tenant object: <code>current_account.projects.all</code>. This has to be remembered everywhere: in model method declarations and in controller actions. Otherwise, you&#8217;re exposing content of other tenants.</p>
8
- <p>In addition, you have to actively babysit other parts of your app: <code>validates_uniqueness_of</code> requires you to scope it to the current tenant. You also have to protect agaist all sorts of form-injections that could allow one tenant to gain access or temper with the content of another tenant (see <a href="http://www.slideshare.net/tardate/multitenancy-with-rails">Paul Gallaghers</a> presentation for more on these dangers).</p>
9
- <h4>Enter acts_as_tenant</h4>
10
- <p>I wanted to implement all the concerns above in an easy to manage, out of the way fashion. We should be able to add a single declaration to our model and that should implement:</p>
11
- <ol>
12
- <li>scoping all searches to the current <code>Account</code></li>
13
- <li>scoping the uniqueness validator to the current <code>Account</code></li>
14
- <li>protecting against various nastiness trying to circumvent the scoping.</li>
15
- </ol>
16
- <p>The result is <code>acts_as_tenant</code> (<a href="https://github.com/ErwinM/acts_as_tenant">github</a>), a rails gem that will add multi tenancy using a shared database to your rails app in an out-of-your way fashion.</p>
17
- <p>In the <span class="caps">README</span>, you will find more information on using <code>acts_as_tenant</code> in your projects, so we&#8217;ll give you a high-level overview here. Let&#8217;s suppose that you have an app to which you want to add multi tenancy, tenants are represented by the <code>Account</code> model and <code>Project</code> is one of the models that should be scoped by tenant:</p>
18
-
19
- ```ruby
20
- class Addaccounttoproject < ActiveRecord::Migration
21
- def change
22
- add_column :projects, :account_id, :integer
23
- end
24
- end
25
-
26
- class Project < ActiveRecord::Base
27
- acts_as_tenant(:account)
28
- validates_uniqueness_to_tenant :name
29
- end
30
- ```
31
- What does adding these two methods accomplish:
32
- <ol>
33
- <li>it ensures every search on the project model will be scoped to the current tenant,</li>
34
- <li>it adds validation for every association confirming the associated object does indeed belong to the current tenant,</li>
35
- <li>it validates the uniqueness of `:name` to the current tenant,</li>
36
- <li>it implements a bunch of safeguards preventing all kinds of nastiness from exposing other tenants data (mainly form-injection attacks).</li>
37
- </ol>
38
- <p>Ofcourse, all the above assumes `acts_as_tenant` actually knows who the current tenant is. Two strategies are implemented to help with this.</p>
39
- <p><strong>Using the subdomain to workout the current tenant</strong></p>
40
-
41
- ```ruby
42
- class ApplicationController < ActionController::Base
43
- set_current_tenant_by_subdomain(:account, :subdomain)
44
- end
45
- ```
46
- <p>Adding the above method to your `application_controller` tells `acts_as_tenant`:</p>
47
- <ol>
48
- <li>the current tenant should be found based on the subdomain (e.g. account1.myappdomain.com),</li>
49
- <li>tenants are represented by the `Account`-model and</li>
50
- <li>the `Account` model has a column named `subdomain` that should be used the lookup the current account, using the current subdomain.</li>
51
- </ol>
52
- <p><strong>Passing the current account to acts_as_tenant yourself</strong></p>
53
-
54
- ```ruby
55
- class ApplicationController < ActionController::Base
56
- current_account = Account.method_to_find_the_current_account
57
- set_current_tenant_to(current_account)
58
- end
59
- ```
60
- <p>`Acts_as_tenant` also adds a handy helper to your controllers `current_tenant`, containing the current tenant object.</p>
61
- <h4>Great! Anything else I should know? A few caveats:</h4>
62
- <ul>
63
- <li>scoping of models *only* works if `acts_as_tenant` has a current_tenant available. If you do not set one by one of the methods described above, *no scope* will be applied!</li>
64
- <li>for validating uniqueness within a tenant scope you must use the `validates_uniqueness_to_tenant`method. This method takes all the options the regular `validates_uniqueness_of` method takes.</li>
65
- <li>it is probably best to add the `acts_as_tenant` declaration after any other `default_scope` declarations you add to a model (I am not exactly sure how rails 3 handles the chaining. If someone can enlighten me, thanks!).</li>
66
- </ul>
67
- <p>We have been testing <a href="https://github.com/ErwinM/acts_as_tenant">acts_as_tenant</a> within Roll Call during recent weeks and it seems to be behaving well. Having said that, we welcome any feedback. This is my first real attempt at a plugin and the possibility of various improvements is almost a given.</p>
@@ -1,2 +0,0 @@
1
- ActiveRecord::Base.send(:include, ActsAsTenant::ModelExtensions)
2
- ActionController::Base.extend ActsAsTenant::ControllerExtensions
@@ -1,22 +0,0 @@
1
- require 'rails/all'
2
- require 'database_cleaner'
3
- require 'yaml'
4
-
5
- dbconfig = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'database.yml')))
6
- ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
7
- ActiveRecord::Base.establish_connection(dbconfig[ENV['DB'] || 'sqlite'])
8
-
9
- RSpec.configure do |config|
10
- config.before(:suite) do
11
- DatabaseCleaner[:active_record].strategy = :transaction
12
- DatabaseCleaner[:active_record].clean_with(:truncation)
13
- end
14
-
15
- config.before(:each) do
16
- DatabaseCleaner[:active_record].start
17
- end
18
-
19
- config.after(:each) do
20
- DatabaseCleaner[:active_record].clean
21
- end
22
- end
@@ -1,143 +0,0 @@
1
- ActiveRecord::Schema.define(:version => 1) do
2
- create_table :accounts, :force => true do |t|
3
- t.column :name, :string
4
- t.column :subdomain, :string
5
- t.column :domain, :string
6
- end
7
-
8
- create_table :projects, :force => true do |t|
9
- t.column :name, :string
10
- t.column :account_id, :integer
11
- end
12
-
13
- create_table :managers, :force => true do |t|
14
- t.column :name, :string
15
- t.column :project_id, :integer
16
- t.column :account_id, :integer
17
- end
18
-
19
- create_table :tasks, :force => true do |t|
20
- t.column :name, :string
21
- t.column :account_id, :integer
22
- t.column :project_id, :integer
23
- t.column :completed, :boolean
24
- end
25
-
26
- create_table :countries, :force => true do |t|
27
- t.column :name, :string
28
- end
29
-
30
- create_table :unscoped_models, :force => true do |t|
31
- t.column :name, :string
32
- end
33
-
34
- create_table :aliased_tasks, :force => true do |t|
35
- t.column :name, :string
36
- t.column :project_alias_id, :integer
37
- t.column :account_id, :integer
38
- end
39
-
40
- create_table :unique_tasks, :force => true do |t|
41
- t.column :name, :string
42
- t.column :user_defined_scope, :string
43
- t.column :project_id, :integer
44
- t.column :account_id, :integer
45
- end
46
-
47
- create_table :custom_foreign_key_tasks, :force => true do |t|
48
- t.column :name, :string
49
- t.column :accountID, :integer
50
- end
51
-
52
- create_table :articles, :force => true do |t|
53
- t.column :title, :string
54
- end
55
-
56
- create_table :comments, :force => true do |t|
57
- t.column :commentable_id, :integer
58
- t.column :commentable_type, :string
59
- t.column :account_id, :integer
60
- end
61
-
62
- create_table :polymorphic_tenant_comments, :force => true do |t|
63
- t.column :polymorphic_tenant_commentable_id, :integer
64
- t.column :polymorphic_tenant_commentable_type, :string
65
- t.column :account_id, :integer
66
- end
67
-
68
- end
69
-
70
- class Account < ActiveRecord::Base
71
- has_many :projects
72
- end
73
-
74
- class Project < ActiveRecord::Base
75
- has_one :manager
76
- has_many :tasks
77
- has_many :polymorphic_tenant_comments, as: :polymorphic_tenant_commentable
78
- acts_as_tenant :account
79
-
80
- validates_uniqueness_to_tenant :name
81
- end
82
-
83
- class Manager < ActiveRecord::Base
84
- belongs_to :project
85
- acts_as_tenant :account
86
- end
87
-
88
- class Task < ActiveRecord::Base
89
- belongs_to :project
90
- default_scope -> { where(:completed => nil).order("name") }
91
-
92
- acts_as_tenant :account
93
- validates_uniqueness_of :name
94
- end
95
-
96
- class UnscopedModel < ActiveRecord::Base
97
- validates_uniqueness_of :name
98
- end
99
-
100
- class AliasedTask < ActiveRecord::Base
101
- acts_as_tenant(:account)
102
- belongs_to :project_alias, :class_name => "Project"
103
- end
104
-
105
- class UniqueTask < ActiveRecord::Base
106
- acts_as_tenant(:account)
107
- belongs_to :project
108
- validates_uniqueness_to_tenant :name, scope: :user_defined_scope
109
- end
110
-
111
- class CustomForeignKeyTask < ActiveRecord::Base
112
- acts_as_tenant(:account, :foreign_key => "accountID")
113
- validates_uniqueness_to_tenant :name
114
- end
115
-
116
- class CustomPrimaryKeyTask < ActiveRecord::Base
117
- self.table_name = 'projects'
118
- acts_as_tenant(:account, :foreign_key => "name", :primary_key => "name")
119
- validates_presence_of :name
120
- end
121
-
122
- class Comment < ActiveRecord::Base
123
- belongs_to :commentable, polymorphic: true
124
- belongs_to :task, -> { where(comments: { commentable_type: 'Task' }) }, foreign_key: 'commentable_id'
125
- acts_as_tenant :account
126
- end
127
-
128
- class Article < ActiveRecord::Base
129
- has_many :polymorphic_tenant_comments, as: :polymorphic_tenant_commentable
130
- end
131
-
132
- class PolymorphicTenantComment < ActiveRecord::Base
133
- belongs_to :polymorphic_tenant_commentable, polymorphic: true
134
- belongs_to :account
135
- acts_as_tenant :polymorphic_tenant_commentable, polymorphic: true
136
- end
137
-
138
- class GlobalProject < ActiveRecord::Base
139
- self.table_name = 'projects'
140
-
141
- acts_as_tenant :account, has_global_records: true
142
- validates_uniqueness_to_tenant :name
143
- end
@@ -1,476 +0,0 @@
1
- require 'spec_helper'
2
- require 'active_record_models'
3
-
4
- describe ActsAsTenant do
5
- after { ActsAsTenant.current_tenant = nil }
6
-
7
- # Setting and getting
8
- describe 'Setting the current tenant' do
9
- before { ActsAsTenant.current_tenant = :foo }
10
- it { ActsAsTenant.current_tenant == :foo }
11
- end
12
-
13
- describe 'is_scoped_as_tenant should return the correct value when true' do
14
- it {expect(Project.respond_to?(:scoped_by_tenant?)).to eq(true)}
15
- end
16
-
17
- describe 'is_scoped_as_tenant should return the correct value when false' do
18
- it {expect(UnscopedModel.respond_to?(:scoped_by_tenant?)).to eq(false)}
19
- end
20
-
21
- describe 'tenant_id should be immutable, if already set' do
22
- before do
23
- @account = Account.create!(:name => 'foo')
24
- @project = @account.projects.create!(:name => 'bar')
25
- end
26
-
27
- it { expect {@project.account_id = @account.id + 1}.to raise_error(ActsAsTenant::Errors::TenantIsImmutable) }
28
- end
29
-
30
- describe 'setting tenant_id to the same value should not error' do
31
- before do
32
- @account = Account.create!(:name => 'foo')
33
- @project = @account.projects.create!(:name => 'bar')
34
- end
35
-
36
- it { expect {@project.account_id = @account.id}.not_to raise_error }
37
- end
38
-
39
- describe 'setting tenant_id to a string with same to_i value should not error' do
40
- before do
41
- @account = Account.create!(:name => 'foo')
42
- @project = @account.projects.create!(:name => 'bar')
43
- end
44
-
45
- it { expect {@project.account_id = @account.id.to_s}.not_to raise_error }
46
- end
47
-
48
- describe 'tenant_id should be mutable, if not already set' do
49
- before do
50
- @account = Account.create!(:name => 'foo')
51
- @project = Project.create!(:name => 'bar')
52
- end
53
-
54
- it { expect(@project.account_id).to be_nil }
55
- it { expect { @project.account = @account }.not_to raise_error }
56
- end
57
-
58
- describe 'tenant_id should auto populate after initialization' do
59
- before do
60
- @account = Account.create!(:name => 'foo')
61
- ActsAsTenant.current_tenant = @account
62
- end
63
- it {expect(Project.new.account_id).to eq(@account.id)}
64
- end
65
-
66
- describe 'Handles custom foreign_key on tenant model' do
67
- before do
68
- @account = Account.create!(:name => 'foo')
69
- ActsAsTenant.current_tenant = @account
70
- @custom_foreign_key_task = CustomForeignKeyTask.create!(:name => 'foo')
71
- end
72
-
73
- it { expect(@custom_foreign_key_task.account).to eq(@account) }
74
- end
75
-
76
- describe 'Handles custom primary_key on tenant model' do
77
- before do
78
- @account = Account.create!(:name => 'foo')
79
- CustomPrimaryKeyTask.create!(name: 'bar')
80
- ActsAsTenant.current_tenant = @account
81
- @custom_primary_key_task = CustomPrimaryKeyTask.create!
82
- end
83
-
84
- it { expect(@custom_primary_key_task.account).to eq(@account) }
85
- it { expect(CustomPrimaryKeyTask.count).to eq(1) }
86
- end
87
-
88
- # Scoping models
89
- describe 'Project.all should be scoped to the current tenant if set' do
90
- before do
91
- @account1 = Account.create!(:name => 'foo')
92
- @account2 = Account.create!(:name => 'bar')
93
-
94
- @project1 = @account1.projects.create!(:name => 'foobar')
95
- @project2 = @account2.projects.create!(:name => 'baz')
96
-
97
- ActsAsTenant.current_tenant= @account1
98
- @projects = Project.all
99
- end
100
-
101
- it { expect(@projects.length).to eq(1) }
102
- it { expect(@projects).to eq([@project1]) }
103
- end
104
-
105
- describe 'Project.unscoped.all should return the unscoped value' do
106
- before do
107
- @account1 = Account.create!(:name => 'foo')
108
- @account2 = Account.create!(:name => 'bar')
109
-
110
- @project1 = @account1.projects.create!(:name => 'foobar')
111
- @project2 = @account2.projects.create!(:name => 'baz')
112
-
113
- ActsAsTenant.current_tenant= @account1
114
- @projects = Project.unscoped
115
- end
116
-
117
- it { expect(@projects.count).to eq(2) }
118
- end
119
-
120
- describe 'Querying the tenant from a scoped model without a tenant set' do
121
- before do
122
- @project = Project.create!(:name => 'bar')
123
- end
124
-
125
- it { @project.account }
126
- end
127
-
128
- describe 'Querying the tenant from a scoped model with a tenant set' do
129
- before do
130
- @account = Account.create!(:name => 'foo')
131
- @project = @account.projects.create!(:name => 'foobar')
132
- ActsAsTenant.current_tenant= @account1
133
- end
134
-
135
- it { @project.account }
136
- end
137
-
138
- describe 'A tenant model with global records' do
139
- before do
140
- @account = Account.create!(:name => 'foo')
141
- @project1 = GlobalProject.create!(:name => 'foobar global')
142
- @project2 = GlobalProject.create!(:name => 'unaccessible project', :account => Account.create!)
143
- ActsAsTenant.current_tenant = @account
144
- @project3 = GlobalProject.create!(:name => 'foobar')
145
- end
146
-
147
- it 'should return two projects' do
148
- expect(GlobalProject.all.count).to eq(2)
149
- end
150
-
151
- it 'should validate the project name against the global records too' do
152
- expect(GlobalProject.new(:name => 'foobar').valid?).to be(false)
153
- expect(GlobalProject.new(:name => 'foobar new').valid?).to be(true)
154
- expect(GlobalProject.new(:name => 'foobar global').valid?).to be(false)
155
- expect(@project1.valid?).to be(true)
156
- end
157
-
158
- it 'should add the model to ActsAsTenant.models_with_global_records' do
159
- expect(ActsAsTenant.models_with_global_records.include?(GlobalProject)).to be(true)
160
- expect(ActsAsTenant.models_with_global_records.include?(Project)).to be(false)
161
- end
162
- end
163
-
164
- # Associations
165
- describe 'Associations should be correctly scoped by current tenant' do
166
- before do
167
- @account = Account.create!(:name => 'foo')
168
- @project = Project.create!(:name => 'foobar', :account => @account )
169
- # the next line should normally be (nearly) impossible: a task assigned to a tenant project,
170
- # but the task has no tenant assigned
171
- @task1 = Task.create!(:name => 'no_tenant', :project => @project)
172
-
173
- ActsAsTenant.current_tenant = @account
174
- @task2 = @project.tasks.create!(:name => 'baz')
175
-
176
- @project.reload
177
- end
178
-
179
- it 'should correctly set the tenant on the task created with current_tenant set' do
180
- expect(@task2.account).to eq(@account)
181
- end
182
-
183
- it 'should filter out the non-tenant task from the project' do
184
- expect(@project.tasks.length).to eq(1)
185
- end
186
- end
187
-
188
- describe 'Associations can only be made with in-scope objects' do
189
- before do
190
- @account = Account.create!(:name => 'foo')
191
- @project1 = Project.create!(:name => 'inaccessible_project', :account => Account.create!)
192
- ActsAsTenant.current_tenant = @account
193
-
194
- @project2 = Project.create!(:name => 'accessible_project')
195
- @task = @project2.tasks.create!(:name => 'bar')
196
- end
197
-
198
- it { expect(@task.update_attributes(:project_id => @project1.id)).to eq(false) }
199
- end
200
-
201
- describe "Create and save an AaT-enabled child without it having a parent" do
202
- before do
203
- @account = Account.create!(:name => 'baz')
204
- ActsAsTenant.current_tenant = @account
205
- end
206
- it { expect(Task.create(:name => 'bar').valid?).to eq(true) }
207
- end
208
-
209
- describe "It should be possible to use aliased associations" do
210
- it { expect(AliasedTask.create(:name => 'foo', :project_alias => @project2).valid?).to eq(true) }
211
- end
212
-
213
- describe "It should be possible to use associations with foreign_key from polymorphic" do
214
- context 'tenanted objects have a polymorphic association' do
215
- before do
216
- @account = Account.create!(name: 'foo')
217
- ActsAsTenant.current_tenant = @account
218
- @project = Project.create!(name: 'project', account: @account)
219
- @comment = Comment.new commentable: @project, account: @account
220
- end
221
-
222
- it { expect(@comment.save!).to eq(true) }
223
- end
224
-
225
- context 'tenant is polymorphic' do
226
- before do
227
- @account = Account.create!(name: 'foo')
228
- @project = Project.create!(name: 'polymorphic project')
229
- ActsAsTenant.current_tenant = @project
230
- @comment = PolymorphicTenantComment.new(account: @account)
231
- end
232
-
233
- it 'populates commentable_type with the current tenant' do
234
- expect(@comment.polymorphic_tenant_commentable_id).to eql(@project.id)
235
- expect(@comment.polymorphic_tenant_commentable_type).to eql(@project.class.to_s)
236
- end
237
-
238
- context 'with another type of tenant, same id' do
239
- before do
240
- @comment.save!
241
- @article = Article.create!(id: @project.id, title: 'article title')
242
- @comment_on_article = @article.polymorphic_tenant_comments.create!
243
- end
244
-
245
- it 'correctly scopes to the current tenant type' do
246
- expect(@comment_on_article).to be_persisted
247
- expect(@comment).to be_persisted
248
- expect(PolymorphicTenantComment.count).to eql(1)
249
- expect(PolymorphicTenantComment.all.first.attributes).to eql(@comment.attributes)
250
- end
251
- end
252
-
253
- end
254
- end
255
-
256
- # Additional default_scopes
257
- describe 'When dealing with a user defined default_scope' do
258
- before do
259
- @account = Account.create!(:name => 'foo')
260
- @project1 = Project.create!(:name => 'inaccessible')
261
- @task1 = Task.create!(:name => 'no_tenant', :project => @project1)
262
-
263
- ActsAsTenant.current_tenant = @account
264
- @project2 = Project.create!(:name => 'accessible')
265
- @task2 = @project2.tasks.create!(:name => 'bar')
266
- @task3 = @project2.tasks.create!(:name => 'baz')
267
- @task4 = @project2.tasks.create!(:name => 'foo')
268
- @task5 = @project2.tasks.create!(:name => 'foobar', :completed => true )
269
-
270
- @tasks= Task.all
271
- end
272
-
273
- it 'should apply both the tenant scope and the user defined default_scope, including :order' do
274
- expect(@tasks.length).to eq(3)
275
- expect(@tasks).to eq([@task2, @task3, @task4])
276
- end
277
- end
278
-
279
- # Validates_uniqueness
280
- describe 'When using validates_uniqueness_to_tenant in a aat model' do
281
- before do
282
- account = Account.create!(:name => 'foo')
283
- ActsAsTenant.current_tenant = account
284
- Project.create!(:name => 'existing_name')
285
- end
286
-
287
- it 'should not be possible to create a duplicate within the same tenant' do
288
- expect(Project.create(:name => 'existing_name').valid?).to eq(false)
289
- end
290
-
291
- it 'should be possible to create a duplicate outside the tenant scope' do
292
- account = Account.create!(:name => 'baz')
293
- ActsAsTenant.current_tenant = account
294
- expect(Project.create(:name => 'bar').valid?).to eq(true)
295
- end
296
- end
297
-
298
- describe 'Handles user defined scopes' do
299
- before do
300
- UniqueTask.create!(:name => 'foo', :user_defined_scope => 'unique_scope')
301
- end
302
-
303
- it { expect(UniqueTask.create(:name => 'foo', :user_defined_scope => 'another_scope')).to be_valid }
304
- it { expect(UniqueTask.create(:name => 'foo', :user_defined_scope => 'unique_scope')).not_to be_valid }
305
- end
306
-
307
- describe 'When using validates_uniqueness_of in a NON-aat model' do
308
- before do
309
- UnscopedModel.create!(:name => 'foo')
310
- end
311
- it 'should not be possible to create duplicates' do
312
- expect(UnscopedModel.create(:name => 'foo').valid?).to eq(false)
313
- end
314
- end
315
-
316
- # ::with_tenant
317
- describe "::with_tenant" do
318
- it "should set current_tenant to the specified tenant inside the block" do
319
- @account = Account.create!(:name => 'baz')
320
-
321
- ActsAsTenant.with_tenant(@account) do
322
- expect(ActsAsTenant.current_tenant).to eq(@account)
323
- end
324
- end
325
-
326
- it "should reset current_tenant to the previous tenant once exiting the block" do
327
- @account1 = Account.create!(:name => 'foo')
328
- @account2 = Account.create!(:name => 'bar')
329
-
330
- ActsAsTenant.current_tenant = @account1
331
- ActsAsTenant.with_tenant @account2 do
332
-
333
- end
334
-
335
- expect(ActsAsTenant.current_tenant).to eq(@account1)
336
- end
337
-
338
- it "should return the value of the block" do
339
- @account1 = Account.create!(:name => 'foo')
340
- @account2 = Account.create!(:name => 'bar')
341
-
342
- ActsAsTenant.current_tenant = @account1
343
- value = ActsAsTenant.with_tenant @account2 do
344
- "something"
345
- end
346
-
347
- expect(value).to eq "something"
348
- end
349
-
350
- it "should raise an error when no block is provided" do
351
- expect { ActsAsTenant.with_tenant(nil) }.to raise_error(ArgumentError, /block required/)
352
- end
353
- end
354
-
355
- describe "::without_tenant" do
356
- it "should set current_tenant to nil inside the block" do
357
- ActsAsTenant.without_tenant do
358
- expect(ActsAsTenant.current_tenant).to be_nil
359
- end
360
- end
361
-
362
- it "should set current_tenant to nil even if default_tenant is set" do
363
- begin
364
- old_default_tenant = ActsAsTenant.default_tenant
365
- ActsAsTenant.default_tenant = Account.create!(name: 'foo')
366
- ActsAsTenant.without_tenant do
367
- expect(ActsAsTenant.current_tenant).to be_nil
368
- end
369
- ensure
370
- ActsAsTenant.default_tenant = old_default_tenant
371
- end
372
- end
373
-
374
- it "should reset current_tenant to the previous tenant once exiting the block" do
375
- @account1 = Account.create!(:name => 'foo')
376
-
377
- ActsAsTenant.current_tenant = @account1
378
- ActsAsTenant.without_tenant do
379
- end
380
-
381
- expect(ActsAsTenant.current_tenant).to eq(@account1)
382
- end
383
-
384
- it "should return the value of the block" do
385
- value = ActsAsTenant.without_tenant do
386
- "something"
387
- end
388
-
389
- expect(value).to eq "something"
390
- end
391
-
392
- it "should raise an error when no block is provided" do
393
- expect { ActsAsTenant.without_tenant }.to raise_error(ArgumentError, /block required/)
394
- end
395
- end
396
-
397
- # Tenant required
398
- context "tenant required" do
399
- before do
400
- @account1 = Account.create!(:name => 'foo')
401
- @project1 = @account1.projects.create!(:name => 'foobar')
402
- allow(ActsAsTenant.configuration).to receive_messages(require_tenant: true)
403
- end
404
-
405
- describe "raises exception if no tenant specified" do
406
- it "should raise an error when no tenant is provided" do
407
- expect { Project.all }.to raise_error(ActsAsTenant::Errors::NoTenantSet)
408
- end
409
- end
410
-
411
- describe "does not raise exception when run in unscoped mode" do
412
- it "should not raise an error when no tenant is provided" do
413
- expect do
414
- ActsAsTenant.without_tenant { Project.all }
415
- end.to_not raise_error
416
- end
417
- end
418
- end
419
-
420
- context "no tenant required" do
421
- describe "does not raise exception if no tenant specified" do
422
- before do
423
- @account1 = Account.create!(:name => 'foo')
424
- @project1 = @account1.projects.create!(:name => 'foobar')
425
- end
426
-
427
- it "should not raise an error when no tenant is provided" do
428
- expect { Project.all }.to_not raise_error
429
- end
430
- end
431
- end
432
-
433
- describe "ActsAsTenant.default_tenant=" do
434
- before(:each) do
435
- @account = Account.create!
436
- end
437
-
438
- after(:each) do
439
- ActsAsTenant.default_tenant = nil
440
- end
441
-
442
- it "provides current_tenant" do
443
- ActsAsTenant.default_tenant = @account
444
- expect(ActsAsTenant.current_tenant).to eq(@account)
445
- end
446
-
447
- it "can be overridden by assignment" do
448
- ActsAsTenant.default_tenant = @account
449
- @account2 = Account.create!
450
- ActsAsTenant.current_tenant = @account2
451
- expect(ActsAsTenant.current_tenant).not_to eq(@account)
452
- end
453
-
454
- it "can be overridden by with_tenant" do
455
- ActsAsTenant.default_tenant = @account
456
- @account2 = Account.create!
457
- ActsAsTenant.with_tenant @account2 do
458
- expect(ActsAsTenant.current_tenant).to eq(@account2)
459
- end
460
- expect(ActsAsTenant.current_tenant).to eq(@account)
461
- end
462
-
463
- it "doesn't override existing current_tenant" do
464
- @account2 = Account.create!
465
- ActsAsTenant.current_tenant = @account2
466
- ActsAsTenant.default_tenant = @account
467
- expect(ActsAsTenant.current_tenant).to eq(@account2)
468
- end
469
-
470
- it "survives request resets" do
471
- ActsAsTenant.default_tenant = @account
472
- RequestStore.clear!
473
- expect(ActsAsTenant.current_tenant).to eq(@account)
474
- end
475
- end
476
- end