ransack 0.1.0
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 +4 -0
- data/Gemfile +11 -0
- data/LICENSE +20 -0
- data/README.rdoc +5 -0
- data/Rakefile +19 -0
- data/lib/ransack.rb +24 -0
- data/lib/ransack/adapters/active_record.rb +2 -0
- data/lib/ransack/adapters/active_record/base.rb +17 -0
- data/lib/ransack/adapters/active_record/context.rb +153 -0
- data/lib/ransack/configuration.rb +39 -0
- data/lib/ransack/constants.rb +23 -0
- data/lib/ransack/context.rb +152 -0
- data/lib/ransack/helpers.rb +2 -0
- data/lib/ransack/helpers/form_builder.rb +172 -0
- data/lib/ransack/helpers/form_helper.rb +27 -0
- data/lib/ransack/locale/en.yml +67 -0
- data/lib/ransack/naming.rb +53 -0
- data/lib/ransack/nodes.rb +7 -0
- data/lib/ransack/nodes/and.rb +8 -0
- data/lib/ransack/nodes/attribute.rb +36 -0
- data/lib/ransack/nodes/condition.rb +209 -0
- data/lib/ransack/nodes/grouping.rb +207 -0
- data/lib/ransack/nodes/node.rb +34 -0
- data/lib/ransack/nodes/or.rb +8 -0
- data/lib/ransack/nodes/sort.rb +39 -0
- data/lib/ransack/nodes/value.rb +120 -0
- data/lib/ransack/predicate.rb +57 -0
- data/lib/ransack/search.rb +114 -0
- data/lib/ransack/translate.rb +92 -0
- data/lib/ransack/version.rb +3 -0
- data/ransack.gemspec +29 -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 +4 -0
- data/spec/blueprints/tags.rb +3 -0
- data/spec/console.rb +22 -0
- data/spec/helpers/ransack_helper.rb +2 -0
- data/spec/playground.rb +37 -0
- data/spec/ransack/adapters/active_record/base_spec.rb +30 -0
- data/spec/ransack/adapters/active_record/context_spec.rb +29 -0
- data/spec/ransack/configuration_spec.rb +11 -0
- data/spec/ransack/helpers/form_builder_spec.rb +39 -0
- data/spec/ransack/nodes/compound_condition_spec.rb +0 -0
- data/spec/ransack/nodes/condition_spec.rb +0 -0
- data/spec/ransack/nodes/grouping_spec.rb +13 -0
- data/spec/ransack/predicate_spec.rb +25 -0
- data/spec/ransack/search_spec.rb +182 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/support/schema.rb +102 -0
- metadata +200 -0
    
        data/.gitignore
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    | @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            source "http://rubygems.org"
         | 
| 2 | 
            +
            gemspec
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            gem 'arel', :git => 'git://github.com/rails/arel.git'
         | 
| 5 | 
            +
            gem 'rack', :git => 'git://github.com/rack/rack.git'
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            git 'git://github.com/rails/rails.git' do
         | 
| 8 | 
            +
              gem 'activesupport'
         | 
| 9 | 
            +
              gem 'activerecord'
         | 
| 10 | 
            +
              gem 'actionpack'
         | 
| 11 | 
            +
            end
         | 
    
        data/LICENSE
    ADDED
    
    | @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            Copyright (c) 2010 Ernie Miller
         | 
| 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.rdoc
    ADDED
    
    
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,19 @@ | |
| 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
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            desc "Open an irb session with Ransack and the sample data used in specs"
         | 
| 13 | 
            +
            task :console do
         | 
| 14 | 
            +
              require 'irb'
         | 
| 15 | 
            +
              require 'irb/completion'
         | 
| 16 | 
            +
              require 'console'
         | 
| 17 | 
            +
              ARGV.clear
         | 
| 18 | 
            +
              IRB.start
         | 
| 19 | 
            +
            end
         | 
    
        data/lib/ransack.rb
    ADDED
    
    | @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            require 'ransack/configuration'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Ransack
         | 
| 4 | 
            +
              extend Configuration
         | 
