Reference string constants rather than repeatedly

allocating the same string in order to deduplicate
commonly-used strings, reduce object allocations
and memory usage.
This commit is contained in:
Jon Atack 2014-10-06 23:27:55 +02:00
parent d2e185de4c
commit 46ab1030f7
14 changed files with 155 additions and 120 deletions

View File

@ -135,11 +135,11 @@ module Arel
"#{
o.name
}(#{
o.distinct ? 'DISTINCT ' : ''
o.distinct ? DISTINCT : EMPTY_STRING
}#{
o.expressions.map { |x| visit x }.join(', ')
o.expressions.map { |x| visit x }.join(COMMA_SPACE)
})#{
o.alias ? " AS #{visit o.alias}" : ''
o.alias ? " AS #{visit o.alias}" : EMPTY_STRING
}"
end
@ -161,7 +161,7 @@ module Arel
quote(value, attr && column_for(attr))
end
}
.join ', '
.join(COMMA_SPACE)
})"
end
end

View File

@ -30,7 +30,8 @@ module Ransack
.reorder(viz.accept(search.sorts))
end
opts[:distinct] ?
relation.select("DISTINCT #{@klass.quoted_table_name}.*") : relation
relation.select(DISTINCT + @klass.quoted_table_name + '.*'.freeze) :
relation
end
def attribute_method?(str, klass = @klass)
@ -44,11 +45,11 @@ module Ransack
while !found_assoc && remainder.unshift(segments.pop) &&
segments.size > 0 do
assoc, poly_class = unpolymorphize_association(
segments.join('_')
segments.join(UNDERSCORE)
)
if found_assoc = get_association(assoc, klass)
exists = attribute_method?(
remainder.join('_'), poly_class || found_assoc.klass
remainder.join(UNDERSCORE), poly_class || found_assoc.klass
)
end
end
@ -96,13 +97,15 @@ module Ransack
found_assoc = nil
while remainder.unshift(segments.pop) && segments.size > 0 &&
!found_assoc do
assoc, klass = unpolymorphize_association(segments.join('_'))
assoc, klass = unpolymorphize_association(
segments.join(UNDERSCORE)
)
if found_assoc = get_association(assoc, parent)
join = build_or_find_association(
found_assoc.name, parent, klass
)
parent, attr_name = get_parent_and_attribute_name(
remainder.join('_'), join
remainder.join(UNDERSCORE), join
)
end
end

View File

@ -29,8 +29,8 @@ module Ransack
.reorder(viz.accept(search.sorts))
end
opts[:distinct] ?
relation.select("DISTINCT #{@klass.quoted_table_name}.*") :
relation
relation.select(DISTINCT + @klass.quoted_table_name + '.*'.freeze) :
relation
end
def attribute_method?(str, klass = @klass)
@ -44,11 +44,11 @@ module Ransack
while !found_assoc && remainder.unshift(segments.pop) &&
segments.size > 0 do
assoc, poly_class = unpolymorphize_association(
segments.join('_')
segments.join(UNDERSCORE)
)
if found_assoc = get_association(assoc, klass)
exists = attribute_method?(
remainder.join('_'), poly_class || found_assoc.klass
remainder.join(UNDERSCORE), poly_class || found_assoc.klass
)
end
end
@ -109,13 +109,15 @@ module Ransack
found_assoc = nil
while remainder.unshift(segments.pop) && segments.size > 0 &&
!found_assoc do
assoc, klass = unpolymorphize_association(segments.join('_'))
assoc, klass = unpolymorphize_association(
segments.join(UNDERSCORE)
)
if found_assoc = get_association(assoc, parent)
join = build_or_find_association(
found_assoc.name, parent, klass
)
parent, attr_name = get_parent_and_attribute_name(
remainder.join('_'), join
remainder.join(UNDERSCORE), join
)
end
end
@ -142,22 +144,22 @@ module Ransack
buckets = relation.joins_values.group_by do |join|
case join
when String
'string_join'
'string_join'.freeze
when Hash, Symbol, Array
'association_join'
'association_join'.freeze
when ::ActiveRecord::Associations::JoinDependency::JoinAssociation
'stashed_join'
'stashed_join'.freeze
when Arel::Nodes::Join
'join_node'
'join_node'.freeze
else
raise 'unknown class: %s' % join.class.name
end
end
association_joins = buckets['association_join'] || []
stashed_association_joins = buckets['stashed_join'] || []
join_nodes = buckets['join_node'] || []
string_joins = (buckets['string_join'] || [])
association_joins = buckets['association_join'.freeze] || []
stashed_association_joins = buckets['stashed_join'.freeze] || []
join_nodes = buckets['join_node'.freeze] || []
string_joins = (buckets['string_join'.freeze] || [])
.map { |x| x.strip }
.uniq

