diff --git a/lib/ransack/adapters/active_record/3.0/context.rb b/lib/ransack/adapters/active_record/3.0/context.rb index e725a2cdf..1d5ffd23e 100644 --- a/lib/ransack/adapters/active_record/3.0/context.rb +++ b/lib/ransack/adapters/active_record/3.0/context.rb @@ -44,7 +44,7 @@ def attribute_method?(str, klass = @klass) if ransackable_attribute?(str, klass) exists = true - elsif (segments = str.split(/_/)).size > 1 + elsif (segments = str.split(Constants::UNDERSCORE)).size > 1 remainder = [] found_assoc = nil while !found_assoc && remainder.unshift(segments.pop) && @@ -98,7 +98,7 @@ def get_parent_and_attribute_name(str, parent = @base) if ransackable_attribute?(str, klassify(parent)) attr_name = str - elsif (segments = str.split(/_/)).size > 1 + elsif (segments = str.split(Constants::UNDERSCORE)).size > 1 remainder = [] found_assoc = nil while remainder.unshift(segments.pop) && segments.size > 0 && diff --git a/lib/ransack/adapters/active_record/3.1/context.rb b/lib/ransack/adapters/active_record/3.1/context.rb index 8a9cbdc07..719098f3e 100644 --- a/lib/ransack/adapters/active_record/3.1/context.rb +++ b/lib/ransack/adapters/active_record/3.1/context.rb @@ -38,7 +38,7 @@ def attribute_method?(str, klass = @klass) if ransackable_attribute?(str, klass) exists = true - elsif (segments = str.split(/_/)).size > 1 + elsif (segments = str.split(Constants::UNDERSCORE)).size > 1 remainder = [] found_assoc = nil while !found_assoc && remainder.unshift(segments.pop) && @@ -105,7 +105,7 @@ def get_parent_and_attribute_name(str, parent = @base) if ransackable_attribute?(str, klassify(parent)) attr_name = str - elsif (segments = str.split(/_/)).size > 1 + elsif (segments = str.split(Constants::UNDERSCORE)).size > 1 remainder = [] found_assoc = nil while remainder.unshift(segments.pop) && segments.size > 0 && diff --git a/lib/ransack/adapters/active_record/base.rb b/lib/ransack/adapters/active_record/base.rb index 6975e80bd..bed853524 100644 --- a/lib/ransack/adapters/active_record/base.rb +++ b/lib/ransack/adapters/active_record/base.rb @@ -32,7 +32,7 @@ def ransack_alias(new_name, old_name) # For overriding with a whitelist array of strings. # def ransackable_attributes(auth_object = nil) - if Ransack::SUPPORTS_ATTRIBUTE_ALIAS + @ransackable_attributes ||= if Ransack::SUPPORTS_ATTRIBUTE_ALIAS column_names + _ransackers.keys + _ransack_aliases.keys + attribute_aliases.keys else @@ -45,7 +45,7 @@ def ransackable_attributes(auth_object = nil) # For overriding with a whitelist array of strings. # def ransackable_associations(auth_object = nil) - reflect_on_all_associations.map { |a| a.name.to_s } + @ransackable_associations ||= reflect_on_all_associations.map { |a| a.name.to_s } end # Ransortable_attributes, by default, returns the names diff --git a/lib/ransack/adapters/active_record/context.rb b/lib/ransack/adapters/active_record/context.rb index 07477175d..235b73809 100644 --- a/lib/ransack/adapters/active_record/context.rb +++ b/lib/ransack/adapters/active_record/context.rb @@ -45,7 +45,7 @@ def attribute_method?(str, klass = @klass) exists = false if ransackable_attribute?(str, klass) exists = true - elsif (segments = str.split(/_/)).size > 1 + elsif (segments = str.split(Constants::UNDERSCORE)).size > 1 remainder = [] found_assoc = nil while !found_assoc && remainder.unshift(segments.pop) && @@ -268,7 +268,8 @@ def build_joins(relation) association_joins = buckets[:association_join] stashed_association_joins = buckets[:stashed_join] join_nodes = buckets[:join_node].uniq - string_joins = buckets[:string_join].map(&:strip).uniq + string_joins = buckets[:string_join].map(&:strip) + string_joins.uniq! join_list = if ::ActiveRecord::VERSION::MAJOR >= 5 @@ -295,10 +296,9 @@ def build_joins(relation) end def convert_join_strings_to_ast(table, joins) + joins.map! { |join| table.create_string_join(Arel.sql(join)) unless join.blank? } + joins.compact! joins - .flatten - .reject(&:blank?) - .map { |join| table.create_string_join(Arel.sql(join)) } end def build_or_find_association(name, parent = @base, klass = nil) diff --git a/lib/ransack/adapters/active_record/ransack/context.rb b/lib/ransack/adapters/active_record/ransack/context.rb index 708efeaa2..68f35f205 100644 --- a/lib/ransack/adapters/active_record/ransack/context.rb +++ b/lib/ransack/adapters/active_record/ransack/context.rb @@ -37,15 +37,14 @@ def initialize(object, options = {}) @base = @join_dependency.join_base @engine = @base.arel_engine end + end - @default_table = Arel::Table.new( - @base.table_name, as: @base.aliased_table_name, type_caster: self - ) - @bind_pairs = Hash.new do |hash, key| - parent, attr_name = get_parent_and_attribute_name(key) - if parent && attr_name - hash[key] = [parent, attr_name] - end + def bind_pair_for(key) + @bind_pairs ||= {} + + @bind_pairs[key] ||= begin + parent, attr_name = get_parent_and_attribute_name(key.to_s) + [parent, attr_name] if parent && attr_name end end diff --git a/lib/ransack/adapters/mongoid/context.rb b/lib/ransack/adapters/mongoid/context.rb index f1a1db41f..3ca2ebeb9 100644 --- a/lib/ransack/adapters/mongoid/context.rb +++ b/lib/ransack/adapters/mongoid/context.rb @@ -34,7 +34,7 @@ def type_for(attr) name = '_id' if name == 'id' - t = object.klass.fields[name].try(:type) || @bind_pairs[attr.name].first.fields[name].type + t = object.klass.fields[name].try(:type) || bind_pair_for(attr.name).first.fields[name].type t.to_s.demodulize.underscore.to_sym end @@ -61,7 +61,7 @@ def attribute_method?(str, klass = @klass) exists = false if ransackable_attribute?(str, klass) exists = true - elsif (segments = str.split(/_/)).size > 1 + elsif (segments = str.split(Constants::UNDERSCORE)).size > 1 remainder = [] found_assoc = nil while !found_assoc && remainder.unshift( @@ -111,7 +111,7 @@ def get_parent_and_attribute_name(str, parent = @base) if ransackable_attribute?(str, klassify(parent)) attr_name = str - elsif (segments = str.split(/_/)).size > 1 + elsif (segments = str.split(Constants::UNDERSCORE)).size > 1 remainder = [] found_assoc = nil while remainder.unshift( diff --git a/lib/ransack/adapters/mongoid/ransack/context.rb b/lib/ransack/adapters/mongoid/ransack/context.rb index bba2b5072..4767dcbc3 100644 --- a/lib/ransack/adapters/mongoid/ransack/context.rb +++ b/lib/ransack/adapters/mongoid/ransack/context.rb @@ -30,15 +30,14 @@ def initialize(object, options = {}) @base = @object.klass # @engine = @base.arel_engine + end + + def bind_pair_for(key) + @bind_pairs ||= {} - # @default_table = Arel::Table.new( - # @base.table_name, :as => @base.aliased_table_name, :engine => @engine - # ) - @bind_pairs = Hash.new do |hash, key| + @bind_pairs[key] ||= begin parent, attr_name = get_parent_and_attribute_name(key.to_s) - if parent && attr_name - hash[key] = [parent, attr_name] - end + [parent, attr_name] if parent && attr_name end end diff --git a/lib/ransack/configuration.rb b/lib/ransack/configuration.rb index 51d9048fa..498cd2b7f 100644 --- a/lib/ransack/configuration.rb +++ b/lib/ransack/configuration.rb @@ -5,7 +5,27 @@ module Ransack module Configuration mattr_accessor :predicates, :options - self.predicates = {} + + class PredicateCollection + attr_reader :sorted_names_with_underscores + + def initialize + @collection = {} + @sorted_names_with_underscores = [] + end + + delegate :[], :keys, to: :@collection + + def []=(key, value) + @sorted_names_with_underscores << [key, '_' + key] + @sorted_names_with_underscores.sort! { |(a, _), (b, _)| b.length <=> a.length } + + @collection[key] = value + end + end + + self.predicates = PredicateCollection.new + self.options = { :search_key => :q, :ignore_unknown_conditions => true, diff --git a/lib/ransack/context.rb b/lib/ransack/context.rb index cc2efa9d0..494fa4d63 100644 --- a/lib/ransack/context.rb +++ b/lib/ransack/context.rb @@ -40,7 +40,7 @@ def klassify(obj) # Convert a string representing a chain of associations and an attribute # into the attribute itself def contextualize(str) - parent, attr_name = @bind_pairs[str] + parent, attr_name = bind_pair_for(str) table_for(parent)[attr_name] end @@ -59,24 +59,24 @@ def scope_arity(scope) def bind(object, str) return nil unless str - object.parent, object.attr_name = @bind_pairs[str] + object.parent, object.attr_name = bind_pair_for(str) end def traverse(str, base = @base) str ||= ''.freeze - if (segments = str.split(/_/)).size > 0 + if (segments = str.split(Constants::UNDERSCORE)).size > 0 remainder = [] found_assoc = nil while !found_assoc && segments.size > 0 do # Strip the _of_Model_type text from the association name, but hold # onto it in klass, for use as the next base assoc, klass = unpolymorphize_association( - segments.join('_'.freeze) + segments.join(Constants::UNDERSCORE) ) if found_assoc = get_association(assoc, base) base = traverse( - remainder.join('_'.freeze), klass || found_assoc.klass + remainder.join(Constants::UNDERSCORE), klass || found_assoc.klass ) end @@ -93,9 +93,9 @@ def association_path(str, base = @base) base = klassify(base) str ||= ''.freeze path = [] - segments = str.split(/_/) + segments = str.split(Constants::UNDERSCORE) association_parts = [] - if (segments = str.split(/_/)).size > 0 + if (segments = str.split(Constants::UNDERSCORE)).size > 0 while segments.size > 0 && !base.columns_hash[segments.join(Constants::UNDERSCORE)] && association_parts << segments.shift do @@ -135,7 +135,7 @@ def ransackable_association?(str, klass) end def ransackable_scope?(str, klass) - klass.ransackable_scopes(auth_object).any? { |s| s.to_s == str } + klass.ransackable_scopes(auth_object).any? { |s| s.to_sym == str.to_sym } end def searchable_attributes(str = ''.freeze) diff --git a/lib/ransack/nodes/grouping.rb b/lib/ransack/nodes/grouping.rb index 5f6746e2f..bab036cdb 100644 --- a/lib/ransack/nodes/grouping.rb +++ b/lib/ransack/nodes/grouping.rb @@ -191,7 +191,7 @@ def read_attribute(name) end def strip_predicate_and_index(str) - string = str.split(/\(/).first + string = str[/(.+?)\(/, 1] || str Predicate.detect_and_strip_from_string!(string) string end diff --git a/lib/ransack/predicate.rb b/lib/ransack/predicate.rb index 935729469..ff7be7ac4 100644 --- a/lib/ransack/predicate.rb +++ b/lib/ransack/predicate.rb @@ -9,34 +9,26 @@ def names Ransack.predicates.keys end - def names_by_decreasing_length - names.sort { |a, b| b.length <=> a.length } - end - def named(name) Ransack.predicates[name.to_s] end def detect_and_strip_from_string!(str) - if p = detect_from_string(str) - str.sub! /_#{p}$/, ''.freeze - p - end + detect_from_string str, chomp: true end - def detect_from_string(str) - names_by_decreasing_length.detect { |p| str.end_with?("_#{p}") } - end + def detect_from_string(str, chomp: false) + return unless str -# def name_from_attribute_name(attribute_name) -# names_by_decreasing_length.detect { -# |p| attribute_name.to_s.match(/_#{p}$/) -# } -# end + Ransack.predicates.sorted_names_with_underscores.each do |predicate, underscored| + if str.end_with? underscored + str.chomp! underscored if chomp + return predicate + end + end -# def for_attribute_name(attribute_name) -# self.named(detect_from_string(attribute_name.to_s)) -# end + nil + end end diff --git a/lib/ransack/translate.rb b/lib/ransack/translate.rb index 93821ddd6..178b0a936 100644 --- a/lib/ransack/translate.rb +++ b/lib/ransack/translate.rb @@ -24,8 +24,8 @@ def self.attribute(key, options = {}) base_ancestors = base_class.ancestors.select { |x| x.respond_to?(:model_name) } - predicate = Predicate.detect_from_string(original_name) - attributes_str = original_name.sub(/_#{predicate}$/, ''.freeze) + attributes_str = original_name.dup # will be modified by ⬇ + predicate = Predicate.detect_and_strip_from_string!(attributes_str) attribute_names = attributes_str.split(/_and_|_or_/) combinator = attributes_str.match(/_and_/) ? :and : :or defaults = base_ancestors.map do |klass|