ransack_abbreviator 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +31 -0
- data/LICENSE +20 -0
- data/README.md +7 -0
- data/Rakefile +10 -0
- data/lib/ransack_abbreviator/abbreviators/decoder.rb +80 -0
- data/lib/ransack_abbreviator/abbreviators/encoder.rb +49 -0
- data/lib/ransack_abbreviator/adapters/active_record/base.rb +63 -0
- data/lib/ransack_abbreviator/adapters/active_record.rb +3 -0
- data/lib/ransack_abbreviator/configuration.rb +32 -0
- data/lib/ransack_abbreviator/constants.rb +5 -0
- data/lib/ransack_abbreviator/ransack_extensions/context.rb +34 -0
- data/lib/ransack_abbreviator/ransack_extensions/nodes/condition.rb +17 -0
- data/lib/ransack_abbreviator/ransack_extensions/nodes/grouping.rb +18 -0
- data/lib/ransack_abbreviator/ransack_extensions/search.rb +30 -0
- data/lib/ransack_abbreviator/version.rb +3 -0
- data/lib/ransack_abbreviator/view_helpers.rb +39 -0
- data/lib/ransack_abbreviator.rb +28 -0
- data/ransack_abbreviator.gemspec +27 -0
- data/spec/blueprints/articles.rb +5 -0
- data/spec/blueprints/comments.rb +5 -0
- data/spec/blueprints/notes.rb +3 -0
- data/spec/blueprints/people.rb +5 -0
- data/spec/blueprints/tags.rb +3 -0
- data/spec/ransack_abbreviator/adapters/active_record/base_spec.rb +85 -0
- data/spec/ransack_abbreviator/configuration_spec.rb +14 -0
- data/spec/ransack_abbreviator/helper_spec.rb +90 -0
- data/spec/ransack_abbreviator/search_spec.rb +379 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/support/schema.rb +105 -0
- metadata +160 -0
data/.gitignore
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
gemspec
|
3
|
+
|
4
|
+
gem 'rake'
|
5
|
+
|
6
|
+
rails = ENV['RAILS'] || '3-2-stable'
|
7
|
+
|
8
|
+
gem 'arel', '3.0.2'
|
9
|
+
|
10
|
+
case rails
|
11
|
+
when /\// # A path
|
12
|
+
gem 'activesupport', :path => "#{rails}/activesupport"
|
13
|
+
gem 'activemodel', :path => "#{rails}/activemodel"
|
14
|
+
gem 'activerecord', :path => "#{rails}/activerecord"
|
15
|
+
gem 'actionpack', :path => "#{rails}/activerecord"
|
16
|
+
when /^v/ # A tagged version
|
17
|
+
git 'git://github.com/rails/rails.git', :tag => rails do
|
18
|
+
gem 'activesupport'
|
19
|
+
gem 'activemodel'
|
20
|
+
gem 'activerecord'
|
21
|
+
gem 'actionpack'
|
22
|
+
end
|
23
|
+
else
|
24
|
+
git 'git://github.com/rails/rails.git', :branch => rails do
|
25
|
+
gem 'activesupport'
|
26
|
+
gem 'activemodel'
|
27
|
+
gem 'activerecord'
|
28
|
+
gem 'actionpack'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Jamie Davidson
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module RansackAbbreviator
|
2
|
+
module Abbreviators
|
3
|
+
module Decoder
|
4
|
+
def decode_possible_abbr(possible_abbr)
|
5
|
+
possible_assoc_abbr, possible_attr_abbr = extract_possible_assoc_and_attribute_abbr(possible_abbr)
|
6
|
+
parent_of_attribute = self.klass
|
7
|
+
decoded_str = ""
|
8
|
+
if possible_assoc_abbr
|
9
|
+
decoded_str, parent_of_attribute = decode_assoc_abbr(possible_assoc_abbr)
|
10
|
+
end
|
11
|
+
|
12
|
+
if attr_name = decode_column_abbr(possible_attr_abbr, parent_of_attribute)
|
13
|
+
decoded_str << attr_name
|
14
|
+
else
|
15
|
+
decoded_str << possible_attr_abbr
|
16
|
+
end
|
17
|
+
|
18
|
+
decoded_str
|
19
|
+
end
|
20
|
+
|
21
|
+
def decode_assoc_abbr(possible_assoc_abbr)
|
22
|
+
# possible_assoc_abbr can be a chain of abbreviated associations, so decode them all and reconstruct into
|
23
|
+
# the format expected by Ransack
|
24
|
+
decoded_str = ""
|
25
|
+
segments = possible_assoc_abbr.split(/_/)
|
26
|
+
association_parts = []
|
27
|
+
klass = self.klass
|
28
|
+
|
29
|
+
while segments.size > 0 && association_parts << segments.shift
|
30
|
+
assoc_to_test = association_parts.join('_')
|
31
|
+
if polymorphic_association_specified?(assoc_to_test)
|
32
|
+
assoc_name, class_type = get_polymorphic_assoc_and_class_type(assoc_to_test)
|
33
|
+
klass = Kernel.const_get(class_type)
|
34
|
+
decoded_str << "of_#{class_type}_type_"
|
35
|
+
association_parts.clear
|
36
|
+
elsif assoc_name = klass.ransackable_assoc_name_for(assoc_to_test)
|
37
|
+
assoc = klass.reflect_on_all_associations.find{|a| a.name.to_s == assoc_name}
|
38
|
+
decoded_str << "#{assoc_name}_"
|
39
|
+
unless assoc.options[:polymorphic]
|
40
|
+
# Get the model for this association, as the next association/attribute will be related to it
|
41
|
+
klass = assoc.klass
|
42
|
+
association_parts.clear
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
decoded_str = "#{possible_assoc_abbr}_" if decoded_str.blank?
|
48
|
+
[decoded_str, klass]
|
49
|
+
end
|
50
|
+
|
51
|
+
def decode_column_abbr(possible_attr_abbr, klass=@klass)
|
52
|
+
klass.ransackable_column_name_for(possible_attr_abbr)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def get_polymorphic_assoc_and_class_type(possible_assoc_abbr)
|
58
|
+
assoc_name = class_type = nil
|
59
|
+
if (match = possible_assoc_abbr.match(/_of_([^_]+?)_type$/))
|
60
|
+
assoc_name = self.klass.ransackable_assoc_name_for(match.pre_match)
|
61
|
+
class_type = RansackAbbreviator.assoc_name_for(match.captures.first).camelize
|
62
|
+
end
|
63
|
+
[assoc_name, class_type]
|
64
|
+
end
|
65
|
+
|
66
|
+
def extract_possible_assoc_and_attribute_abbr(s)
|
67
|
+
possible_assoc = possible_attr_name = nil
|
68
|
+
if s.include?(".")
|
69
|
+
parts = s.split(".")
|
70
|
+
possible_assoc = parts[0]
|
71
|
+
possible_attr_name = parts[1]
|
72
|
+
else
|
73
|
+
possible_attr_name = s
|
74
|
+
end
|
75
|
+
|
76
|
+
[possible_assoc, possible_attr_name]
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module RansackAbbreviator
|
2
|
+
module Abbreviators
|
3
|
+
module Encoder
|
4
|
+
def encode_ransack_str(str)
|
5
|
+
encoded_str = ""
|
6
|
+
associations, attr_name = self.get_associations_and_attribute(str)
|
7
|
+
parent_of_attribute = self.klass
|
8
|
+
|
9
|
+
if attr_name
|
10
|
+
unless associations.blank?
|
11
|
+
encoded_associations, parent_of_attribute = self.encode_associations(associations, str)
|
12
|
+
encoded_str = "#{encoded_associations}."
|
13
|
+
end
|
14
|
+
encoded_str << self.encode_attribute(attr_name, parent_of_attribute)
|
15
|
+
else
|
16
|
+
encoded_str = str
|
17
|
+
end
|
18
|
+
|
19
|
+
encoded_str
|
20
|
+
end
|
21
|
+
|
22
|
+
def encode_associations(associations, ransack_name)
|
23
|
+
klass = self.klass
|
24
|
+
encoded_str = ""
|
25
|
+
|
26
|
+
associations.each_with_index do |assoc, i|
|
27
|
+
encoded_str << "_" unless i == 0
|
28
|
+
encoded_str << klass.ransackable_assoc_abbr_for(assoc.name.to_s)
|
29
|
+
if assoc.options[:polymorphic] && (match = ransack_name.match(/_of_([^_]+?)_type.*$/))
|
30
|
+
# Polymorphic belongs_to format detected
|
31
|
+
# Lookup the association abbreviation out of all association abbreviations, as the value
|
32
|
+
# can be the name of any model (e.g. PersonItem...which is why we underscore)
|
33
|
+
klass_name = match.captures.first
|
34
|
+
encoded_str << "_of_#{RansackAbbreviator.assoc_abbreviation_for(klass_name.underscore)}_type"
|
35
|
+
klass = Kernel.const_get(klass_name)
|
36
|
+
else
|
37
|
+
klass = assoc.klass
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
[encoded_str, klass]
|
42
|
+
end
|
43
|
+
|
44
|
+
def encode_attribute(attr_name, parent=@klass)
|
45
|
+
parent.ransackable_column_abbr_for(attr_name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RansackAbbreviator
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
module Base
|
5
|
+
|
6
|
+
def self.extended(base)
|
7
|
+
base.class_eval do
|
8
|
+
class_attribute :_ransack_column_abbreviations, :_ransack_assoc_abbreviations
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def ransack_abbreviate_column(column_name, column_abbr)
|
13
|
+
ransack_abbreviate_column!(column_name, column_abbr) rescue {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def ransack_abbreviate_column!(column_name, column_abbr)
|
17
|
+
raise ActiveModel::MissingAttributeError, "missing attribute: #{column_name}" unless column_names.include?(column_name)
|
18
|
+
raise "column #{self.ransackable_column_abbreviations.key(column_abbr)} has abbreviaiton #{column_abbr}" if self.ransackable_column_abbreviations.has_value?(column_abbr)
|
19
|
+
self._ransack_column_abbreviations[column_name] = column_abbr
|
20
|
+
end
|
21
|
+
|
22
|
+
def ransack_abbreviate_assoc(assoc_name, assoc_abbr)
|
23
|
+
ransack_abbreviate_assoc!(assoc_name, assoc_abbr) rescue {}
|
24
|
+
end
|
25
|
+
|
26
|
+
def ransack_abbreviate_assoc!(assoc_name, assoc_abbr)
|
27
|
+
raise ActiveModel::MissingAttributeError, "missing association: #{assoc_name}" unless reflect_on_all_associations.map{|a| a.name.to_s}.include?(assoc_name)
|
28
|
+
raise "association #{self.ransackable_assoc_abbreviations.key(assoc_abbr)} has abbreviaiton #{assoc_abbr}" if self.ransackable_assoc_abbreviations.has_value?(assoc_abbr)
|
29
|
+
self._ransack_assoc_abbreviations[assoc_name] = assoc_abbr
|
30
|
+
end
|
31
|
+
|
32
|
+
def ransackable_column_abbreviations
|
33
|
+
self._ransack_column_abbreviations ||= RansackAbbreviator.column_abbreviations.select{ |key, val| column_names.include?(key) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def ransackable_assoc_abbreviations
|
37
|
+
associations = reflect_on_all_associations.map{|a| a.name.to_s}
|
38
|
+
self._ransack_assoc_abbreviations ||= RansackAbbreviator.assoc_abbreviations.select{ |key, val| associations.include?(key) }
|
39
|
+
end
|
40
|
+
|
41
|
+
def ransackable_column_name_for(possible_abbr)
|
42
|
+
possible_abbr = possible_abbr.to_s
|
43
|
+
column_names.include?(possible_abbr) ? possible_abbr : self.ransackable_column_abbreviations.key(possible_abbr)
|
44
|
+
end
|
45
|
+
|
46
|
+
def ransackable_column_abbr_for(column_name)
|
47
|
+
column_name = column_name.to_s
|
48
|
+
self.ransackable_column_abbreviations.has_key?(column_name) ? self.ransackable_column_abbreviations[column_name] : column_name
|
49
|
+
end
|
50
|
+
|
51
|
+
def ransackable_assoc_name_for(possible_abbr)
|
52
|
+
possible_abbr = possible_abbr.to_s
|
53
|
+
reflect_on_all_associations.map{|a| a.name.to_s}.include?(possible_abbr) ? possible_abbr : self.ransackable_assoc_abbreviations.key(possible_abbr)
|
54
|
+
end
|
55
|
+
|
56
|
+
def ransackable_assoc_abbr_for(assoc)
|
57
|
+
assoc = assoc.to_s
|
58
|
+
self.ransackable_assoc_abbreviations.has_key?(assoc) ? self.ransackable_assoc_abbreviations[assoc] : assoc
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
module RansackAbbreviator
|
4
|
+
module Configuration
|
5
|
+
mattr_accessor :column_abbreviations, :assoc_abbreviations
|
6
|
+
self.column_abbreviations = self.assoc_abbreviations = {}
|
7
|
+
|
8
|
+
def configure
|
9
|
+
yield self
|
10
|
+
end
|
11
|
+
|
12
|
+
def column_abbreviation_for(column)
|
13
|
+
self.column_abbreviations.has_key?(column) ? self.column_abbreviations[column] : column
|
14
|
+
end
|
15
|
+
|
16
|
+
def column_name_for(column_abbr)
|
17
|
+
self.column_abbreviations.has_value?(column_abbr) ? self.column_abbreviations.key(column_abbr) : column_abbr
|
18
|
+
end
|
19
|
+
|
20
|
+
def assoc_abbreviation_for(assoc)
|
21
|
+
self.assoc_abbreviations.has_key?(assoc) ? self.assoc_abbreviations[assoc] : assoc
|
22
|
+
end
|
23
|
+
|
24
|
+
def assoc_name_for(assoc_abbr)
|
25
|
+
self.assoc_abbreviations.has_value?(assoc_abbr) ? self.assoc_abbreviations.key(assoc_abbr) : assoc_abbr
|
26
|
+
end
|
27
|
+
|
28
|
+
def config_dir
|
29
|
+
defined?(Rails) ? Rails.root.join("config") : Pathname.new("config")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'ransack_abbreviator/abbreviators/decoder'
|
2
|
+
require 'ransack_abbreviator/abbreviators/encoder'
|
3
|
+
|
4
|
+
module Ransack
|
5
|
+
class Context
|
6
|
+
include RansackAbbreviator::Abbreviators::Decoder
|
7
|
+
include RansackAbbreviator::Abbreviators::Encoder
|
8
|
+
|
9
|
+
def get_associations_and_attribute(str, klass = @klass, associations = [])
|
10
|
+
attr_name = nil
|
11
|
+
if ransackable_attribute?(str, klass)
|
12
|
+
attr_name = str
|
13
|
+
elsif (segments = str.split(/_/)).size > 1
|
14
|
+
remainder = []
|
15
|
+
found_assoc = nil
|
16
|
+
while !found_assoc && remainder.unshift(segments.pop) && segments.size > 0 do
|
17
|
+
assoc, poly_class = unpolymorphize_association(segments.join('_'))
|
18
|
+
if found_assoc = get_association(assoc, klass)
|
19
|
+
attr_name = remainder.join('_')
|
20
|
+
associations, attr_name = get_associations_and_attribute(attr_name, poly_class || found_assoc.klass, associations << found_assoc)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
[associations, attr_name]
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def polymorphic_association_specified?(str)
|
31
|
+
str && str.match(/_of_([^_]+?)_type$/)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Nodes
|
3
|
+
class Condition
|
4
|
+
alias_method :ransack_condition_build, :build
|
5
|
+
|
6
|
+
def build(params)
|
7
|
+
decoded_attr_names = []
|
8
|
+
params[:a].each do |possible_abbr|
|
9
|
+
decoded_str = @context.decode_possible_abbr(possible_abbr)
|
10
|
+
decoded_attr_names << (!decoded_str.blank? ? decoded_str : possible_abbr)
|
11
|
+
end
|
12
|
+
params[:a] = decoded_attr_names
|
13
|
+
ransack_condition_build(params)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Ransack
|
2
|
+
module Nodes
|
3
|
+
class Grouping < Node
|
4
|
+
def strip_predicate_and_index_from_param_string(param_string)
|
5
|
+
strip_predicate_and_index(param_string)
|
6
|
+
end
|
7
|
+
|
8
|
+
def special_condition?(str)
|
9
|
+
case str
|
10
|
+
when /^(g|c|m|groupings|conditions|combinator)=?$/
|
11
|
+
true
|
12
|
+
else
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Ransack
|
2
|
+
class Search
|
3
|
+
alias_method :ransack_search_build, :build
|
4
|
+
|
5
|
+
def build(params)
|
6
|
+
# Loop through each of the params and test if any contain abbreviations. If so, convert them to the normal Ransack language
|
7
|
+
new_params = {}
|
8
|
+
collapse_multiparameter_attributes!(params.with_indifferent_access).each do |key, value|
|
9
|
+
pred = Predicate.detect_from_string(key)
|
10
|
+
str = base.strip_predicate_and_index_from_param_string(key)
|
11
|
+
if base.special_condition?(str)
|
12
|
+
# Maintain the param as-is
|
13
|
+
new_params[key] = value
|
14
|
+
else
|
15
|
+
conjunctions = str.split("_").select{|s| s == "and" || s == "or" }
|
16
|
+
full_str = ""
|
17
|
+
str.split(/_and_|_or_/).each do |possible_abbr|
|
18
|
+
decoded_str = self.context.decode_possible_abbr(possible_abbr)
|
19
|
+
full_str << (!decoded_str.blank? ? decoded_str : possible_abbr)
|
20
|
+
full_str << "_#{conjunctions.shift}_" if !conjunctions.blank?
|
21
|
+
end
|
22
|
+
full_str << "_#{pred}" if pred
|
23
|
+
new_params[full_str] = value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
ransack_search_build(new_params)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module RansackAbbreviator
|
2
|
+
module ViewHelpers
|
3
|
+
def ransack_abbreviation_for(ransack_search_object, ransack_name)
|
4
|
+
str = ransack_name.is_a?(Symbol) ? ransack_name.to_s : ransack_name.dup
|
5
|
+
pred = Ransack::Predicate.detect_and_strip_from_string!(str)
|
6
|
+
conjunctions = str.split("_").select{|s| s == "and" || s == "or" }
|
7
|
+
abbr_str = ""
|
8
|
+
str.split(/_and_|_or_/).each do |s|
|
9
|
+
abbr_str << ransack_search_object.context.encode_ransack_str(s)
|
10
|
+
abbr_str << "_#{conjunctions.shift}_" if !conjunctions.blank?
|
11
|
+
end
|
12
|
+
|
13
|
+
pred ? "#{abbr_str}_#{pred}" : abbr_str
|
14
|
+
end
|
15
|
+
|
16
|
+
def ransack_abbreviations_for(ransack_search_object, params)
|
17
|
+
new_params = nil
|
18
|
+
|
19
|
+
case params
|
20
|
+
when Hash
|
21
|
+
new_params = {}
|
22
|
+
params.each do |ransack_name, value|
|
23
|
+
new_params[ransack_abbreviation_for(ransack_search_object, ransack_name)] = value
|
24
|
+
end
|
25
|
+
when Array
|
26
|
+
new_params = []
|
27
|
+
params.each do |ransack_name|
|
28
|
+
new_params << ransack_abbreviation_for(ransack_search_object, ransack_name)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
raise ArgumentError, "don't know how to interpret abbreviations for #{params}"
|
32
|
+
end
|
33
|
+
|
34
|
+
new_params
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
ActionView::Base.send :include, RansackAbbreviator::ViewHelpers
|