ErwinM / acts_as_tenant

Easy multi-tenancy for Rails in a shared database setup.
MIT License
1.53k stars 263 forks source link

Removing all Tenant Models #261

Closed aximuseng closed 7 months ago

aximuseng commented 3 years ago

Can you return all the models that are active in the Tenant?

I am looking to have active_admin 'wipe' a tenant and override all callbacks (had_many and ancestry restrictions) etc.

excid3 commented 3 years ago

That's not really in the scope of this gem. You'd just need to delete all the records yourself. As long as the current_tenant is set, you can delete_all on the models probably.

aximuseng commented 3 years ago

To clarify I mean like the functionality Searchkick has:

Searchkick.models.each do |model| puts "Reindexing #{model.name} on #{tenant.tenant_name}" model.reindex end

It knows which models there are indexes on.

excid3 commented 3 years ago

ActsAsTenant doesn't keep track of what models you have scoped anywhere.

aximuseng commented 3 years ago

Ok - was just hoping there was a quick method like Searchkick has. I even looked in the source code for SK trying to figure out how that method works and apply that in AAT - no luck so far.

I am guessing there is nothing past Model.account.present? to test each model and confirm it has a tenant. Looking to ensure I don't orphan a ton of records each time I destroy a tenant.

drj17 commented 2 years ago

@aximuseng did you figure out any convenient solution for this? I ended up just adding a has_many reference for every tenant models with dependent: :destroy, but it's somewhat error prone.

aximuseng commented 2 years ago

I never got past this post and my efforts stalled. That is probably a good starting point. As you mentioned the potential for errors / disaster is there. In my case I have some pretty complex relationships and while testing various solutions I ran into issues where unless I have the tenant models destroyed in a very specific order it throws foreign key errors etc.

It feels like that scene from Apollo 13 where ground back-up team is trying to engineer the perfect sequence in the simulator and power-up the spacecraft without overloading the battery 🤣🚀

bearded-avenger commented 2 years ago

I do think Chris is right, it's not the job of this gem to do that. Apartment has a .drop method but that's a little different due to its schema based approach.

I am glad this was brought up though, as it’s been somewhat challenging to find the right way to do this while maintaining data integrity, and ensuring no other tenant data is deleted.

Dependent: destroy Not advised because it instantiates all the children. We have 93 models attached to a tenant. I killed the box deleting a site.

Dependent: destroy_async This was introduced in rails 6, and from what I can tell wipes the foreign key and leaves them orphaned which can cause app errors while the end user waits for the records to be deleted.

Bypassing rails with on_delete :cascade (postgresql) This means leaving the rails ecosystem and adding an on_delete :cascade fk reference to each of the 93 tables. This just makes me queasy because I prefer to keep things in Rails. There’s the potential for error here because it wont delete any grandchildren from the children.

dependent: :delete_all The chaining here is an issue. Consider a Site has many orders, then orders has_many items. You'd get a fk reference error, which to fix means running dependent: :delete_all on the order to the items. This would then involve update every single model that has children to this method, which isn't great if you have callbacks or cleanup methods from after destroy callbacks on the model.

At the end of the day, there's no great way to do this, so it comes down to, which way sucks less?

What I'm starting to lean towards is something like destroy_async, but since we're on Rails 5 we'll have to write our own logic.

blombard commented 6 months ago

I have the same problem. I would like to be able to delete all data linked to a tenant.

I wrote this funny script to solve this problem but I am not sure yet if I would trust it with production data? 😇

ActsAsTenant.current_tenant = Tenant.first

ActiveRecord::Base.transaction do
  data = true
  while data
    data = false
    ApplicationRecord.descendants.each do |model|
      next unless model.column_names.include?('tenant_id')

      begin
        ActiveRecord::Base.transaction(requires_new: true) do
          model.delete_all
        end
      rescue ActiveRecord::StatementInvalid
        data = true
      end
    end
  end

  ActsAsTenant.current_tenant.destroy!
end