ActiveRecord@ActionController で scoped_access をいろいろな書式で

ActiveRecordを詳しく「優しいRailsの育て方」 より。
もっとシンプルにできるけど。
つい機能追加やら汎用化やらをしてしまう。
Rails 2.1.0 で動作確認。
コメントいただいたので scoped_access_filter.rb を修正しました。

使い方

基本。

class MailController < ApplicationController
  scoped_access Mail, :mine
  
  def mine
    unless user.admin?
      { :find   => {:conditions => ["owner = ?", user]},
        :create => {:owner      => user               } }
    end
  end

  def show
    @mail = Mail.find(params[:id])
  end
end

複数のモデルを対象とする。

scoped_access Mail, User, Group, :not_deleted

def not_deleted(model)
  if model.columns.any? { |c| c.name == 'deleted_at' }
    { :find => {:conditions => ["NOT deleted_at IS NULL"]} }
  end
end

条件を lambda (block) で定義する。

scoped_access Mail, lambda { {:find => {:conditions => ["updated_at < ?", Date.today]}} }
scoped_access(Mail) do |model, controller|
  # ...
end

条件を直接(Hash で)定義する。

scoped_access User, :find => {:conditions => ['admin = ?', true]}

named_scope の条件を利用する。

class User < ActiveRecord::Base
  named_scope :admins, :conditions => ['admin = ?', true]
  # ...
end

scoped_access User, :name => :admins

フィルタを適用(除外)するアクションを指定する。(before_filter 等と同様)

scoped_access Mail, :mine, :only => :show

scoped_access User, :find => {:conditions => ['admin = ?', true]}, :except => :list

scoped_access_filter.rb

class ScopedAccessFilter
  def initialize(model, *args, &block)
    options = args.extract_options!
    @constraints = case
      when options[:method]
        options[:method]
      when options[:name] # named_scope
        {:find => model.send(options[:name]).proxy_options}
      when !options.empty?
        options
    end
    @constraints ||= args.pop unless args.last.is_a?(Class) and args.last < ActiveRecord::Base
    @constraints ||= block || :method_scoping
    @models = [model, args].flatten
  end

  def filter(controller)
    scoped_models = []
    @models.each do |model|
      constraints = create_constraints(controller, model)
      next unless constraints
      ActiveRecord::Base.logger.debug("ScopedAccessFilter: %s.with_scope(%s)" % [model, constraints.inspect])
      model.instance_eval do
        scoped_methods << with_scope(constraints) { current_scoped_methods }
      end
      scoped_models << model
    end

    begin
      yield
    ensure
      scoped_models.each do |model|
        model.instance_eval do
          scoped_methods.pop
        end
      end
    end
  end

  private
  def create_constraints(controller, model)
    cproc = case @constraints
      when Proc, Method
        @constraints
      when String, Symbol
        controller.method(@constraints)
    end
    return @constraints unless cproc
    args = case cproc.arity
      when 0; []
      when 1; [model]
      else  ; [model, controller]
    end
    cproc.call(*args)
  end
end

module ActionController::Filters::ClassMethods
  def scoped_access(model, *args, &block)
    options = args.extract_options!
    scoped_options = {}
    options.reject! { |k, v| (scoped_options[k] = v; true) unless [:only, :except].include? k }
    args << scoped_options unless scoped_options.empty?
    around_filter ScopedAccessFilter.new(model, *args, &block), options
  end
end