diff --git a/lib/ransack/adapters/active_record/base.rb b/lib/ransack/adapters/active_record/base.rb index 6d9ef7b31..2428e7137 100644 --- a/lib/ransack/adapters/active_record/base.rb +++ b/lib/ransack/adapters/active_record/base.rb @@ -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 @@ -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 diff --git a/lib/ransack/adapters/mongoid/base.rb b/lib/ransack/adapters/mongoid/base.rb index 0eeba3e1c..f0035333a 100644 --- a/lib/ransack/adapters/mongoid/base.rb +++ b/lib/ransack/adapters/mongoid/base.rb @@ -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 @@ -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) diff --git a/lib/ransack/context.rb b/lib/ransack/context.rb index 09bc83aad..8be64d627 100644 --- a/lib/ransack/context.rb +++ b/lib/ransack/context.rb @@ -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) diff --git a/lib/ransack/nodes/condition.rb b/lib/ransack/nodes/condition.rb index 3f4ebc0f9..923989b40 100644 --- a/lib/ransack/nodes/condition.rb +++ b/lib/ransack/nodes/condition.rb @@ -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, @@ -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 diff --git a/spec/mongoid/adapters/mongoid/base_spec.rb b/spec/mongoid/adapters/mongoid/base_spec.rb index b3625a8b9..88cef22c6 100644 --- a/spec/mongoid/adapters/mongoid/base_spec.rb +++ b/spec/mongoid/adapters/mongoid/base_spec.rb @@ -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: '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 @@ 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' } @@ -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' } diff --git a/spec/mongoid/nodes/condition_spec.rb b/spec/mongoid/nodes/condition_spec.rb index 0043974a9..8829ea89f 100644 --- a/spec/mongoid/nodes/condition_spec.rb +++ b/spec/mongoid/nodes/condition_spec.rb @@ -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)) } diff --git a/spec/mongoid/support/schema.rb b/spec/mongoid/support/schema.rb index 7b3360482..f4c89a6a8 100644 --- a/spec/mongoid/support/schema.rb +++ b/spec/mongoid/support/schema.rb @@ -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 diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index 9a9038cc8..c9d3e97c7 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -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: '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 @@ 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' } diff --git a/spec/ransack/nodes/condition_spec.rb b/spec/ransack/nodes/condition_spec.rb index 3c480aff1..6cb728ddc 100644 --- a/spec/ransack/nodes/condition_spec.rb +++ b/spec/ransack/nodes/condition_spec.rb @@ -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( diff --git a/spec/support/schema.rb b/spec/support/schema.rb index b05d4607c..624069a38 100644 --- a/spec/support/schema.rb +++ b/spec/support/schema.rb @@ -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