ransack_abbreviator 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rspec
6
+ config/*
7
+ bin/*
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## v0.0.1
2
+
3
+ * initial release
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
@@ -0,0 +1,7 @@
1
+ ransack-abbreviator
2
+ ===================
3
+
4
+
5
+ ## To Do
6
+ * Support abbreviation of 'ransacker' attributes
7
+ * Extend the 'attribute_select' form helper to support returning attributes as their abbreviations
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler'
2
+ require 'rspec/core/rake_task'
3
+
4
+ Bundler::GemHelper.install_tasks
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |rspec|
7
+ rspec.rspec_opts = ['--backtrace']
8
+ end
9
+
10
+ task :default => :spec
@@ -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,3 @@
1
+ require 'active_record'
2
+ require 'ransack_abbreviator/adapters/active_record/base'
3
+ ActiveRecord::Base.extend RansackAbbreviator::Adapters::ActiveRecord::Base
@@ -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,5 @@
1
+ module RansackAbbreviator
2
+ module Constants
3
+ RESERVED_KEYWORDS = %w(or and of type)
4
+ end
5
+ 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,3 @@
1
+ module RansackAbbreviator
2
+ VERSION = "0.0.1"
3
+ 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