Rails 3 devise, current_user is not accessible in a Model ?

in my project.rb model, I'm trying to create a scope with a dynamic variable:

scope :instanceprojects, lambda { 
    where("projects.instance_id = ?", current_user.instance_id)
} 

I get the following error:

undefined local variable or method `current_user' for #<Class:0x102fe3af0>

Where in the controller I can access current_user.instance_id... Is there a reason the model can't access it and a way to get access? Also, is this the right place to create a scope like the above, or does that belong in the controller?


Solution 1:

This doesn't make much sense, as you already pointed. The current_user doesn't belong to model logic at all, it should be handled on the controller level.

But you can still create scope like that, just pass the parameter to it from the controller:

scope :instanceprojects, lambda { |user|
    where("projects.instance_id = ?", user.instance_id)
} 

Now you can call it in the controller:

Model.instanceprojects(current_user)

Solution 2:

The already accepted answer provides a really correct way to achieve this.

But here's the thread-safe version of User.current_user trick.

class User
  class << self
    def current_user=(user)
      Thread.current[:current_user] = user
    end

    def current_user
      Thread.current[:current_user]
    end
  end
end

class ApplicationController
  before_filter :set_current_user

  def set_current_user
    User.current_user = current_user
  end
end

This works as expected, however it can be considered dirty, because we basically define a global variable here.

Solution 3:

Ryan Bates lays out a pretty safe way to implement this kind of strategy in this railscast

This a paid episode (don't down vote me!) but you can browse the source code for free

Here he creates a current_tenant method, but you could easily substitute current_user instead.

Here are the key bits of code...

#application_controller.rb
around_filter :scope_current_tenant

private

def current_tenant
  Tenant.find_by_subdomain! request.subdomain
end
helper_method :current_tenant

def scope_current_tenant
  Tenant.current_id = current_tenant.id
  yield
ensure
  Tenant.current_id = nil
end

#models/tenant.rb

def self.current_id=(id)
  Thread.current[:tenant_id] = id
end

def self.current_id
  Thread.current[:tenant_id]
end

Then in the model you can do something like...

default_scope { where(tenant_id: Tenant.current_id) }