| 5 | 
            +
            end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Ransack.configure do |config|
         | 
| 8 | 
            +
              Ransack::Constants::AREL_PREDICATES.each do |name|
         | 
| 9 | 
            +
                config.add_predicate name, :arel_predicate => name
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
              Ransack::Constants::DERIVED_PREDICATES.each do |args|
         | 
| 13 | 
            +
                config.add_predicate *args
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            require 'ransack/translate'
         | 
| 18 | 
            +
            require 'ransack/search'
         | 
| 19 | 
            +
            require 'ransack/adapters/active_record'
         | 
| 20 | 
            +
            require 'ransack/helpers'
         | 
| 21 | 
            +
            require 'action_controller'
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            ActiveRecord::Base.extend Ransack::Adapters::ActiveRecord::Base
         | 
| 24 | 
            +
            ActionController::Base.helper Ransack::Helpers::FormHelper
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            module Ransack
         | 
| 2 | 
            +
              module Adapters
         | 
| 3 | 
            +
                module ActiveRecord
         | 
| 4 | 
            +
                  module Base
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                    def self.extended(base)
         | 
| 7 | 
            +
                      alias :search :ransack unless base.method_defined? :search
         | 
| 8 | 
            +
                    end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    def ransack(params = {})
         | 
| 11 | 
            +
                      Search.new(self, params)
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1,153 @@ | |
| 1 | 
            +
            require 'ransack/context'
         | 
| 2 | 
            +
            require 'active_record'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Ransack
         | 
| 5 | 
            +
              module Adapters
         | 
| 6 | 
            +
                module ActiveRecord
         | 
| 7 | 
            +
                  class Context < ::Ransack::Context
         | 
| 8 | 
            +
                    # Because the AR::Associations namespace is insane
         | 
| 9 | 
            +
                    JoinDependency = ::ActiveRecord::Associations::JoinDependency
         | 
| 10 | 
            +
                    JoinPart = JoinDependency::JoinPart
         | 
| 11 | 
            +
                    JoinAssociation = JoinDependency::JoinAssociation
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def evaluate(search, opts = {})
         | 
| 14 | 
            +
                      relation = @object.where(accept(search.base)).order(accept(search.sorts))
         | 
| 15 | 
            +
                      opts[:distinct] ? relation.group(@klass.arel_table[@klass.primary_key]) : relation
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    def attribute_method?(str, klass = @klass)
         | 
| 19 | 
            +
                      exists = false
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                      if column = get_column(str, klass)
         | 
| 22 | 
            +
                        exists = true
         | 
| 23 | 
            +
                      elsif (segments = str.split(/_/)).size > 1
         | 
| 24 | 
            +
                        remainder = []
         | 
| 25 | 
            +
                        found_assoc = nil
         | 
| 26 | 
            +
                        while !found_assoc && remainder.unshift(segments.pop) && segments.size > 0 do
         | 
| 27 | 
            +
                          if found_assoc = get_association(segments.join('_'), klass)
         | 
| 28 | 
            +
                            exists = attribute_method?(remainder.join('_'), found_assoc.klass)
         | 
| 29 | 
            +
                          end
         | 
| 30 | 
            +
                        end
         | 
| 31 | 
            +
                      end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                      exists
         | 
| 34 | 
            +
                    end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                    def type_for(attr)
         | 
| 37 | 
            +
                      return nil unless attr
         | 
| 38 | 
            +
                      name    = attr.name.to_s
         | 
| 39 | 
            +
                      table   = attr.relation.table_name
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                      unless @engine.connection_pool.table_exists?(table)
         | 
| 42 | 
            +
                        raise "No table named #{table} exists"
         | 
| 43 | 
            +
                      end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      @engine.connection_pool.columns_hash[table][name].type
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    private
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    def klassify(obj)
         | 
| 51 | 
            +
                      if Class === obj && ::ActiveRecord::Base > obj
         | 
| 52 | 
            +
                        obj
         | 
| 53 | 
            +
                      elsif obj.respond_to? :klass
         | 
