From 659bcab3c895fb8fe67b2d2b235d231da3a3cd05 Mon Sep 17 00:00:00 2001 From: Tomas Patro Date: Tue, 7 Apr 2020 12:56:06 +0200 Subject: [PATCH 1/6] Allow changing tenant on models using new without_tenant! block --- lib/acts_as_tenant/model_extensions.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/acts_as_tenant/model_extensions.rb b/lib/acts_as_tenant/model_extensions.rb index b9a1030..3991017 100644 --- a/lib/acts_as_tenant/model_extensions.rb +++ b/lib/acts_as_tenant/model_extensions.rb @@ -5,6 +5,7 @@ module ModelExtensions class_methods do def acts_as_tenant(tenant = :account, **options) ActsAsTenant.set_tenant_klass(tenant) + ActsAsTenant.mutable_tenant!(false) ActsAsTenant.add_global_record_model(self) if options[:has_global_records] @@ -13,7 +14,7 @@ def acts_as_tenant(tenant = :account, **options) fkey = valid_options[:foreign_key] || ActsAsTenant.fkey pkey = valid_options[:primary_key] || ActsAsTenant.pkey polymorphic_type = valid_options[:foreign_type] || ActsAsTenant.polymorphic_type - belongs_to tenant, **valid_options + belongs_to tenant, valid_options default_scope lambda { if ActsAsTenant.configuration.require_tenant && ActsAsTenant.current_tenant.nil? && !ActsAsTenant.unscoped? @@ -78,7 +79,7 @@ def acts_as_tenant(tenant = :account, **options) define_method "#{ActsAsTenant.tenant_klass}=" do |model| super(model) - raise ActsAsTenant::Errors::TenantIsImmutable if send("#{fkey}_changed?") && persisted? && !send("#{fkey}_was").nil? + raise ActsAsTenant::Errors::TenantIsImmutable if send("#{fkey}_changed?") && persisted? && !send("#{fkey}_was").nil? && !ActsAsTenant.mutable_tenant? model end } From be6776b49879444e648b974f047029865407b820 Mon Sep 17 00:00:00 2001 From: Tomas Patro Date: Tue, 7 Apr 2020 13:02:22 +0200 Subject: [PATCH 2/6] Added records to readme about without_tenant! feature --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 1123d0a..a0cf74f 100644 --- a/README.md +++ b/README.md @@ -137,6 +137,16 @@ end ``` This is useful in shared routes such as admin panels or internal dashboards when `require_tenant` option is enabled throughout the app. +### Allowing tenant updating for a block ### + +```ruby +ActsAsTenant.without_tenant! do + # Tenant updating is enabled for all code in this block +end +``` + +This will allow you to change the tenant of a model. This feature is useful for admin screens, where it is ok to allow certain users to change the tenant on existing models in specific cases. + ### Require tenant to be set always ### 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 configuration options. From ef3fdd3b576f024bb2382f829cf7899a4b5ab0b9 Mon Sep 17 00:00:00 2001 From: Tomas Patro Date: Tue, 23 Mar 2021 10:12:01 +0100 Subject: [PATCH 3/6] Change without_tenant! block to with_mutable_tenant --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a0cf74f..5d1e338 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ This is useful in shared routes such as admin panels or internal dashboards when ### Allowing tenant updating for a block ### ```ruby -ActsAsTenant.without_tenant! do +ActsAsTenant.with_mutable_tenant do # Tenant updating is enabled for all code in this block end ``` From 460f050de3bc38d6b077c5f5e97440c266f1ebda Mon Sep 17 00:00:00 2001 From: Tomas Patro Date: Tue, 23 Mar 2021 10:39:55 +0100 Subject: [PATCH 4/6] Update with_mutable_tenant to new code structure --- lib/acts_as_tenant.rb | 16 +++++++++++++ lib/acts_as_tenant/model_extensions.rb | 3 ++- spec/models/model_extensions_spec.rb | 33 ++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/acts_as_tenant.rb b/lib/acts_as_tenant.rb index e98da15..bba747c 100644 --- a/lib/acts_as_tenant.rb +++ b/lib/acts_as_tenant.rb @@ -12,6 +12,7 @@ module ActsAsTenant @@configuration = nil @@tenant_klass = nil @@models_with_global_records = [] + @@mutable_tenant = false class << self attr_accessor :test_tenant @@ -80,6 +81,14 @@ def self.default_tenant @default_tenant unless unscoped end + def self.mutable_tenant!(toggle) + @@mutable_tenant = toggle + end + + def self.mutable_tenant? + @@mutable_tenant + end + def self.with_tenant(tenant, &block) if block.nil? raise ArgumentError, "block required" @@ -109,6 +118,13 @@ def self.without_tenant(&block) self.current_tenant = old_tenant self.unscoped = old_unscoped end + + def self.with_mutable_tenant(&block) + ActsAsTenant.mutable_tenant!(true) + self.without_tenant(&block) + ensure + ActsAsTenant.mutable_tenant!(false) + end end ActiveSupport.on_load(:active_record) do |base| diff --git a/lib/acts_as_tenant/model_extensions.rb b/lib/acts_as_tenant/model_extensions.rb index 3991017..163a446 100644 --- a/lib/acts_as_tenant/model_extensions.rb +++ b/lib/acts_as_tenant/model_extensions.rb @@ -73,7 +73,7 @@ def acts_as_tenant(tenant = :account, **options) to_include = Module.new { define_method "#{fkey}=" do |integer| write_attribute(fkey.to_s, integer) - raise ActsAsTenant::Errors::TenantIsImmutable if send("#{fkey}_changed?") && persisted? && !send("#{fkey}_was").nil? + raise ActsAsTenant::Errors::TenantIsImmutable if send("#{fkey}_changed?") && persisted? && !send("#{fkey}_was").nil? && !ActsAsTenant.mutable_tenant? integer end @@ -96,6 +96,7 @@ def validates_uniqueness_to_tenant(fields, args = {}) raise ActsAsTenant::Errors::ModelNotScopedByTenant unless respond_to?(:scoped_by_tenant?) fkey = reflect_on_association(ActsAsTenant.tenant_klass).foreign_key + pkey = reflect_on_association(ActsAsTenant.tenant_klass).active_record_primary_key validation_args = args.clone validation_args[:scope] = if args[:scope] diff --git a/spec/models/model_extensions_spec.rb b/spec/models/model_extensions_spec.rb index 5f72a48..07d6a93 100644 --- a/spec/models/model_extensions_spec.rb +++ b/spec/models/model_extensions_spec.rb @@ -352,6 +352,39 @@ end end + describe "::with_mutable_tenant" do + it "should return the value of the block" do + value = ActsAsTenant.with_mutable_tenant do + "something" + end + expect(value).to eq "something" + end + + it "should raise an error when no block is provided" do + expect { ActsAsTenant.with_mutable_tenant }.to raise_error(ArgumentError, /block required/) + end + + it "should set tenant back to immutable after the block" do + ActsAsTenant.with_mutable_tenant do + "something" + end + expect(ActsAsTenant.mutable_tenant?).to eq false + end + + describe 'mutability' do + before do + @account = Account.create!(:name => 'foo') + @project = @account.projects.create!(:name => 'bar') + end + + it "should allow tenant_id to change inside the block" do + new_account_id = @account.id + 1 + expect{ ActsAsTenant.with_mutable_tenant { @project.account_id = new_account_id } }.to_not raise_error(ActsAsTenant::Errors::TenantIsImmutable) + expect(@project.account_id).to eq new_account_id + end + end + end + # Tenant required context "tenant required" do before do From d2bb873759dc0e55d993b88b16d93c35c4fceeae Mon Sep 17 00:00:00 2001 From: Tomas Patro Date: Tue, 23 Mar 2021 10:56:42 +0100 Subject: [PATCH 5/6] Fix styling based on standardrb --- lib/acts_as_tenant.rb | 2 +- lib/acts_as_tenant/model_extensions.rb | 1 - spec/models/model_extensions_spec.rb | 12 +++++------- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/lib/acts_as_tenant.rb b/lib/acts_as_tenant.rb index bba747c..8e8b208 100644 --- a/lib/acts_as_tenant.rb +++ b/lib/acts_as_tenant.rb @@ -121,7 +121,7 @@ def self.without_tenant(&block) def self.with_mutable_tenant(&block) ActsAsTenant.mutable_tenant!(true) - self.without_tenant(&block) + without_tenant(&block) ensure ActsAsTenant.mutable_tenant!(false) end diff --git a/lib/acts_as_tenant/model_extensions.rb b/lib/acts_as_tenant/model_extensions.rb index 163a446..e870baf 100644 --- a/lib/acts_as_tenant/model_extensions.rb +++ b/lib/acts_as_tenant/model_extensions.rb @@ -96,7 +96,6 @@ def validates_uniqueness_to_tenant(fields, args = {}) raise ActsAsTenant::Errors::ModelNotScopedByTenant unless respond_to?(:scoped_by_tenant?) fkey = reflect_on_association(ActsAsTenant.tenant_klass).foreign_key - pkey = reflect_on_association(ActsAsTenant.tenant_klass).active_record_primary_key validation_args = args.clone validation_args[:scope] = if args[:scope] diff --git a/spec/models/model_extensions_spec.rb b/spec/models/model_extensions_spec.rb index 07d6a93..2b9f087 100644 --- a/spec/models/model_extensions_spec.rb +++ b/spec/models/model_extensions_spec.rb @@ -354,9 +354,7 @@ describe "::with_mutable_tenant" do it "should return the value of the block" do - value = ActsAsTenant.with_mutable_tenant do - "something" - end + value = ActsAsTenant.with_mutable_tenant { "something" } expect(value).to eq "something" end @@ -371,15 +369,15 @@ expect(ActsAsTenant.mutable_tenant?).to eq false end - describe 'mutability' do + describe "mutability" do before do - @account = Account.create!(:name => 'foo') - @project = @account.projects.create!(:name => 'bar') + @account = Account.create!(name: "foo") + @project = @account.projects.create!(name: "bar") end it "should allow tenant_id to change inside the block" do new_account_id = @account.id + 1 - expect{ ActsAsTenant.with_mutable_tenant { @project.account_id = new_account_id } }.to_not raise_error(ActsAsTenant::Errors::TenantIsImmutable) + expect { ActsAsTenant.with_mutable_tenant { @project.account_id = new_account_id } }.to_not raise_error(ActsAsTenant::Errors::TenantIsImmutable) expect(@project.account_id).to eq new_account_id end end From 98cf5b3a490e79477138c76da31b9de9df324b17 Mon Sep 17 00:00:00 2001 From: Tomas Patro Date: Tue, 23 Mar 2021 11:07:31 +0100 Subject: [PATCH 6/6] Fix Ruby 3 compatibility issue --- lib/acts_as_tenant/model_extensions.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/acts_as_tenant/model_extensions.rb b/lib/acts_as_tenant/model_extensions.rb index e870baf..7e97875 100644 --- a/lib/acts_as_tenant/model_extensions.rb +++ b/lib/acts_as_tenant/model_extensions.rb @@ -14,7 +14,7 @@ def acts_as_tenant(tenant = :account, **options) fkey = valid_options[:foreign_key] || ActsAsTenant.fkey pkey = valid_options[:primary_key] || ActsAsTenant.pkey polymorphic_type = valid_options[:foreign_type] || ActsAsTenant.polymorphic_type - belongs_to tenant, valid_options + belongs_to tenant, **valid_options default_scope lambda { if ActsAsTenant.configuration.require_tenant && ActsAsTenant.current_tenant.nil? && !ActsAsTenant.unscoped?