influitive / apartment

Database multi-tenancy for Rack (and Rails) applications
2.66k stars 464 forks source link

Feature Request: Exclude ActiveStorage attachments from tenancy #611

Open davidlowry opened 4 years ago

davidlowry commented 4 years ago

Feature request, or How To??

Steps to reproduce

Have an excluded model, add ActiveStorage to that model. Here, Teams have "Logos" and "Branded Backgrounds" which are, ideally, global. My Teams also act as the Tenants here, but the fixture list can be global, and the issue may stand alone.

config.excluded_models = %w{ User Order Team Fixture }

I've tried to exclude some variation of ActiveStorage/ActiveStorage::Attached/ActiveStorage::Attached::One but get a undefined methodestablish_connection' for [x]` in response.

Desired behavior

A Tenanted model's attachments should be globally accessible

Actual behavior

While the Tenant model is visible, adding an attachment either in the public domain or in a tenant, it displays no attachments for each of the other tenants. If this was Paperclip or some such I don't imagine there'd be an issue, but using Rail's Own Brand storage solution I can't work out the solution.

System configuration

Ruby 2.6.3 Rails 5.2 Apartment 2.2 DB: Sqlite locally/Mysql in production test

`Apartment.configure do |config|

config.excluded_models = %w{ User Order Team Fixture }

config.excluded_models << 'ActiveStorage::Attached::One'

config.tenant_names = lambda { Team.clients.pluck :tenant_identifier }

config.use_schemas = true end`

rubendinho commented 4 years ago

Also running into a similar problem:

The model has an attachment using ActiveStorage

class Client < ApplicationRecord
  has_one_attached :logo
end

The model is excluded like so:

Apartment.configure do |config|
...
  config.excluded_models = ['Client']
...
end

When switched onto a tenant DB, the logo is not available. $ Apartment::Tenant.switch('foo') { Client.first.logo.attached? } # = false

When accessing from the public DB, the logo is attached. $ Apartment::Tenant.switch { Client.first.logo.attached? } # = true

Is there a way to make ActiveStorage play nice with Apartment?

rubendinho commented 4 years ago

Update: I was able to get things working by adding these two models to excluded_models: config.excluded_models = ['Client', 'ActiveStorage::Attachment', 'ActiveStorage::Blob']

But this obviously means all ActiveStorage data is only on the "public" DB.

cimm commented 4 years ago

I was wondering why purge_later didn’t work on my ActiveStorage objects! They are running outside of the Apartment::Tenant scope of course. Thanks!

archonic commented 4 years ago

In my app, some attachments should be tenanted and other should be public. User avatars for example should be public but attachments on tenanted resources should be tenanted. There's an inherent incompatibility between schemas and polymorphism. It should still work if you use Apartment::Tenant.switch at the right times, but it doesn't.

If I call resource.reload in my avatar helper, that ensures that resource.avatar.attached? is correct and consistent, but it also introduces an N+1. I'm not sure why, but this isn't working:

def avatar_url(resource, width)
  Apartment::Tenant.switch("public") do
    if resource.avatar.attached?
      size_str = "#{width}x#{width}"
      full_url_for_avatar(
        resource.avatar.variant(resize: size_str)
      )
    else
      ""
    end
  end
end

While this is working:

def avatar_url(resource, width)
  Apartment::Tenant.switch("public") do
    # NOTE ActiveStorage + Apartment is buggy?
    if resource.avatar.attached? || resource.reload.avatar.attached?
      size_str = "#{width}x#{width}"
      full_url_for_avatar(
        resource.avatar.variant(resize: size_str)
      )
    else
      ""
    end
  end
end

This may have something to do with the way I'm including the attachment blob in the ActiveRecord::Relation but I don't see any issue with includes(user: { avatar_attachment: :blob }).