Merge pull request #623 from rzane/ransack-alias

Allow creating aliases for ransack attributes.
This commit is contained in:
Jon Atack 2015-12-20 00:04:47 +01:00
commit 1de34535ea
10 changed files with 110 additions and 6 deletions

View File

@ -7,7 +7,9 @@ module Ransack
alias :search :ransack unless base.respond_to? :search
base.class_eval do
class_attribute :_ransackers
class_attribute :_ransack_aliases
self._ransackers ||= {}
self._ransack_aliases ||= {}
end
end
@ -20,15 +22,19 @@ module Ransack
.new(self, name, opts, &block)
end
def ransack_alias(new_name, old_name)
self._ransack_aliases.store(new_name.to_s, old_name.to_s)
end
# Ransackable_attributes, by default, returns all column names
# and any defined ransackers as an array of strings.
# For overriding with a whitelist array of strings.
#
def ransackable_attributes(auth_object = nil)
if Ransack::SUPPORTS_ATTRIBUTE_ALIAS
column_names + _ransackers.keys + attribute_aliases.keys
column_names + _ransackers.keys + _ransack_aliases.keys + attribute_aliases.keys
else
column_names + _ransackers.keys
column_names + _ransackers.keys + _ransack_aliases.keys
end
end

View File

@ -33,6 +33,14 @@ module Ransack
end
module ClassMethods
def _ransack_aliases
@_ransack_aliases ||= {}
end
def _ransack_aliases=(value)
@_ransack_aliases = value
end
def _ransackers
@_ransackers ||= {}
end
@ -49,13 +57,17 @@ module Ransack
alias_method :search, :ransack
def ransack_alias(new_name, old_name)
self._ransack_aliases.store(new_name.to_s, old_name.to_s)
end
def ransacker(name, opts = {}, &block)
self._ransackers = _ransackers.merge name.to_s => Ransacker
.new(self, name, opts, &block)
end
def all_ransackable_attributes
['id'] + column_names.select { |c| c != '_id' } + _ransackers.keys
['id'] + column_names.select { |c| c != '_id' } + _ransackers.keys + _ransack_aliases.keys
end
def ransackable_attributes(auth_object = nil)

View File

@ -120,6 +120,10 @@ module Ransack
end
end
def ransackable_alias(str)
klass._ransack_aliases.fetch(str, str)
end
def ransackable_attribute?(str, klass)
klass.ransackable_attributes(auth_object).include?(str) ||
klass.ransortable_attributes(auth_object).include?(str)

View File

@ -9,9 +9,9 @@ module Ransack
class << self
def extract(context, key, values)
attributes, predicate = extract_attributes_and_predicate(key, context)
attributes, predicate, combinator = extract_attributes_and_predicate(key, context)
if attributes.size > 0 && predicate
combinator = key.match(/_(or|and)_/) ? $1 : nil
condition = self.new(context)
condition.build(
:a => attributes,
@ -38,12 +38,15 @@ module Ransack
unless predicate || Ransack.options[:ignore_unknown_conditions]
raise ArgumentError, "No valid predicate for #{key}"
end
str = context.ransackable_alias(str) if context.present?
combinator = str.match(/_(or|and)_/) ? $1 : nil
if context.present? && context.attribute_method?(str)
attributes = [str]
else
attributes = str.split(/_and_|_or_/)
end
[attributes, predicate]
[attributes, predicate, combinator]
end
end

View File

@ -50,6 +50,21 @@ module Ransack
end
end
describe '#ransack_alias' do
it 'translates an alias to the correct attributes' do
p = Person.create!(name: 'Meatloaf', email: 'babies@example.com')
s = Person.ransack(term_cont: 'atlo')
expect(s.result.to_a).to eq [p]
s = Person.ransack(term_cont: 'babi')
expect(s.result.to_a).to eq [p]
s = Person.ransack(term_cont: 'nomatch')
expect(s.result.to_a).to eq []
end
end
describe '#ransacker' do
# For infix tests
def self.sane_adapter?
@ -213,6 +228,7 @@ module Ransack
it { should include 'name' }
it { should include 'reversed_name' }
it { should include 'doubled_name' }
it { should include 'term' }
it { should include 'only_search' }
it { should_not include 'only_sort' }
it { should_not include 'only_admin' }
@ -224,6 +240,7 @@ module Ransack
it { should include 'name' }
it { should include 'reversed_name' }
it { should include 'doubled_name' }
it { should include 'term' }
it { should include 'only_search' }
it { should_not include 'only_sort' }
it { should include 'only_admin' }

View File

@ -4,6 +4,21 @@ module Ransack
module Nodes
describe Condition do
context 'with an alias' do
subject {
Condition.extract(
Context.for(Person), 'term_start', Person.first(2).map(&:name)
)
}
specify { expect(subject.combinator).to eq 'or' }
specify { expect(subject.predicate.name).to eq 'start' }
it 'converts the alias to the correct attributes' do
expect(subject.attributes.map(&:name)).to eq(['name', 'email'])
end
end
context 'with multiple values and an _any predicate' do
subject { Condition.extract(Context.for(Person), 'name_eq_any', Person.first(2).map(&:name)) }

View File

@ -20,6 +20,8 @@ class Person
has_many :articles
has_many :comments
ransack_alias :term, :name_or_email
# has_many :authored_article_comments, :through => :articles,
# :source => :comments, :foreign_key => :person_id

View File

@ -94,6 +94,32 @@ module Ransack
end
end
describe '#ransack_alias' do
it 'translates an alias to the correct attributes' do
p = Person.create!(name: 'Meatloaf', email: 'babies@example.com')
s = Person.ransack(term_cont: 'atlo')
expect(s.result.to_a).to eq [p]
s = Person.ransack(term_cont: 'babi')
expect(s.result.to_a).to eq [p]
s = Person.ransack(term_cont: 'nomatch')
expect(s.result.to_a).to eq []
end
it 'also works with associations' do
dad = Person.create!(name: 'Birdman')
son = Person.create!(name: 'Weezy', parent: dad)
s = Person.ransack(daddy_eq: 'Birdman')
expect(s.result.to_a).to eq [son]
s = Person.ransack(daddy_eq: 'Drake')
expect(s.result.to_a).to eq []
end
end
describe '#ransacker' do
# For infix tests
def self.sane_adapter?
@ -416,6 +442,7 @@ module Ransack
it { should include 'name' }
it { should include 'reversed_name' }
it { should include 'doubled_name' }
it { should include 'term' }
it { should include 'only_search' }
it { should_not include 'only_sort' }
it { should_not include 'only_admin' }

View File

@ -4,6 +4,21 @@ module Ransack
module Nodes
describe Condition do
context 'with an alias' do
subject {
Condition.extract(
Context.for(Person), 'term_start', Person.first(2).map(&:name)
)
}
specify { expect(subject.combinator).to eq 'or' }
specify { expect(subject.predicate.name).to eq 'start' }
it 'converts the alias to the correct attributes' do
expect(subject.attributes.map(&:name)).to eq(['name', 'email'])
end
end
context 'with multiple values and an _any predicate' do
subject {
Condition.extract(

View File

@ -52,6 +52,9 @@ class Person < ActiveRecord::Base
alias_attribute :full_name, :name
ransack_alias :term, :name_or_email
ransack_alias :daddy, :parent_name
ransacker :reversed_name, formatter: proc { |v| v.reverse } do |parent|
parent.table[:name]
end