View File

@ -50,10 +50,10 @@ module Ransack
while !found_assoc && remainder.unshift(
segments.pop) && segments.size > 0 do
assoc, poly_class = unpolymorphize_association(
segments.join('_')
segments.join(UNDERSCORE)
)
if found_assoc = get_association(assoc, klass)
exists = attribute_method?(remainder.join('_'),
exists = attribute_method?(remainder.join(UNDERSCORE),
poly_class || found_assoc.klass
)
end
@ -138,11 +138,15 @@ module Ransack
found_assoc = nil
while remainder.unshift(
segments.pop) && segments.size > 0 && !found_assoc do
assoc, klass = unpolymorphize_association(segments.join('_'))
assoc, klass = unpolymorphize_association(
segments.join(UNDERSCORE)
)
if found_assoc = get_association(assoc, parent)
join = build_or_find_association(found_assoc.name, parent, klass)
join = build_or_find_association(
found_assoc.name, parent, klass
)
parent, attr_name = get_parent_and_attribute_name(
remainder.join('_'), join
remainder.join(UNDERSCORE), join
)
end
end
@ -171,25 +175,25 @@ module Ransack
buckets = relation.joins_values.group_by do |join|
case join
when String
'string_join'
'string_join'.freeze
when Hash, Symbol, Array
'association_join'
'association_join'.freeze
when JoinDependency, JoinDependency::JoinAssociation
'stashed_join'
'stashed_join'.freeze
when Arel::Nodes::Join
'join_node'
'join_node'.freeze
else
raise 'unknown class: %s' % join.class.name
end
end
association_joins = buckets['association_join'] || []
association_joins = buckets['association_join'.freeze] || []
stashed_association_joins = buckets['stashed_join'] || []
stashed_association_joins = buckets['stashed_join'.freeze] || []
join_nodes = buckets['join_node'] || []
join_nodes = buckets['join_node'.freeze] || []
string_joins = (buckets['string_join'] || [])
string_joins = (buckets['string_join'.freeze] || [])
.map { |x| x.strip }
.uniq
@ -204,14 +208,14 @@ module Ransack
join_dependency.alias_tracker.aliases[join.left.name.downcase] = 1
end
if ::ActiveRecord::VERSION::STRING >= '4.1'
if ::ActiveRecord::VERSION::STRING >= '4.1'.freeze
join_dependency
else
join_dependency.graft(*stashed_association_joins)
end
end
if ::ActiveRecord::VERSION::STRING >= '4.1'
if ::ActiveRecord::VERSION::STRING >= '4.1'.freeze
def build_or_find_association(name, parent = @base, klass = nil)
found_association = @join_dependency.join_root.children

View File

@ -3,19 +3,28 @@ require 'ransack/predicate'
ASC = 'asc'.freeze
DESC = 'desc'.freeze
ASC_DESC = %w(asc desc).freeze
ASC_ARROW = '▲'.freeze
DESC_ARROW = '▼'.freeze
OR = 'or'.freeze
AND = 'and'.freeze
SORT = 'sort'.freeze
SORT_LINK = 'sort_link'.freeze
SUFFIXES = %w(_any _all).freeze
ATTRIBUTE = 'attribute'.freeze
SEARCH = 'search'.freeze
DEFAULT_SEARCH_KEY = 'q'.freeze
ATTRIBUTE = 'attribute'.freeze
DISTINCT = 'DISTINCT '.freeze
COMBINATOR = 'combinator'.freeze
CONDITIONS = 'conditions'.freeze
SPACE = ' '.freeze
COMMA_SPACE = ', '.freeze
UNDERSCORE = '_'.freeze
NON_BREAKING_SPACE = ' '.freeze
EMPTY_STRING = ''.freeze
ASC_DESC = %w(asc desc).freeze
AND_OR = %w(and or).freeze
IN_NOT_IN = %w(in not_in).freeze
SUFFIXES = %w(_any _all).freeze
module Ransack
module Configuration

View File

@ -4,119 +4,126 @@ module Ransack
FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
BOOLEAN_VALUES = TRUE_VALUES + FALSE_VALUES
AREL_PREDICATES = %w(eq not_eq matches does_not_match lt lteq gt gteq in not_in)
AREL_PREDICATES = %w(
eq not_eq matches does_not_match lt lteq gt gteq in not_in
).freeze
EQ = 'eq'.freeze
NOT_EQ = 'not_eq'.freeze
EQ_ANY = 'eq_any'.freeze
NOT_EQ_ALL = 'not_eq_all'.freeze
DERIVED_PREDICATES = [
['cont', {
:arel_predicate => 'matches',
['cont'.freeze, {
:arel_predicate => 'matches'.freeze,
:formatter => proc { |v| "%#{escape_wildcards(v)}%" }
}
],
['i_cont', {
:arel_predicate => 'i_matches',
['i_cont'.freeze, {
:arel_predicate => 'i_matches'.freeze,
:formatter => proc { |v| "%#{escape_wildcards(v)}%" }
}
],
['not_cont', {
:arel_predicate => 'does_not_match',
['not_cont'.freeze, {
:arel_predicate => 'does_not_match'.freeze,
:formatter => proc { |v| "%#{escape_wildcards(v)}%" }
}
],
['i_not_cont', {
:arel_predicate => 'i_does_not_match',
['i_not_cont'.freeze, {
:arel_predicate => 'i_does_not_match'.freeze,
:formatter => proc { |v| "%#{escape_wildcards(v)}%" }
}
],
['start', {
:arel_predicate => 'matches',
['start'.freeze, {
:arel_predicate => 'matches'.freeze,
:formatter => proc { |v| "#{escape_wildcards(v)}%" }
}
],
['not_start', {
:arel_predicate => 'does_not_match',
['not_start'.freeze, {
:arel_predicate => 'does_not_match'.freeze,
:formatter => proc { |v| "#{escape_wildcards(v)}%" }
}
],
['end', {
:arel_predicate => 'matches',
['end'.freeze, {
:arel_predicate => 'matches'.freeze,
:formatter => proc { |v| "%#{escape_wildcards(v)}" }
}
],
['not_end', {
:arel_predicate => 'does_not_match',
['not_end'.freeze, {
:arel_predicate => 'does_not_match'.freeze,
:formatter => proc { |v| "%#{escape_wildcards(v)}" }
}
],
['true', {
:arel_predicate => proc { |v| v ? 'eq' : 'not_eq' },
['true'.freeze, {
:arel_predicate => proc { |v| v ? EQ : NOT_EQ },
:compounds => false,
:type => :boolean,
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
:formatter => proc { |v| true }
}
],
['not_true', {
:arel_predicate => proc { |v| v ? 'not_eq' : 'eq' },
['not_true'.freeze, {
:arel_predicate => proc { |v| v ? NOT_EQ : EQ },
:compounds => false,
:type => :boolean,
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
:formatter => proc { |v| true }
}
],
['false', {
:arel_predicate => proc { |v| v ? 'eq' : 'not_eq' },
['false'.freeze, {
:arel_predicate => proc { |v| v ? EQ : NOT_EQ },
:compounds => false,
:type => :boolean,
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
:formatter => proc { |v| false }
}
],
['not_false', {
:arel_predicate => proc { |v| v ? 'not_eq' : 'eq' },
['not_false'.freeze, {
:arel_predicate => proc { |v| v ? NOT_EQ : EQ },
:compounds => false,
:type => :boolean,
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
:formatter => proc { |v| false }
}
],
['present', {
:arel_predicate => proc { |v| v ? 'not_eq_all' : 'eq_any' },
['present'.freeze, {
:arel_predicate => proc { |v| v ? NOT_EQ_ALL : EQ_ANY },
:compounds => false,
:type => :boolean,
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
:formatter => proc { |v| [nil, ''] }
:formatter => proc { |v| [nil, EMPTY_STRING] }
}
],
['blank', {
:arel_predicate => proc { |v| v ? 'eq_any' : 'not_eq_all' },
['blank'.freeze, {
:arel_predicate => proc { |v| v ? EQ_ANY : NOT_EQ_ALL },
:compounds => false,
:type => :boolean,
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
:formatter => proc { |v| [nil, ''] }
:formatter => proc { |v| [nil, EMPTY_STRING] }
}
],
['null', {
:arel_predicate => proc { |v| v ? 'eq' : 'not_eq' },
['null'.freeze, {
:arel_predicate => proc { |v| v ? EQ : NOT_EQ },
:compounds => false,
:type => :boolean,
:validator => proc { |v| BOOLEAN_VALUES.include?(v)},
:formatter => proc { |v| nil }
}
],
['not_null', {
:arel_predicate => proc { |v| v ? 'not_eq' : 'eq' },
['not_null'.freeze, {
:arel_predicate => proc { |v| v ? NOT_EQ : EQ },
:compounds => false,
:type => :boolean,
:validator => proc { |v| BOOLEAN_VALUES.include?(v) },
:formatter => proc { |v| nil } }
]
]
].freeze
module_function
# replace % \ to \% \\
def escape_wildcards(unescaped)
case ActiveRecord::Base.connection.adapter_name
when "Mysql2", "PostgreSQL"
when "Mysql2".freeze, "PostgreSQL".freeze
# Necessary for PostgreSQL and MySQL
unescaped.to_s.gsub(/([\\|\%|.])/, '\\\\\\1')
else

View File

@ -37,7 +37,7 @@ module Ransack
@join_type = options[:join_type] || Polyamorous::OuterJoin
@search_key = options[:search_key] || Ransack.options[:search_key]
if ::ActiveRecord::VERSION::STRING >= "4.1"
if ::ActiveRecord::VERSION::STRING >= "4.1".freeze
@base = @join_dependency.join_root
@engine = @base.base_klass.arel_engine
else
@ -95,7 +95,7 @@ module Ransack
end
def traverse(str, base = @base)
str ||= ''
str ||= EMPTY_STRING
if (segments = str.split(/_/)).size > 0
remainder = []
@ -103,9 +103,11 @@ module Ransack
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('_'))
assoc, klass = unpolymorphize_association(segments.join(UNDERSCORE))
if found_assoc = get_association(assoc, base)
base = traverse(remainder.join('_'), klass || found_assoc.klass)
base = traverse(
remainder.join(UNDERSCORE), klass || found_assoc.klass
)
end
remainder.unshift segments.pop
@ -119,15 +121,16 @@ module Ransack
def association_path(str, base = @base)
base = klassify(base)
str ||= ''
str ||= EMPTY_STRING
path = []
segments = str.split(/_/)
association_parts = []
if (segments = str.split(/_/)).size > 0
while segments.size > 0 && !base.columns_hash[segments.join('_')] &&
while segments.size > 0 && !base.columns_hash[segments.join(UNDERSCORE)] &&
association_parts << segments.shift do
assoc, klass = unpolymorphize_association(association_parts
.join('_'))
assoc, klass = unpolymorphize_association(
association_parts.join(UNDERSCORE)
)
if found_assoc = get_association(assoc, base)
path += association_parts
association_parts = []
@ -136,7 +139,7 @@ module Ransack
end
end
path.join('_')
path.join(UNDERSCORE)
end
def unpolymorphize_association(str)
@ -160,15 +163,15 @@ module Ransack
klass.ransackable_scopes(auth_object).any? { |s| s.to_s == str }
end
def searchable_attributes(str = '')
def searchable_attributes(str = EMPTY_STRING)
traverse(str).ransackable_attributes(auth_object)
end
def sortable_attributes(str = '')
def sortable_attributes(str = EMPTY_STRING)
traverse(str).ransortable_attributes(auth_object)
end
def searchable_associations(str = '')
def searchable_associations(str = EMPTY_STRING)
traverse(str).ransackable_associations(auth_object)
end
end

View File

@ -1,11 +1,13 @@
require 'action_view'
RANSACK_FORM_BUILDER = 'RANSACK_FORM_BUILDER'.freeze
require 'simple_form' if
(ENV['RANSACK_FORM_BUILDER'] || '').match('SimpleForm')
(ENV[RANSACK_FORM_BUILDER] || EMPTY_STRING).match('SimpleForm'.freeze)
module Ransack
module Helpers
class FormBuilder < (ENV['RANSACK_FORM_BUILDER'].try(:constantize) ||
class FormBuilder < (ENV[RANSACK_FORM_BUILDER].try(:constantize) ||
ActionView::Helpers::FormBuilder)
def label(method, *args, &block)
@ -32,7 +34,7 @@ module Ransack
raise ArgumentError, formbuilder_error_message(
"#{action}_select") unless object.respond_to?(:context)
options[:include_blank] = true unless options.has_key?(:include_blank)
bases = [''] + association_array(options[:associations])
bases = [EMPTY_STRING] + association_array(options[:associations])
if bases.size > 1
collection = attribute_collection_for_bases(action, bases)
object.name ||= default if can_use_default?(
@ -50,7 +52,7 @@ module Ransack
def sort_direction_select(options = {}, html_options = {})
raise ArgumentError, formbuilder_error_message(
'sort_direction') unless object.respond_to?(:context)
'sort_direction'.freeze) unless object.respond_to?(:context)
template_collection_select(:dir, sort_array, options, html_options)
end
@ -107,7 +109,7 @@ module Ransack
def predicate_select(options = {}, html_options = {})
options[:compounds] = true if options[:compounds].nil?
default = options.delete(:default) || 'cont'
default = options.delete(:default) || 'cont'.freeze
keys = options[:compounds] ? Predicate.names :
Predicate.names.reject { |k| k.match(/_(any|all)$/) }
@ -116,7 +118,9 @@ module Ransack
keys = keys.select { |k| only.call(k) }
else
only = Array.wrap(only).map(&:to_s)
keys = keys.select { |k| only.include? k.sub(/_(any|all)$/, '') }
keys = keys.select {
|k| only.include? k.sub(/_(any|all)$/, EMPTY_STRING)
}
end
end
collection = keys.map { |k| [k, Translate.predicate(k)] }
@ -172,7 +176,7 @@ module Ransack
([prefix] + association_object(obj))
.compact
.flatten
.map { |v| [prefix, v].compact.join('_') }
.map { |v| [prefix, v].compact.join(UNDERSCORE) }
end
def association_object(obj)
@ -192,7 +196,7 @@ module Ransack
when Array, Hash
association_array(value, key.to_s)
else
[key.to_s, [key, value].join('_')]
[key.to_s, [key, value].join(UNDERSCORE)]
end
end
end
@ -227,7 +231,7 @@ module Ransack
end
def attr_from_base_and_column(base, column)
[base, column].reject { |v| v.blank? }.join('_')
[base, column].reject { |v| v.blank? }.join(UNDERSCORE)
end
def formbuilder_error_message(action)

View File

@ -105,7 +105,7 @@ module Ransack
end
def combinator=(val)
@combinator = ['and', 'or'].detect { |v| v == val.to_s } || nil
@combinator = AND_OR.detect { |v| v == val.to_s } || nil
end
alias :m= :combinator=
alias :m :combinator
@ -180,9 +180,9 @@ module Ransack
if predicates.size > 1
case combinator
when 'and'
when AND
Arel::Nodes::Grouping.new(Arel::Nodes::And.new(predicates))
when 'or'
when OR
predicates.inject(&:or)
end
else
@ -226,14 +226,14 @@ module Ransack
def inspect
data = [
['attributes', a.try(:map, &:name)],
['predicate', p],
['combinator', m],
['values', v.try(:map, &:value)]
['attributes'.freeze, a.try(:map, &:name)],
['predicate'.freeze, p],
[COMBINATOR, m],
['values'.freeze, v.try(:map, &:value)]
]
.reject { |e| e[1].blank? }
.map { |v| "#{v[0]}: #{v[1]}" }
.join(', ')
.join(COMMA_SPACE)
"Condition <#{data}>"
end
@ -241,7 +241,7 @@ module Ransack
def valid_combinator?
attributes.size < 2 ||
['and', 'or'].include?(combinator)
AND_OR.include?(combinator)
end
end

View File

@ -69,7 +69,7 @@ module Ransack
def respond_to?(method_id)
super or begin
method_name = method_id.to_s
writer = method_name.sub!(/\=$/, '')
writer = method_name.sub!(/\=$/, EMPTY_STRING)
attribute_method?(method_name) ? true : false
end
end
@ -115,7 +115,7 @@ module Ransack
def method_missing(method_id, *args)
method_name = method_id.to_s
writer = method_name.sub!(/\=$/, '')
writer = method_name.sub!(/\=$/, EMPTY_STRING)
if attribute_method?(method_name)
writer ?
write_attribute(method_name, *args) :
@ -161,10 +161,10 @@ module Ransack
end
def inspect
data = [['conditions', conditions], ['combinator', combinator]]
data = [[CONDITIONS, conditions], [COMBINATOR, combinator]]
.reject { |e| e[1].blank? }
.map { |v| "#{v[0]}: #{v[1]}" }
.join(', ')
.join(COMMA_SPACE)
"Grouping <#{data}>"
end

View File

@ -19,7 +19,7 @@ module Ransack
def detect_and_strip_from_string!(str)
if p = detect_from_string(str)
str.sub! /_#{p}$/, ''
str.sub! /_#{p}$/, EMPTY_STRING
p
end
end
@ -49,7 +49,7 @@ module Ransack
lambda { |v| v.respond_to?(:empty?) ? !v.empty? : !v.nil? }
@compound = opts[:compound]
@wants_array = opts[:wants_array] == true || @compound ||
['in', 'not_in'].include?(@arel_predicate)
IN_NOT_IN.include?(@arel_predicate)
end
def eql?(other)

View File

@ -88,7 +88,7 @@ module Ransack
def method_missing(method_id, *args)
method_name = method_id.to_s
getter_name = method_name.sub(/=$/, '')
getter_name = method_name.sub(/=$/, EMPTY_STRING)
if base.attribute_method?(getter_name)
base.send(method_id, *args)
elsif @context.ransackable_scope?(getter_name, @context.object)
@ -107,7 +107,10 @@ module Ransack
[:class, klass.name],
([:scope, @scope_args] if @scope_args.present?),
[:base, base.inspect]
].compact.map { |d| d.join(': ') }.join(', ')
]
.compact.map { |d| d.join(': '.freeze) }
.join(COMMA_SPACE)
"Ransack::Search<#{details}>"
end

View File

@ -23,7 +23,7 @@ module Ransack
|x| x.respond_to?(:model_name)
}
predicate = Predicate.detect_from_string(original_name)
attributes_str = original_name.sub(/_#{predicate}$/, '')
attributes_str = original_name.sub(/_#{predicate}$/, EMPTY_STRING)
attribute_names = attributes_str.split(/_and_|_or_/)
combinator = attributes_str.match(/_and_/) ? :and : :or
defaults = base_ancestors.map do |klass|
@ -71,7 +71,7 @@ module Ransack
def self.attribute_name(context, name, include_associations = nil)
@context, @name = context, name
@assoc_path = context.association_path(name)
@attr_name = @name.sub(/^#{@assoc_path}_/, '')
@attr_name = @name.sub(/^#{@assoc_path}_/, EMPTY_STRING)
associated_class = @context.traverse(@assoc_path) if @assoc_path.present?
@include_associated = include_associations && associated_class

View File

@ -18,7 +18,7 @@ module Ransack
end
def visit_Ransack_Nodes_Grouping(object)
object.combinator == 'or' ? visit_or(object) : visit_and(object)
object.combinator == OR ? visit_or(object) : visit_and(object)
end
def visit_and(object)
@ -61,7 +61,7 @@ module Ransack
end
DISPATCH = Hash.new do |hash, klass|
hash[klass] = "visit_#{klass.name.gsub('::', '_')}"
hash[klass] = "visit_#{klass.name.gsub('::'.freeze, UNDERSCORE)}"
end
end