| 54 | 
            +
                        obj.klass
         | 
| 55 | 
            +
                      elsif obj.respond_to? :active_record
         | 
| 56 | 
            +
                        obj.active_record
         | 
| 57 | 
            +
                      else
         | 
| 58 | 
            +
                        raise ArgumentError, "Don't know how to klassify #{obj}"
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    def get_attribute(str, parent = @base)
         | 
| 63 | 
            +
                      attribute = nil
         | 
| 64 | 
            +
             | 
| 65 | 
            +
                      if column = get_column(str, parent)
         | 
| 66 | 
            +
                        attribute = parent.table[str]
         | 
| 67 | 
            +
                      elsif (segments = str.split(/_/)).size > 1
         | 
| 68 | 
            +
                        remainder = []
         | 
| 69 | 
            +
                        found_assoc = nil
         | 
| 70 | 
            +
                        while remainder.unshift(segments.pop) && segments.size > 0 && !found_assoc do
         | 
| 71 | 
            +
                          if found_assoc = get_association(segments.join('_'), parent)
         | 
| 72 | 
            +
                            join = build_or_find_association(found_assoc.name, parent)
         | 
| 73 | 
            +
                            attribute = get_attribute(remainder.join('_'), join)
         | 
| 74 | 
            +
                          end
         | 
| 75 | 
            +
                        end
         | 
| 76 | 
            +
                      end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                      attribute
         | 
| 79 | 
            +
                    end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                    def get_column(str, parent = @base)
         | 
| 82 | 
            +
                      klassify(parent).columns_hash[str]
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    def get_association(str, parent = @base)
         | 
| 86 | 
            +
                      klassify(parent).reflect_on_all_associations.detect {|a| a.name.to_s == str}
         | 
| 87 | 
            +
                    end
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                    def join_dependency(relation)
         | 
| 90 | 
            +
                      if relation.respond_to?(:join_dependency) # MetaWhere will enable this
         | 
| 91 | 
            +
                        relation.join_dependency
         | 
| 92 | 
            +
                      else
         | 
| 93 | 
            +
                        build_join_dependency(relation)
         | 
| 94 | 
            +
                      end
         | 
| 95 | 
            +
                    end
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                    def build_join_dependency(relation)
         | 
| 98 | 
            +
                      buckets = relation.joins_values.group_by do |join|
         | 
| 99 | 
            +
                        case join
         | 
| 100 | 
            +
                        when String
         | 
| 101 | 
            +
                          'string_join'
         | 
| 102 | 
            +
                        when Hash, Symbol, Array
         | 
| 103 | 
            +
                          'association_join'
         | 
| 104 | 
            +
                        when ActiveRecord::Associations::JoinDependency::JoinAssociation
         | 
| 105 | 
            +
                          'stashed_join'
         | 
| 106 | 
            +
                        when Arel::Nodes::Join
         | 
| 107 | 
            +
                          'join_node'
         | 
| 108 | 
            +
                        else
         | 
| 109 | 
            +
                          raise 'unknown class: %s' % join.class.name
         | 
| 110 | 
            +
                        end
         | 
| 111 | 
            +
                      end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                      association_joins         = buckets['association_join'] || []
         | 
| 114 | 
            +
                      stashed_association_joins = buckets['stashed_join'] || []
         | 
| 115 | 
            +
                      join_nodes                = buckets['join_node'] || []
         | 
| 116 | 
            +
                      string_joins              = (buckets['string_join'] || []).map { |x|
         | 
| 117 | 
            +
                        x.strip
         | 
| 118 | 
            +
                      }.uniq
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                      join_list = relation.send :custom_join_ast, relation.table.from(relation.table), string_joins
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                      join_dependency = JoinDependency.new(
         | 
| 123 | 
            +
                        relation.klass,
         | 
| 124 | 
            +
                        association_joins,
         | 
| 125 | 
            +
                        join_list
         | 
| 126 | 
            +
                      )
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                      join_nodes.each do |join|
         | 
| 129 | 
            +
                        join_dependency.table_aliases[join.left.name.downcase] = 1
         | 
