2
votes

Facing very wired issue while using Paperclip (With s3 storage) and Apartment gem.

Apartment gem is being used for multi-tenancy with Postgres DB. ( i.e Separate Schema for every tenant )

For Storage, I would like to create a separate folder for every tenant.

class Media < ApplicationRecord

  belongs_to :user

  has_attached_file :file,
          :storage => :s3,
          :s3_credentials => {
                  :access_key_id => "ACCESSKEY",
                  :secret_access_key => "SECRETE KEY"
                },
          :path => "#{Apartment::Tenant.current}/:class/:attachment/:id_partition/:style/:filename"


end

Above is my media folder, which belongs to User.

class User < ApplicationRecord

  has_attached_file :avatar,
      :storage => :s3,
      :s3_credentials => {
              :access_key_id => "ACCESSKEY",
              :secret_access_key => "SECRETE KEY"
            },
      :path => "#{Apartment::Tenant.current}/:class/:attachment/:id_partition/:style/:filename"
end

Apartment.rb file looks like below

require 'apartment/elevators/subdomain'
require 'rescued_apartment_middleware'

Apartment.configure do |config|
  config.excluded_models = %w{ Account }
  config.tenant_names = lambda { Account.pluck :subdomain }
  config.use_schemas = true
end

Rails.application.config.middleware.insert_before Warden::Manager, RescuedApartmentElevator
Apartment::Elevators::Subdomain.excluded_subdomains = ['www','admin']

rescued_apartment_elevator.rb file looks like below

class RescuedApartmentElevator < Apartment::Elevators::Subdomain
  def call(env)
   begin
     super
   rescue Apartment::TenantNotFound
     env[:apartment_tenant_not_found] = true # to be later referenced in your ApplicationController
     @app.call(env) # the middleware call method should return this, but it was probably short-circuited by the raise
   end
 end
end

In application_controller.rb I have handled the code something like below

class ApplicationController < ActionController::Base
  before_action :load_schema, :authenticate_user!, :set_mailer_host
  before_action :configure_permitted_parameters, if: :devise_controller?

  private 

  def load_schema
    Apartment::Tenant.switch!
    return if request.subdomain == "www"

    if request.subdomain == "" 
      redirect_to root_url(subdomain: 'www')
    else 
      if current_account
        Apartment::Tenant.switch!(current_account.subdomain)
      else
        if request.env[:apartment_tenant_not_found]
         redirect_to root_url(subdomain: 'www'), notice: 'Account you are looking for do not exists'
        end
      end
    end
   end

end

I start my server, go to one of the tenant e.g http://apple.lvh.me:3000 (apple is tenant name here)

1. If I go to edit user profile and upload the file for user profile, Apartment::Tenant.current is returning "public" in model and hence the file is getting uploaded in public folder instead of "apple" folder.

I am using devise gem (latest version) below is how my user_controller.rb looks like

class UsersController < ApplicationController
  before_action : set_user, only: [:edit,:update]

  def update
   if @user.update(user_params)
    if @user.pending_reconfirmation?
      redirect_to edit_user_path(current_user), notice: "Your profile is successfully updated! But if you changed the email address, then please confirm it before!"
    else
      redirect_to edit_user_path(current_user), notice: "Your profile is successfully updated!"
    end
  else
    render "edit"
  end
 end

end

Problem 2 **When I go to media and add some media, they would go properly to "apple" folder on s3 bucket. I would sign out, go to different tanent i.e http://google.lvh.me:3000/ (google is tenant name here). If I go to media listing, URL will continue to point to Apartment::Tenant.current -> apple only. This results in wrong URL for google tenant and images wont display.

If I shut down and restart the server, then rails will start returning google for Apartment::Tenant.current method and hence URL pointing correctly to correct folder. **

To Summerise, below are my problems.

  1. Using Apartment::Tenant.current in model for paperclip custom path - is it right way? Is there some other method that could return me current tanent so I can use it for creating folder on s3 bucket.
  2. Have I configured the middleware correctly in my apartment.rb file?
  3. Is there different way of doing this?
1

1 Answers

2
votes

After posting to StackOverflow, I was able to figureout the solution. Posting it here in case someone else stumbleupon this in future.

My Mistake was tryign to use #{Apartment::Tenant.current} into path directly. This is wrong. paperclip support Interpolates, I should be using that in order to generate dynamic paths.

so below are the changes required.

has_attached_file :file,
                    :storage => :s3,
                    :s3_credentials => {
                      :access_key_id => "AccessKey",
                      :secret_access_key => "SecreteKey"
                    },
                    :path => ":tenant/:class/:attachment/:id_partition/:style/:filename"

Notice the :tenant being introduced in the path.

Add following line into paperclip.rb

Paperclip.interpolates :tenant do |attachment, style|
  Apartment::Tenant.current
end

This is it. Hope it helps someone!