diff --git a/Gemfile b/Gemfile index 58c34d4..d344b4b 100644 --- a/Gemfile +++ b/Gemfile @@ -11,10 +11,8 @@ when /\// # A path {:path => arel} when /^v/ # A tagged version {:git => 'git://github.com/rails/arel.git', :tag => arel} -when /^\w-$/ # A branch name - {:git => 'git://github.com/rails/arel.git', :branch => arel} else - arel + {:git => 'git://github.com/rails/arel.git', :branch => arel} end gem 'arel', arel_opts diff --git a/lib/ransack/adapters/active_record.rb b/lib/ransack/adapters/active_record.rb index 037525e..8782d84 100644 --- a/lib/ransack/adapters/active_record.rb +++ b/lib/ransack/adapters/active_record.rb @@ -4,6 +4,8 @@ ActiveRecord::Base.extend Ransack::Adapters::ActiveRecord::Base case ActiveRecord::VERSION::STRING when /^3\.0\./ require 'ransack/adapters/active_record/3.0/context' +when /^3\.1\./ + require 'ransack/adapters/active_record/3.1/context' else require 'ransack/adapters/active_record/context' end \ No newline at end of file diff --git a/lib/ransack/adapters/active_record/3.0/context.rb b/lib/ransack/adapters/active_record/3.0/context.rb index c735d38..5a38087 100644 --- a/lib/ransack/adapters/active_record/3.0/context.rb +++ b/lib/ransack/adapters/active_record/3.0/context.rb @@ -10,6 +10,11 @@ module Ransack # Because the AR::Associations namespace is insane JoinDependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency JoinBase = JoinDependency::JoinBase + + def initialize(object, options = {}) + super + @arel_visitor = Arel::Visitors.visitor_for @engine + end def evaluate(search, opts = {}) viz = Visitor.new diff --git a/lib/ransack/adapters/active_record/3.1/context.rb b/lib/ransack/adapters/active_record/3.1/context.rb new file mode 100644 index 0000000..4ea1b74 --- /dev/null +++ b/lib/ransack/adapters/active_record/3.1/context.rb @@ -0,0 +1,163 @@ +require 'ransack/context' +require 'polyamorous' + +module Ransack + module Adapters + module ActiveRecord + class Context < ::Ransack::Context + # Because the AR::Associations namespace is insane + JoinDependency = ::ActiveRecord::Associations::JoinDependency + JoinPart = JoinDependency::JoinPart + + def initialize(object, options = {}) + super + @arel_visitor = Arel::Visitors.visitor_for @engine + end + + def evaluate(search, opts = {}) + viz = Visitor.new + relation = @object.except(:order).where(viz.accept(search.base)).order(viz.accept(search.sorts)) + opts[:distinct] ? relation.select("DISTINCT #{@klass.quoted_table_name}.*") : relation + end + + def attribute_method?(str, klass = @klass) + exists = false + + if ransackable_attribute?(str, klass) + exists = true + elsif (segments = str.split(/_/)).size > 1 + remainder = [] + found_assoc = nil + while !found_assoc && remainder.unshift(segments.pop) && segments.size > 0 do + assoc, poly_class = unpolymorphize_association(segments.join('_')) + if found_assoc = get_association(assoc, klass) + exists = attribute_method?(remainder.join('_'), poly_class || found_assoc.klass) + end + end + end + + exists + end + + def table_for(parent) + parent.table + end + + def klassify(obj) + if Class === obj && ::ActiveRecord::Base > obj + obj + elsif obj.respond_to? :klass + obj.klass + elsif obj.respond_to? :active_record + obj.active_record + else + raise ArgumentError, "Don't know how to klassify #{obj}" + end + end + + def type_for(attr) + return nil unless attr && attr.valid? + name = attr.arel_attribute.name.to_s + table = attr.arel_attribute.relation.table_name + + unless @engine.connection_pool.table_exists?(table) + raise "No table named #{table} exists" + end + + @engine.connection_pool.columns_hash[table][name].type + end + + private + + def get_parent_and_attribute_name(str, parent = @base) + attr_name = nil + + if ransackable_attribute?(str, klassify(parent)) + attr_name = str + elsif (segments = str.split(/_/)).size > 1 + remainder = [] + found_assoc = nil + while remainder.unshift(segments.pop) && segments.size > 0 && !found_assoc do + assoc, klass = unpolymorphize_association(segments.join('_')) + 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) + end + end + end + + [parent, attr_name] + end + + def get_association(str, parent = @base) + klass = klassify parent + ransackable_association?(str, klass) && + klass.reflect_on_all_associations.detect {|a| a.name.to_s == str} + end + + def join_dependency(relation) + if relation.respond_to?(:join_dependency) # Squeel will enable this + relation.join_dependency + else + build_join_dependency(relation) + end + end + + def build_join_dependency(relation) + buckets = relation.joins_values.group_by do |join| + case join + when String + 'string_join' + when Hash, Symbol, Array + 'association_join' + when ::ActiveRecord::Associations::JoinDependency::JoinAssociation + 'stashed_join' + when Arel::Nodes::Join + 'join_node' + 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'] || []).map { |x| + x.strip + }.uniq + + join_list = relation.send :custom_join_ast, relation.table.from(relation.table), string_joins + + join_dependency = JoinDependency.new( + relation.klass, + association_joins, + join_list + ) + + join_nodes.each do |join| + join_dependency.table_aliases[join.left.name.downcase] = 1 + end + + join_dependency.graft(*stashed_association_joins) + end + + def build_or_find_association(name, parent = @base, klass = nil) + found_association = @join_dependency.join_associations.detect do |assoc| + assoc.reflection.name == name && + assoc.parent == parent && + (!klass || assoc.reflection.klass == klass) + end + unless found_association + @join_dependency.send(:build, Polyamorous::Join.new(name, @join_type, klass), parent) + found_association = @join_dependency.join_associations.last + # Leverage the stashed association functionality in AR + @object = @object.joins(found_association) + end + + found_association + end + + end + end + end +end diff --git a/lib/ransack/adapters/active_record/context.rb b/lib/ransack/adapters/active_record/context.rb index 540974f..30d5c26 100644 --- a/lib/ransack/adapters/active_record/context.rb +++ b/lib/ransack/adapters/active_record/context.rb @@ -1,157 +1,31 @@ require 'ransack/context' +require 'ransack/adapters/active_record/3.1/context' require 'polyamorous' module Ransack module Adapters module ActiveRecord class Context < ::Ransack::Context - # Because the AR::Associations namespace is insane - JoinDependency = ::ActiveRecord::Associations::JoinDependency - JoinPart = JoinDependency::JoinPart - - def evaluate(search, opts = {}) - viz = Visitor.new - relation = @object.except(:order).where(viz.accept(search.base)).order(viz.accept(search.sorts)) - opts[:distinct] ? relation.select("DISTINCT #{@klass.quoted_table_name}.*") : relation + + # Redefine a few things that have changed with 3.2. + + def initialize(object, options = {}) + super + @arel_visitor = @engine.connection.visitor end - - def attribute_method?(str, klass = @klass) - exists = false - - if ransackable_attribute?(str, klass) - exists = true - elsif (segments = str.split(/_/)).size > 1 - remainder = [] - found_assoc = nil - while !found_assoc && remainder.unshift(segments.pop) && segments.size > 0 do - assoc, poly_class = unpolymorphize_association(segments.join('_')) - if found_assoc = get_association(assoc, klass) - exists = attribute_method?(remainder.join('_'), poly_class || found_assoc.klass) - end - end - end - - exists - end - - def table_for(parent) - parent.table - end - - def klassify(obj) - if Class === obj && ::ActiveRecord::Base > obj - obj - elsif obj.respond_to? :klass - obj.klass - elsif obj.respond_to? :active_record - obj.active_record - else - raise ArgumentError, "Don't know how to klassify #{obj}" - end - end - + def type_for(attr) return nil unless attr && attr.valid? name = attr.arel_attribute.name.to_s table = attr.arel_attribute.relation.table_name - unless @engine.connection_pool.table_exists?(table) + unless @engine.connection.table_exists?(table) raise "No table named #{table} exists" end - @engine.connection_pool.columns_hash[table][name].type + @engine.connection.schema_cache.columns_hash[table][name].type end - - private - - def get_parent_and_attribute_name(str, parent = @base) - attr_name = nil - - if ransackable_attribute?(str, klassify(parent)) - attr_name = str - elsif (segments = str.split(/_/)).size > 1 - remainder = [] - found_assoc = nil - while remainder.unshift(segments.pop) && segments.size > 0 && !found_assoc do - assoc, klass = unpolymorphize_association(segments.join('_')) - 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) - end - end - end - - [parent, attr_name] - end - - def get_association(str, parent = @base) - klass = klassify parent - ransackable_association?(str, klass) && - klass.reflect_on_all_associations.detect {|a| a.name.to_s == str} - end - - def join_dependency(relation) - if relation.respond_to?(:join_dependency) # Squeel will enable this - relation.join_dependency - else - build_join_dependency(relation) - end - end - - def build_join_dependency(relation) - buckets = relation.joins_values.group_by do |join| - case join - when String - 'string_join' - when Hash, Symbol, Array - 'association_join' - when ::ActiveRecord::Associations::JoinDependency::JoinAssociation - 'stashed_join' - when Arel::Nodes::Join - 'join_node' - 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'] || []).map { |x| - x.strip - }.uniq - - join_list = relation.send :custom_join_ast, relation.table.from(relation.table), string_joins - - join_dependency = JoinDependency.new( - relation.klass, - association_joins, - join_list - ) - - join_nodes.each do |join| - join_dependency.table_aliases[join.left.name.downcase] = 1 - end - - join_dependency.graft(*stashed_association_joins) - end - - def build_or_find_association(name, parent = @base, klass = nil) - found_association = @join_dependency.join_associations.detect do |assoc| - assoc.reflection.name == name && - assoc.parent == parent && - (!klass || assoc.reflection.klass == klass) - end - unless found_association - @join_dependency.send(:build, Polyamorous::Join.new(name, @join_type, klass), parent) - found_association = @join_dependency.join_associations.last - # Leverage the stashed association functionality in AR - @object = @object.joins(found_association) - end - - found_association - end - + end end end diff --git a/lib/ransack/context.rb b/lib/ransack/context.rb index 54bbb64..2e703e9 100644 --- a/lib/ransack/context.rb +++ b/lib/ransack/context.rb @@ -34,7 +34,6 @@ module Ransack @join_type = options[:join_type] || Arel::OuterJoin @base = @join_dependency.join_base @engine = @base.arel_engine - @arel_visitor = Arel::Visitors.visitor_for @engine @default_table = Arel::Table.new(@base.table_name, :as => @base.aliased_table_name, :engine => @engine) @bind_pairs = Hash.new do |hash, key| parent, attr_name = get_parent_and_attribute_name(key.to_s) diff --git a/lib/ransack/version.rb b/lib/ransack/version.rb index e506b28..b8e0d45 100644 --- a/lib/ransack/version.rb +++ b/lib/ransack/version.rb @@ -1,3 +1,3 @@ module Ransack - VERSION = "0.5.8" + VERSION = "0.6.0" end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 145d24d..b27a91e 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -20,7 +20,13 @@ Sham.define do end RSpec.configure do |config| - config.before(:suite) { Schema.create } + config.before(:suite) do + puts '=' * 80 + puts "Running specs against ActiveRecord #{ActiveRecord::VERSION::STRING} and ARel #{Arel::VERSION}..." + puts '=' * 80 + Schema.create + end + config.before(:all) { Sham.reset(:before_all) } config.before(:each) { Sham.reset(:before_each) }