| 130 | 
            +
                      end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                      join_dependency.graft(*stashed_association_joins)
         | 
| 133 | 
            +
                    end
         | 
| 134 | 
            +
             | 
| 135 | 
            +
                    def build_or_find_association(name, parent = @base)
         | 
| 136 | 
            +
                      found_association = @join_dependency.join_associations.detect do |assoc|
         | 
| 137 | 
            +
                        assoc.reflection.name == name &&
         | 
| 138 | 
            +
                        assoc.parent == parent
         | 
| 139 | 
            +
                      end
         | 
| 140 | 
            +
                      unless found_association
         | 
| 141 | 
            +
                        @join_dependency.send(:build, name.to_sym, parent, Arel::Nodes::OuterJoin)
         | 
| 142 | 
            +
                        found_association = @join_dependency.join_associations.last
         | 
| 143 | 
            +
                        # Leverage the stashed association functionality in AR
         | 
| 144 | 
            +
                        @object = @object.joins(found_association)
         | 
| 145 | 
            +
                      end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                      found_association
         | 
| 148 | 
            +
                    end
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                  end
         | 
| 151 | 
            +
                end
         | 
| 152 | 
            +
              end
         | 
| 153 | 
            +
            end
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            require 'ransack/constants'
         | 
| 2 | 
            +
            require 'ransack/predicate'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            module Ransack
         | 
| 5 | 
            +
              module Configuration
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                mattr_accessor :predicates
         | 
| 8 | 
            +
                self.predicates = {}
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def self.predicate_keys
         | 
| 11 | 
            +
                  predicates.keys.sort {|a,b| b.length <=> a.length}
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def configure
         | 
| 15 | 
            +
                  yield self
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def add_predicate(name, opts = {})
         | 
| 19 | 
            +
                  name = name.to_s
         | 
| 20 | 
            +
                  opts[:name] = name
         | 
| 21 | 
            +
                  compounds = opts.delete(:compounds)
         | 
| 22 | 
            +
                  compounds = true if compounds.nil?
         | 
| 23 | 
            +
                  opts[:arel_predicate] = opts[:arel_predicate].to_s
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  self.predicates[name] = Predicate.new(opts)
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  ['_any', '_all'].each do |suffix|
         | 
| 28 | 
            +
                    self.predicates[name + suffix] = Predicate.new(
         | 
| 29 | 
            +
                      opts.merge(
         | 
| 30 | 
            +
                        :name => name + suffix,
         | 
| 31 | 
            +
                        :arel_predicate => opts[:arel_predicate] + suffix,
         | 
| 32 | 
            +
                        :compound => true
         | 
| 33 | 
            +
                      )
         | 
| 34 | 
            +
                    )
         | 
| 35 | 
            +
                  end if compounds
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            module Ransack
         | 
| 2 | 
            +
              module Constants
         | 
| 3 | 
            +
                TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set
         | 
