Ransackers get classy. Also, laying the groundwork for authorization
This commit is contained in:
parent
2725cf28d4
commit
77e570cf9c
|
@ -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'
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -24,7 +24,7 @@ module Ransack
|
|||
|
||||
def type
|
||||
if ransacker
|
||||
return ransacker[:type]
|
||||
return ransacker.type
|
||||
else
|
||||
context.type_for(attr)
|
||||
end
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue