2

I want to create a default scope to filter all queries depending on the current user. Is it possible to pass the current user as an argument to the default_scope? (I know this can be done with regular scopes) If not, what would be another solution?

alberto911
  • 23
  • 4

2 Answers2

2

Instead of using default_scope which has a few pitfalls, you should consider using a named scope with a lambda. For example scope :by_user, -> (user) { where('user_id = ?', user.id) }

You can then use a before_filter in your controllers to easily use this scope in all the actions you need.

This is also the proper way to do it since you won't have access to helper methods in your model. Your models should never have to worry about session data, either.

Edit: how to use the scope in before_filter inside a controller:

before_filter :set_object, only: [:show, :edit, :update, :destroy]

[the rest of your controller code here]

private
def set_object
   @object = Object.by_user(current_user)
end

obviously you'd change this depending on your requirements. Here we're assuming you only need a valid @object depending on current_user inside your show, edit, update, and destroy actions

Community
  • 1
  • 1
Mario
  • 1,349
  • 11
  • 16
1

You cannot pass an argument to a default scope, but you can have a default scope's conditions referencing a proxy hash that executes its procs every time a value is retrieved:

module Proxies
  # ProxyHash can be used when there is a need to have procs inside hashes, as it executes all procs before returning the hash
  # ==== Example
  #   default_scope(:conditions => Proxies::ProxyHash.new(:user_id => lambda{Thread.current[:current_user].try(:id)}))
  class ProxyHash < ::Hash
    instance_methods.each{|m| undef_method m unless m =~ /(^__|^nil\?$|^method_missing$|^object_id$|proxy_|^respond_to\?$|^send$)/}

    def [](_key)
      call_hash_procs(@target, @original_hash)
      ProxyHash.new(@original_hash[_key])
    end

    # Returns the \target of the proxy, same as +target+.
    def proxy_target
      @target
    end

    # Does the proxy or its \target respond to +symbol+?
    def respond_to?(*args)
      super(*args) || @target.respond_to?(*args)
    end

    # Returns the target of this proxy, same as +proxy_target+.
    def target
      @target
    end

    # Sets the target of this proxy to <tt>\target</tt>.
    def target=(target)
      @target = target
    end

    def send(method, *args)
      if respond_to?(method)
        super
      else
        @target.send(method, *args)
      end
    end

    def initialize(*_find_args)
      @original_hash = _find_args.extract_options!

      @target = @original_hash.deep_dup
    end

    private
      # Forwards any missing method call to the \target.
      def method_missing(method, *args, &block)
        if @target.respond_to?(method)
          call_hash_procs(@target, @original_hash)
          @target.send(method, *args, &block)
        else
          super
        end
      end

      def call_hash_procs(_hash, _original_hash)
        _hash.each do |_key, _value|
          if _value.is_a?(Hash)
            call_hash_procs(_value, _original_hash[_key]) if _original_hash.has_key?(_key)
          else
            _hash[_key] = _original_hash[_key].call if _original_hash[_key].is_a?(Proc)
          end
        end
      end
  end
end

Then in ApplicationController you can use an around_filter to set/unset Thread.current[:current_user] at the begin/end of each request:

class ApplicationController < ActionController::Base
  around_filter :set_unset_current_user

  protected
    def set_unset_current_user
      Thread.current[:current_user] = current_user if logged_in?

      yield
    ensure
      Thread.current[:current_user] = nil
    end
end
roniegh
  • 191
  • 1
  • 6