| 4 | 
            +
                FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                AREL_PREDICATES = %w(eq not_eq matches does_not_match lt lteq gt gteq in not_in)
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                DERIVED_PREDICATES = [
         | 
| 9 | 
            +
                  ['cont', {:arel_predicate => 'matches', :formatter => proc {|v| "%#{v}%"}}],
         | 
| 10 | 
            +
                  ['not_cont', {:arel_predicate => 'does_not_match', :formatter => proc {|v| "%#{v}%"}}],
         | 
| 11 | 
            +
                  ['start', {:arel_predicate => 'matches', :formatter => proc {|v| "#{v}%"}}],
         | 
| 12 | 
            +
                  ['not_start', {:arel_predicate => 'does_not_match', :formatter => proc {|v| "#{v}%"}}],
         | 
| 13 | 
            +
                  ['end', {:arel_predicate => 'matches', :formatter => proc {|v| "%#{v}"}}],
         | 
| 14 | 
            +
                  ['not_end', {:arel_predicate => 'does_not_match', :formatter => proc {|v| "%#{v}"}}],
         | 
| 15 | 
            +
                  ['true', {:arel_predicate => 'eq', :compounds => false, :type => :boolean, :validator => proc {|v| TRUE_VALUES.include?(v)}}],
         | 
| 16 | 
            +
                  ['false', {:arel_predicate => 'eq', :compounds => false, :type => :boolean, :validator => proc {|v| TRUE_VALUES.include?(v)}, :formatter => proc {|v| !v}}],
         | 
| 17 | 
            +
                  ['present', {:arel_predicate => 'not_eq_all', :compounds => false, :type => :boolean, :validator => proc {|v| TRUE_VALUES.include?(v)}, :formatter => proc {|v| [nil, '']}}],
         | 
| 18 | 
            +
                  ['blank', {:arel_predicate => 'eq_any', :compounds => false, :type => :boolean, :validator => proc {|v| TRUE_VALUES.include?(v)}, :formatter => proc {|v| [nil, '']}}],
         | 
| 19 | 
            +
                  ['null', {:arel_predicate => 'eq', :compounds => false, :type => :boolean, :validator => proc {|v| TRUE_VALUES.include?(v)}, :formatter => proc {|v| nil}}],
         | 
| 20 | 
            +
                  ['not_null', {:arel_predicate => 'not_eq', :compounds => false, :type => :boolean, :validator => proc {|v| TRUE_VALUES.include?(v)}, :formatter => proc {|v| nil}}]
         | 
| 21 | 
            +
                ]
         | 
| 22 | 
            +
              end
         | 
| 23 | 
            +
            end
         | 
| @@ -0,0 +1,152 @@ | |
| 1 | 
            +
            module Ransack
         | 
| 2 | 
            +
              class Context
         | 
| 3 | 
            +
                attr_reader :search, :object, :klass, :base, :engine, :arel_visitor
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                class << self
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  def for(object)
         | 
| 8 | 
            +
                    context = Class === object ? for_class(object) : for_object(object)
         | 
| 9 | 
            +
                    context or raise ArgumentError, "Don't know what context to use for #{object}"
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def for_class(klass)
         | 
| 13 | 
            +
                    if klass < ActiveRecord::Base
         | 
| 14 | 
            +
                      Adapters::ActiveRecord::Context.new(klass)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
                  end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  def for_object(object)
         | 
| 19 | 
            +
                    case object
         | 
| 20 | 
            +
                    when ActiveRecord::Relation
         | 
| 21 | 
            +
                      Adapters::ActiveRecord::Context.new(object.klass)
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def can_accept?(object)
         | 
| 26 | 
            +
                    method_defined? DISPATCH[object.class]
         | 
| 27 | 
            +
                  end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def initialize(object)
         | 
| 32 | 
            +
                  @object = object.scoped
         | 
| 33 | 
            +
                  @klass = @object.klass
         | 
| 34 | 
            +
                  @join_dependency = join_dependency(@object)
         | 
| 35 | 
            +
                  @base = @join_dependency.join_base
         | 
| 36 | 
            +
                  @engine = @base.arel_engine
         | 
| 37 | 
            +
                  @arel_visitor = Arel::Visitors.visitor_for @engine
         | 
| 38 | 
            +
                  @default_table = Arel::Table.new(@base.table_name, :as => @base.aliased_table_name, :engine => @engine)
         | 
| 39 | 
            +
                  @attributes = Hash.new do |hash, key|
         | 
| 40 | 
            +
                    if attribute = get_attribute(key.to_s)
         | 
| 41 | 
            +
                      hash[key] = attribute
         | 
| 42 | 
            +
                    end
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                # Convert a string representing a chain of associations and an attribute
         | 
| 47 | 
            +
                # into the attribute itself
         | 
| 48 | 
            +
                def contextualize(str)
         | 
| 49 | 
            +
                  @attributes[str]
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                def traverse(str, base = @base)
         | 
| 53 | 
            +
                  str ||= ''
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                  if (segments = str.split(/_/)).size > 0
         | 
| 56 | 
            +
                    association_parts = []
         | 
| 57 | 
            +
                    found_assoc = nil
         | 
