ransack_abbreviator 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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