Ransackers get classy. Also, laying the groundwork for authorization

This commit is contained in:
Ernie Miller 2011-04-11 20:07:23 -04:00
parent 2725cf28d4
commit 77e570cf9c
10 changed files with 64 additions and 35 deletions

View File

@ -16,6 +16,7 @@ end
require 'ransack/translate'
require 'ransack/search'
require 'ransack/ransacker'
require 'ransack/adapters/active_record'
require 'ransack/helpers'
require 'action_controller'

View File

@ -5,23 +5,27 @@ module Ransack
def self.extended(base)
alias :search :ransack unless base.method_defined? :search
end
def ransack(params = {})
Search.new(self, params)
end
def ransacker(name, opts = {}, &block)
unless method_defined?(:_ransackers)
base.instance_eval do
class_attribute :_ransackers
self._ransackers ||= {}
end
end
opts[:type] ||= :string
opts[:args] ||= [:parent]
opts[:callable] ||= block || (method(name) if method_defined?(name)) || proc {|parent| parent.table[name]}
def ransack(params = {}, options = {})
Search.new(self, params, options)
end
_ransackers[name.to_s] = opts
def ransacker(name, opts = {}, &block)
Ransacker.new(self, name, opts, &block)
end
# TODO: Let's actually do some authorization. Whitelist-only.
def ransackable_attributes(auth_object)
column_names + _ransackers.keys
end
def ransackable_associations(auth_object)
reflect_on_all_associations.map {|a| a.name.to_s}
end
end

View File

@ -18,7 +18,7 @@ module Ransack
def attribute_method?(str, klass = @klass)
exists = false
if get_ransacker(str, klass) || get_column(str, klass)
if ransackable_attribute?(str, klass)
exists = true
elsif (segments = str.split(/_/)).size > 1
remainder = []
@ -67,14 +67,15 @@ module Ransack
def get_parent_and_attribute_name(str, parent = @base)
attr_name = nil
if get_ransacker(str, parent) || get_column(str, parent)
if ransackable_attribute?(str, klassify(parent))
attr_name = str
elsif (segments = str.split(/_/)).size > 1
remainder = []
found_assoc = nil
while remainder.unshift(segments.pop) && segments.size > 0 && !found_assoc do
assoc, klass = unpolymorphize_association(segments.join('_'))
if found_assoc = get_association(assoc, parent)
if ransackable_association?(assoc, klassify(parent))
found_assoc = get_association(assoc, parent)
join = build_or_find_association(found_assoc.name, parent, klass)
parent, attr_name = get_parent_and_attribute_name(remainder.join('_'), join)
end
@ -84,13 +85,12 @@ module Ransack
[parent, attr_name]
end
def get_ransacker(str, parent = @base)
klass = klassify(parent)
klass._ransackers[str] if klass.respond_to?(:_ransackers)
def ransackable_attribute?(str, klass)
klass.ransackable_attributes(auth_object).include? str
end
def get_column(str, parent = @base)
klassify(parent).columns_hash[str]
def ransackable_association?(str, klass)
klass.ransackable_associations(auth_object).include? str
end
def get_association(str, parent = @base)

View File

@ -3,6 +3,7 @@ require 'ransack/visitor'
module Ransack
class Context
attr_reader :search, :object, :klass, :base, :engine, :arel_visitor
attr_accessor :auth_object
class << self
@ -103,8 +104,8 @@ module Ransack
end
end
def searchable_columns(str = '')
traverse(str).column_names
def searchable_attributes(str = '')
traverse(str).ransackable_attributes(auth_object)
end
end

View File

@ -25,7 +25,7 @@ module Ransack
collection = bases.map do |base|
[
Translate.association(base, :context => object.context),
object.context.searchable_columns(base).map do |c|
object.context.searchable_attributes(base).map do |c|
[
attr_from_base_and_column(base, c),
Translate.attribute(attr_from_base_and_column(base, c), :context => object.context)
@ -38,7 +38,7 @@ module Ransack
objectify_options(options), @default_options.merge(html_options)
)
else
collection = object.context.searchable_columns(bases.first).map do |c|
collection = object.context.searchable_attributes(bases.first).map do |c|
[
attr_from_base_and_column(bases.first, c),
Translate.attribute(attr_from_base_and_column(bases.first, c), :context => object.context)
@ -59,7 +59,7 @@ module Ransack
collection = bases.map do |base|
[
Translate.association(base, :context => object.context),
object.context.searchable_columns(base).map do |c|
object.context.searchable_attributes(base).map do |c|
[
attr_from_base_and_column(base, c),
Translate.attribute(attr_from_base_and_column(base, c), :context => object.context)
@ -75,7 +75,7 @@ module Ransack
objectify_options(options), @default_options.merge(html_options)
)
else
collection = object.context.searchable_columns(bases.first).map do |c|
collection = object.context.searchable_attributes(bases.first).map do |c|
[
attr_from_base_and_column(bases.first, c),
Translate.attribute(attr_from_base_and_column(bases.first, c), :context => object.context)

View File

@ -24,7 +24,7 @@ module Ransack
def type
if ransacker
return ransacker[:type]
return ransacker.type
else
context.type_for(attr)
end

View File

@ -5,15 +5,11 @@ module Ransack
attr_accessor :parent, :attr_name
def attr
@attr ||= ransacker ? ransacker[:callable].call(*args_for_ransacker(ransacker)) : context.table_for(parent)[attr_name]
@attr ||= ransacker ? ransacker.attr_from(self) : context.table_for(parent)[attr_name]
end
def ransacker
klass._ransackers[attr_name] if klass.respond_to?(:_ransackers)
end
def args_for_ransacker(ransacker)
ransacker[:args].map {|a| self.send(a)}
klass._ransackers[attr_name]
end
def klass

View File

@ -193,7 +193,7 @@ module Ransack
def formatted_values_for_attribute(attr)
casted_values_for_attribute(attr).map do |val|
val = attr.ransacker[:formatter].call(val) if attr.ransacker && attr.ransacker[:formatter]
val = attr.ransacker.formatter.call(val) if attr.ransacker && attr.ransacker.formatter
val = predicate.formatter.call(val) if predicate.formatter
val
end

26
lib/ransack/ransacker.rb Normal file
View File

@ -0,0 +1,26 @@
module Ransack
class Ransacker
attr_reader :name, :type, :formatter, :args
delegate :call, :to => :@callable
def initialize(klass, name, opts = {}, &block)
@klass, @name = klass, name
@type = opts[:type] || :string
@args = opts[:args] || [:parent]
@formatter = opts[:formatter]
@callable = opts[:callable] || block ||
(@klass.method(name) if @klass.respond_to?(name)) ||
proc {|parent| parent.table[name]}
@klass._ransackers[name.to_s] = self
end
def attr_from(bindable)
call(*args.map {|arg| bindable.send(arg)})
end
end
end

View File

@ -13,9 +13,10 @@ module Ransack
:build_and, :build_or, :build_condition,
:translate, :to => :base
def initialize(object, params = {})
def initialize(object, params = {}, options = {})
params ||= {}
@context = Context.for(object)
@context.auth_object = options[:auth_object]
@base = Nodes::And.new(@context)
build(params.with_indifferent_access)
end