| 58 | 
            +
                    while !found_assoc && segments.size > 0 && association_parts << segments.shift do
         | 
| 59 | 
            +
                      if found_assoc = get_association(association_parts.join('_'), base)
         | 
| 60 | 
            +
                        base = traverse(segments.join('_'), found_assoc.klass)
         | 
| 61 | 
            +
                      end
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
                    raise ArgumentError, "No association matches #{str}" unless found_assoc
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  klassify(base)
         | 
| 67 | 
            +
                end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                def association_path(str, base = @base)
         | 
| 70 | 
            +
                  base = klassify(base)
         | 
| 71 | 
            +
                  str ||= ''
         | 
| 72 | 
            +
                  path = []
         | 
| 73 | 
            +
                  segments = str.split(/_/)
         | 
| 74 | 
            +
                  association_parts = []
         | 
| 75 | 
            +
                  if (segments = str.split(/_/)).size > 0
         | 
| 76 | 
            +
                    while segments.size > 0 && !base.columns_hash[segments.join('_')] && association_parts << segments.shift do
         | 
| 77 | 
            +
                      if found_assoc = get_association(association_parts.join('_'), base)
         | 
| 78 | 
            +
                        path += association_parts
         | 
| 79 | 
            +
                        association_parts = []
         | 
| 80 | 
            +
                        base = klassify(found_assoc)
         | 
| 81 | 
            +
                      end
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  path.join('_')
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                def searchable_columns(str = '')
         | 
| 89 | 
            +
                  traverse(str).column_names
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                def accept(object)
         | 
| 93 | 
            +
                  visit(object)
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                def can_accept?(object)
         | 
| 97 | 
            +
                  respond_to? DISPATCH[object.class]
         | 
| 98 | 
            +
                end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                def visit_Array(object)
         | 
| 101 | 
            +
                  object.map {|o| accept(o)}.compact
         | 
| 102 | 
            +
                end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                def visit_Ransack_Nodes_Condition(object)
         | 
| 105 | 
            +
                  object.apply_predicate if object.valid?
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                def visit_Ransack_Nodes_And(object)
         | 
| 109 | 
            +
                  nodes = object.values.map {|o| accept(o)}.compact
         | 
| 110 | 
            +
                  return nil unless nodes.size > 0
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                  if nodes.size > 1
         | 
| 113 | 
            +
                    Arel::Nodes::Grouping.new(Arel::Nodes::And.new(nodes))
         | 
| 114 | 
            +
                  else
         | 
| 115 | 
            +
                    nodes.first
         | 
| 116 | 
            +
                  end
         | 
| 117 | 
            +
                end
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                def visit_Ransack_Nodes_Sort(object)
         | 
| 120 | 
            +
                  object.attr.send(object.dir) if object.valid?
         | 
| 121 | 
            +
                end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                def visit_Ransack_Nodes_Or(object)
         | 
| 124 | 
            +
                  nodes = object.values.map {|o| accept(o)}.compact
         | 
| 125 | 
            +
                  return nil unless nodes.size > 0
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                  if nodes.size > 1
         | 
| 128 | 
            +
                    nodes.inject(&:or)
         | 
| 129 | 
            +
                  else
         | 
| 130 | 
            +
                    nodes.first
         | 
| 131 | 
            +
                  end
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                def quoted?(object)
         | 
| 135 | 
            +
                  case object
         | 
| 136 | 
            +
                  when Arel::Nodes::SqlLiteral, Bignum, Fixnum
         | 
| 137 | 
            +
                    false
         | 
| 138 | 
            +
                  else
         | 
| 139 | 
            +
                    true
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                def visit(object)
         | 
| 144 | 
            +
                  send(DISPATCH[object.class], object)
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                DISPATCH = Hash.new do |hash, klass|
         | 
| 148 | 
            +
                  hash[klass] = "visit_#{klass.name.gsub('::', '_')}"
         | 
| 149 | 
            +
                end
         | 
| 150 | 
            +
             | 
| 151 | 
            +
              end
         | 
| 152 | 
            +
            end
         |