Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow creating aliases for ransack attributes #623

Merged
merged 1 commit into from
Dec 19, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions lib/ransack/adapters/active_record/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ def self.extended(base)
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

Expand All @@ -20,15 +22,19 @@ def ransacker(name, opts = {}, &block)
.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

Expand Down
14 changes: 13 additions & 1 deletion lib/ransack/adapters/mongoid/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ def quote_column_name name
end

module ClassMethods
def _ransack_aliases
@_ransack_aliases ||= {}
end

def _ransack_aliases=(value)
@_ransack_aliases = value
end

def _ransackers
@_ransackers ||= {}
end
Expand All @@ -49,13 +57,17 @@ def ransack(params = {}, options = {})

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)
Expand Down
4 changes: 4 additions & 0 deletions lib/ransack/context.rb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,10 @@ def unpolymorphize_association(str)
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)
Expand Down
9 changes: 6 additions & 3 deletions lib/ransack/nodes/condition.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ class Condition < Node

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,
Expand All @@ -38,12 +38,15 @@ def extract_attributes_and_predicate(key, context = nil)
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

Expand Down
17 changes: 17 additions & 0 deletions spec/mongoid/adapters/mongoid/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,21 @@ module Mongoid
end
end

describe '#ransack_alias' do
it 'translates an alias to the correct attributes' do
p = Person.create!(name: 'Meatloaf', email: '[email protected]')

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?
Expand Down Expand Up @@ -213,6 +228,7 @@ def self.sane_adapter?
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' }
Expand All @@ -224,6 +240,7 @@ def self.sane_adapter?
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' }
Expand Down
15 changes: 15 additions & 0 deletions spec/mongoid/nodes/condition_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)) }

Expand Down
2 changes: 2 additions & 0 deletions spec/mongoid/support/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
27 changes: 27 additions & 0 deletions spec/ransack/adapters/active_record/base_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,32 @@ module ActiveRecord
end
end

describe '#ransack_alias' do
it 'translates an alias to the correct attributes' do
p = Person.create!(name: 'Meatloaf', email: '[email protected]')

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?
Expand Down Expand Up @@ -416,6 +442,7 @@ def self.simple_escaping?
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' }
Expand Down
15 changes: 15 additions & 0 deletions spec/ransack/nodes/condition_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
3 changes: 3 additions & 0 deletions spec/support/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down