mirror of
https://github.com/activerecord-hackery/ransack.git
synced 2022-11-09 13:47:45 -05:00
Use model scopes with Ransack (coded by @avit and @glebm).
This commit is contained in:
parent
17abebce82
commit
72dd5d12d5
6 changed files with 122 additions and 3 deletions
31
README.md
31
README.md
|
@ -278,6 +278,37 @@ ENV['RANSACK_FORM_BUILDER'] = '::SimpleForm::FormBuilder'
|
|||
require 'rails/all'
|
||||
```
|
||||
|
||||
### Authorization
|
||||
|
||||
By default Ransack exposes search for any model column, so take care to
|
||||
sanitize params and only pass allowed keys. Alternately, you can define these
|
||||
methods on your model classes for applying selective authorization based on a
|
||||
given auth object:
|
||||
|
||||
* `def ransackable_attributes(auth_object = nil)`
|
||||
* `def ransackable_associations(auth_object = nil)`
|
||||
* `def ransackable_scopes(auth_object = nil)`
|
||||
* `def ransortable_attributes(auth_object = nil)` (for sorting)
|
||||
|
||||
Any values not included in the arrays returned from these methods will be
|
||||
ignored. The auth object should be optional when building the search, and is
|
||||
ignored by default:
|
||||
|
||||
```
|
||||
Employee.search({'salary_gt' => 100000}, {auth_object: current_user})
|
||||
```
|
||||
|
||||
### Scopes
|
||||
|
||||
Searching by scope requires defining a whitelist of `ransackable_scopes` on the
|
||||
model class. By default all class methods (e.g. scopes) are ignored. Scopes
|
||||
will be applied for matching `true` values, or for given values if the scope
|
||||
accepts a value:
|
||||
|
||||
```
|
||||
Employee.search({'active' => true, 'hired_since' => '2013-01-01'})
|
||||
```
|
||||
|
||||
### I18n
|
||||
|
||||
Ransack translation files are available in
|
||||
|
|
|
@ -36,6 +36,11 @@ module Ransack
|
|||
reflect_on_all_associations.map { |a| a.name.to_s }
|
||||
end
|
||||
|
||||
# For overriding with a whitelist of symbols
|
||||
def ransackable_scopes(auth_object = nil)
|
||||
[]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -77,6 +77,19 @@ module Ransack
|
|||
table_for(parent)[attr_name]
|
||||
end
|
||||
|
||||
def chain_scope(scope, args)
|
||||
return unless @klass.method(scope) && args != false
|
||||
@object = if scope_arity(scope) < 1 && args == true
|
||||
@object.public_send(scope)
|
||||
else
|
||||
@object.public_send(scope, *args)
|
||||
end
|
||||
end
|
||||
|
||||
def scope_arity(scope)
|
||||
@klass.method(scope).arity
|
||||
end
|
||||
|
||||
def bind(object, str)
|
||||
object.parent, object.attr_name = @bind_pairs[str]
|
||||
end
|
||||
|
@ -143,6 +156,10 @@ module Ransack
|
|||
klass.ransackable_associations(auth_object).include? str
|
||||
end
|
||||
|
||||
def ransackable_scope?(str, klass)
|
||||
klass.ransackable_scopes(auth_object).any? { |s| s.to_s == str }
|
||||
end
|
||||
|
||||
def searchable_attributes(str = '')
|
||||
traverse(str).ransackable_attributes(auth_object)
|
||||
end
|
||||
|
|
|
@ -20,6 +20,7 @@ module Ransack
|
|||
@context = Context.for(object, options)
|
||||
@context.auth_object = options[:auth_object]
|
||||
@base = Nodes::Grouping.new(@context, 'and')
|
||||
@scope_args = {}
|
||||
build(params.with_indifferent_access)
|
||||
end
|
||||
|
||||
|
@ -33,6 +34,8 @@ module Ransack
|
|||
send("#{key}=", value)
|
||||
elsif base.attribute_method?(key)
|
||||
base.send("#{key}=", value)
|
||||
elsif @context.ransackable_scope?(key, @context.object)
|
||||
add_scope(key, value)
|
||||
elsif !Ransack.options[:ignore_unknown_conditions]
|
||||
raise ArgumentError, "Invalid search term #{key}"
|
||||
end
|
||||
|
@ -82,20 +85,40 @@ module Ransack
|
|||
|
||||
def method_missing(method_id, *args)
|
||||
method_name = method_id.to_s
|
||||
writer = method_name.sub!(/\=$/, '')
|
||||
if base.attribute_method?(method_name)
|
||||
getter_name = method_name.sub(/=$/, '')
|
||||
if base.attribute_method?(getter_name)
|
||||
base.send(method_id, *args)
|
||||
elsif @context.ransackable_scope?(getter_name, @context.object)
|
||||
if method_name =~ /=$/
|
||||
add_scope getter_name, args
|
||||
else
|
||||
@scope_args[method_name]
|
||||
end
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
def inspect
|
||||
"Ransack::Search<class: #{klass.name}, base: #{base.inspect}>"
|
||||
details = [
|
||||
[:class, klass.name],
|
||||
([:scope, @scope_args] if @scope_args.present?),
|
||||
[:base, base.inspect]
|
||||
].compact.map { |d| d.join(': ') }.join(', ')
|
||||
"Ransack::Search<#{details}>"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def add_scope(key, args)
|
||||
if @context.scope_arity(key) == 1
|
||||
@scope_args[key] = args.is_a?(Array) ? args[0] : args
|
||||
else
|
||||
@scope_args[key] = args
|
||||
end
|
||||
@context.chain_scope(key, args)
|
||||
end
|
||||
|
||||
def collapse_multiparameter_attributes!(attrs)
|
||||
attrs.keys.each do |k|
|
||||
if k.include?("(")
|
||||
|
|
|
@ -17,6 +17,39 @@ module Ransack
|
|||
it 'has a Relation as its object' do
|
||||
expect(subject.object).to be_an ::ActiveRecord::Relation
|
||||
end
|
||||
|
||||
context 'with scopes' do
|
||||
before do
|
||||
Person.stub :ransackable_scopes => [:active, :over_age]
|
||||
end
|
||||
|
||||
it "applies true scopes" do
|
||||
search = Person.search('active' => true)
|
||||
search.result.to_sql.should include "active = 1"
|
||||
end
|
||||
|
||||
it "ignores unlisted scopes" do
|
||||
search = Person.search('restricted' => true)
|
||||
search.result.to_sql.should_not include "restricted"
|
||||
end
|
||||
|
||||
it "ignores false scopes" do
|
||||
search = Person.search('active' => false)
|
||||
search.result.to_sql.should_not include "active"
|
||||
end
|
||||
|
||||
it "passes values to scopes" do
|
||||
search = Person.search('over_age' => 18)
|
||||
search.result.to_sql.should include "age > 18"
|
||||
end
|
||||
|
||||
it "chains scopes" do
|
||||
search = Person.search('over_age' => 18, 'active' => true)
|
||||
search.result.to_sql.should include "age > 18"
|
||||
search.result.to_sql.should include "active = 1"
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
describe '#ransacker' do
|
||||
|
@ -233,6 +266,12 @@ module Ransack
|
|||
it { should include 'articles' }
|
||||
end
|
||||
|
||||
describe '#ransackable_scopes' do
|
||||
subject { Person.ransackable_scopes }
|
||||
|
||||
it { should eq [] }
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -36,6 +36,10 @@ class Person < ActiveRecord::Base
|
|||
:source => :comments, :foreign_key => :person_id
|
||||
has_many :notes, :as => :notable
|
||||
|
||||
scope :restricted, lambda { where("restricted = 1") }
|
||||
scope :active, lambda { where("active = 1") }
|
||||
scope :over_age, lambda { |y| where(["age > ?", y]) }
|
||||
|
||||
ransacker :reversed_name, :formatter => proc { |v| v.reverse } do |parent|
|
||||
parent.table[:name]
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue