ruby-on-railsscopemulti-tenantdefault-scopeacts-as-list

acts_as_list in multi-tenant application - how do I set the scope?


Question

I'm using acts_as_list in a multi-tenant app. I modeled the multi-tenancy using this Railscast: #388 Multitenancy with Scopes (subscription required).

I need to ensure my around_filter works so that tenants can't affect other tenants' data.

What's the best way to scope acts_as_list to enforce multi-tenancy since acts_as_list ignores default_scope?

Background

The basic idea is to use an around_filter in application_controller.rb like this:

class ApplicationController < ActionController::Base
  include SessionsHelper
  around_filter :update_current_tenant

  #current_tenant is defined in sessions_helper.rb, included above
  def update_current_tenant
    Tenant.current_id = current_tenant.id if current_tenant
  end
end

That way, every time a page is loaded in the app, the current_tenant is set, and I can use default_scope to limit access to only stuff for the current_tenant. Like this:

  default_scope { where(tenant_id: Tenant.current_id) }

But acts_as_list uses Model.unscoped (Submission.unscoped, in this case), bypassing my default_scope.

Here's the specific model I'm working on:

class Submission < ActiveRecord::Base
  default_scope { where(tenant_id: Tenant.current_id) }

  acts_as_list scope: [:for_date, :tenant_id] # Works, but is this safe?

  # The one below is closer to what I want, but it bombs 
  #acts_as_list :scope => 'for_date = #{for_date} AND tenant_id = #{Tenant.current_id}'
end

I do this in a few other places, but I use an id column instead of for_date, so the acts_as_list declaration looks like:

acts_as_list :scope => 'task_id = #{task_id} AND tenant_id = #{Tenant.current_id}'

That works well. But when I try this with my Submission model, and include for_date in the acts_as_list scope, I get this error:

PG::UndefinedFunction: ERROR:  operator does not exist: date = integer
LINE 1: ... (submissions.position IS NOT NULL) AND (for_date = 2013-11-...
                                                           ^
HINT:  No operator matches the given name and argument type(s). You might need to add explicit type casts.
: SELECT  "submissions".* FROM "submissions"  WHERE (submissions.position IS NOT NULL) AND (for_date = 2013-11-25 AND tenant_id = 1) ORDER BY submissions.position DESC LIMIT 1

How would I add such an explicit typecast? Or is there a better way to do this?


Solution

  • Turns out I just needed to escape the for_date (I think because it's a "date" data type that has single quotes, as opposed to an integer data type, but I'm not sure):

    acts_as_list :scope => 'for_date = \'#{for_date}\' AND tenant_id = #{Tenant.current_id}'
    

    That did the trick.