From 797f786f5982845b0b956a6105be766443fb98c0 Mon Sep 17 00:00:00 2001 From: Alessandro Verlato Date: Thu, 17 Jul 2014 11:19:39 +0200 Subject: [PATCH 001/992] Test --- ransack.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ransack.gemspec b/ransack.gemspec index cf924f317..cd5eb4679 100644 --- a/ransack.gemspec +++ b/ransack.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |s| s.add_dependency 'activerecord', '>= 3.0' s.add_dependency 'activesupport', '>= 3.0' s.add_dependency 'i18n' - s.add_dependency 'polyamorous', '~> 1.0.0' + s.add_dependency 'polyamorous', '~> 1.1.0' s.add_development_dependency 'rspec', '~> 2.14.0' s.add_development_dependency 'machinist', '~> 1.0.6' s.add_development_dependency 'faker', '~> 0.9.5' From 6796c9665a19d3173d57012162c07b6cc0ab4d79 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Thu, 31 Jul 2014 22:15:58 -0700 Subject: [PATCH 002/992] added pry --- Gemfile | 2 ++ spec/spec_helper.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/Gemfile b/Gemfile index 4ee34eb79..eacad063e 100644 --- a/Gemfile +++ b/Gemfile @@ -7,6 +7,8 @@ rails = ENV['RAILS'] || '4-1-stable' gem 'polyamorous', '~> 1.1.0' +gem 'pry' + case rails when /\// # A path gem 'activesupport', path: "#{rails}/activesupport" diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9338d52b4..c89db0797 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,7 @@ require 'sham' require 'faker' require 'ransack' +require 'pry' I18n.enforce_available_locales = false Time.zone = 'Eastern Time (US & Canada)' From e5b6014a5300da024d1a9b210dbc69e05538ac4e Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Thu, 31 Jul 2014 23:36:12 -0700 Subject: [PATCH 003/992] ActiveRecord and Arel abstracted --- lib/ransack.rb | 1 + .../adapters/active_record/ransack/context.rb | 65 +++++++++++++++++++ .../active_record/ransack/nodes/condition.rb | 27 ++++++++ .../active_record/ransack/translate.rb | 12 ++++ .../adapters/active_record/ransack/visitor.rb | 24 +++++++ lib/ransack/context.rb | 62 ++++-------------- lib/ransack/nodes.rb | 1 + lib/ransack/nodes/condition.rb | 18 +---- lib/ransack/search.rb | 1 + lib/ransack/translate.rb | 11 ++-- lib/ransack/visitor.rb | 16 +---- 11 files changed, 153 insertions(+), 85 deletions(-) create mode 100644 lib/ransack/adapters/active_record/ransack/context.rb create mode 100644 lib/ransack/adapters/active_record/ransack/nodes/condition.rb create mode 100644 lib/ransack/adapters/active_record/ransack/translate.rb create mode 100644 lib/ransack/adapters/active_record/ransack/visitor.rb diff --git a/lib/ransack.rb b/lib/ransack.rb index 70d4a6298..262cf35c6 100644 --- a/lib/ransack.rb +++ b/lib/ransack.rb @@ -19,6 +19,7 @@ class UntraversableAssociationError < StandardError; end; end require 'ransack/translate' +require 'ransack/adapters/active_record/ransack/translate' if defined?(::ActiveRecord::Base) require 'ransack/search' require 'ransack/ransacker' require 'ransack/adapters/active_record' if defined?(::ActiveRecord::Base) diff --git a/lib/ransack/adapters/active_record/ransack/context.rb b/lib/ransack/adapters/active_record/ransack/context.rb new file mode 100644 index 000000000..1217b3f31 --- /dev/null +++ b/lib/ransack/adapters/active_record/ransack/context.rb @@ -0,0 +1,65 @@ +require 'ransack/visitor' + +module Ransack + class Context + attr_reader :arel_visitor + + class << self + + def for_class(klass, options = {}) + if klass < ActiveRecord::Base + Adapters::ActiveRecord::Context.new(klass, options) + end + end + + def for_object(object, options = {}) + case object + when ActiveRecord::Relation + Adapters::ActiveRecord::Context.new(object.klass, options) + end + end + + end # << self + + def initialize(object, options = {}) + @object = relation_for(object) + @klass = @object.klass + @join_dependency = join_dependency(@object) + @join_type = options[:join_type] || Arel::OuterJoin + @search_key = options[:search_key] || Ransack.options[:search_key] + + if ::ActiveRecord::VERSION::STRING >= "4.1" + @base = @join_dependency.join_root + @engine = @base.base_klass.arel_engine + else + @base = @join_dependency.join_base + @engine = @base.arel_engine + end + + @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) + if parent && attr_name + hash[key] = [parent, attr_name] + end + end + end + + def klassify(obj) + if Class === obj && ::ActiveRecord::Base > obj + obj + elsif obj.respond_to? :klass + obj.klass + elsif obj.respond_to? :active_record # Rails 3 + obj.active_record + elsif obj.respond_to? :base_klass # Rails 4 + obj.base_klass + else + raise ArgumentError, "Don't know how to klassify #{obj.inspect}" + end + end + + end +end diff --git a/lib/ransack/adapters/active_record/ransack/nodes/condition.rb b/lib/ransack/adapters/active_record/ransack/nodes/condition.rb new file mode 100644 index 000000000..eb74a3772 --- /dev/null +++ b/lib/ransack/adapters/active_record/ransack/nodes/condition.rb @@ -0,0 +1,27 @@ +module Ransack + module Nodes + class Condition + + def arel_predicate + predicates = attributes.map do |attr| + attr.attr.send( + arel_predicate_for_attribute(attr), + formatted_values_for_attribute(attr) + ) + end + + if predicates.size > 1 + case combinator + when 'and' + Arel::Nodes::Grouping.new(Arel::Nodes::And.new(predicates)) + when 'or' + predicates.inject(&:or) + end + else + predicates.first + end + end + + end # Condition + end +end diff --git a/lib/ransack/adapters/active_record/ransack/translate.rb b/lib/ransack/adapters/active_record/ransack/translate.rb new file mode 100644 index 000000000..a6c273009 --- /dev/null +++ b/lib/ransack/adapters/active_record/ransack/translate.rb @@ -0,0 +1,12 @@ +module Ransack + module Translate + + def self.i18n_key(klass) + if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0 + klass.model_name.i18n_key.to_s.tr('.', '/') + else + klass.model_name.i18n_key.to_s + end + end + end +end diff --git a/lib/ransack/adapters/active_record/ransack/visitor.rb b/lib/ransack/adapters/active_record/ransack/visitor.rb new file mode 100644 index 000000000..1f26f8635 --- /dev/null +++ b/lib/ransack/adapters/active_record/ransack/visitor.rb @@ -0,0 +1,24 @@ +module Ransack + class Visitor + def visit_and(object) + nodes = object.values.map { |o| accept(o) }.compact + return nil unless nodes.size > 0 + + if nodes.size > 1 + Arel::Nodes::Grouping.new(Arel::Nodes::And.new(nodes)) + else + nodes.first + end + end + + def quoted?(object) + case object + when Arel::Nodes::SqlLiteral, Bignum, Fixnum + false + else + true + end + end + + end +end diff --git a/lib/ransack/context.rb b/lib/ransack/context.rb index bf7a98cd8..b7747eca7 100644 --- a/lib/ransack/context.rb +++ b/lib/ransack/context.rb @@ -1,12 +1,21 @@ require 'ransack/visitor' +require 'ransack/adapters/active_record/ransack/visitor' if defined?(::ActiveRecord::Base) module Ransack class Context - attr_reader :search, :object, :klass, :base, :engine, :arel_visitor + attr_reader :search, :object, :klass, :base, :engine attr_accessor :auth_object, :search_key class << self + def for_class(klass, options = {}) + raise "not implemented" + end + + def for_object(object, options = {}) + raise "not implemented" + end + def for(object, options = {}) context = Class === object ? for_class(object, options) : @@ -15,59 +24,14 @@ def for(object, options = {}) "Don't know what context to use for #{object}" end - def for_class(klass, options = {}) - if klass < ActiveRecord::Base - Adapters::ActiveRecord::Context.new(klass, options) - end - end - - def for_object(object, options = {}) - case object - when ActiveRecord::Relation - Adapters::ActiveRecord::Context.new(object.klass, options) - end - end - - end + end # << self def initialize(object, options = {}) - @object = relation_for(object) - @klass = @object.klass - @join_dependency = join_dependency(@object) - @join_type = options[:join_type] || Arel::OuterJoin - @search_key = options[:search_key] || Ransack.options[:search_key] - - if ::ActiveRecord::VERSION::STRING >= "4.1" - @base = @join_dependency.join_root - @engine = @base.base_klass.arel_engine - else - @base = @join_dependency.join_base - @engine = @base.arel_engine - end - - @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) - if parent && attr_name - hash[key] = [parent, attr_name] - end - end + raise "not implemented" end def klassify(obj) - if Class === obj && ::ActiveRecord::Base > obj - obj - elsif obj.respond_to? :klass - obj.klass - elsif obj.respond_to? :active_record # Rails 3 - obj.active_record - elsif obj.respond_to? :base_klass # Rails 4 - obj.base_klass - else - raise ArgumentError, "Don't know how to klassify #{obj.inspect}" - end + raise "not implemented" end # Convert a string representing a chain of associations and an attribute diff --git a/lib/ransack/nodes.rb b/lib/ransack/nodes.rb index ce667dd5b..acd622950 100644 --- a/lib/ransack/nodes.rb +++ b/lib/ransack/nodes.rb @@ -3,5 +3,6 @@ require 'ransack/nodes/attribute' require 'ransack/nodes/value' require 'ransack/nodes/condition' +require 'ransack/adapters/active_record/ransack/nodes/condition' if defined?(::ActiveRecord::Base) require 'ransack/nodes/sort' require 'ransack/nodes/grouping' \ No newline at end of file diff --git a/lib/ransack/nodes/condition.rb b/lib/ransack/nodes/condition.rb index e2202ecbf..85142e94c 100644 --- a/lib/ransack/nodes/condition.rb +++ b/lib/ransack/nodes/condition.rb @@ -171,23 +171,7 @@ def predicate_name alias :p :predicate_name def arel_predicate - predicates = attributes.map do |attr| - attr.attr.send( - arel_predicate_for_attribute(attr), - formatted_values_for_attribute(attr) - ) - end - - if predicates.size > 1 - case combinator - when 'and' - Arel::Nodes::Grouping.new(Arel::Nodes::And.new(predicates)) - when 'or' - predicates.inject(&:or) - end - else - predicates.first - end + raise "not implemented" end def validated_values diff --git a/lib/ransack/search.rb b/lib/ransack/search.rb index 0948d48eb..11139bdc8 100644 --- a/lib/ransack/search.rb +++ b/lib/ransack/search.rb @@ -1,5 +1,6 @@ require 'ransack/nodes' require 'ransack/context' +require 'ransack/adapters/active_record/ransack/context' if defined?(::ActiveRecord::Base) require 'ransack/naming' module Ransack diff --git a/lib/ransack/translate.rb b/lib/ransack/translate.rb index 079e0e18b..c9ac3f911 100644 --- a/lib/ransack/translate.rb +++ b/lib/ransack/translate.rb @@ -103,11 +103,12 @@ def self.attribute_name(context, name, include_associations = nil) end def self.i18n_key(klass) - if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0 - klass.model_name.i18n_key.to_s.tr('.', '/') - else - klass.model_name.i18n_key.to_s - end + # if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0 + # klass.model_name.i18n_key.to_s.tr('.', '/') + # else + # klass.model_name.i18n_key.to_s + # end + raise "not implemented" end end end diff --git a/lib/ransack/visitor.rb b/lib/ransack/visitor.rb index 9f0030bd6..c86d44194 100644 --- a/lib/ransack/visitor.rb +++ b/lib/ransack/visitor.rb @@ -22,14 +22,7 @@ def visit_Ransack_Nodes_Grouping(object) end def visit_and(object) - nodes = object.values.map { |o| accept(o) }.compact - return nil unless nodes.size > 0 - - if nodes.size > 1 - Arel::Nodes::Grouping.new(Arel::Nodes::And.new(nodes)) - else - nodes.first - end + raise "not implemented" end def visit_or(object) @@ -48,12 +41,7 @@ def visit_Ransack_Nodes_Sort(object) end def quoted?(object) - case object - when Arel::Nodes::SqlLiteral, Bignum, Fixnum - false - else - true - end + raise "not implemented" end def visit(object) From d16c8b0c1bfe548e06fd4b27cfa0a579d565d39e Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Thu, 31 Jul 2014 23:42:22 -0700 Subject: [PATCH 004/992] mongoid initialized --- lib/ransack.rb | 2 + lib/ransack/adapters/mongoid.rb | 9 + lib/ransack/adapters/mongoid/3.2/.gitkeep | 0 lib/ransack/adapters/mongoid/base.rb | 47 ++++ lib/ransack/adapters/mongoid/context.rb | 231 ++++++++++++++++++ .../adapters/mongoid/ransack/context.rb | 65 +++++ .../mongoid/ransack/nodes/condition.rb | 27 ++ .../adapters/mongoid/ransack/translate.rb | 12 + .../adapters/mongoid/ransack/visitor.rb | 24 ++ lib/ransack/context.rb | 1 + lib/ransack/nodes.rb | 1 + lib/ransack/search.rb | 1 + 12 files changed, 420 insertions(+) create mode 100644 lib/ransack/adapters/mongoid.rb create mode 100644 lib/ransack/adapters/mongoid/3.2/.gitkeep create mode 100644 lib/ransack/adapters/mongoid/base.rb create mode 100644 lib/ransack/adapters/mongoid/context.rb create mode 100644 lib/ransack/adapters/mongoid/ransack/context.rb create mode 100644 lib/ransack/adapters/mongoid/ransack/nodes/condition.rb create mode 100644 lib/ransack/adapters/mongoid/ransack/translate.rb create mode 100644 lib/ransack/adapters/mongoid/ransack/visitor.rb diff --git a/lib/ransack.rb b/lib/ransack.rb index 262cf35c6..b2a016d8b 100644 --- a/lib/ransack.rb +++ b/lib/ransack.rb @@ -20,9 +20,11 @@ class UntraversableAssociationError < StandardError; end; require 'ransack/translate' require 'ransack/adapters/active_record/ransack/translate' if defined?(::ActiveRecord::Base) +require 'ransack/adapters/mongoid/ransack/translate' if defined?(::Mongoid) require 'ransack/search' require 'ransack/ransacker' require 'ransack/adapters/active_record' if defined?(::ActiveRecord::Base) +require 'ransack/adapters/mongoid' if defined?(::Mongoid) require 'ransack/helpers' require 'action_controller' diff --git a/lib/ransack/adapters/mongoid.rb b/lib/ransack/adapters/mongoid.rb new file mode 100644 index 000000000..3374ca5a9 --- /dev/null +++ b/lib/ransack/adapters/mongoid.rb @@ -0,0 +1,9 @@ +require 'ransack/adapters/mongoid/base' +Mongoid::Document.extend Ransack::Adapters::Mongoid::Base + +case Mongoid::VERSION::STRING +when /^3\.2\./ + require 'ransack/adapters/mongoid/3.2/context' +else + require 'ransack/adapters/mongoid/context' +end diff --git a/lib/ransack/adapters/mongoid/3.2/.gitkeep b/lib/ransack/adapters/mongoid/3.2/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/lib/ransack/adapters/mongoid/base.rb b/lib/ransack/adapters/mongoid/base.rb new file mode 100644 index 000000000..e68f11422 --- /dev/null +++ b/lib/ransack/adapters/mongoid/base.rb @@ -0,0 +1,47 @@ +module Ransack + module Adapters + module Mongoid + module Base + + def self.extended(base) + alias :search :ransack unless base.respond_to? :search + base.class_eval do + class_attribute :_ransackers + self._ransackers ||= {} + end + end + + def ransack(params = {}, options = {}) + params = params.presence || {} + Search.new(self, params ? params.delete_if { + |k, v| v.blank? && v != false } : params, options) + end + + def ransacker(name, opts = {}, &block) + self._ransackers = _ransackers.merge name.to_s => Ransacker + .new(self, name, opts, &block) + end + + def ransackable_attributes(auth_object = nil) + column_names + _ransackers.keys + end + + def ransortable_attributes(auth_object = nil) + # Here so users can overwrite the attributes + # that show up in the sort_select + ransackable_attributes(auth_object) + end + + def ransackable_associations(auth_object = nil) + reflect_on_all_associations.map { |a| a.name.to_s } + end + + # For overriding with a whitelist of symbols + def ransackable_scopes(auth_object = nil) + [] + end + + end + end + end +end diff --git a/lib/ransack/adapters/mongoid/context.rb b/lib/ransack/adapters/mongoid/context.rb new file mode 100644 index 000000000..c0c1a37dc --- /dev/null +++ b/lib/ransack/adapters/mongoid/context.rb @@ -0,0 +1,231 @@ +require 'ransack/context' +require 'polyamorous' + +module Ransack + module Adapters + module Mongoid + class Context < ::Ransack::Context + + # Because the AR::Associations namespace is insane + JoinDependency = ::Mongoid::Associations::JoinDependency + JoinPart = JoinDependency::JoinPart + + def initialize(object, options = {}) + super + @arel_visitor = @engine.connection.visitor + end + + def relation_for(object) + object.all + 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 + + schema_cache = @engine.connection.schema_cache + raise "No table named #{table} exists" unless schema_cache.table_exists?(table) + schema_cache.columns_hash(table)[name].type + end + + def evaluate(search, opts = {}) + viz = Visitor.new + relation = @object.where(viz.accept(search.base)) + if search.sorts.any? + relation = relation.except(:order) + .reorder(viz.accept(search.sorts)) + end + opts[:distinct] ? relation.distinct : 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 && ::Mongoid::Base > obj + obj + elsif obj.respond_to? :klass + obj.klass + elsif obj.respond_to? :base_klass + obj.base_klass + else + raise ArgumentError, "Don't know how to klassify #{obj}" + end + 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 + + # Checkout mongoid/relation/query_methods.rb +build_joins+ for + # reference. Lots of duplicated code maybe we can avoid it + 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 JoinDependency, 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.alias_tracker.aliases[join.left.name.downcase] = 1 + end + + if ::Mongoid::VERSION::STRING >= '4.1' + join_dependency + else + join_dependency.graft(*stashed_association_joins) + end + end + + if ::Mongoid::VERSION::STRING >= '4.1' + + def build_or_find_association(name, parent = @base, klass = nil) + found_association = @join_dependency.join_root.children + .detect do |assoc| + assoc.reflection.name == name && + (@associations_pot.nil? || @associations_pot[assoc] == parent) && + (!klass || assoc.reflection.klass == klass) + end + + unless found_association + jd = JoinDependency.new( + parent.base_klass, + Polyamorous::Join.new(name, @join_type, klass), + [] + ) + found_association = jd.join_root.children.last + associations found_association, parent + + # TODO maybe we dont need to push associations here, we could loop + # through the @associations_pot instead + @join_dependency.join_root.children.push found_association + + # Builds the arel nodes properly for this association + @join_dependency.send( + :construct_tables!, jd.join_root, found_association + ) + + # Leverage the stashed association functionality in AR + @object = @object.joins(jd) + end + found_association + end + + def associations(assoc, parent) + @associations_pot ||= {} + @associations_pot[assoc] = parent + end + + else + + 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 +end diff --git a/lib/ransack/adapters/mongoid/ransack/context.rb b/lib/ransack/adapters/mongoid/ransack/context.rb new file mode 100644 index 000000000..1217b3f31 --- /dev/null +++ b/lib/ransack/adapters/mongoid/ransack/context.rb @@ -0,0 +1,65 @@ +require 'ransack/visitor' + +module Ransack + class Context + attr_reader :arel_visitor + + class << self + + def for_class(klass, options = {}) + if klass < ActiveRecord::Base + Adapters::ActiveRecord::Context.new(klass, options) + end + end + + def for_object(object, options = {}) + case object + when ActiveRecord::Relation + Adapters::ActiveRecord::Context.new(object.klass, options) + end + end + + end # << self + + def initialize(object, options = {}) + @object = relation_for(object) + @klass = @object.klass + @join_dependency = join_dependency(@object) + @join_type = options[:join_type] || Arel::OuterJoin + @search_key = options[:search_key] || Ransack.options[:search_key] + + if ::ActiveRecord::VERSION::STRING >= "4.1" + @base = @join_dependency.join_root + @engine = @base.base_klass.arel_engine + else + @base = @join_dependency.join_base + @engine = @base.arel_engine + end + + @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) + if parent && attr_name + hash[key] = [parent, attr_name] + end + end + end + + def klassify(obj) + if Class === obj && ::ActiveRecord::Base > obj + obj + elsif obj.respond_to? :klass + obj.klass + elsif obj.respond_to? :active_record # Rails 3 + obj.active_record + elsif obj.respond_to? :base_klass # Rails 4 + obj.base_klass + else + raise ArgumentError, "Don't know how to klassify #{obj.inspect}" + end + end + + end +end diff --git a/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb b/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb new file mode 100644 index 000000000..eb74a3772 --- /dev/null +++ b/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb @@ -0,0 +1,27 @@ +module Ransack + module Nodes + class Condition + + def arel_predicate + predicates = attributes.map do |attr| + attr.attr.send( + arel_predicate_for_attribute(attr), + formatted_values_for_attribute(attr) + ) + end + + if predicates.size > 1 + case combinator + when 'and' + Arel::Nodes::Grouping.new(Arel::Nodes::And.new(predicates)) + when 'or' + predicates.inject(&:or) + end + else + predicates.first + end + end + + end # Condition + end +end diff --git a/lib/ransack/adapters/mongoid/ransack/translate.rb b/lib/ransack/adapters/mongoid/ransack/translate.rb new file mode 100644 index 000000000..a6c273009 --- /dev/null +++ b/lib/ransack/adapters/mongoid/ransack/translate.rb @@ -0,0 +1,12 @@ +module Ransack + module Translate + + def self.i18n_key(klass) + if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0 + klass.model_name.i18n_key.to_s.tr('.', '/') + else + klass.model_name.i18n_key.to_s + end + end + end +end diff --git a/lib/ransack/adapters/mongoid/ransack/visitor.rb b/lib/ransack/adapters/mongoid/ransack/visitor.rb new file mode 100644 index 000000000..1f26f8635 --- /dev/null +++ b/lib/ransack/adapters/mongoid/ransack/visitor.rb @@ -0,0 +1,24 @@ +module Ransack + class Visitor + def visit_and(object) + nodes = object.values.map { |o| accept(o) }.compact + return nil unless nodes.size > 0 + + if nodes.size > 1 + Arel::Nodes::Grouping.new(Arel::Nodes::And.new(nodes)) + else + nodes.first + end + end + + def quoted?(object) + case object + when Arel::Nodes::SqlLiteral, Bignum, Fixnum + false + else + true + end + end + + end +end diff --git a/lib/ransack/context.rb b/lib/ransack/context.rb index b7747eca7..a61fe0bb3 100644 --- a/lib/ransack/context.rb +++ b/lib/ransack/context.rb @@ -1,5 +1,6 @@ require 'ransack/visitor' require 'ransack/adapters/active_record/ransack/visitor' if defined?(::ActiveRecord::Base) +require 'ransack/adapters/mongoid/ransack/visitor' if defined?(::Mongoid) module Ransack class Context diff --git a/lib/ransack/nodes.rb b/lib/ransack/nodes.rb index acd622950..a8447cd92 100644 --- a/lib/ransack/nodes.rb +++ b/lib/ransack/nodes.rb @@ -4,5 +4,6 @@ require 'ransack/nodes/value' require 'ransack/nodes/condition' require 'ransack/adapters/active_record/ransack/nodes/condition' if defined?(::ActiveRecord::Base) +require 'ransack/adapters/mongoid/ransack/nodes/condition' if defined?(::Mongoid) require 'ransack/nodes/sort' require 'ransack/nodes/grouping' \ No newline at end of file diff --git a/lib/ransack/search.rb b/lib/ransack/search.rb index 11139bdc8..9038ebfeb 100644 --- a/lib/ransack/search.rb +++ b/lib/ransack/search.rb @@ -1,6 +1,7 @@ require 'ransack/nodes' require 'ransack/context' require 'ransack/adapters/active_record/ransack/context' if defined?(::ActiveRecord::Base) +require 'ransack/adapters/mongoid/ransack/context' if defined?(::Mongoid) require 'ransack/naming' module Ransack From b3765cc060f9a086b4fc10fa2fa8b5e47dbfc69a Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Thu, 31 Jul 2014 23:42:45 -0700 Subject: [PATCH 005/992] mongoid 4.0 added to Gemfile --- Gemfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index eacad063e..7476f700a 100644 --- a/Gemfile +++ b/Gemfile @@ -12,23 +12,25 @@ gem 'pry' case rails when /\// # A path gem 'activesupport', path: "#{rails}/activesupport" - gem 'activerecord', path: "#{rails}/activerecord" + gem 'activerecord', path: "#{rails}/activerecord", require: false gem 'actionpack', path: "#{rails}/actionpack" when /^v/ # A tagged version git 'git://github.com/rails/rails.git', :tag => rails do gem 'activesupport' gem 'activemodel' - gem 'activerecord' + gem 'activerecord', require: false gem 'actionpack' end else git 'git://github.com/rails/rails.git', :branch => rails do gem 'activesupport' gem 'activemodel' - gem 'activerecord' + gem 'activerecord', require: false gem 'actionpack' end if rails == '3-0-stable' gem 'mysql2', '< 0.3' end end + +gem 'mongoid', '~> 4.0.0', require: false From 9a796f157ea9420079b8a82ad233128db7883701 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Fri, 1 Aug 2014 02:04:54 -0700 Subject: [PATCH 006/992] specs set up for mongoid --- Rakefile | 6 ++ spec/mongoid/configuration_spec.rb | 68 +++++++++++++ spec/mongoid/support/mongoid.yml | 6 ++ spec/mongoid/support/schema.rb | 148 +++++++++++++++++++++++++++++ spec/mongoid_spec_helper.rb | 59 ++++++++++++ 5 files changed, 287 insertions(+) create mode 100644 spec/mongoid/configuration_spec.rb create mode 100644 spec/mongoid/support/mongoid.yml create mode 100644 spec/mongoid/support/schema.rb create mode 100644 spec/mongoid_spec_helper.rb diff --git a/Rakefile b/Rakefile index 46350ff7e..10269b336 100644 --- a/Rakefile +++ b/Rakefile @@ -4,6 +4,12 @@ require 'rspec/core/rake_task' Bundler::GemHelper.install_tasks RSpec::Core::RakeTask.new(:spec) do |rspec| + ENV['SPEC'] = 'spec/ransack/**/*_spec.rb' + rspec.rspec_opts = ['--backtrace'] +end + +RSpec::Core::RakeTask.new(:mongoid) do |rspec| + ENV['SPEC'] = 'spec/mongoid/**/*_spec.rb' rspec.rspec_opts = ['--backtrace'] end diff --git a/spec/mongoid/configuration_spec.rb b/spec/mongoid/configuration_spec.rb new file mode 100644 index 000000000..0b09aaec9 --- /dev/null +++ b/spec/mongoid/configuration_spec.rb @@ -0,0 +1,68 @@ +require 'mongoid_spec_helper' + +module Ransack + describe Configuration do + it 'yields Ransack on configure' do + Ransack.configure do |config| + expect(config).to eq Ransack + end + end + + it 'adds predicates' do + Ransack.configure do |config| + config.add_predicate :test_predicate + end + + expect(Ransack.predicates).to have_key 'test_predicate' + expect(Ransack.predicates).to have_key 'test_predicate_any' + expect(Ransack.predicates).to have_key 'test_predicate_all' + end + + it 'avoids creating compound predicates if compounds: false' do + Ransack.configure do |config| + config.add_predicate( + :test_predicate_without_compound, + :compounds => false + ) + end + expect(Ransack.predicates) + .to have_key 'test_predicate_without_compound' + expect(Ransack.predicates) + .not_to have_key 'test_predicate_without_compound_any' + expect(Ransack.predicates) + .not_to have_key 'test_predicate_without_compound_all' + end + + it 'should have default value for search key' do + expect(Ransack.options[:search_key]).to eq :q + end + + it 'changes default search key parameter' do + # store original state so we can restore it later + before = Ransack.options.clone + + Ransack.configure do |config| + config.search_key = :query + end + + expect(Ransack.options[:search_key]).to eq :query + + # restore original state so we don't break other tests + Ransack.options = before + end + + it 'adds predicates that take arrays, overriding compounds' do + Ransack.configure do |config| + config.add_predicate( + :test_array_predicate, + :wants_array => true, + :compounds => true + ) + end + + expect(Ransack.predicates['test_array_predicate'].wants_array).to eq true + expect(Ransack.predicates).not_to have_key 'test_array_predicate_any' + expect(Ransack.predicates).not_to have_key 'test_array_predicate_all' + end + end +end diff --git a/spec/mongoid/support/mongoid.yml b/spec/mongoid/support/mongoid.yml new file mode 100644 index 000000000..999569418 --- /dev/null +++ b/spec/mongoid/support/mongoid.yml @@ -0,0 +1,6 @@ +test: + sessions: + default: + database: ransack_mongoid_test + hosts: + - localhost:27017 diff --git a/spec/mongoid/support/schema.rb b/spec/mongoid/support/schema.rb new file mode 100644 index 000000000..a842ad21a --- /dev/null +++ b/spec/mongoid/support/schema.rb @@ -0,0 +1,148 @@ +require 'mongoid' + +Mongoid.load!(File.expand_path("../mongoid.yml", __FILE__), :test) + +class Person + include Mongoid::Document + include Mongoid::Timestamps + + field :name, type: String + field :email, type: String + field :only_search, type: String + field :only_sort, type: String + field :only_admin, type: String + field :salary, type: Integer + field :awesome, type: Boolean, default: false + + belongs_to :parent, :class_name => 'Person', inverse_of: :children + has_many :children, :class_name => 'Person', inverse_of: :parent + + has_many :articles + has_many :comments + + # has_many :authored_article_comments, :through => :articles, + # :source => :comments, :foreign_key => :person_id + + has_many :notes, :as => :notable + + default_scope -> { order(id: :desc) } + + scope :restricted, lambda { where("restricted = 1") } + scope :active, lambda { where("active = 1") } + scope :over_age, lambda { |y| where(["age > ?", y]) } + + ransacker :reversed_name, :formatter => proc { |v| v.reverse } do |parent| + parent.table[:name] + end + + # ransacker :doubled_name do |parent| + # Arel::Nodes::InfixOperation.new( + # '||', parent.table[:name], parent.table[:name] + # ) + # end + + def self.ransackable_attributes(auth_object = nil) + if auth_object == :admin + column_names + _ransackers.keys - ['only_sort'] + else + column_names + _ransackers.keys - ['only_sort', 'only_admin'] + end + end + + def self.ransortable_attributes(auth_object = nil) + if auth_object == :admin + column_names + _ransackers.keys - ['only_search'] + else + column_names + _ransackers.keys - ['only_search', 'only_admin'] + end + end + + def self.ransackable_attributes(auth_object = nil) + if auth_object == :admin + column_names + _ransackers.keys - ['only_sort'] + else + column_names + _ransackers.keys - ['only_sort', 'only_admin'] + end + end + + def self.ransortable_attributes(auth_object = nil) + if auth_object == :admin + column_names + _ransackers.keys - ['only_search'] + else + column_names + _ransackers.keys - ['only_search', 'only_admin'] + end + end +end + +class Article + include Mongoid::Document + + field :person_id, type: Integer + field :title, type: String + field :body, type: String + + belongs_to :person + has_many :comments + # has_and_belongs_to_many :tags + has_many :notes, :as => :notable +end + +module Namespace + class Article < ::Article + + end +end + +class Comment + include Mongoid::Document + + field :article_id, type: Integer + field :person_id, type: Integer + field :body, type: String + + + belongs_to :article + belongs_to :person +end + +class Tag + include Mongoid::Document + + field :name, type: String + + # has_and_belongs_to_many :articles +end + +class Note + include Mongoid::Document + + field :notable_id, type: Integer + field :notable_type, type: String + field :note, type: String + + belongs_to :notable, :polymorphic => true +end + +module Schema + def self.create + 10.times do + person = Person.create! + Note.create!(:notable => person) + 3.times do + article = Article.create!(:person => person) + 3.times do + # article.tags = [Tag.create!, Tag.create!, Tag.create!] + end + Note.create!(:notable => article) + 10.times do + Comment.create!(:article => article, :person => person) + end + end + end + + Comment.create!( + :body => 'First post!', + :article => Article.create!(:title => 'Hello, world!') + ) + end +end diff --git a/spec/mongoid_spec_helper.rb b/spec/mongoid_spec_helper.rb new file mode 100644 index 000000000..05ff251c9 --- /dev/null +++ b/spec/mongoid_spec_helper.rb @@ -0,0 +1,59 @@ +# require 'machinist/active_record' +require 'sham' +require 'faker' +require 'pry' +require 'mongoid' +require 'ransack' + +I18n.enforce_available_locales = false +Time.zone = 'Eastern Time (US & Canada)' +I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')] + +Dir[File.expand_path('../{helpers,mongoid/support}/*.rb', __FILE__)] +.each do |f| + require f +end + +Sham.define do + name { Faker::Name.name } + title { Faker::Lorem.sentence } + body { Faker::Lorem.paragraph } + salary { |index| 30000 + (index * 1000) } + tag_name { Faker::Lorem.words(3).join(' ') } + note { Faker::Lorem.words(7).join(' ') } + only_admin { Faker::Lorem.words(3).join(' ') } + only_search { Faker::Lorem.words(3).join(' ') } + only_sort { Faker::Lorem.words(3).join(' ') } + notable_id { |id| id } +end + +RSpec.configure do |config| + config.alias_it_should_behave_like_to :it_has_behavior, 'has behavior' + + config.before(:suite) do + puts '=' * 80 + connection_name = Mongoid.default_session.inspect + puts "Running specs against #{connection_name}, Mongoid #{ + Mongoid::VERSION}, Moped #{Moped::VERSION} and Origin #{Origin::VERSION}..." + puts '=' * 80 + Schema.create + end + + config.before(:all) { Sham.reset(:before_all) } + config.before(:each) { Sham.reset(:before_each) } + + config.include RansackHelper +end + +RSpec::Matchers.define :be_like do |expected| + match do |actual| + actual.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip == + expected.gsub(/^\s+|\s+$/, '').gsub(/\s+/, ' ').strip + end +end + +RSpec::Matchers.define :have_attribute_method do |expected| + match do |actual| + actual.attribute_method?(expected) + end +end From 7cb697a98c95459bb5661d8d434e42a465f577c0 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Fri, 1 Aug 2014 02:05:25 -0700 Subject: [PATCH 007/992] adapter/mongoid little bit adapted --- lib/ransack/adapters/mongoid.rb | 2 +- lib/ransack/adapters/mongoid/base.rb | 78 +++++++++++++------------ lib/ransack/adapters/mongoid/context.rb | 9 +-- 3 files changed, 48 insertions(+), 41 deletions(-) diff --git a/lib/ransack/adapters/mongoid.rb b/lib/ransack/adapters/mongoid.rb index 3374ca5a9..df9d0af94 100644 --- a/lib/ransack/adapters/mongoid.rb +++ b/lib/ransack/adapters/mongoid.rb @@ -1,7 +1,7 @@ require 'ransack/adapters/mongoid/base' Mongoid::Document.extend Ransack::Adapters::Mongoid::Base -case Mongoid::VERSION::STRING +case Mongoid::VERSION when /^3\.2\./ require 'ransack/adapters/mongoid/3.2/context' else diff --git a/lib/ransack/adapters/mongoid/base.rb b/lib/ransack/adapters/mongoid/base.rb index e68f11422..10a714a2a 100644 --- a/lib/ransack/adapters/mongoid/base.rb +++ b/lib/ransack/adapters/mongoid/base.rb @@ -4,44 +4,50 @@ module Mongoid module Base def self.extended(base) - alias :search :ransack unless base.respond_to? :search - base.class_eval do - class_attribute :_ransackers - self._ransackers ||= {} - end + base::ClassMethods.class_eval do + def _ransackers + @_ransackers ||= {} + end + + def _ransackers=(value) + @_ransackers = value + end + + def ransack(params = {}, options = {}) + params = params.presence || {} + Search.new(self, params ? params.delete_if { + |k, v| v.blank? && v != false } : params, options) + end + + alias_method :search, :ransack + + def ransacker(name, opts = {}, &block) + self._ransackers = _ransackers.merge name.to_s => Ransacker + .new(self, name, opts, &block) + end + + def ransackable_attributes(auth_object = nil) + column_names + _ransackers.keys + end + + def ransortable_attributes(auth_object = nil) + # Here so users can overwrite the attributes + # that show up in the sort_select + ransackable_attributes(auth_object) + end + + def ransackable_associations(auth_object = nil) + reflect_on_all_associations.map { |a| a.name.to_s } + end + + # For overriding with a whitelist of symbols + def ransackable_scopes(auth_object = nil) + [] + end + end # base::ClassMethods.class_eval end - def ransack(params = {}, options = {}) - params = params.presence || {} - Search.new(self, params ? params.delete_if { - |k, v| v.blank? && v != false } : params, options) - end - - def ransacker(name, opts = {}, &block) - self._ransackers = _ransackers.merge name.to_s => Ransacker - .new(self, name, opts, &block) - end - - def ransackable_attributes(auth_object = nil) - column_names + _ransackers.keys - end - - def ransortable_attributes(auth_object = nil) - # Here so users can overwrite the attributes - # that show up in the sort_select - ransackable_attributes(auth_object) - end - - def ransackable_associations(auth_object = nil) - reflect_on_all_associations.map { |a| a.name.to_s } - end - - # For overriding with a whitelist of symbols - def ransackable_scopes(auth_object = nil) - [] - end - - end + end # Base end end end diff --git a/lib/ransack/adapters/mongoid/context.rb b/lib/ransack/adapters/mongoid/context.rb index c0c1a37dc..d8330246d 100644 --- a/lib/ransack/adapters/mongoid/context.rb +++ b/lib/ransack/adapters/mongoid/context.rb @@ -7,8 +7,8 @@ module Mongoid class Context < ::Ransack::Context # Because the AR::Associations namespace is insane - JoinDependency = ::Mongoid::Associations::JoinDependency - JoinPart = JoinDependency::JoinPart + # JoinDependency = ::Mongoid::Associations::JoinDependency + # JoinPart = JoinDependency::JoinPart def initialize(object, options = {}) super @@ -112,6 +112,7 @@ def join_dependency(relation) if relation.respond_to?(:join_dependency) # Squeel will enable this relation.join_dependency else + binding.pry build_join_dependency(relation) end end @@ -155,14 +156,14 @@ def build_join_dependency(relation) join_dependency.alias_tracker.aliases[join.left.name.downcase] = 1 end - if ::Mongoid::VERSION::STRING >= '4.1' + if ::Mongoid::VERSION >= '4.1' join_dependency else join_dependency.graft(*stashed_association_joins) end end - if ::Mongoid::VERSION::STRING >= '4.1' + if ::Mongoid::VERSION >= '4.1' def build_or_find_association(name, parent = @base, klass = nil) found_association = @join_dependency.join_root.children From 718a05b331681a6ccc52028f5f4ae38c56264f9d Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Fri, 1 Aug 2014 21:06:39 -0700 Subject: [PATCH 008/992] ransack made more abstract and added mongoid --- .../active_record/ransack/constants.rb | 94 +++++++++++++ lib/ransack/adapters/mongoid.rb | 5 +- .../adapters/mongoid/attributes/attribute.rb | 63 +++++++++ lib/ransack/adapters/mongoid/base.rb | 124 +++++++++++++----- lib/ransack/adapters/mongoid/context.rb | 30 +++-- .../adapters/mongoid/ransack/constants.rb | 88 +++++++++++++ .../adapters/mongoid/ransack/context.rb | 26 ++-- lib/ransack/adapters/mongoid/table.rb | 35 +++++ lib/ransack/configuration.rb | 2 + lib/ransack/constants.rb | 90 ------------- 10 files changed, 409 insertions(+), 148 deletions(-) create mode 100644 lib/ransack/adapters/active_record/ransack/constants.rb create mode 100644 lib/ransack/adapters/mongoid/attributes/attribute.rb create mode 100644 lib/ransack/adapters/mongoid/ransack/constants.rb create mode 100644 lib/ransack/adapters/mongoid/table.rb diff --git a/lib/ransack/adapters/active_record/ransack/constants.rb b/lib/ransack/adapters/active_record/ransack/constants.rb new file mode 100644 index 000000000..2ddbc70b3 --- /dev/null +++ b/lib/ransack/adapters/active_record/ransack/constants.rb @@ -0,0 +1,94 @@ +module Ransack + module Constants + DERIVED_PREDICATES = [ + ['cont', { + :arel_predicate => 'matches', + :formatter => proc { |v| "%#{escape_wildcards(v)}%" } + } + ], + ['not_cont', { + :arel_predicate => 'does_not_match', + :formatter => proc { |v| "%#{escape_wildcards(v)}%" } + } + ], + ['start', { + :arel_predicate => 'matches', + :formatter => proc { |v| "#{escape_wildcards(v)}%" } + } + ], + ['not_start', { + :arel_predicate => 'does_not_match', + :formatter => proc { |v| "#{escape_wildcards(v)}%" } + } + ], + ['end', { + :arel_predicate => 'matches', + :formatter => proc { |v| "%#{escape_wildcards(v)}" } + } + ], + ['not_end', { + :arel_predicate => 'does_not_match', + :formatter => proc { |v| "%#{escape_wildcards(v)}" } + } + ], + ['true', { + :arel_predicate => 'eq', + :compounds => false, + :type => :boolean, + :validator => proc { |v| TRUE_VALUES.include?(v) } + } + ], + ['false', { + :arel_predicate => 'eq', + :compounds => false, + :type => :boolean, + :validator => proc { |v| TRUE_VALUES.include?(v) }, + :formatter => proc { |v| !v } + } + ], + ['present', { + :arel_predicate => proc { |v| v ? 'not_eq_all' : 'eq_any' }, + :compounds => false, + :type => :boolean, + :validator => proc { |v| BOOLEAN_VALUES.include?(v) }, + :formatter => proc { |v| [nil, ''] } + } + ], + ['blank', { + :arel_predicate => proc { |v| v ? 'eq_any' : 'not_eq_all' }, + :compounds => false, + :type => :boolean, + :validator => proc { |v| BOOLEAN_VALUES.include?(v) }, + :formatter => proc { |v| [nil, ''] } + } + ], + ['null', { + :arel_predicate => proc { |v| v ? 'eq' : 'not_eq' }, + :compounds => false, + :type => :boolean, + :validator => proc { |v| BOOLEAN_VALUES.include?(v)}, + :formatter => proc { |v| nil } + } + ], + ['not_null', { + :arel_predicate => proc { |v| v ? 'not_eq' : 'eq' }, + :compounds => false, + :type => :boolean, + :validator => proc { |v| BOOLEAN_VALUES.include?(v) }, + :formatter => proc { |v| nil } } + ] + ] + + module_function + # replace % \ to \% \\ + def escape_wildcards(unescaped) + case ActiveRecord::Base.connection.adapter_name + when "Mysql2", "PostgreSQL" + # Necessary for PostgreSQL and MySQL + unescaped.to_s.gsub(/([\\|\%|.])/, '\\\\\\1') + else + unescaped + end + end + end +end diff --git a/lib/ransack/adapters/mongoid.rb b/lib/ransack/adapters/mongoid.rb index df9d0af94..99847d459 100644 --- a/lib/ransack/adapters/mongoid.rb +++ b/lib/ransack/adapters/mongoid.rb @@ -1,5 +1,8 @@ require 'ransack/adapters/mongoid/base' -Mongoid::Document.extend Ransack::Adapters::Mongoid::Base +Mongoid::Document.send :include, Ransack::Adapters::Mongoid::Base + +require 'ransack/adapters/mongoid/attributes/attribute' +require 'ransack/adapters/mongoid/table' case Mongoid::VERSION when /^3\.2\./ diff --git a/lib/ransack/adapters/mongoid/attributes/attribute.rb b/lib/ransack/adapters/mongoid/attributes/attribute.rb new file mode 100644 index 000000000..95e6f5e30 --- /dev/null +++ b/lib/ransack/adapters/mongoid/attributes/attribute.rb @@ -0,0 +1,63 @@ +module Ransack + module Adapters + module Mongoid + module Attributes + class Attribute < Struct.new :relation, :name + # include Arel::Expressions + # include Arel::Predications + # include Arel::AliasPredication + # include Arel::OrderPredications + # include Arel::Math + + ### + # Create a node for lowering this attribute + def lower + relation.lower self + end + + def eq(other) + { name => other } + end + + def not_eq(other) + { name.to_sym.ne => other } + end + + def matches(other) + { name => /#{Regexp.escape(other)}/i } + end + + def does_not_match(other) + { "$not" => { name => /#{Regexp.escape(other)}/i } } + end + + def not_eq_all(other) + q = [] + other.each do |value| + q << { name.to_sym.ne => value } + end + { "$and" => q } + end + + def eq_any(other) + q = [] + other.each do |value| + q << { name => value } + end + { "$or" => q } + end + end + + class String < Attribute; end + class Time < Attribute; end + class Boolean < Attribute; end + class Decimal < Attribute; end + class Float < Attribute; end + class Integer < Attribute; end + class Undefined < Attribute; end + end + + Attribute = Attributes::Attribute + end # Attributes + end +end diff --git a/lib/ransack/adapters/mongoid/base.rb b/lib/ransack/adapters/mongoid/base.rb index 10a714a2a..4f47e2373 100644 --- a/lib/ransack/adapters/mongoid/base.rb +++ b/lib/ransack/adapters/mongoid/base.rb @@ -1,52 +1,110 @@ +require 'delegate' + module Ransack module Adapters module Mongoid module Base - def self.extended(base) - base::ClassMethods.class_eval do - def _ransackers - @_ransackers ||= {} - end + extend ActiveSupport::Concern - def _ransackers=(value) - @_ransackers = value - end + included do + end - def ransack(params = {}, options = {}) - params = params.presence || {} - Search.new(self, params ? params.delete_if { - |k, v| v.blank? && v != false } : params, options) + class ColumnWrapper < SimpleDelegator + def type + _super = super + case _super + when BSON::ObjectId, Object + :string + else + _super.name.underscore.to_sym end + end + end - alias_method :search, :ransack + class Connection + def initialize model + @model = model + end - def ransacker(name, opts = {}, &block) - self._ransackers = _ransackers.merge name.to_s => Ransacker - .new(self, name, opts, &block) - end + def quote_column_name name + name + end + end - def ransackable_attributes(auth_object = nil) - column_names + _ransackers.keys - end + module ClassMethods + def _ransackers + @_ransackers ||= {} + end - def ransortable_attributes(auth_object = nil) - # Here so users can overwrite the attributes - # that show up in the sort_select - ransackable_attributes(auth_object) - end + def _ransackers=(value) + @_ransackers = value + end - def ransackable_associations(auth_object = nil) - reflect_on_all_associations.map { |a| a.name.to_s } - end + def ransack(params = {}, options = {}) + params = params.presence || {} + Search.new(self, params ? params.delete_if { + |k, v| v.blank? && v != false } : params, options) + end + + alias_method :search, :ransack + + def ransacker(name, opts = {}, &block) + self._ransackers = _ransackers.merge name.to_s => Ransacker + .new(self, name, opts, &block) + end + + def ransackable_attributes(auth_object = nil) + column_names + _ransackers.keys + end + + def ransortable_attributes(auth_object = nil) + # Here so users can overwrite the attributes + # that show up in the sort_select + ransackable_attributes(auth_object) + end + + def ransackable_associations(auth_object = nil) + reflect_on_all_associations.map { |a| a.name.to_s } + end + + # For overriding with a whitelist of symbols + def ransackable_scopes(auth_object = nil) + [] + end + + # imitating active record + + def joins_values *args + criteria + end + + def group_by *args, &block + criteria + end + + def columns + @columns ||= fields.map(&:second).map{ |c| ColumnWrapper.new(c) } + end + + def column_names + @column_names ||= fields.map(&:first) + end + + def columns_hash + columns.index_by(&:name) + end - # For overriding with a whitelist of symbols - def ransackable_scopes(auth_object = nil) - [] - end - end # base::ClassMethods.class_eval end + + # base::ClassMethods.class_eval do + + # end # base::ClassMethods.class_eval + + # def self.extended(base) + # end + end # Base end end diff --git a/lib/ransack/adapters/mongoid/context.rb b/lib/ransack/adapters/mongoid/context.rb index d8330246d..3cbcf7dde 100644 --- a/lib/ransack/adapters/mongoid/context.rb +++ b/lib/ransack/adapters/mongoid/context.rb @@ -12,7 +12,7 @@ class Context < ::Ransack::Context def initialize(object, options = {}) super - @arel_visitor = @engine.connection.visitor + # @arel_visitor = @engine.connection.visitor end def relation_for(object) @@ -22,11 +22,23 @@ def relation_for(object) def type_for(attr) return nil unless attr && attr.valid? name = attr.arel_attribute.name.to_s - table = attr.arel_attribute.relation.table_name + # table = attr.arel_attribute.relation.table_name - schema_cache = @engine.connection.schema_cache - raise "No table named #{table} exists" unless schema_cache.table_exists?(table) - schema_cache.columns_hash(table)[name].type + # schema_cache = @engine.connection.schema_cache + # raise "No table named #{table} exists" unless schema_cache.table_exists?(table) + # schema_cache.columns_hash(table)[name].type + + # when :date + # when :datetime, :timestamp, :time + # when :boolean + # when :integer + # when :float + # when :decimal + # else # :string + + t = object.klass.fields[name].type + + t.to_s.demodulize.underscore.to_sym end def evaluate(search, opts = {}) @@ -62,11 +74,12 @@ def attribute_method?(str, klass = @klass) end def table_for(parent) - parent.table + # parent.table + Ransack::Adapters::Mongoid::Table.new(parent) end def klassify(obj) - if Class === obj && ::Mongoid::Base > obj + if Class === obj && obj.ancestors.include?(::Mongoid::Document) obj elsif obj.respond_to? :klass obj.klass @@ -112,12 +125,11 @@ def join_dependency(relation) if relation.respond_to?(:join_dependency) # Squeel will enable this relation.join_dependency else - binding.pry build_join_dependency(relation) end end - # Checkout mongoid/relation/query_methods.rb +build_joins+ for + # Checkout active_record/relation/query_methods.rb +build_joins+ for # reference. Lots of duplicated code maybe we can avoid it def build_join_dependency(relation) buckets = relation.joins_values.group_by do |join| diff --git a/lib/ransack/adapters/mongoid/ransack/constants.rb b/lib/ransack/adapters/mongoid/ransack/constants.rb new file mode 100644 index 000000000..d3f743a01 --- /dev/null +++ b/lib/ransack/adapters/mongoid/ransack/constants.rb @@ -0,0 +1,88 @@ +module Ransack + module Constants + DERIVED_PREDICATES = [ + ['cont', { + :arel_predicate => 'matches', + :formatter => proc { |v| "#{escape_wildcards(v)}" } + } + ], + ['not_cont', { + :arel_predicate => 'does_not_match', + :formatter => proc { |v| "#{escape_wildcards(v)}" } + } + ], + ['start', { + :arel_predicate => 'matches', + :formatter => proc { |v| "#{escape_wildcards(v)}%" } + } + ], + ['not_start', { + :arel_predicate => 'does_not_match', + :formatter => proc { |v| "#{escape_wildcards(v)}%" } + } + ], + ['end', { + :arel_predicate => 'matches', + :formatter => proc { |v| "%#{escape_wildcards(v)}" } + } + ], + ['not_end', { + :arel_predicate => 'does_not_match', + :formatter => proc { |v| "%#{escape_wildcards(v)}" } + } + ], + ['true', { + :arel_predicate => 'eq', + :compounds => false, + :type => :boolean, + :validator => proc { |v| TRUE_VALUES.include?(v) } + } + ], + ['false', { + :arel_predicate => 'eq', + :compounds => false, + :type => :boolean, + :validator => proc { |v| TRUE_VALUES.include?(v) }, + :formatter => proc { |v| !v } + } + ], + ['present', { + :arel_predicate => proc { |v| v ? 'not_eq_all' : 'eq_any' }, + :compounds => false, + :type => :boolean, + :validator => proc { |v| BOOLEAN_VALUES.include?(v) }, + :formatter => proc { |v| [nil, ''] } + } + ], + ['blank', { + :arel_predicate => proc { |v| v ? 'eq_any' : 'not_eq_all' }, + :compounds => false, + :type => :boolean, + :validator => proc { |v| BOOLEAN_VALUES.include?(v) }, + :formatter => proc { |v| [nil, ''] } + } + ], + ['null', { + :arel_predicate => proc { |v| v ? 'eq' : 'not_eq' }, + :compounds => false, + :type => :boolean, + :validator => proc { |v| BOOLEAN_VALUES.include?(v)}, + :formatter => proc { |v| nil } + } + ], + ['not_null', { + :arel_predicate => proc { |v| v ? 'not_eq' : 'eq' }, + :compounds => false, + :type => :boolean, + :validator => proc { |v| BOOLEAN_VALUES.include?(v) }, + :formatter => proc { |v| nil } } + ] + ] + + module_function + # does nothing + def escape_wildcards(unescaped) + unescaped + end + end +end diff --git a/lib/ransack/adapters/mongoid/ransack/context.rb b/lib/ransack/adapters/mongoid/ransack/context.rb index 1217b3f31..08d83ccc3 100644 --- a/lib/ransack/adapters/mongoid/ransack/context.rb +++ b/lib/ransack/adapters/mongoid/ransack/context.rb @@ -2,17 +2,18 @@ module Ransack class Context - attr_reader :arel_visitor + # attr_reader :arel_visitor class << self def for_class(klass, options = {}) - if klass < ActiveRecord::Base - Adapters::ActiveRecord::Context.new(klass, options) + if klass.ancestors.include?(::Mongoid::Document) + Adapters::Mongoid::Context.new(klass, options) end end def for_object(object, options = {}) + binding.pry case object when ActiveRecord::Relation Adapters::ActiveRecord::Context.new(object.klass, options) @@ -24,21 +25,16 @@ def for_object(object, options = {}) def initialize(object, options = {}) @object = relation_for(object) @klass = @object.klass - @join_dependency = join_dependency(@object) - @join_type = options[:join_type] || Arel::OuterJoin + # @join_dependency = join_dependency(@object) + # @join_type = options[:join_type] || Arel::OuterJoin @search_key = options[:search_key] || Ransack.options[:search_key] - if ::ActiveRecord::VERSION::STRING >= "4.1" - @base = @join_dependency.join_root - @engine = @base.base_klass.arel_engine - else - @base = @join_dependency.join_base - @engine = @base.arel_engine - end + @base = @object.klass + # @engine = @base.arel_engine - @default_table = Arel::Table.new( - @base.table_name, :as => @base.aliased_table_name, :engine => @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) if parent && attr_name diff --git a/lib/ransack/adapters/mongoid/table.rb b/lib/ransack/adapters/mongoid/table.rb new file mode 100644 index 000000000..04c6209df --- /dev/null +++ b/lib/ransack/adapters/mongoid/table.rb @@ -0,0 +1,35 @@ +module Ransack + module Adapters + module Mongoid + class Table + attr_accessor :name + + alias :table_name :name + + def initialize object, engine = nil + @object = object + @name = object.collection.name + @engine = engine + @columns = nil + @aliases = [] + @table_alias = nil + @primary_key = nil + + if Hash === engine + # @engine = engine[:engine] || Table.engine + + # Sometime AR sends an :as parameter to table, to let the table know + # that it is an Alias. We may want to override new, and return a + # TableAlias node? + # @table_alias = engine[:as] unless engine[:as].to_s == @name + end + end + + def [] name + Ransack::Adapters::Mongoid::Attribute.new self, name + end + + end + end + end +end diff --git a/lib/ransack/configuration.rb b/lib/ransack/configuration.rb index 696db7b62..7a383e257 100644 --- a/lib/ransack/configuration.rb +++ b/lib/ransack/configuration.rb @@ -1,4 +1,6 @@ require 'ransack/constants' +require 'ransack/adapters/active_record/ransack/constants' if defined?(::ActiveRecord::Base) +require 'ransack/adapters/mongoid/ransack/constants' if defined?(::Mongoid) require 'ransack/predicate' module Ransack diff --git a/lib/ransack/constants.rb b/lib/ransack/constants.rb index bd0dc7cfc..b7d558f06 100644 --- a/lib/ransack/constants.rb +++ b/lib/ransack/constants.rb @@ -6,95 +6,5 @@ module Constants AREL_PREDICATES = %w(eq not_eq matches does_not_match lt lteq gt gteq in not_in) - DERIVED_PREDICATES = [ - ['cont', { - :arel_predicate => 'matches', - :formatter => proc { |v| "%#{escape_wildcards(v)}%" } - } - ], - ['not_cont', { - :arel_predicate => 'does_not_match', - :formatter => proc { |v| "%#{escape_wildcards(v)}%" } - } - ], - ['start', { - :arel_predicate => 'matches', - :formatter => proc { |v| "#{escape_wildcards(v)}%" } - } - ], - ['not_start', { - :arel_predicate => 'does_not_match', - :formatter => proc { |v| "#{escape_wildcards(v)}%" } - } - ], - ['end', { - :arel_predicate => 'matches', - :formatter => proc { |v| "%#{escape_wildcards(v)}" } - } - ], - ['not_end', { - :arel_predicate => 'does_not_match', - :formatter => proc { |v| "%#{escape_wildcards(v)}" } - } - ], - ['true', { - :arel_predicate => 'eq', - :compounds => false, - :type => :boolean, - :validator => proc { |v| TRUE_VALUES.include?(v) } - } - ], - ['false', { - :arel_predicate => 'eq', - :compounds => false, - :type => :boolean, - :validator => proc { |v| TRUE_VALUES.include?(v) }, - :formatter => proc { |v| !v } - } - ], - ['present', { - :arel_predicate => proc { |v| v ? 'not_eq_all' : 'eq_any' }, - :compounds => false, - :type => :boolean, - :validator => proc { |v| BOOLEAN_VALUES.include?(v) }, - :formatter => proc { |v| [nil, ''] } - } - ], - ['blank', { - :arel_predicate => proc { |v| v ? 'eq_any' : 'not_eq_all' }, - :compounds => false, - :type => :boolean, - :validator => proc { |v| BOOLEAN_VALUES.include?(v) }, - :formatter => proc { |v| [nil, ''] } - } - ], - ['null', { - :arel_predicate => proc { |v| v ? 'eq' : 'not_eq' }, - :compounds => false, - :type => :boolean, - :validator => proc { |v| BOOLEAN_VALUES.include?(v)}, - :formatter => proc { |v| nil } - } - ], - ['not_null', { - :arel_predicate => proc { |v| v ? 'not_eq' : 'eq' }, - :compounds => false, - :type => :boolean, - :validator => proc { |v| BOOLEAN_VALUES.include?(v) }, - :formatter => proc { |v| nil } } - ] - ] - - module_function - # replace % \ to \% \\ - def escape_wildcards(unescaped) - case ActiveRecord::Base.connection.adapter_name - when "Mysql2", "PostgreSQL" - # Necessary for PostgreSQL and MySQL - unescaped.to_s.gsub(/([\\|\%|.])/, '\\\\\\1') - else - unescaped - end - end end end From 9e1204e1799d7936fc3fe4988f2d1a8958c13d35 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Fri, 1 Aug 2014 21:06:56 -0700 Subject: [PATCH 009/992] added mongoid/predicate_spec --- spec/mongoid/dependencies_spec.rb | 6 ++ spec/mongoid/helpers/ransack_helper.rb | 11 +++ spec/mongoid/predicate_spec.rb | 110 +++++++++++++++++++++++++ spec/mongoid_spec_helper.rb | 2 +- 4 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 spec/mongoid/dependencies_spec.rb create mode 100644 spec/mongoid/helpers/ransack_helper.rb create mode 100644 spec/mongoid/predicate_spec.rb diff --git a/spec/mongoid/dependencies_spec.rb b/spec/mongoid/dependencies_spec.rb new file mode 100644 index 000000000..c94ee086b --- /dev/null +++ b/spec/mongoid/dependencies_spec.rb @@ -0,0 +1,6 @@ +describe 'Ransack' do + it 'can be required without errors' do + output = `bundle exec ruby -e "require 'ransack'" 2>&1` + expect(output).to be_empty + end +end diff --git a/spec/mongoid/helpers/ransack_helper.rb b/spec/mongoid/helpers/ransack_helper.rb new file mode 100644 index 000000000..1cc73ea74 --- /dev/null +++ b/spec/mongoid/helpers/ransack_helper.rb @@ -0,0 +1,11 @@ +module RansackHelper + def quote_table_name(table) + # ActiveRecord::Base.connection.quote_table_name(table) + table + end + + def quote_column_name(column) + # ActiveRecord::Base.connection.quote_column_name(column) + column + end +end \ No newline at end of file diff --git a/spec/mongoid/predicate_spec.rb b/spec/mongoid/predicate_spec.rb new file mode 100644 index 000000000..4e6ec5292 --- /dev/null +++ b/spec/mongoid/predicate_spec.rb @@ -0,0 +1,110 @@ +require 'mongoid_spec_helper' + +module Ransack + describe Predicate do + + before do + @s = Search.new(Person) + end + + shared_examples 'wildcard escaping' do |method, value| + it 'automatically converts integers to strings' do + subject.parent_id_cont = 1 + expect { subject.result }.to_not raise_error + end + + it "escapes '%', '.' and '\\\\' in value" do + subject.send(:"#{method}=", '%._\\') + expect(subject.result.selector).to eq(value) + end + end + + describe 'eq' do + it 'generates an equality condition for boolean true' do + @s.awesome_eq = true + expect(@s.result.selector).to eq({ "awesome" => true }) + end + + it 'generates an equality condition for boolean false' do + @s.awesome_eq = false + expect(@s.result.selector).to eq({ "awesome" => false }) + end + + it 'does not generate a condition for nil' do + @s.awesome_eq = nil + expect(@s.result.selector).to eq({ }) + end + end + + describe 'cont' do + + it_has_behavior 'wildcard escaping', :name_cont, { 'name' => /%\._\\/i } do + subject { @s } + end + + it 'generates a regex query' do + @s.name_cont = 'ric' + expect(@s.result.selector).to eq({ 'name' => /ric/i }) + end + end + + describe 'not_cont' do + it_has_behavior 'wildcard escaping', :name_not_cont, { "$not" => { 'name' => /%\._\\/i } } do + subject { @s } + end + + it 'generates a regex query' do + @s.name_not_cont = 'ric' + expect(@s.result.selector).to eq({ "$not" => { 'name' => /ric/i } }) + end + end + + describe 'null' do + it 'generates a value IS NULL query' do + @s.name_null = true + expect(@s.result.selector).to eq({ 'name' => nil }) + end + + it 'generates a value IS NOT NULL query when assigned false' do + @s.name_null = false + expect(@s.result.selector).to eq( { 'name' => { '$ne' => nil } }) + end + end + + describe 'not_null' do + it 'generates a value IS NOT NULL query' do + @s.name_not_null = true + expect(@s.result.selector).to eq({ 'name' => { '$ne' => nil } }) + end + + it 'generates a value IS NULL query when assigned false' do + @s.name_not_null = false + expect(@s.result.selector).to eq({ 'name' => nil }) + end + end + + describe 'present' do + it %q[generates a value IS NOT NULL AND value != '' query] do + @s.name_present = true + expect(@s.result.selector).to eq({ '$and' => [ { 'name' => { '$ne' => nil } }, { 'name' => { '$ne' => '' } } ] }) + end + + it %q[generates a value IS NULL OR value = '' query when assigned false] do + @s.name_present = false + expect(@s.result.selector).to eq({ '$or' => [ { 'name' => nil }, { 'name' => '' } ] }) + end + end + + describe 'blank' do + it %q[generates a value IS NULL OR value = '' query] do + @s.name_blank = true + expect(@s.result.selector).to eq({ '$or' => [ { 'name' => nil}, { 'name' => '' } ] }) + end + + it %q[generates a value IS NOT NULL AND value != '' query when assigned false] do + @s.name_blank = false + expect(@s.result.selector).to eq({ '$and' => [ { 'name' => { '$ne' => nil}}, { 'name' => { '$ne' => '' }} ] }) + end + end + end +end diff --git a/spec/mongoid_spec_helper.rb b/spec/mongoid_spec_helper.rb index 05ff251c9..0fa6518e0 100644 --- a/spec/mongoid_spec_helper.rb +++ b/spec/mongoid_spec_helper.rb @@ -9,7 +9,7 @@ Time.zone = 'Eastern Time (US & Canada)' I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')] -Dir[File.expand_path('../{helpers,mongoid/support}/*.rb', __FILE__)] +Dir[File.expand_path('../{mongoid/helpers,mongoid/support}/*.rb', __FILE__)] .each do |f| require f end From b3234d20f36dbef4d5803205e0735c5c41ebd6e7 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Fri, 1 Aug 2014 21:12:27 -0700 Subject: [PATCH 010/992] load DERIVED_PREDICATES only if it has been defined --- lib/ransack.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/ransack.rb b/lib/ransack.rb index b2a016d8b..2dec917f6 100644 --- a/lib/ransack.rb +++ b/lib/ransack.rb @@ -13,8 +13,10 @@ class UntraversableAssociationError < StandardError; end; config.add_predicate name, :arel_predicate => name end - Ransack::Constants::DERIVED_PREDICATES.each do |args| - config.add_predicate *args + if defined?(Ransack::Constants::DERIVED_PREDICATES) + Ransack::Constants::DERIVED_PREDICATES.each do |args| + config.add_predicate *args + end end end From 1799766abd384c6b3b307abf3b3e5e1ffbdf5bfd Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 10:33:51 -0700 Subject: [PATCH 011/992] added mongoid search specs --- spec/mongoid/search_spec.rb | 447 +++++++++++++++++++++++++++++++++ spec/mongoid/support/schema.rb | 21 +- 2 files changed, 455 insertions(+), 13 deletions(-) create mode 100644 spec/mongoid/search_spec.rb diff --git a/spec/mongoid/search_spec.rb b/spec/mongoid/search_spec.rb new file mode 100644 index 000000000..8477ff623 --- /dev/null +++ b/spec/mongoid/search_spec.rb @@ -0,0 +1,447 @@ +require 'mongoid_spec_helper' + +module Ransack + describe Search do + describe '#initialize' do + it "removes empty conditions before building" do + expect_any_instance_of(Search).to receive(:build).with({}) + Search.new(Person, :name_eq => '') + end + + it "keeps conditions with a false value before building" do + expect_any_instance_of(Search).to receive(:build).with({"name_eq" => false}) + Search.new(Person, :name_eq => false) + end + + it "keeps conditions with a value before building" do + expect_any_instance_of(Search).to receive(:build).with({"name_eq" => 'foobar'}) + Search.new(Person, :name_eq => 'foobar') + end + + it "removes empty suffixed conditions before building" do + expect_any_instance_of(Search).to receive(:build).with({}) + Search.new(Person, :name_eq_any => ['']) + end + + it "keeps suffixed conditions with a false value before building" do + expect_any_instance_of(Search).to receive(:build).with({"name_eq_any" => [false]}) + Search.new(Person, :name_eq_any => [false]) + end + + it "keeps suffixed conditions with a value before building" do + expect_any_instance_of(Search).to receive(:build).with({"name_eq_any" => ['foobar']}) + Search.new(Person, :name_eq_any => ['foobar']) + end + + it 'does not raise exception for string :params argument' do + expect { Search.new(Person, '') }.not_to raise_error + end + end + + describe '#build' do + it 'creates conditions for top-level attributes' do + search = Search.new(Person, :name_eq => 'Ernie') + condition = search.base[:name_eq] + expect(condition).to be_a Nodes::Condition + expect(condition.predicate.name).to eq 'eq' + expect(condition.attributes.first.name).to eq 'name' + expect(condition.value).to eq 'Ernie' + end + + context 'joins' do + before { pending 'not implemented for mongoid' } + + it 'creates conditions for association attributes' do + search = Search.new(Person, :children_name_eq => 'Ernie') + condition = search.base[:children_name_eq] + binding.pry + expect(condition).to be_a Nodes::Condition + expect(condition.predicate.name).to eq 'eq' + expect(condition.attributes.first.name).to eq 'children_name' + expect(condition.value).to eq 'Ernie' + end + + it 'creates conditions for polymorphic belongs_to association attributes' do + search = Search.new(Note, :notable_of_Person_type_name_eq => 'Ernie') + condition = search.base[:notable_of_Person_type_name_eq] + expect(condition).to be_a Nodes::Condition + expect(condition.predicate.name).to eq 'eq' + expect(condition.attributes.first.name).to eq 'notable_of_Person_type_name' + expect(condition.value).to eq 'Ernie' + end + + it 'creates conditions for multiple polymorphic belongs_to association attributes' do + search = Search.new(Note, + :notable_of_Person_type_name_or_notable_of_Article_type_title_eq => 'Ernie') + condition = search. + base[:notable_of_Person_type_name_or_notable_of_Article_type_title_eq] + expect(condition).to be_a Nodes::Condition + expect(condition.predicate.name).to eq 'eq' + expect(condition.attributes.first.name).to eq 'notable_of_Person_type_name' + expect(condition.attributes.last.name).to eq 'notable_of_Article_type_title' + expect(condition.value).to eq 'Ernie' + end + before { skip } + it 'accepts arrays of groupings with joins' do + search = Search.new(Person, + g: [ + { :m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie' }, + { :m => 'or', :name_eq => 'Bert', :children_name_eq => 'Bert' }, + ] + ) + ors = search.groupings + expect(ors.size).to eq(2) + or1, or2 = ors + expect(or1).to be_a Nodes::Grouping + expect(or1.combinator).to eq 'or' + expect(or2).to be_a Nodes::Grouping + expect(or2.combinator).to eq 'or' + end + + it 'accepts "attributes" hashes for groupings' do + search = Search.new(Person, + g: { + '0' => { m: 'or', name_eq: 'Ernie', children_name_eq: 'Ernie' }, + '1' => { m: 'or', name_eq: 'Bert', children_name_eq: 'Bert' }, + } + ) + ors = search.groupings + expect(ors.size).to eq(2) + or1, or2 = ors + expect(or1).to be_a Nodes::Grouping + expect(or1.combinator).to eq 'or' + expect(or2).to be_a Nodes::Grouping + expect(or2.combinator).to eq 'or' + end + + it 'accepts "attributes" hashes for conditions' do + search = Search.new(Person, + :c => { + '0' => { :a => ['name'], :p => 'eq', :v => ['Ernie'] }, + '1' => { :a => ['children_name', 'parent_name'], + :p => 'eq', :v => ['Ernie'], :m => 'or' } + } + ) + conditions = search.base.conditions + expect(conditions.size).to eq(2) + expect(conditions.map { |c| c.class }) + .to eq [Nodes::Condition, Nodes::Condition] + end + + before { skip } + it 'does not evaluate the query on #inspect' do + search = Search.new(Person, :children_id_in => [1, 2, 3]) + expect(search.inspect).not_to match /ActiveRecord/ + end + end + + it 'discards empty conditions' do + search = Search.new(Person, :children_name_eq => '') + condition = search.base[:children_name_eq] + expect(condition).to be_nil + end + + it 'accepts arrays of groupings' do + search = Search.new(Person, + g: [ + { :m => 'or', :name_eq => 'Ernie', :email_eq => 'ernie@example.org' }, + { :m => 'or', :name_eq => 'Bert', :email_eq => 'bert@example.org' }, + ] + ) + ors = search.groupings + expect(ors.size).to eq(2) + or1, or2 = ors + expect(or1).to be_a Nodes::Grouping + expect(or1.combinator).to eq 'or' + expect(or2).to be_a Nodes::Grouping + expect(or2.combinator).to eq 'or' + end + + it 'creates conditions for custom predicates that take arrays' do + Ransack.configure do |config| + config.add_predicate 'ary_pred', :wants_array => true + end + + search = Search.new(Person, :name_ary_pred => ['Ernie', 'Bert']) + condition = search.base[:name_ary_pred] + expect(condition).to be_a Nodes::Condition + expect(condition.predicate.name).to eq 'ary_pred' + expect(condition.attributes.first.name).to eq 'name' + expect(condition.value).to eq ['Ernie', 'Bert'] + end + + context 'with an invalid condition' do + subject { Search.new(Person, :unknown_attr_eq => 'Ernie') } + + context "when ignore_unknown_conditions is false" do + before do + Ransack.configure { |config| config.ignore_unknown_conditions = false } + end + + specify { expect { subject }.to raise_error ArgumentError } + end + + context "when ignore_unknown_conditions is true" do + before do + Ransack.configure { |config| config.ignore_unknown_conditions = true } + end + + specify { expect { subject }.not_to raise_error } + end + end + end + + describe '#result' do + let(:people_name_field) { + "#{quote_table_name("people")}.#{quote_column_name("name")}" + } + # let(:children_people_name_field) { + # "#{quote_table_name("children_people")}.#{quote_column_name("name")}" + # } + + it 'evaluates arrays of groupings' do + search = Search.new(Person, + :g => [ + { :m => 'or', :name_eq => 'Ernie', :email_eq => 'ernie@example.org' }, + { :m => 'or', :name_eq => 'Bert', :email_eq => 'bert@example.org' } + ] + ) + expect(search.result).to be_an Mongoid::Criteria + selector = search.result.selector + expect(selector.keys).to eq ['$and'] + first, second = selector.values.first + expect(first).to eq({ '$or' => [ { 'name' => 'Ernie' }, { 'email' => 'ernie@example.org' } ] }) + expect(second).to eq({ '$or' => [ { 'name' => 'Bert' }, { 'email' => 'bert@example.org' } ] }) + end + + context 'with joins' do + before { pending 'not implemented for mongoid' } + + it 'evaluates conditions contextually' do + search = Search.new(Person, :children_name_eq => 'Ernie') + expect(search.result).to be_an ActiveRecord::Relation + where = search.result.where_values.first + expect(where.to_sql).to match /#{children_people_name_field} = 'Ernie'/ + end + + it 'evaluates compound conditions contextually' do + search = Search.new(Person, :children_name_or_name_eq => 'Ernie') + expect(search.result).to be_an ActiveRecord::Relation + where = search.result.where_values.first + expect(where.to_sql).to match /#{children_people_name_field + } = 'Ernie' OR #{people_name_field} = 'Ernie'/ + end + + it 'evaluates polymorphic belongs_to association conditions contextually' do + search = Search.new(Note, :notable_of_Person_type_name_eq => 'Ernie') + expect(search.result).to be_an ActiveRecord::Relation + where = search.result.where_values.first + expect(where.to_sql).to match /#{people_name_field} = 'Ernie'/ + end + + it 'evaluates nested conditions' do + search = Search.new(Person, :children_name_eq => 'Ernie', + :g => [ + { :m => 'or', + :name_eq => 'Ernie', + :children_children_name_eq => 'Ernie' + } + ] + ) + expect(search.result).to be_an ActiveRecord::Relation + where = search.result.where_values.first + expect(where.to_sql).to match /#{children_people_name_field} = 'Ernie'/ + expect(where.to_sql).to match /#{people_name_field} = 'Ernie'/ + expect(where.to_sql).to match /#{quote_table_name("children_people_2") + }.#{quote_column_name("name")} = 'Ernie'/ + end + + it 'evaluates arrays of groupings' do + search = Search.new(Person, + :g => [ + { :m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie' }, + { :m => 'or', :name_eq => 'Bert', :children_name_eq => 'Bert' } + ] + ) + expect(search.result).to be_an ActiveRecord::Relation + where = search.result.where_values.first + sql = where.to_sql + first, second = sql.split(/ AND /) + expect(first).to match /#{people_name_field} = 'Ernie'/ + expect(first).to match /#{children_people_name_field} = 'Ernie'/ + expect(second).to match /#{people_name_field} = 'Bert'/ + expect(second).to match /#{children_people_name_field} = 'Bert'/ + end + + it 'returns distinct records when passed :distinct => true' do + search = Search.new( + Person, :g => [ + { :m => 'or', + :comments_body_cont => 'e', + :articles_comments_body_cont => 'e' + } + ] + ) + if ActiveRecord::VERSION::MAJOR == 3 + all_or_load, uniq_or_distinct = :all, :uniq + else + all_or_load, uniq_or_distinct = :load, :distinct + end + expect(search.result.send(all_or_load).size). + to eq(9000) + expect(search.result(:distinct => true).size). + to eq(10) + expect(search.result.send(all_or_load).send(uniq_or_distinct)). + to eq search.result(:distinct => true).send(all_or_load) + end + end + end + + describe '#sorts=' do + before do + @s = Search.new(Person) + end + + it 'creates sorts based on a single attribute/direction' do + @s.sorts = 'id desc' + expect(@s.sorts.size).to eq(1) + sort = @s.sorts.first + expect(sort).to be_a Nodes::Sort + expect(sort.name).to eq 'id' + expect(sort.dir).to eq 'desc' + expect(@s.result.options).to eq({ :sort => { '_id' => -1 } }) + end + + it 'creates sorts based on a single attribute and uppercase direction' do + @s.sorts = 'id DESC' + expect(@s.sorts.size).to eq(1) + sort = @s.sorts.first + expect(sort).to be_a Nodes::Sort + expect(sort.name).to eq 'id' + expect(sort.dir).to eq 'desc' + expect(@s.result.options).to eq({ :sort => { '_id' => -1 } }) + end + + it 'creates sorts based on a single attribute and without direction' do + @s.sorts = 'id' + expect(@s.sorts.size).to eq(1) + sort = @s.sorts.first + expect(sort).to be_a Nodes::Sort + expect(sort.name).to eq 'id' + expect(sort.dir).to eq 'asc' + expect(@s.result.options).to eq({ :sort => { '_id' => 1 } }) + end + + it 'creates sorts based on multiple attributes/directions in array format' do + @s.sorts = ['id desc', { :name => 'name', :dir => 'asc' }] + expect(@s.sorts.size).to eq(2) + sort1, sort2 = @s.sorts + expect(sort1).to be_a Nodes::Sort + expect(sort1.name).to eq 'id' + expect(sort1.dir).to eq 'desc' + expect(sort2).to be_a Nodes::Sort + expect(sort2.name).to eq 'name' + expect(sort2.dir).to eq 'asc' + expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} }) + end + + it 'creates sorts based on multiple attributes and uppercase directions in array format' do + @s.sorts = ['id DESC', { :name => 'name', :dir => 'ASC' }] + expect(@s.sorts.size).to eq(2) + sort1, sort2 = @s.sorts + expect(sort1).to be_a Nodes::Sort + expect(sort1.name).to eq 'id' + expect(sort1.dir).to eq 'desc' + expect(sort2).to be_a Nodes::Sort + expect(sort2.name).to eq 'name' + expect(sort2.dir).to eq 'asc' + expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} }) + end + + it 'creates sorts based on multiple attributes and different directions in array format' do + @s.sorts = ['id DESC', { name: 'name', dir: nil }] + expect(@s.sorts.size).to eq(2) + sort1, sort2 = @s.sorts + expect(sort1).to be_a Nodes::Sort + expect(sort1.name).to eq 'id' + expect(sort1.dir).to eq 'desc' + expect(sort2).to be_a Nodes::Sort + expect(sort2.name).to eq 'name' + expect(sort2.dir).to eq 'asc' + expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} }) + end + + it 'creates sorts based on multiple attributes/directions in hash format' do + @s.sorts = { + '0' => { :name => 'id', :dir => 'desc' }, + '1' => { :name => 'name', :dir => 'asc' } + } + expect(@s.sorts.size).to eq(2) + expect(@s.sorts).to be_all { |s| Nodes::Sort === s } + id_sort = @s.sorts.detect { |s| s.name == 'id' } + name_sort = @s.sorts.detect { |s| s.name == 'name' } + expect(id_sort.dir).to eq 'desc' + expect(name_sort.dir).to eq 'asc' + expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} }) + end + + it 'creates sorts based on multiple attributes and uppercase directions in hash format' do + @s.sorts = { + '0' => { :name => 'id', :dir => 'DESC' }, + '1' => { :name => 'name', :dir => 'ASC' } + } + expect(@s.sorts.size).to eq(2) + expect(@s.sorts).to be_all { |s| Nodes::Sort === s } + id_sort = @s.sorts.detect { |s| s.name == 'id' } + name_sort = @s.sorts.detect { |s| s.name == 'name' } + expect(id_sort.dir).to eq 'desc' + expect(name_sort.dir).to eq 'asc' + expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} }) + end + + it 'creates sorts based on multiple attributes and different directions in hash format' do + @s.sorts = { + '0' => { :name => 'id', :dir => 'DESC' }, + '1' => { :name => 'name', :dir => nil } + } + expect(@s.sorts.size).to eq(2) + expect(@s.sorts).to be_all { |s| Nodes::Sort === s } + id_sort = @s.sorts.detect { |s| s.name == 'id' } + name_sort = @s.sorts.detect { |s| s.name == 'name' } + expect(id_sort.dir).to eq 'desc' + expect(name_sort.dir).to eq 'asc' + expect(@s.result.options).to eq({ :sort=>{"_id"=>-1, "name"=>1} }) + end + + it 'overrides existing sort' do + @s.sorts = 'id asc' + expect(@s.result.first.id.to_s).to eq Person.min(:id).to_s + end + end + + describe '#method_missing' do + before do + @s = Search.new(Person) + end + + it 'raises NoMethodError when sent an invalid attribute' do + expect { @s.blah }.to raise_error NoMethodError + end + + it 'sets condition attributes when sent valid attributes' do + @s.name_eq = 'Ernie' + expect(@s.name_eq).to eq 'Ernie' + end + + context 'with joins' do + before { pending 'not implemented for mongoid' } + it 'allows chaining to access nested conditions' do + @s.groupings = [ + { :m => 'or', :name_eq => 'Ernie', :children_name_eq => 'Ernie' } + ] + expect(@s.groupings.first.children_name_eq).to eq 'Ernie' + end + end + end + end +end diff --git a/spec/mongoid/support/schema.rb b/spec/mongoid/support/schema.rb index a842ad21a..6ff4ca587 100644 --- a/spec/mongoid/support/schema.rb +++ b/spec/mongoid/support/schema.rb @@ -43,33 +43,33 @@ class Person def self.ransackable_attributes(auth_object = nil) if auth_object == :admin - column_names + _ransackers.keys - ['only_sort'] + super - ['only_sort'] else - column_names + _ransackers.keys - ['only_sort', 'only_admin'] + super - ['only_sort', 'only_admin'] end end def self.ransortable_attributes(auth_object = nil) if auth_object == :admin - column_names + _ransackers.keys - ['only_search'] + super - ['only_search'] else - column_names + _ransackers.keys - ['only_search', 'only_admin'] + super - ['only_search', 'only_admin'] end end def self.ransackable_attributes(auth_object = nil) if auth_object == :admin - column_names + _ransackers.keys - ['only_sort'] + super - ['only_sort'] else - column_names + _ransackers.keys - ['only_sort', 'only_admin'] + super - ['only_sort', 'only_admin'] end end def self.ransortable_attributes(auth_object = nil) if auth_object == :admin - column_names + _ransackers.keys - ['only_search'] + super - ['only_search'] else - column_names + _ransackers.keys - ['only_search', 'only_admin'] + super - ['only_search', 'only_admin'] end end end @@ -77,7 +77,6 @@ def self.ransortable_attributes(auth_object = nil) class Article include Mongoid::Document - field :person_id, type: Integer field :title, type: String field :body, type: String @@ -96,8 +95,6 @@ class Article < ::Article class Comment include Mongoid::Document - field :article_id, type: Integer - field :person_id, type: Integer field :body, type: String @@ -116,8 +113,6 @@ class Tag class Note include Mongoid::Document - field :notable_id, type: Integer - field :notable_type, type: String field :note, type: String belongs_to :notable, :polymorphic => true From b85abef5ed3d61fecf7b1748e02503411814c466 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 10:35:51 -0700 Subject: [PATCH 012/992] ransack method for Mongoid::Document improved --- lib/ransack/adapters/mongoid/base.rb | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/lib/ransack/adapters/mongoid/base.rb b/lib/ransack/adapters/mongoid/base.rb index 4f47e2373..79b74972a 100644 --- a/lib/ransack/adapters/mongoid/base.rb +++ b/lib/ransack/adapters/mongoid/base.rb @@ -55,7 +55,7 @@ def ransacker(name, opts = {}, &block) end def ransackable_attributes(auth_object = nil) - column_names + _ransackers.keys + ['id'] + column_names.select { |c| c != '_id' } + _ransackers.keys end def ransortable_attributes(auth_object = nil) @@ -65,7 +65,11 @@ def ransortable_attributes(auth_object = nil) end def ransackable_associations(auth_object = nil) - reflect_on_all_associations.map { |a| a.name.to_s } + reflect_on_all_associations_all.map { |a| a.name.to_s } + end + + def reflect_on_all_associations_all + reflect_on_all_associations(:belongs_to, :has_one, :has_many) end # For overriding with a whitelist of symbols @@ -76,12 +80,12 @@ def ransackable_scopes(auth_object = nil) # imitating active record def joins_values *args - criteria + [] end - def group_by *args, &block - criteria - end + # def group_by *args, &block + # criteria + # end def columns @columns ||= fields.map(&:second).map{ |c| ColumnWrapper.new(c) } @@ -97,14 +101,6 @@ def columns_hash end - - # base::ClassMethods.class_eval do - - # end # base::ClassMethods.class_eval - - # def self.extended(base) - # end - end # Base end end From dace90ec936108e927d218829c92b1a9d7a39f26 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 10:36:23 -0700 Subject: [PATCH 013/992] sorting for mongoid works --- lib/ransack/adapters/mongoid.rb | 1 + .../adapters/mongoid/attributes/attribute.rb | 20 ++-- lib/ransack/adapters/mongoid/context.rb | 98 ++++++------------- lib/ransack/adapters/mongoid/inquiry_hash.rb | 23 +++++ .../adapters/mongoid/ransack/visitor.rb | 2 +- 5 files changed, 69 insertions(+), 75 deletions(-) create mode 100644 lib/ransack/adapters/mongoid/inquiry_hash.rb diff --git a/lib/ransack/adapters/mongoid.rb b/lib/ransack/adapters/mongoid.rb index 99847d459..af21dade3 100644 --- a/lib/ransack/adapters/mongoid.rb +++ b/lib/ransack/adapters/mongoid.rb @@ -3,6 +3,7 @@ require 'ransack/adapters/mongoid/attributes/attribute' require 'ransack/adapters/mongoid/table' +require 'ransack/adapters/mongoid/inquiry_hash' case Mongoid::VERSION when /^3\.2\./ diff --git a/lib/ransack/adapters/mongoid/attributes/attribute.rb b/lib/ransack/adapters/mongoid/attributes/attribute.rb index 95e6f5e30..3d7530fa7 100644 --- a/lib/ransack/adapters/mongoid/attributes/attribute.rb +++ b/lib/ransack/adapters/mongoid/attributes/attribute.rb @@ -16,19 +16,19 @@ def lower end def eq(other) - { name => other } + { name => other }.to_inquiry end def not_eq(other) - { name.to_sym.ne => other } + { name.to_sym.ne => other }.to_inquiry end def matches(other) - { name => /#{Regexp.escape(other)}/i } + { name => /#{Regexp.escape(other)}/i }.to_inquiry end def does_not_match(other) - { "$not" => { name => /#{Regexp.escape(other)}/i } } + { "$not" => { name => /#{Regexp.escape(other)}/i } }.to_inquiry end def not_eq_all(other) @@ -36,7 +36,7 @@ def not_eq_all(other) other.each do |value| q << { name.to_sym.ne => value } end - { "$and" => q } + { "$and" => q }.to_inquiry end def eq_any(other) @@ -44,7 +44,15 @@ def eq_any(other) other.each do |value| q << { name => value } end - { "$or" => q } + { "$or" => q }.to_inquiry + end + + def asc + { name => :asc } + end + + def desc + { name => :desc } end end diff --git a/lib/ransack/adapters/mongoid/context.rb b/lib/ransack/adapters/mongoid/context.rb index 3cbcf7dde..c9272b0e9 100644 --- a/lib/ransack/adapters/mongoid/context.rb +++ b/lib/ransack/adapters/mongoid/context.rb @@ -45,10 +45,18 @@ def evaluate(search, opts = {}) viz = Visitor.new relation = @object.where(viz.accept(search.base)) if search.sorts.any? - relation = relation.except(:order) - .reorder(viz.accept(search.sorts)) + ary_sorting = viz.accept(search.sorts) + sorting = {} + ary_sorting.each do |s| + sorting.merge! Hash[s.map { |k, d| [k.to_s == 'id' ? '_id' : k, d] }] + end + relation = relation.order_by(sorting) + # relation = relation.except(:order) + # .reorder(viz.accept(search.sorts)) end - opts[:distinct] ? relation.distinct : relation + # -- mongoid has different distinct method + # opts[:distinct] ? relation.distinct : relation + relation end def attribute_method?(str, klass = @klass) @@ -118,7 +126,7 @@ def get_parent_and_attribute_name(str, parent = @base) 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 } + klass.reflect_on_all_associations_all.detect { |a| a.name.to_s == str } end def join_dependency(relation) @@ -168,74 +176,28 @@ def build_join_dependency(relation) join_dependency.alias_tracker.aliases[join.left.name.downcase] = 1 end - if ::Mongoid::VERSION >= '4.1' - join_dependency - else - join_dependency.graft(*stashed_association_joins) - end + join_dependency # ActiveRecord::Associations::JoinDependency end - if ::Mongoid::VERSION >= '4.1' - - def build_or_find_association(name, parent = @base, klass = nil) - found_association = @join_dependency.join_root.children - .detect do |assoc| - assoc.reflection.name == name && - (@associations_pot.nil? || @associations_pot[assoc] == parent) && - (!klass || assoc.reflection.klass == klass) - end - - unless found_association - jd = JoinDependency.new( - parent.base_klass, - Polyamorous::Join.new(name, @join_type, klass), - [] - ) - found_association = jd.join_root.children.last - associations found_association, parent - - # TODO maybe we dont need to push associations here, we could loop - # through the @associations_pot instead - @join_dependency.join_root.children.push found_association - - # Builds the arel nodes properly for this association - @join_dependency.send( - :construct_tables!, jd.join_root, found_association - ) - - # Leverage the stashed association functionality in AR - @object = @object.joins(jd) - end - found_association - end - - def associations(assoc, parent) - @associations_pot ||= {} - @associations_pot[assoc] = parent + # ActiveRecord method + 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 - - else - - 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 + 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 diff --git a/lib/ransack/adapters/mongoid/inquiry_hash.rb b/lib/ransack/adapters/mongoid/inquiry_hash.rb new file mode 100644 index 000000000..8d7ec023d --- /dev/null +++ b/lib/ransack/adapters/mongoid/inquiry_hash.rb @@ -0,0 +1,23 @@ +module Ransack + module Adapters + module Mongoid + class InquiryHash < Hash + + def or(other) + { '$or' => [ self, other] }.to_inquiry + end + + def and(other) + { '$and' => [ self, other] }.to_inquiry + end + + end + end + end +end + +class Hash + def to_inquiry + ::Ransack::Adapters::Mongoid::InquiryHash[self] + end +end diff --git a/lib/ransack/adapters/mongoid/ransack/visitor.rb b/lib/ransack/adapters/mongoid/ransack/visitor.rb index 1f26f8635..74e523fb1 100644 --- a/lib/ransack/adapters/mongoid/ransack/visitor.rb +++ b/lib/ransack/adapters/mongoid/ransack/visitor.rb @@ -5,7 +5,7 @@ def visit_and(object) return nil unless nodes.size > 0 if nodes.size > 1 - Arel::Nodes::Grouping.new(Arel::Nodes::And.new(nodes)) + nodes.inject(&:and) else nodes.first end From 55d6b88de1520c2802646a479d1bd8ce2f478342 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 10:38:40 -0700 Subject: [PATCH 014/992] mongoid translate spec works --- lib/ransack/adapters/mongoid/ransack/translate.rb | 11 ++++++----- spec/mongoid/translate_spec.rb | 14 ++++++++++++++ 2 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 spec/mongoid/translate_spec.rb diff --git a/lib/ransack/adapters/mongoid/ransack/translate.rb b/lib/ransack/adapters/mongoid/ransack/translate.rb index a6c273009..f3219274c 100644 --- a/lib/ransack/adapters/mongoid/ransack/translate.rb +++ b/lib/ransack/adapters/mongoid/ransack/translate.rb @@ -2,11 +2,12 @@ module Ransack module Translate def self.i18n_key(klass) - if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0 - klass.model_name.i18n_key.to_s.tr('.', '/') - else - klass.model_name.i18n_key.to_s - end + # if ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 0 + # klass.model_name.i18n_key.to_s.tr('.', '/') + # else + # klass.model_name.i18n_key.to_s + # end + klass.model_name.i18n_key.to_s end end end diff --git a/spec/mongoid/translate_spec.rb b/spec/mongoid/translate_spec.rb new file mode 100644 index 000000000..2ebfdf481 --- /dev/null +++ b/spec/mongoid/translate_spec.rb @@ -0,0 +1,14 @@ +require 'mongoid_spec_helper' + +module Ransack + describe Translate do + + describe '.attribute' do + it 'translate namespaced attribute like AR does' do + ar_translation = ::Namespace::Article.human_attribute_name(:title) + ransack_translation = Ransack::Translate.attribute(:title, :context => ::Namespace::Article.search.context) + expect(ransack_translation).to eq ar_translation + end + end + end +end From 2ef4210f8547b3504fb7573981dd208338697919 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 11:08:29 -0700 Subject: [PATCH 015/992] added active_record style method Mongoid::Document.first(n) --- lib/ransack/adapters/mongoid/base.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/ransack/adapters/mongoid/base.rb b/lib/ransack/adapters/mongoid/base.rb index 79b74972a..8793dce01 100644 --- a/lib/ransack/adapters/mongoid/base.rb +++ b/lib/ransack/adapters/mongoid/base.rb @@ -83,6 +83,14 @@ def joins_values *args [] end + def first(*args) + if args.size == 0 + super + else + self.criteria.limit(args.first) + end + end + # def group_by *args, &block # criteria # end From 915205c02d4ac6313517e7184335b0f487c513df Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 11:08:51 -0700 Subject: [PATCH 016/992] added mongoid/node specs and sham,machinist --- spec/mongoid/nodes/condition_spec.rb | 34 ++++++++++++++++++++++++++++ spec/mongoid/nodes/grouping_spec.rb | 13 +++++++++++ spec/mongoid/support/schema.rb | 10 ++++---- spec/mongoid_spec_helper.rb | 4 ++-- 4 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 spec/mongoid/nodes/condition_spec.rb create mode 100644 spec/mongoid/nodes/grouping_spec.rb diff --git a/spec/mongoid/nodes/condition_spec.rb b/spec/mongoid/nodes/condition_spec.rb new file mode 100644 index 000000000..7ab8e8459 --- /dev/null +++ b/spec/mongoid/nodes/condition_spec.rb @@ -0,0 +1,34 @@ +require 'mongoid_spec_helper' + +module Ransack + module Nodes + describe Condition do + + context 'with multiple values and an _any predicate' do + subject { Condition.extract(Context.for(Person), 'name_eq_any', Person.first(2).map(&:name)) } + + specify { expect(subject.values.size).to eq(2) } + end + + context 'with an invalid predicate' do + subject { Condition.extract(Context.for(Person), 'name_invalid', Person.first.name) } + + context "when ignore_unknown_conditions is false" do + before do + Ransack.configure { |config| config.ignore_unknown_conditions = false } + end + + specify { expect { subject }.to raise_error ArgumentError } + end + + context "when ignore_unknown_conditions is true" do + before do + Ransack.configure { |config| config.ignore_unknown_conditions = true } + end + + specify { subject.should be_nil } + end + end + end + end +end diff --git a/spec/mongoid/nodes/grouping_spec.rb b/spec/mongoid/nodes/grouping_spec.rb new file mode 100644 index 000000000..a814ed1d7 --- /dev/null +++ b/spec/mongoid/nodes/grouping_spec.rb @@ -0,0 +1,13 @@ +require 'mongoid_spec_helper' + +module Ransack + module Nodes + describe Grouping do + before do + @g = 1 + end + + + end + end +end \ No newline at end of file diff --git a/spec/mongoid/support/schema.rb b/spec/mongoid/support/schema.rb index 6ff4ca587..505d2bae2 100644 --- a/spec/mongoid/support/schema.rb +++ b/spec/mongoid/support/schema.rb @@ -121,16 +121,16 @@ class Note module Schema def self.create 10.times do - person = Person.create! - Note.create!(:notable => person) + person = Person.make.save! + Note.make.save!(:notable => person) 3.times do article = Article.create!(:person => person) 3.times do - # article.tags = [Tag.create!, Tag.create!, Tag.create!] + # article.tags = [Tag.make.save!, Tag.make.save!, Tag.make.save!] end - Note.create!(:notable => article) + Note.create.save!(:notable => article) 10.times do - Comment.create!(:article => article, :person => person) + Comment.create.save!(:article => article, :person => person) end end end diff --git a/spec/mongoid_spec_helper.rb b/spec/mongoid_spec_helper.rb index 0fa6518e0..1d27b54f4 100644 --- a/spec/mongoid_spec_helper.rb +++ b/spec/mongoid_spec_helper.rb @@ -1,4 +1,4 @@ -# require 'machinist/active_record' +require 'machinist/object' require 'sham' require 'faker' require 'pry' @@ -9,7 +9,7 @@ Time.zone = 'Eastern Time (US & Canada)' I18n.load_path += Dir[File.join(File.dirname(__FILE__), 'support', '*.yml')] -Dir[File.expand_path('../{mongoid/helpers,mongoid/support}/*.rb', __FILE__)] +Dir[File.expand_path('../{mongoid/helpers,mongoid/support,blueprints}/*.rb', __FILE__)] .each do |f| require f end From d2b02689cb9f69af3731479dcd0bdea8871f278b Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 14:25:52 -0700 Subject: [PATCH 017/992] added some features to ransack/mongoid --- lib/ransack/adapters/mongoid.rb | 4 +-- lib/ransack/adapters/mongoid/base.rb | 13 +++++++++- spec/mongoid/support/schema.rb | 39 +++++++++------------------- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/lib/ransack/adapters/mongoid.rb b/lib/ransack/adapters/mongoid.rb index af21dade3..836ce9a9d 100644 --- a/lib/ransack/adapters/mongoid.rb +++ b/lib/ransack/adapters/mongoid.rb @@ -1,11 +1,11 @@ require 'ransack/adapters/mongoid/base' -Mongoid::Document.send :include, Ransack::Adapters::Mongoid::Base +::Mongoid::Document.send :include, Ransack::Adapters::Mongoid::Base require 'ransack/adapters/mongoid/attributes/attribute' require 'ransack/adapters/mongoid/table' require 'ransack/adapters/mongoid/inquiry_hash' -case Mongoid::VERSION +case ::Mongoid::VERSION when /^3\.2\./ require 'ransack/adapters/mongoid/3.2/context' else diff --git a/lib/ransack/adapters/mongoid/base.rb b/lib/ransack/adapters/mongoid/base.rb index 8793dce01..740b3c876 100644 --- a/lib/ransack/adapters/mongoid/base.rb +++ b/lib/ransack/adapters/mongoid/base.rb @@ -54,10 +54,14 @@ def ransacker(name, opts = {}, &block) .new(self, name, opts, &block) end - def ransackable_attributes(auth_object = nil) + def all_ransackable_attributes ['id'] + column_names.select { |c| c != '_id' } + _ransackers.keys end + def ransackable_attributes(auth_object = nil) + all_ransackable_attributes + end + def ransortable_attributes(auth_object = nil) # Here so users can overwrite the attributes # that show up in the sort_select @@ -107,6 +111,13 @@ def columns_hash columns.index_by(&:name) end + def table + name = ::Ransack::Adapters::Mongoid::Attributes::Attribute.new(self.criteria, :name) + { + :name => name + } + end + end end # Base diff --git a/spec/mongoid/support/schema.rb b/spec/mongoid/support/schema.rb index 505d2bae2..7b3360482 100644 --- a/spec/mongoid/support/schema.rb +++ b/spec/mongoid/support/schema.rb @@ -27,49 +27,34 @@ class Person default_scope -> { order(id: :desc) } - scope :restricted, lambda { where("restricted = 1") } - scope :active, lambda { where("active = 1") } - scope :over_age, lambda { |y| where(["age > ?", y]) } + scope :restricted, lambda { where(restricted: 1) } + scope :active, lambda { where(active: 1) } + scope :over_age, lambda { |y| where(:age.gt => y) } ransacker :reversed_name, :formatter => proc { |v| v.reverse } do |parent| parent.table[:name] end - # ransacker :doubled_name do |parent| - # Arel::Nodes::InfixOperation.new( - # '||', parent.table[:name], parent.table[:name] - # ) - # end - - def self.ransackable_attributes(auth_object = nil) - if auth_object == :admin - super - ['only_sort'] - else - super - ['only_sort', 'only_admin'] - end - end - - def self.ransortable_attributes(auth_object = nil) - if auth_object == :admin - super - ['only_search'] - else - super - ['only_search', 'only_admin'] - end + ransacker :doubled_name do |parent| + # Arel::Nodes::InfixOperation.new( + # '||', parent.table[:name], parent.table[:name] + # ) + parent.table[:name] end def self.ransackable_attributes(auth_object = nil) if auth_object == :admin - super - ['only_sort'] + all_ransackable_attributes - ['only_sort'] else - super - ['only_sort', 'only_admin'] + all_ransackable_attributes - ['only_sort', 'only_admin'] end end def self.ransortable_attributes(auth_object = nil) if auth_object == :admin - super - ['only_search'] + all_ransackable_attributes - ['only_search'] else - super - ['only_search', 'only_admin'] + all_ransackable_attributes - ['only_search', 'only_admin'] end end end From 905f6a2fc27ebd64dbbf0ca5ce5f01a3d30654f3 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 14:26:27 -0700 Subject: [PATCH 018/992] added adapters/mongoid/base specs --- spec/mongoid/adapters/mongoid/base_spec.rb | 270 ++++++++++++++++++ spec/mongoid/adapters/mongoid/context_spec.rb | 51 ++++ 2 files changed, 321 insertions(+) create mode 100644 spec/mongoid/adapters/mongoid/base_spec.rb create mode 100644 spec/mongoid/adapters/mongoid/context_spec.rb diff --git a/spec/mongoid/adapters/mongoid/base_spec.rb b/spec/mongoid/adapters/mongoid/base_spec.rb new file mode 100644 index 000000000..5d97bceba --- /dev/null +++ b/spec/mongoid/adapters/mongoid/base_spec.rb @@ -0,0 +1,270 @@ +require 'mongoid_spec_helper' + +module Ransack + module Adapters + module Mongoid + describe Base do + + subject { Person } + + it { should respond_to :ransack } + it { should respond_to :search } + + describe '#search' do + subject { Person.search } + + it { should be_a Search } + it 'has a Mongoid::Criteria as its object' do + expect(subject.object).to be_an ::Mongoid::Criteria + end + + context 'with scopes' do + before do + Person.stub :ransackable_scopes => [:active, :over_age] + end + + it "applies true scopes" do + search = Person.search('active' => true) + expect(search.result.selector).to eq({ 'active' => 1 }) + end + + it "ignores unlisted scopes" do + search = Person.search('restricted' => true) + expect(search.result.selector).to_not eq({ 'restricted' => 1}) + end + + it "ignores false scopes" do + search = Person.search('active' => false) + expect(search.result.selector).to_not eq({ 'active' => 1 }) + end + + it "passes values to scopes" do + search = Person.search('over_age' => 18) + expect(search.result.selector).to eq({ 'age' => { '$gt' => 18 } }) + end + + it "chains scopes" do + search = Person.search('over_age' => 18, 'active' => true) + expect(search.result.selector).to eq({ 'age' => { '$gt' => 18 }, 'active' => 1 }) + end + end + end + + describe '#ransacker' do + # For infix tests + def self.sane_adapter? + case ::Mongoid::Document.connection.adapter_name + when "SQLite3", "PostgreSQL" + true + else + false + end + end + # # in schema.rb, class Person: + # ransacker :reversed_name, formatter: proc { |v| v.reverse } do |parent| + # parent.table[:name] + # end + + # ransacker :doubled_name do |parent| + # Arel::Nodes::InfixOperation.new( + # '||', parent.table[:name], parent.table[:name] + # ) + # end + + it 'creates ransack attributes' do + s = Person.search(:reversed_name_eq => 'htimS cirA') + expect(s.result.size).to eq(Person.where(name: 'Aric Smith').count) + + expect(s.result.first).to eq Person.where(name: 'Aric Smith').first + end + + context 'with joins' do + before { pending 'not implemented for mongoid' } + + it 'can be accessed through associations' do + s = Person.search(:children_reversed_name_eq => 'htimS cirA') + expect(s.result.to_sql).to match( + /#{quote_table_name("children_people")}.#{ + quote_column_name("name")} = 'Aric Smith'/ + ) + end + + it "should keep proper key value pairs in the params hash" do + s = Person.search(:children_reversed_name_eq => 'Testing') + expect(s.result.to_sql).to match /LEFT OUTER JOIN/ + end + + end + + it 'allows an "attribute" to be an InfixOperation' do + s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith') + expect(s.result.first).to eq Person.where(name: 'Aric Smith').first + end if defined?(Arel::Nodes::InfixOperation) && sane_adapter? + + it "doesn't break #count if using InfixOperations" do + s = Person.search(:doubled_name_eq => 'Aric SmithAric Smith') + expect(s.result.count).to eq 1 + end if defined?(Arel::Nodes::InfixOperation) && sane_adapter? + + it "should remove empty key value pairs from the params hash" do + s = Person.search(:reversed_name_eq => '') + expect(s.result.selector).to eq({}) + end + + it "should function correctly when nil is passed in" do + s = Person.search(nil) + end + + it "should function correctly when a blank string is passed in" do + s = Person.search('') + end + + it "should function correctly when using fields with dots in them" do + s = Person.search(:email_cont => "example.com") + expect(s.result.exists?).to be true + + s = Person.search(:email_cont => "example.co.") + expect(s.result.exists?).not_to be true + end + + it "should function correctly when using fields with % in them" do + Person.create!(:name => "110%-er") + s = Person.search(:name_cont => "10%") + expect(s.result.exists?).to be true + end + + it "should function correctly when using fields with backslashes in them" do + Person.create!(:name => "\\WINNER\\") + s = Person.search(:name_cont => "\\WINNER\\") + expect(s.result.exists?).to be true + end + + it 'allows sort by "only_sort" field' do + pending "it doesn't work :(" + s = Person.search( + "s" => { "0" => { "dir" => "desc", "name" => "only_sort" } } + ) + expect(s.result.to_sql).to match( + /ORDER BY #{quote_table_name("people")}.#{ + quote_column_name("only_sort")} ASC/ + ) + end + + it "doesn't sort by 'only_search' field" do + pending "it doesn't work :(" + s = Person.search( + "s" => { "0" => { "dir" => "asc", "name" => "only_search" } } + ) + expect(s.result.to_sql).not_to match( + /ORDER BY #{quote_table_name("people")}.#{ + quote_column_name("only_search")} ASC/ + ) + end + + it 'allows search by "only_search" field' do + s = Person.search(:only_search_eq => 'htimS cirA') + expect(s.result.selector).to eq({'only_search' => 'htimS cirA'}) + end + + it "can't be searched by 'only_sort'" do + s = Person.search(:only_sort_eq => 'htimS cirA') + expect(s.result.selector).not_to eq({'only_sort' => 'htimS cirA'}) + end + + it 'allows sort by "only_admin" field, if auth_object: :admin' do + s = Person.search( + { "s" => { "0" => { "dir" => "asc", "name" => "only_admin" } } }, + { auth_object: :admin } + ) + expect(s.result.options).to eq({ sort: { '_id' => -1, 'only_admin' => 1 } }) + end + + it "doesn't sort by 'only_admin' field, if auth_object: nil" do + s = Person.search( + "s" => { "0" => { "dir" => "asc", "name" => "only_admin" } } + ) + expect(s.result.options).to eq({ sort: {'_id' => -1}}) + end + + it 'allows search by "only_admin" field, if auth_object: :admin' do + s = Person.search( + { :only_admin_eq => 'htimS cirA' }, + { :auth_object => :admin } + ) + expect(s.result.selector).to eq({ 'only_admin' => 'htimS cirA' }) + end + + it "can't be searched by 'only_admin', if auth_object: nil" do + s = Person.search(:only_admin_eq => 'htimS cirA') + expect(s.result.selector).to eq({}) + end + end + + describe '#ransackable_attributes' do + context 'when auth_object is nil' do + subject { Person.ransackable_attributes } + + it { should include 'name' } + it { should include 'reversed_name' } + it { should include 'doubled_name' } + it { should include 'only_search' } + it { should_not include 'only_sort' } + it { should_not include 'only_admin' } + end + + context 'with auth_object :admin' do + subject { Person.ransackable_attributes(:admin) } + + it { should include 'name' } + it { should include 'reversed_name' } + it { should include 'doubled_name' } + it { should include 'only_search' } + it { should_not include 'only_sort' } + it { should include 'only_admin' } + end + end + + describe '#ransortable_attributes' do + context 'when auth_object is nil' do + subject { Person.ransortable_attributes } + + it { should include 'name' } + it { should include 'reversed_name' } + it { should include 'doubled_name' } + it { should include 'only_sort' } + it { should_not include 'only_search' } + it { should_not include 'only_admin' } + end + + context 'with auth_object :admin' do + subject { Person.ransortable_attributes(:admin) } + + it { should include 'name' } + it { should include 'reversed_name' } + it { should include 'doubled_name' } + it { should include 'only_sort' } + it { should_not include 'only_search' } + it { should include 'only_admin' } + end + end + + describe '#ransackable_associations' do + before { pending "not implemented for mongoid" } + + subject { Person.ransackable_associations } + + it { should include 'parent' } + it { should include 'children' } + it { should include 'articles' } + end + + describe '#ransackable_scopes' do + subject { Person.ransackable_scopes } + + it { should eq [] } + end + + end + end + end +end diff --git a/spec/mongoid/adapters/mongoid/context_spec.rb b/spec/mongoid/adapters/mongoid/context_spec.rb new file mode 100644 index 000000000..3d9c076ad --- /dev/null +++ b/spec/mongoid/adapters/mongoid/context_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +module Ransack + module Adapters + module ActiveRecord + describe Context do + subject { Context.new(Person) } + + describe '#relation_for' do + it 'returns relation for given object' do + expect(subject.object).to be_an ::ActiveRecord::Relation + end + end + + describe '#evaluate' do + it 'evaluates search objects' do + search = Search.new(Person, :name_eq => 'Joe Blow') + result = subject.evaluate(search) + + expect(result).to be_an ::ActiveRecord::Relation + expect(result.to_sql).to match /#{quote_column_name("name")} = 'Joe Blow'/ + end + + it 'SELECTs DISTINCT when distinct: true' do + search = Search.new(Person, :name_eq => 'Joe Blow') + result = subject.evaluate(search, :distinct => true) + + expect(result).to be_an ::ActiveRecord::Relation + expect(result.to_sql).to match /SELECT DISTINCT/ + end + end + + it 'contextualizes strings to attributes' do + attribute = subject.contextualize 'children_children_parent_name' + expect(attribute).to be_a Arel::Attributes::Attribute + expect(attribute.name.to_s).to eq 'name' + expect(attribute.relation.table_alias).to eq 'parents_people' + end + + it 'builds new associations if not yet built' do + attribute = subject.contextualize 'children_articles_title' + expect(attribute).to be_a Arel::Attributes::Attribute + expect(attribute.name.to_s).to eq 'title' + expect(attribute.relation.name).to eq 'articles' + expect(attribute.relation.table_alias).to be_nil + end + + end + end + end +end From b1f358a198c1d587fe551528910e4cb9af94d8bf Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 14:52:46 -0700 Subject: [PATCH 019/992] mongoid/context spec added --- spec/mongoid/adapters/mongoid/context_spec.rb | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/spec/mongoid/adapters/mongoid/context_spec.rb b/spec/mongoid/adapters/mongoid/context_spec.rb index 3d9c076ad..49025495d 100644 --- a/spec/mongoid/adapters/mongoid/context_spec.rb +++ b/spec/mongoid/adapters/mongoid/context_spec.rb @@ -1,12 +1,13 @@ -require 'spec_helper' +require 'mongoid_spec_helper' module Ransack module Adapters - module ActiveRecord + module Mongoid describe Context do subject { Context.new(Person) } describe '#relation_for' do + before { pending "not implemented for mongoid" } it 'returns relation for given object' do expect(subject.object).to be_an ::ActiveRecord::Relation end @@ -17,11 +18,13 @@ module ActiveRecord search = Search.new(Person, :name_eq => 'Joe Blow') result = subject.evaluate(search) - expect(result).to be_an ::ActiveRecord::Relation - expect(result.to_sql).to match /#{quote_column_name("name")} = 'Joe Blow'/ + expect(result).to be_an ::Mongoid::Criteria + expect(result.selector).to eq({ 'name' => 'Joe Blow' }) end it 'SELECTs DISTINCT when distinct: true' do + pending "distinct doesn't work" + search = Search.new(Person, :name_eq => 'Joe Blow') result = subject.evaluate(search, :distinct => true) @@ -31,13 +34,15 @@ module ActiveRecord end it 'contextualizes strings to attributes' do - attribute = subject.contextualize 'children_children_parent_name' - expect(attribute).to be_a Arel::Attributes::Attribute + attribute = subject.contextualize 'name' + expect(attribute).to be_a ::Ransack::Adapters::Mongoid::Attributes::Attribute expect(attribute.name.to_s).to eq 'name' - expect(attribute.relation.table_alias).to eq 'parents_people' + # expect(attribute.relation.table_alias).to eq 'parents_people' end it 'builds new associations if not yet built' do + pending "not implemented for mongoid" + attribute = subject.contextualize 'children_articles_title' expect(attribute).to be_a Arel::Attributes::Attribute expect(attribute.name.to_s).to eq 'title' From 59920feb59f121cc0a1ccf378a5e1fdffe2ae9bb Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 15:16:16 -0700 Subject: [PATCH 020/992] by default loads ActiveRecord's CONSTANTS --- lib/ransack.rb | 12 ++++++++---- lib/ransack/configuration.rb | 2 -- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/ransack.rb b/lib/ransack.rb index 2dec917f6..cb90271f0 100644 --- a/lib/ransack.rb +++ b/lib/ransack.rb @@ -2,6 +2,12 @@ require 'ransack/configuration' +if defined?(::Mongoid) + require 'ransack/adapters/mongoid/ransack/constants' +else + require 'ransack/adapters/active_record/ransack/constants' +end + module Ransack extend Configuration @@ -13,10 +19,8 @@ class UntraversableAssociationError < StandardError; end; config.add_predicate name, :arel_predicate => name end - if defined?(Ransack::Constants::DERIVED_PREDICATES) - Ransack::Constants::DERIVED_PREDICATES.each do |args| - config.add_predicate *args - end + Ransack::Constants::DERIVED_PREDICATES.each do |args| + config.add_predicate *args end end diff --git a/lib/ransack/configuration.rb b/lib/ransack/configuration.rb index 7a383e257..696db7b62 100644 --- a/lib/ransack/configuration.rb +++ b/lib/ransack/configuration.rb @@ -1,6 +1,4 @@ require 'ransack/constants' -require 'ransack/adapters/active_record/ransack/constants' if defined?(::ActiveRecord::Base) -require 'ransack/adapters/mongoid/ransack/constants' if defined?(::Mongoid) require 'ransack/predicate' module Ransack From b6574263f6766387f6076792c3af607178ff8c98 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 17:00:42 -0700 Subject: [PATCH 021/992] added more predication methods. --- .../adapters/mongoid/attributes/attribute.rb | 36 +------ .../mongoid/attributes/predications.rb | 102 ++++++++++++++++++ spec/mongoid/predicate_spec.rb | 33 +++++- 3 files changed, 138 insertions(+), 33 deletions(-) create mode 100644 lib/ransack/adapters/mongoid/attributes/predications.rb diff --git a/lib/ransack/adapters/mongoid/attributes/attribute.rb b/lib/ransack/adapters/mongoid/attributes/attribute.rb index 3d7530fa7..5100b1c97 100644 --- a/lib/ransack/adapters/mongoid/attributes/attribute.rb +++ b/lib/ransack/adapters/mongoid/attributes/attribute.rb @@ -1,3 +1,5 @@ +require 'ransack/adapters/mongoid/attributes/predications' + module Ransack module Adapters module Mongoid @@ -9,44 +11,14 @@ class Attribute < Struct.new :relation, :name # include Arel::OrderPredications # include Arel::Math + include ::Ransack::Adapters::Mongoid::Attributes::Predications + ### # Create a node for lowering this attribute def lower relation.lower self end - def eq(other) - { name => other }.to_inquiry - end - - def not_eq(other) - { name.to_sym.ne => other }.to_inquiry - end - - def matches(other) - { name => /#{Regexp.escape(other)}/i }.to_inquiry - end - - def does_not_match(other) - { "$not" => { name => /#{Regexp.escape(other)}/i } }.to_inquiry - end - - def not_eq_all(other) - q = [] - other.each do |value| - q << { name.to_sym.ne => value } - end - { "$and" => q }.to_inquiry - end - - def eq_any(other) - q = [] - other.each do |value| - q << { name => value } - end - { "$or" => q }.to_inquiry - end - def asc { name => :asc } end diff --git a/lib/ransack/adapters/mongoid/attributes/predications.rb b/lib/ransack/adapters/mongoid/attributes/predications.rb new file mode 100644 index 000000000..cfb6563d5 --- /dev/null +++ b/lib/ransack/adapters/mongoid/attributes/predications.rb @@ -0,0 +1,102 @@ +module Ransack + module Adapters + module Mongoid + module Attributes + module Predications + + def eq(other) + { name => other }.to_inquiry + end + + def not_eq(other) + { name => { '$ne' => other } }.to_inquiry + end + + def matches(other) + { name => /#{Regexp.escape(other)}/i }.to_inquiry + end + + def does_not_match(other) + { "$not" => { name => /#{Regexp.escape(other)}/i } }.to_inquiry + end + + def eq_all(others) + grouping_all :eq, others + end + + def not_eq_all(others) + grouping_all :not_eq, others + end + + def eq_any(others) + grouping_any :eq, others + end + + def not_eq_any(others) + grouping_any :not_eq, others + end + + def gteq right + { name => { '$gte' => right } }.to_inquiry + end + + def gteq_any others + grouping_any :gteq, others + end + + def gteq_all others + grouping_all :gteq, others + end + + def gt right + { name => { '$gt' => right } } + end + + def gt_any others + grouping_any :gt, others + end + + def gt_all others + grouping_all :gt, others + end + + def lt right + { name => { '$lt' => right } } + end + + def lt_any others + grouping_any :lt, others + end + + def lt_all others + grouping_all :lt, others + end + + def lteq right + { name => { '$lte' => right } }.to_inquiry + end + + def lteq_any others + grouping_any :lteq, others + end + + def lteq_all others + grouping_all :lteq, others + end + + private + + def grouping_any method_id, others + nodes = others.map { |e| send(method_id, e) } + { "$or" => nodes }.to_inquiry + end + + def grouping_all method_id, others + nodes = others.map { |e| send(method_id, e) } + { "$and" => nodes }.to_inquiry + end + end + end + end + end +end diff --git a/spec/mongoid/predicate_spec.rb b/spec/mongoid/predicate_spec.rb index 4e6ec5292..ad5ea482b 100644 --- a/spec/mongoid/predicate_spec.rb +++ b/spec/mongoid/predicate_spec.rb @@ -37,7 +37,6 @@ module Ransack end describe 'cont' do - it_has_behavior 'wildcard escaping', :name_cont, { 'name' => /%\._\\/i } do subject { @s } end @@ -106,5 +105,37 @@ module Ransack expect(@s.result.selector).to eq({ '$and' => [ { 'name' => { '$ne' => nil}}, { 'name' => { '$ne' => '' }} ] }) end end + + describe 'gt' do + it 'generates an greater than for time' do + time = Time.now + @s.created_at_gt = time + expect(@s.result.selector).to eq({ "created_at" => { '$gt' => time } }) + end + end + + describe 'lt' do + it 'generates an greater than for time' do + time = Time.now + @s.created_at_lt = time + expect(@s.result.selector).to eq({ "created_at" => { '$lt' => time } }) + end + end + + describe 'gteq' do + it 'generates an greater than for time' do + time = Time.now + @s.created_at_gteq = time + expect(@s.result.selector).to eq({ "created_at" => { '$gte' => time } }) + end + end + + describe 'lteq' do + it 'generates an greater than for time' do + time = Time.now + @s.created_at_lteq = time + expect(@s.result.selector).to eq({ "created_at" => { '$lte' => time } }) + end + end end end From 9facab1bc276ec223690077118ea7c87828a5a9f Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 17:05:19 -0700 Subject: [PATCH 022/992] mongoid predications updated --- .../mongoid/attributes/predications.rb | 67 +++++++++++++++---- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/lib/ransack/adapters/mongoid/attributes/predications.rb b/lib/ransack/adapters/mongoid/attributes/predications.rb index cfb6563d5..fb07a33fb 100644 --- a/lib/ransack/adapters/mongoid/attributes/predications.rb +++ b/lib/ransack/adapters/mongoid/attributes/predications.rb @@ -3,37 +3,76 @@ module Adapters module Mongoid module Attributes module Predications + def not_eq other + { name => { '$ne' => other } }.to_inquiry + end + + def not_eq_any others + grouping_any :not_eq, others + end + + def not_eq_all others + grouping_all :not_eq, others + end - def eq(other) + def eq other { name => other }.to_inquiry end - def not_eq(other) - { name => { '$ne' => other } }.to_inquiry + def eq_any others + grouping_any :eq, others + end + + def eq_all others + grouping_all :eq, others + end + + def in other + raise "not implemented" end - def matches(other) + def in_any others + grouping_any :in, others + end + + def in_all others + grouping_all :in, others + end + + def not_in other + raise "not implemented" + end + + def not_in_any others + grouping_any :not_in, others + end + + def not_in_all others + grouping_all :not_in, others + end + + def matches other { name => /#{Regexp.escape(other)}/i }.to_inquiry end - def does_not_match(other) - { "$not" => { name => /#{Regexp.escape(other)}/i } }.to_inquiry + def matches_any others + grouping_any :matches, others end - def eq_all(others) - grouping_all :eq, others + def matches_all others + grouping_all :matches, others end - def not_eq_all(others) - grouping_all :not_eq, others + def does_not_match other + { "$not" => { name => /#{Regexp.escape(other)}/i } }.to_inquiry end - def eq_any(others) - grouping_any :eq, others + def does_not_match_any others + grouping_any :does_not_match, others end - def not_eq_any(others) - grouping_any :not_eq, others + def does_not_match_all others + grouping_all :does_not_match, others end def gteq right From 116884ce60f99b5cb6c942ff2b3aa364bf582b8b Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 18:38:44 -0700 Subject: [PATCH 023/992] added mongoid/attrs/order_predications --- .../adapters/mongoid/attributes/attribute.rb | 10 ++-------- .../mongoid/attributes/order_predications.rb | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 8 deletions(-) create mode 100644 lib/ransack/adapters/mongoid/attributes/order_predications.rb diff --git a/lib/ransack/adapters/mongoid/attributes/attribute.rb b/lib/ransack/adapters/mongoid/attributes/attribute.rb index 5100b1c97..dee29913d 100644 --- a/lib/ransack/adapters/mongoid/attributes/attribute.rb +++ b/lib/ransack/adapters/mongoid/attributes/attribute.rb @@ -1,4 +1,5 @@ require 'ransack/adapters/mongoid/attributes/predications' +require 'ransack/adapters/mongoid/attributes/order_predications' module Ransack module Adapters @@ -12,20 +13,13 @@ class Attribute < Struct.new :relation, :name # include Arel::Math include ::Ransack::Adapters::Mongoid::Attributes::Predications + include ::Ransack::Adapters::Mongoid::Attributes::OrderPredications ### # Create a node for lowering this attribute def lower relation.lower self end - - def asc - { name => :asc } - end - - def desc - { name => :desc } - end end class String < Attribute; end diff --git a/lib/ransack/adapters/mongoid/attributes/order_predications.rb b/lib/ransack/adapters/mongoid/attributes/order_predications.rb new file mode 100644 index 000000000..5639c31a0 --- /dev/null +++ b/lib/ransack/adapters/mongoid/attributes/order_predications.rb @@ -0,0 +1,17 @@ +module Ransack + module Adapters + module Mongoid + module Attributes + module OrderPredications + def asc + { name => :asc } + end + + def desc + { name => :desc } + end + end + end + end + end +end From 80c71f8b29cb1801742f1cad37948bd8664acdb3 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 19:57:23 -0700 Subject: [PATCH 024/992] predications in, not_in added --- lib/ransack/adapters/mongoid/attributes/predications.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ransack/adapters/mongoid/attributes/predications.rb b/lib/ransack/adapters/mongoid/attributes/predications.rb index fb07a33fb..20455a5d8 100644 --- a/lib/ransack/adapters/mongoid/attributes/predications.rb +++ b/lib/ransack/adapters/mongoid/attributes/predications.rb @@ -28,7 +28,7 @@ def eq_all others end def in other - raise "not implemented" + { name => { "$in" => other } } end def in_any others @@ -40,7 +40,7 @@ def in_all others end def not_in other - raise "not implemented" + { "$not" => { name => { "$in" => other } } } end def not_in_any others From aff2151b10750909897b5432b9a69ef579aa4f39 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sat, 2 Aug 2014 20:37:22 -0700 Subject: [PATCH 025/992] added starts, ends --- .../adapters/mongoid/attributes/predications.rb | 4 ++-- .../adapters/mongoid/ransack/constants.rb | 16 ++++++++-------- spec/mongoid/predicate_spec.rb | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/ransack/adapters/mongoid/attributes/predications.rb b/lib/ransack/adapters/mongoid/attributes/predications.rb index 20455a5d8..c7bbea75c 100644 --- a/lib/ransack/adapters/mongoid/attributes/predications.rb +++ b/lib/ransack/adapters/mongoid/attributes/predications.rb @@ -52,7 +52,7 @@ def not_in_all others end def matches other - { name => /#{Regexp.escape(other)}/i }.to_inquiry + { name => /#{other}/i }.to_inquiry end def matches_any others @@ -64,7 +64,7 @@ def matches_all others end def does_not_match other - { "$not" => { name => /#{Regexp.escape(other)}/i } }.to_inquiry + { "$not" => { name => /#{other}/i } }.to_inquiry end def does_not_match_any others diff --git a/lib/ransack/adapters/mongoid/ransack/constants.rb b/lib/ransack/adapters/mongoid/ransack/constants.rb index d3f743a01..9b0fa6abd 100644 --- a/lib/ransack/adapters/mongoid/ransack/constants.rb +++ b/lib/ransack/adapters/mongoid/ransack/constants.rb @@ -3,32 +3,32 @@ module Constants DERIVED_PREDICATES = [ ['cont', { :arel_predicate => 'matches', - :formatter => proc { |v| "#{escape_wildcards(v)}" } + :formatter => proc { |v| "#{escape_regex(v)}" } } ], ['not_cont', { :arel_predicate => 'does_not_match', - :formatter => proc { |v| "#{escape_wildcards(v)}" } + :formatter => proc { |v| "#{escape_regex(v)}" } } ], ['start', { :arel_predicate => 'matches', - :formatter => proc { |v| "#{escape_wildcards(v)}%" } + :formatter => proc { |v| "\\A#{escape_regex(v)}" } } ], ['not_start', { :arel_predicate => 'does_not_match', - :formatter => proc { |v| "#{escape_wildcards(v)}%" } + :formatter => proc { |v| "\\A#{escape_regex(v)}" } } ], ['end', { :arel_predicate => 'matches', - :formatter => proc { |v| "%#{escape_wildcards(v)}" } + :formatter => proc { |v| "#{escape_regex(v)}\\Z" } } ], ['not_end', { :arel_predicate => 'does_not_match', - :formatter => proc { |v| "%#{escape_wildcards(v)}" } + :formatter => proc { |v| "#{escape_regex(v)}\\Z" } } ], ['true', { @@ -81,8 +81,8 @@ module Constants module_function # does nothing - def escape_wildcards(unescaped) - unescaped + def escape_regex(unescaped) + Regexp.escape(unescaped) end end end diff --git a/spec/mongoid/predicate_spec.rb b/spec/mongoid/predicate_spec.rb index ad5ea482b..b73e38dd7 100644 --- a/spec/mongoid/predicate_spec.rb +++ b/spec/mongoid/predicate_spec.rb @@ -137,5 +137,19 @@ module Ransack expect(@s.result.selector).to eq({ "created_at" => { '$lte' => time } }) end end + + describe 'starts_with' do + it 'generates an starts_with' do + @s.name_start = 'ric' + expect(@s.result.selector).to eq({ "name" => /\Aric/i }) + end + end + + describe 'ends_with' do + it 'generates an ends_with' do + @s.name_end = 'ric' + expect(@s.result.selector).to eq({ "name" => /ric\Z/i }) + end + end end end From a51158eabd3bc071dfd07d41f08bf4312de33dab Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sun, 3 Aug 2014 22:43:36 -0700 Subject: [PATCH 026/992] .travis changed --- .travis.yml | 3 +++ Gemfile | 4 +++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6245c571e..490212ccc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,3 +1,5 @@ +services: mongodb + language: ruby before_install: @@ -9,6 +11,7 @@ rvm: - 1.9.3 env: + - RAILS=4-1-stable DB=mongodb - RAILS=4-1-stable DB=sqlite3 - RAILS=4-1-stable DB=mysql - RAILS=4-1-stable DB=postgres diff --git a/Gemfile b/Gemfile index 7476f700a..0a7080452 100644 --- a/Gemfile +++ b/Gemfile @@ -33,4 +33,6 @@ else end end -gem 'mongoid', '~> 4.0.0', require: false +if ENV['DB'] == 'mongodb' + gem 'mongoid', '~> 4.0.0', require: false +end From f718ce512220661d60885242b3153bbeee0d12aa Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sun, 3 Aug 2014 22:48:13 -0700 Subject: [PATCH 027/992] if ENV['DB'] has mongodb then run rake mongoid else spec --- Gemfile | 2 +- Rakefile | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 0a7080452..276f67332 100644 --- a/Gemfile +++ b/Gemfile @@ -33,6 +33,6 @@ else end end -if ENV['DB'] == 'mongodb' +if ENV['DB'] =~ /mongodb/ gem 'mongoid', '~> 4.0.0', require: false end diff --git a/Rakefile b/Rakefile index 10269b336..cc76efa8a 100644 --- a/Rakefile +++ b/Rakefile @@ -13,7 +13,13 @@ RSpec::Core::RakeTask.new(:mongoid) do |rspec| rspec.rspec_opts = ['--backtrace'] end -task :default => :spec +task :default do + if ENV['DB'] =~ /mongodb/ + Rake::Task["mongoid"].invoke + else + Rake::Task["spec"].invoke + end +end desc "Open an irb session with Ransack and the sample data used in specs" task :console do From ee23c4a1bb021a790b65693e30553989d0fee565 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Tue, 26 Aug 2014 23:17:54 -0700 Subject: [PATCH 028/992] predications fixed --- lib/ransack/adapters/mongoid/attributes/predications.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/ransack/adapters/mongoid/attributes/predications.rb b/lib/ransack/adapters/mongoid/attributes/predications.rb index c7bbea75c..d93ef7766 100644 --- a/lib/ransack/adapters/mongoid/attributes/predications.rb +++ b/lib/ransack/adapters/mongoid/attributes/predications.rb @@ -28,7 +28,7 @@ def eq_all others end def in other - { name => { "$in" => other } } + { name => { "$in" => other } }.to_inquiry end def in_any others @@ -40,7 +40,7 @@ def in_all others end def not_in other - { "$not" => { name => { "$in" => other } } } + { "$not" => { name => { "$in" => other } } }.to_inquiry end def not_in_any others @@ -88,7 +88,7 @@ def gteq_all others end def gt right - { name => { '$gt' => right } } + { name => { '$gt' => right } }.to_inquiry end def gt_any others @@ -100,7 +100,7 @@ def gt_all others end def lt right - { name => { '$lt' => right } } + { name => { '$lt' => right } }.to_inquiry end def lt_any others From 1fbbbaefd85d3cf312b732078eb7ad0718903686 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Mon, 6 Oct 2014 14:20:43 -0700 Subject: [PATCH 029/992] mongodb specs fixed for rails >= 4 --- spec/mongoid/dependencies_spec.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/spec/mongoid/dependencies_spec.rb b/spec/mongoid/dependencies_spec.rb index c94ee086b..83029ce14 100644 --- a/spec/mongoid/dependencies_spec.rb +++ b/spec/mongoid/dependencies_spec.rb @@ -1,6 +1,8 @@ -describe 'Ransack' do - it 'can be required without errors' do - output = `bundle exec ruby -e "require 'ransack'" 2>&1` - expect(output).to be_empty +unless ::Rails::VERSION::STRING >= '4' + describe 'Ransack' do + it 'can be required without errors' do + output = `bundle exec ruby -e "require 'ransack'" 2>&1` + expect(output).to be_empty + end end end From b81bf44dc1b66497969f1fa01995d5e8952367c3 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Mon, 6 Oct 2014 14:21:22 -0700 Subject: [PATCH 030/992] previous fix fixed correctly now --- spec/mongoid/dependencies_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/mongoid/dependencies_spec.rb b/spec/mongoid/dependencies_spec.rb index 83029ce14..71653b84b 100644 --- a/spec/mongoid/dependencies_spec.rb +++ b/spec/mongoid/dependencies_spec.rb @@ -1,4 +1,4 @@ -unless ::Rails::VERSION::STRING >= '4' +unless ::ActiveSupport::VERSION::STRING >= '4' describe 'Ransack' do it 'can be required without errors' do output = `bundle exec ruby -e "require 'ransack'" 2>&1` From ffeecac6edaa20d399082eeab0f247dfe81d1619 Mon Sep 17 00:00:00 2001 From: Timo Schilling Date: Sat, 18 Oct 2014 23:23:03 +0200 Subject: [PATCH 031/992] check exact matches before stripping (fixes #452) fix is written with support form @jonatack --- lib/ransack/nodes/grouping.rb | 10 ++++++---- spec/ransack/nodes/grouping_spec.rb | 6 ++++++ spec/support/schema.rb | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/ransack/nodes/grouping.rb b/lib/ransack/nodes/grouping.rb index 7da7d459a..057f2867d 100644 --- a/lib/ransack/nodes/grouping.rb +++ b/lib/ransack/nodes/grouping.rb @@ -126,13 +126,15 @@ def method_missing(method_id, *args) end def attribute_method?(name) - name = strip_predicate_and_index(name) - return true if @context.attribute_method?(name) - case name + stripped_name = strip_predicate_and_index(name) + return true if @context.attribute_method?(stripped_name) || + @context.attribute_method?(name) + case stripped_name when /^(g|c|m|groupings|conditions|combinator)=?$/ true else - name.split(/_and_|_or_/) + stripped_name + .split(/_and_|_or_/) .select { |n| !@context.attribute_method?(n) } .empty? end diff --git a/spec/ransack/nodes/grouping_spec.rb b/spec/ransack/nodes/grouping_spec.rb index 6d6fb0427..623891856 100644 --- a/spec/ransack/nodes/grouping_spec.rb +++ b/spec/ransack/nodes/grouping_spec.rb @@ -22,6 +22,12 @@ module Nodes expect(subject.attribute_method?('terms_and_conditions')).to be_true end end + + context "where the attributes ends with '_start'" do + it 'is true' do + expect(subject.attribute_method?('life_start')).to be_true + end + end end context 'for unknown attributes' do diff --git a/spec/support/schema.rb b/spec/support/schema.rb index 1b448d469..7823780d0 100644 --- a/spec/support/schema.rb +++ b/spec/support/schema.rb @@ -134,6 +134,7 @@ def self.create t.string :only_sort t.string :only_admin t.integer :salary + t.date :life_start t.boolean :awesome, default: false t.boolean :terms_and_conditions, default: false t.timestamps null: false From ed39f64e8b06501571151d9e693d2abefc11f58a Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Fri, 24 Oct 2014 10:41:37 +0200 Subject: [PATCH 032/992] Add specs for attribute names with _or_ and _end --- spec/ransack/nodes/grouping_spec.rb | 19 +++++++++++++++++-- spec/support/schema.rb | 3 +++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/spec/ransack/nodes/grouping_spec.rb b/spec/ransack/nodes/grouping_spec.rb index 623891856..9459b1ec7 100644 --- a/spec/ransack/nodes/grouping_spec.rb +++ b/spec/ransack/nodes/grouping_spec.rb @@ -2,7 +2,9 @@ module Ransack module Nodes + describe Grouping do + before do @g = 1 end @@ -17,17 +19,29 @@ module Nodes expect(subject.attribute_method?('name')).to be_true end - context "where the attribute contains '_and_'" do + context "when the attribute contains '_and_'" do it 'is true' do expect(subject.attribute_method?('terms_and_conditions')).to be_true end end - context "where the attributes ends with '_start'" do + context "when the attribute contains '_or_'" do + it 'is true' do + expect(subject.attribute_method?('true_or_false')).to be_true + end + end + + context "when the attribute ends with '_start'" do it 'is true' do expect(subject.attribute_method?('life_start')).to be_true end end + + context "when the attribute ends with '_end'" do + it 'is true' do + expect(subject.attribute_method?('stop_end')).to be_true + end + end end context 'for unknown attributes' do @@ -36,6 +50,7 @@ module Nodes end end end + end end end diff --git a/spec/support/schema.rb b/spec/support/schema.rb index 7823780d0..0ac6bb294 100644 --- a/spec/support/schema.rb +++ b/spec/support/schema.rb @@ -133,10 +133,13 @@ def self.create t.string :only_search t.string :only_sort t.string :only_admin + t.string :new_start + t.string :stop_end t.integer :salary t.date :life_start t.boolean :awesome, default: false t.boolean :terms_and_conditions, default: false + t.boolean :true_or_false, default: true t.timestamps null: false end From 533a6a1c5847c2b4519685a66427ff30e14f6129 Mon Sep 17 00:00:00 2001 From: Timo Schilling Date: Fri, 24 Oct 2014 11:28:28 +0200 Subject: [PATCH 033/992] add changelog entry for #454 --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d711c669..57ade2bbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,12 @@ henceforth should be documented here. *joeyates* +* Fix checks for `attribute_method?` for method that ends with a pradicate, + for example `_start` and `_end`. Now, a `life_start` attribute will be + recognized instead of raising a NoMethodError `life_start`. + + *Timo Schilling*, *Jon Atack* + ### Changed * Reduce object allocations and memory footprint (with a slight speed gain as From 64da8e17c0477c9b0e0261734a76797d3382d724 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sat, 25 Oct 2014 09:03:43 +0200 Subject: [PATCH 034/992] Add tests for start/not_start/end/not_end predicates --- spec/ransack/predicate_spec.rb | 80 ++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/spec/ransack/predicate_spec.rb b/spec/ransack/predicate_spec.rb index 176b67515..248015b94 100644 --- a/spec/ransack/predicate_spec.rb +++ b/spec/ransack/predicate_spec.rb @@ -162,6 +162,86 @@ module Ransack end end + describe 'start' do + it 'generates a LIKE query with value followed by %' do + @s.name_start = 'Er' + field = "#{quote_table_name("people")}.#{quote_column_name("name")}" + expect(@s.result.to_sql).to match /#{field} I?LIKE 'Er%'/ + end + + it "works with attribute names ending with '_start'" do + @s.new_start_start = 'hEy' + field = "#{quote_table_name("people")}.#{quote_column_name("new_start")}" + expect(@s.result.to_sql).to match /#{field} I?LIKE 'hEy%'/ + end + + it "works with attribute names ending with '_end'" do + @s.stop_end_start = 'begin' + field = "#{quote_table_name("people")}.#{quote_column_name("stop_end")}" + expect(@s.result.to_sql).to match /#{field} I?LIKE 'begin%'/ + end + end + + describe 'not_start' do + it 'generates a NOT LIKE query with value followed by %' do + @s.name_not_start = 'Eri' + field = "#{quote_table_name("people")}.#{quote_column_name("name")}" + expect(@s.result.to_sql).to match /#{field} NOT I?LIKE 'Eri%'/ + end + + it "works with attribute names ending with '_start'" do + @s.new_start_not_start = 'hEy' + field = "#{quote_table_name("people")}.#{quote_column_name("new_start")}" + expect(@s.result.to_sql).to match /#{field} NOT I?LIKE 'hEy%'/ + end + + it "works with attribute names ending with '_end'" do + @s.stop_end_not_start = 'begin' + field = "#{quote_table_name("people")}.#{quote_column_name("stop_end")}" + expect(@s.result.to_sql).to match /#{field} NOT I?LIKE 'begin%'/ + end + end + + describe 'end' do + it 'generates a LIKE query with value preceded by %' do + @s.name_end = 'Miller' + field = "#{quote_table_name("people")}.#{quote_column_name("name")}" + expect(@s.result.to_sql).to match /#{field} I?LIKE '%Miller'/ + end + + it "works with attribute names ending with '_start'" do + @s.new_start_end = 'finish' + field = "#{quote_table_name("people")}.#{quote_column_name("new_start")}" + expect(@s.result.to_sql).to match /#{field} I?LIKE '%finish'/ + end + + it "works with attribute names ending with '_end'" do + @s.stop_end_end = 'Ending' + field = "#{quote_table_name("people")}.#{quote_column_name("stop_end")}" + expect(@s.result.to_sql).to match /#{field} I?LIKE '%Ending'/ + end + end + + describe 'not_end' do + it 'generates a NOT LIKE query with value preceded by %' do + @s.name_not_end = 'Miller' + field = "#{quote_table_name("people")}.#{quote_column_name("name")}" + expect(@s.result.to_sql).to match /#{field} NOT I?LIKE '%Miller'/ + end + + it "works with attribute names ending with '_start'" do + @s.new_start_not_end = 'finish' + field = "#{quote_table_name("people")}.#{quote_column_name("new_start")}" + expect(@s.result.to_sql).to match /#{field} NOT I?LIKE '%finish'/ + end + + it "works with attribute names ending with '_end'" do + @s.stop_end_not_end = 'Ending' + field = "#{quote_table_name("people")}.#{quote_column_name("stop_end")}" + expect(@s.result.to_sql).to match /#{field} NOT I?LIKE '%Ending'/ + end + end + describe 'true' do it 'generates an equality condition for boolean true' do @s.awesome_true = true From b95518bb31d46d2e357b976c18c0bc2e5c5c9e3e Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sat, 25 Oct 2014 09:16:20 +0200 Subject: [PATCH 035/992] Update change log [skip ci] --- CHANGELOG.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57ade2bbb..aee7da3b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,10 +11,13 @@ henceforth should be documented here. *Caleb Land*, *James u007* -* Add test specs for the `lteq`, `lt`, `gteq`, and `gt` predicates. These are +* Add test specs for `lteq`, `lt`, `gteq` and `gt` predicates. These are also tested in Arel, but testing them in Ransack has proven useful to detect issues. +* Add test specs for `start`, `not_start`, `end` and `not_end` predicates, + with emphasis on cases when attribute names end with `_start` and `_end`. + *Jon Atack* ### Fixed @@ -31,15 +34,15 @@ henceforth should be documented here. *Jon Atack* -* Fix checks for `attribute_method?` for method names containing - `_and_` and `_or_`. Now, a `terms_and_conditions` attribute will be - recognized instead of running (failing) checks for `terms` and `conditions`. +* Improve `attribute_method?` parsing for method names containing `_and_` and + `_or_`. Attributes named like `foo_and_bar` or `foo_or_bar` are recognized + now instead of running failing checks for `foo` and `bar`. *joeyates* -* Fix checks for `attribute_method?` for method that ends with a pradicate, - for example `_start` and `_end`. Now, a `life_start` attribute will be - recognized instead of raising a NoMethodError `life_start`. +* Improve `attribute_method?` parsing for method names ending with a + predicate like `_start` and `_end`. For instance, a `life_start` attribute + is now recognized instead of raising a NoMethodError. *Timo Schilling*, *Jon Atack* From 5da6f50532d522338d9950da501415abb62905fa Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sun, 26 Oct 2014 23:21:01 +0100 Subject: [PATCH 036/992] Add semantic versioning to README [skip ci] --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 4c26ec388..15a8a3a3a 100644 --- a/README.md +++ b/README.md @@ -591,6 +591,16 @@ en: title: Old Ransack Namespaced Title ``` +## Semantic Versioning + +Ransack attempts to follow semantic versioning in the format of `x.y.z`, where: + +x stands for a major version (new features that are not backward-compatible) +y stands for a minor version (new features that are backward-compatible) +z stands for a patch (bug fixes) + +In other words: Major.Minor.Patch. + ## Contributions To support the project: From 2cee4dfbcef2210ec69b945b73aecfaf2cf851d0 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sun, 26 Oct 2014 23:22:41 +0100 Subject: [PATCH 037/992] Update change log, prepare release [skip ci] --- CHANGELOG.md | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aee7da3b7..986e5ca1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ This change log was started in August 2014. All notable changes to this project henceforth should be documented here. -## Unreleased +## Version 1.5.0 - 2014-10-26 ### Added * Add support for multiple sort fields and default orders in Ransack @@ -11,12 +11,26 @@ henceforth should be documented here. *Caleb Land*, *James u007* -* Add test specs for `lteq`, `lt`, `gteq` and `gt` predicates. These are - also tested in Arel, but testing them in Ransack has proven useful to - detect issues. +* Add tests for `lteq`, `lt`, `gteq` and `gt` predicates. They are also + tested in Arel, but testing them in Ransack has proven useful to detect + issues. -* Add test specs for `start`, `not_start`, `end` and `not_end` predicates, - with emphasis on cases when attribute names end with `_start` and `_end`. + *Jon Atack* + +* Add tests for unknown attribute names. + + *Joe Yates* + +* Add tests for attribute names containing '_or_' and '_and_'. + + *Joe Yates*, *Jon Atack* + +* Add tests for attribute names ending with '_start' and '_end'. + + *Jon Atack*, *Timo Schilling* + +* Add tests for `start`, `not_start`, `end` and `not_end` predicates, with + emphasis on cases when attribute names end with `_start` and `_end`. *Jon Atack* @@ -38,7 +52,7 @@ henceforth should be documented here. `_or_`. Attributes named like `foo_and_bar` or `foo_or_bar` are recognized now instead of running failing checks for `foo` and `bar`. - *joeyates* + *Joe Yates* * Improve `attribute_method?` parsing for method names ending with a predicate like `_start` and `_end`. For instance, a `life_start` attribute From a98189f42dc1dce80efb00ed0aa6203b3f0fe02e Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sun, 26 Oct 2014 23:23:09 +0100 Subject: [PATCH 038/992] Bump version to 1.5.0 --- lib/ransack/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ransack/version.rb b/lib/ransack/version.rb index 9c15d3834..56ec738cb 100644 --- a/lib/ransack/version.rb +++ b/lib/ransack/version.rb @@ -1,3 +1,3 @@ module Ransack - VERSION = "1.4.1" + VERSION = "1.5.0" end From 543bf6e437e34d030fb42365cfa00bea81ab0b6a Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sun, 26 Oct 2014 23:25:16 +0100 Subject: [PATCH 039/992] Fix formatting [skip ci] --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 15a8a3a3a..c70b7a68c 100644 --- a/README.md +++ b/README.md @@ -595,11 +595,11 @@ en: Ransack attempts to follow semantic versioning in the format of `x.y.z`, where: -x stands for a major version (new features that are not backward-compatible) -y stands for a minor version (new features that are backward-compatible) -z stands for a patch (bug fixes) +`x` stands for a major version (new features that are not backward-compatible). +`y` stands for a minor version (new features that are backward-compatible). +`z` stands for a patch (bug fixes). -In other words: Major.Minor.Patch. +In other words: `Major.Minor.Patch`. ## Contributions From b786fe2dfe16e8a4f13779dbd635f9e9011bc6ad Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Mon, 27 Oct 2014 21:55:02 +0100 Subject: [PATCH 040/992] Remove duplicate code --- spec/support/schema.rb | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/spec/support/schema.rb b/spec/support/schema.rb index 0ac6bb294..e6f0aedaa 100644 --- a/spec/support/schema.rb +++ b/spec/support/schema.rb @@ -66,21 +66,6 @@ def self.ransortable_attributes(auth_object = nil) end end - def self.ransackable_attributes(auth_object = nil) - if auth_object == :admin - column_names + _ransackers.keys - ['only_sort'] - else - column_names + _ransackers.keys - ['only_sort', 'only_admin'] - end - end - - def self.ransortable_attributes(auth_object = nil) - if auth_object == :admin - column_names + _ransackers.keys - ['only_search'] - else - column_names + _ransackers.keys - ['only_search', 'only_admin'] - end - end end class Article < ActiveRecord::Base From 63b0d2387ac1db02ee2e15242cd87359552620b0 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Mon, 27 Oct 2014 21:55:46 +0100 Subject: [PATCH 041/992] Use foo [skip ci] --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 986e5ca1c..b4d23298d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,7 +55,7 @@ henceforth should be documented here. *Joe Yates* * Improve `attribute_method?` parsing for method names ending with a - predicate like `_start` and `_end`. For instance, a `life_start` attribute + predicate like `_start` and `_end`. For instance, a `foo_start` attribute is now recognized instead of raising a NoMethodError. *Timo Schilling*, *Jon Atack* From 5f8621c573d79b3bd595c32e7f5bc6d00a26c49c Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Mon, 27 Oct 2014 22:14:47 +0100 Subject: [PATCH 042/992] Add base specs for search on fields with `_start` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit and `_end`. Add a failing spec for fields with `_and_` … that needs to be fixed. --- .../adapters/active_record/base_spec.rb | 43 +++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index 2a2b4ceda..4d9542008 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -130,15 +130,52 @@ def self.sane_adapter? end it "should function correctly when using fields with % in them" do - Person.create!(:name => "110%-er") + p = Person.create!(:name => "110%-er") s = Person.search(:name_cont => "10%") expect(s.result.exists?).to be true + expect(s.result.to_a).to eq [p] end it "should function correctly when using fields with backslashes in them" do - Person.create!(:name => "\\WINNER\\") + p = Person.create!(:name => "\\WINNER\\") s = Person.search(:name_cont => "\\WINNER\\") - expect(s.result.exists?).to be true + expect(s.result.to_a).to eq [p] + end + + it "should function correctly when an attribute name ends with '_start'" do + p = Person.create!(:new_start => 'Bar and foo', :name => 'Xiang') + + s = Person.search(:new_start_end => ' and foo') + expect(s.result.to_a).to eq [p] + + s = Person.search(:name_or_new_start_start => 'Xia') + expect(s.result.to_a).to eq [p] + + s = Person.search(:new_start_or_name_end => 'iang') + expect(s.result.to_a).to eq [p] + end + + it "should function correctly when an attribute name ends with '_end'" do + p = Person.create!(:stop_end => 'Foo and bar', :name => 'Marianne') + + s = Person.search(:stop_end_start => 'Foo and') + expect(s.result.to_a).to eq [p] + + s = Person.search(:stop_end_or_name_end => 'anne') + expect(s.result.to_a).to eq [p] + + s = Person.search(:name_or_stop_end_end => ' bar') + expect(s.result.to_a).to eq [p] + end + + it "should function correctly when an attribute name has 'and' in it" do + # FIXME: this test does not pass! + p = Person.create!(:terms_and_conditions => 'Accepted') + s = Person.search(:terms_and_conditions_eq => 'Accepted') + # search is not detecting the attribute + puts "Search not detecting the `terms_and_conditions` attribute: #{ + s.result.to_sql}" + # expect(s.result.to_a).to eq [p] end it 'allows sort by "only_sort" field' do From 2fb007b9712f5bcac9361d785775e943553e3075 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Tue, 28 Oct 2014 23:26:17 +0100 Subject: [PATCH 043/992] Remove redundant test [skip ci] --- spec/ransack/adapters/active_record/base_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index 4d9542008..74456aead 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -132,7 +132,6 @@ def self.sane_adapter? it "should function correctly when using fields with % in them" do p = Person.create!(:name => "110%-er") s = Person.search(:name_cont => "10%") - expect(s.result.exists?).to be true expect(s.result.to_a).to eq [p] end @@ -175,7 +174,7 @@ def self.sane_adapter? # search is not detecting the attribute puts "Search not detecting the `terms_and_conditions` attribute: #{ s.result.to_sql}" - # expect(s.result.to_a).to eq [p] + # expect(s.result.to_a).to eq [p] end it 'allows sort by "only_sort" field' do From 0e9f56cbba31f04714acf8cd0567215707fe87d9 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Wed, 29 Oct 2014 23:26:08 +0100 Subject: [PATCH 044/992] More info in test output re problem to fix [skip ci] --- spec/ransack/adapters/active_record/base_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index 74456aead..897be24eb 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -172,8 +172,9 @@ def self.sane_adapter? p = Person.create!(:terms_and_conditions => 'Accepted') s = Person.search(:terms_and_conditions_eq => 'Accepted') # search is not detecting the attribute - puts "Search not detecting the `terms_and_conditions` attribute: #{ - s.result.to_sql}" + puts " + FIXME: Search not detecting the `terms_and_conditions` attribute in + base_spec.rb, line 177: #{s.result.to_sql}" # expect(s.result.to_a).to eq [p] end From 06ab10070d2641ce50afe6c61ce08f7d2955beb2 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Wed, 29 Oct 2014 23:37:34 +0100 Subject: [PATCH 045/992] Fix string constants Fixes #457 (hopefully) --- lib/ransack/adapters/active_record/context.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/ransack/adapters/active_record/context.rb b/lib/ransack/adapters/active_record/context.rb index a04af9623..481cabf81 100644 --- a/lib/ransack/adapters/active_record/context.rb +++ b/lib/ransack/adapters/active_record/context.rb @@ -158,7 +158,7 @@ def get_parent_and_attribute_name(str, parent = @base) 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 } + klass.reflect_on_all_associations.detect { |a| a.name.to_s == str } end def join_dependency(relation) @@ -175,13 +175,13 @@ def build_join_dependency(relation) buckets = relation.joins_values.group_by do |join| case join when String - STRING_JOIN + Ransack::Constants::STRING_JOIN when Hash, Symbol, Array - ASSOCIATION_JOIN + Ransack::Constants::ASSOCIATION_JOIN when JoinDependency, JoinDependency::JoinAssociation - STASHED_JOIN + Ransack::Constants::STASHED_JOIN when Arel::Nodes::Join - JOIN_NODE + Ransack::Constants::JOIN_NODE else raise 'unknown class: %s' % join.class.name end From b715864ed8bea3f486a156d012285d9ded2f0bc9 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Thu, 30 Oct 2014 20:13:05 +0100 Subject: [PATCH 046/992] Update change log [skip ci] --- CHANGELOG.md | 35 +++++++++++++++++++++++++++++++---- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b4d23298d..cc349ae81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ This change log was started in August 2014. All notable changes to this project henceforth should be documented here. +## Version 1.5.1 - 2014-10-30 +### Added + +* Add base specs for search on fields with `_start` and `_end`. + + *Jon Atack* + +* Add a failing spec for detecting attribute fields containing `_and_` that + needs to be fixed. Method names containing `_and_` and `_or_` are still not + parsed/detected correctly. + + *Jon Atack* + +### Fixed + +* Fix a regression caused by incorrect string constants in context.rb. + + *Kazuhiro NISHIYAMA* + +### Changed + +* Remove duplicate code in spec/support/schema.rb. + + *Jon Atack* + + ## Version 1.5.0 - 2014-10-26 ### Added @@ -48,13 +74,14 @@ henceforth should be documented here. *Jon Atack* -* Improve `attribute_method?` parsing for method names containing `_and_` and - `_or_`. Attributes named like `foo_and_bar` or `foo_or_bar` are recognized - now instead of running failing checks for `foo` and `bar`. +* Improve `attribute_method?` parsing for attribute names containing `_and_` + and `_or_`. Attributes named like `foo_and_bar` or `foo_or_bar` are + recognized now instead of running failing checks for `foo` and `bar`. + CORRECTION October 28, 2014: this feature is still not working! *Joe Yates* -* Improve `attribute_method?` parsing for method names ending with a +* Improve `attribute_method?` parsing for attribute names ending with a predicate like `_start` and `_end`. For instance, a `foo_start` attribute is now recognized instead of raising a NoMethodError. From a92ad049018c3fcdb7f51bb2148288f0c42a2783 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Thu, 30 Oct 2014 20:19:05 +0100 Subject: [PATCH 047/992] Fix test, remove Rails 5 warning message --- spec/ransack/adapters/active_record/base_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index 897be24eb..b166f0d93 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -169,12 +169,12 @@ def self.sane_adapter? it "should function correctly when an attribute name has 'and' in it" do # FIXME: this test does not pass! - p = Person.create!(:terms_and_conditions => 'Accepted') - s = Person.search(:terms_and_conditions_eq => 'Accepted') + p = Person.create!(:terms_and_conditions => true) + s = Person.search(:terms_and_conditions_eq => true) # search is not detecting the attribute puts " FIXME: Search not detecting the `terms_and_conditions` attribute in - base_spec.rb, line 177: #{s.result.to_sql}" + base_spec.rb, line 178: #{s.result.to_sql}" # expect(s.result.to_a).to eq [p] end From 25255d95d41a6a82fcea48e1a640d56b5a172110 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Thu, 30 Oct 2014 20:19:16 +0100 Subject: [PATCH 048/992] Bump version from 1.5.0 to 1.5.1 (bug fix release) [skip ci] --- lib/ransack/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ransack/version.rb b/lib/ransack/version.rb index 56ec738cb..a42d0edc8 100644 --- a/lib/ransack/version.rb +++ b/lib/ransack/version.rb @@ -1,3 +1,3 @@ module Ransack - VERSION = "1.5.0" + VERSION = "1.5.1" end From 314e28f9af38a37c2ba46ec53f14a7e63daa41a3 Mon Sep 17 00:00:00 2001 From: Joshua Kovach Date: Thu, 30 Oct 2014 13:24:43 -0400 Subject: [PATCH 049/992] Allow passing stringy booleans as scope args This implements the solution discussed in #403. Fixes #403. --- CHANGELOG.md | 18 ++++++++++++------ README.md | 6 +++--- lib/ransack/search.rb | 12 +++++++++++- .../adapters/active_record/base_spec.rb | 10 ++++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc349ae81..b3e0c8d89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,13 @@ This change log was started in August 2014. All notable changes to this project henceforth should be documented here. +## Master (Unreleased) +### Fixed + +* Add support for passing stringy booleans for ransackable scopes. ([pull request](https://github.com/activerecord-hackery/ransack/pull/460)). + + *Josh Kovach* + ## Version 1.5.1 - 2014-10-30 ### Added @@ -27,14 +34,13 @@ henceforth should be documented here. *Jon Atack* - ## Version 1.5.0 - 2014-10-26 ### Added * Add support for multiple sort fields and default orders in Ransack `sort_link` helpers ([pull request](https://github.com/activerecord-hackery/ransack/pull/438)). - + *Caleb Land*, *James u007* * Add tests for `lteq`, `lt`, `gteq` and `gt` predicates. They are also @@ -44,15 +50,15 @@ henceforth should be documented here. *Jon Atack* * Add tests for unknown attribute names. - + *Joe Yates* * Add tests for attribute names containing '_or_' and '_and_'. - + *Joe Yates*, *Jon Atack* * Add tests for attribute names ending with '_start' and '_end'. - + *Jon Atack*, *Timo Schilling* * Add tests for `start`, `not_start`, `end` and `not_end` predicates, with @@ -164,7 +170,7 @@ henceforth should be documented here. * Rewrite much of the Ransack README documentation, including the Associations section code examples and the Authorizations section detailing how to whitelist attributes, associations, sorts and scopes. - + *Jon Atack* ## Version 1.3.0 - 2014-08-23 diff --git a/README.md b/README.md index c70b7a68c..4d47b8003 100644 --- a/README.md +++ b/README.md @@ -461,6 +461,8 @@ Employee.search({ active: true, hired_since: '2013-01-01' }) Employee.search({ salary_gt: 100_000 }, { auth_object: current_user }) ``` +If the `true` value is being passed via url params or by some other mechanism that will convert it to a string (i.e. `"active" => "true"`), the true value will *not* be passed to the scope. If you want to pass a `'true'` string to the scope, you should wrap it in an array (i.e. `"active" => ['true']`). + Scopes are a recent addition to Ransack and currently have a few caveats: First, a scope involving child associations needs to be defined in the parent table model, not in the child model. Second, scopes with an array as an @@ -470,9 +472,7 @@ wrapped in an array to function (see which is not compatible with Ransack form helpers. For this use case, it may be better for now to use [ransackers] (https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers) instead -where feasible. Finally, there is also -[this issue](https://github.com/activerecord-hackery/ransack/issues/403) -to be aware of. Pull requests with solutions and tests are welcome! +where feasible. Pull requests with solutions and tests are welcome! ### Grouping queries by OR instead of AND diff --git a/lib/ransack/search.rb b/lib/ransack/search.rb index 34dde5410..1e5d15b8f 100644 --- a/lib/ransack/search.rb +++ b/lib/ransack/search.rb @@ -125,7 +125,17 @@ def add_scope(key, args) else @scope_args[key] = args end - @context.chain_scope(key, args) + @context.chain_scope(key, scope_args(args)) + end + + def scope_args(args) + if Ransack::Constants::TRUE_VALUES.include? args + true + elsif Ransack::Constants::FALSE_VALUES.include? args + false + else + args + end end def collapse_multiparameter_attributes!(attrs) diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index b166f0d93..8e0105e7b 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -28,6 +28,11 @@ module ActiveRecord search.result.to_sql.should include "active = 1" end + it "applies stringy true scopes" do + search = Person.search('active' => 'true') + search.result.to_sql.should include "active = 1" + end + it "ignores unlisted scopes" do search = Person.search('restricted' => true) search.result.to_sql.should_not include "restricted" @@ -38,6 +43,11 @@ module ActiveRecord search.result.to_sql.should_not include "active" end + it "ignores stringy false scopes" do + search = Person.search('active' => 'false') + search.result.to_sql.should_not include "active" + end + it "passes values to scopes" do search = Person.search('over_age' => 18) search.result.to_sql.should include "age > 18" From e38edb9560484e2d17b04eb980e17ca89139d3ba Mon Sep 17 00:00:00 2001 From: Joshua Kovach Date: Fri, 31 Oct 2014 11:53:37 -0400 Subject: [PATCH 050/992] Add ability to pass boolean scope args in array --- lib/ransack/search.rb | 10 +++++++--- spec/ransack/adapters/active_record/base_spec.rb | 12 +++++++++++- spec/support/schema.rb | 1 + 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/lib/ransack/search.rb b/lib/ransack/search.rb index 1e5d15b8f..090a7e554 100644 --- a/lib/ransack/search.rb +++ b/lib/ransack/search.rb @@ -123,12 +123,16 @@ def add_scope(key, args) if @context.scope_arity(key) == 1 @scope_args[key] = args.is_a?(Array) ? args[0] : args else - @scope_args[key] = args + @scope_args[key] = args.is_a?(Array) ? sanitized_scope_args(args) : args end - @context.chain_scope(key, scope_args(args)) + @context.chain_scope(key, sanitized_scope_args(args)) end - def scope_args(args) + def sanitized_scope_args(args) + if args.is_a?(Array) + args = args.map(&method(:sanitized_scope_args)) + end + if Ransack::Constants::TRUE_VALUES.include? args true elsif Ransack::Constants::FALSE_VALUES.include? args diff --git a/spec/ransack/adapters/active_record/base_spec.rb b/spec/ransack/adapters/active_record/base_spec.rb index 8e0105e7b..dd3c3aad3 100644 --- a/spec/ransack/adapters/active_record/base_spec.rb +++ b/spec/ransack/adapters/active_record/base_spec.rb @@ -20,7 +20,7 @@ module ActiveRecord context 'with scopes' do before do - Person.stub :ransackable_scopes => [:active, :over_age] + Person.stub :ransackable_scopes => [:active, :over_age, :of_age] end it "applies true scopes" do @@ -33,6 +33,16 @@ module ActiveRecord search.result.to_sql.should include "active = 1" end + it "applies stringy boolean scopes with true value in an array" do + search = Person.search('of_age' => ['true']) + search.result.to_sql.should include "age >= 18" + end + + it "applies stringy boolean scopes with false value in an array" do + search = Person.search('of_age' => ['false']) + search.result.to_sql.should include "age < 18" + end + it "ignores unlisted scopes" do search = Person.search('restricted' => true) search.result.to_sql.should_not include "restricted" diff --git a/spec/support/schema.rb b/spec/support/schema.rb index e6f0aedaa..52073ed84 100644 --- a/spec/support/schema.rb +++ b/spec/support/schema.rb @@ -39,6 +39,7 @@ class Person < ActiveRecord::Base scope :restricted, lambda { where("restricted = 1") } scope :active, lambda { where("active = 1") } scope :over_age, lambda { |y| where(["age > ?", y]) } + scope :of_age, lambda { |of_age| of_age ? where("age >= ?", 18) : where("age < ?", 18) } ransacker :reversed_name, :formatter => proc { |v| v.reverse } do |parent| parent.table[:name] From 903d58840f62257529d967de81617bcf75754fbf Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Fri, 31 Oct 2014 11:50:39 -0700 Subject: [PATCH 051/992] Common constants of adapters moved to ransack/constants.rb --- .../active_record/ransack/constants.rb | 17 ----------------- lib/ransack/constants.rb | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/lib/ransack/adapters/active_record/ransack/constants.rb b/lib/ransack/adapters/active_record/ransack/constants.rb index 116e5f942..168331e90 100644 --- a/lib/ransack/adapters/active_record/ransack/constants.rb +++ b/lib/ransack/adapters/active_record/ransack/constants.rb @@ -1,23 +1,6 @@ module Ransack module Constants - ASC = 'asc'.freeze - DESC = 'desc'.freeze - ASC_ARROW = '▲'.freeze - DESC_ARROW = '▼'.freeze - OR = 'or'.freeze - AND = 'and'.freeze - SORT = 'sort'.freeze - SORT_LINK = 'sort_link'.freeze - SEARCH = 'search'.freeze - DEFAULT_SEARCH_KEY = 'q'.freeze - ATTRIBUTE = 'attribute'.freeze DISTINCT = 'DISTINCT '.freeze - COMBINATOR = 'combinator'.freeze - SPACE = ' '.freeze - COMMA_SPACE = ', '.freeze - UNDERSCORE = '_'.freeze - NON_BREAKING_SPACE = ' '.freeze - EMPTY = ''.freeze STRING_JOIN = 'string_join'.freeze ASSOCIATION_JOIN = 'association_join'.freeze STASHED_JOIN = 'stashed_join'.freeze diff --git a/lib/ransack/constants.rb b/lib/ransack/constants.rb index 1e2c80b1d..0d701c1a7 100644 --- a/lib/ransack/constants.rb +++ b/lib/ransack/constants.rb @@ -1,5 +1,23 @@ module Ransack module Constants + ASC = 'asc'.freeze + DESC = 'desc'.freeze + ASC_ARROW = '▲'.freeze + DESC_ARROW = '▼'.freeze + OR = 'or'.freeze + AND = 'and'.freeze + SORT = 'sort'.freeze + SORT_LINK = 'sort_link'.freeze + SEARCH = 'search'.freeze + DEFAULT_SEARCH_KEY = 'q'.freeze + ATTRIBUTE = 'attribute'.freeze + COMBINATOR = 'combinator'.freeze + SPACE = ' '.freeze + COMMA_SPACE = ', '.freeze + UNDERSCORE = '_'.freeze + NON_BREAKING_SPACE = ' '.freeze + EMPTY = ''.freeze + TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE'].to_set FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE'].to_set BOOLEAN_VALUES = TRUE_VALUES + FALSE_VALUES From f174a4ff5dfcdc1a85e24667033b58eda57b68b3 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sat, 1 Nov 2014 21:50:49 +0100 Subject: [PATCH 052/992] Wrap 80 characters [skip ci] --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d47b8003..73d1503f5 100644 --- a/README.md +++ b/README.md @@ -461,7 +461,10 @@ Employee.search({ active: true, hired_since: '2013-01-01' }) Employee.search({ salary_gt: 100_000 }, { auth_object: current_user }) ``` -If the `true` value is being passed via url params or by some other mechanism that will convert it to a string (i.e. `"active" => "true"`), the true value will *not* be passed to the scope. If you want to pass a `'true'` string to the scope, you should wrap it in an array (i.e. `"active" => ['true']`). +If the `true` value is being passed via url params or by some other mechanism +that will convert it to a string (i.e. `"active" => "true"`), the true value +will *not* be passed to the scope. If you want to pass a `'true'` string to the +scope, you should wrap it in an array (i.e. `"active" => ['true']`). Scopes are a recent addition to Ransack and currently have a few caveats: First, a scope involving child associations needs to be defined in the parent From d6eb50cf3ec4e5c68b93305063ecdf73f0b79b62 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sat, 1 Nov 2014 21:51:10 +0100 Subject: [PATCH 053/992] Update change log [skip ci] --- CHANGELOG.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b3e0c8d89..55c9ccfb6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,13 +3,24 @@ This change log was started in August 2014. All notable changes to this project henceforth should be documented here. ## Master (Unreleased) +### Added + ### Fixed -* Add support for passing stringy booleans for ransackable scopes. ([pull request](https://github.com/activerecord-hackery/ransack/pull/460)). +* Add support for passing stringy booleans for ransackable scopes ( + [pull request](https://github.com/activerecord-hackery/ransack/pull/460)). *Josh Kovach* +### Changed + ## Version 1.5.1 - 2014-10-30 +### Fixed + +* Fix a regression caused by incorrect string constants in `context.rb`. + + *Kazuhiro Nishiyama* + ### Added * Add base specs for search on fields with `_start` and `_end`. @@ -17,20 +28,14 @@ henceforth should be documented here. *Jon Atack* * Add a failing spec for detecting attribute fields containing `_and_` that - needs to be fixed. Method names containing `_and_` and `_or_` are still not - parsed/detected correctly. + needs to be fixed. Attribute names containing `_and_` and `_or_` are still + not parsed/detected correctly. *Jon Atack* -### Fixed - -* Fix a regression caused by incorrect string constants in context.rb. - - *Kazuhiro NISHIYAMA* - ### Changed -* Remove duplicate code in spec/support/schema.rb. +* Remove duplicate code in `spec/support/schema.rb`. *Jon Atack* @@ -101,7 +106,6 @@ henceforth should be documented here. *Jon Atack* - ## Version 1.4.1 - 2014-09-23 ### Fixed @@ -109,7 +113,6 @@ henceforth should be documented here. *Jon Atack* - ## Version 1.4.0 - 2014-09-23 ### Added From 6383963ebbe82cd16a13dd69535ee6ad82237dec Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sun, 2 Nov 2014 01:10:34 +0100 Subject: [PATCH 054/992] Use newer Ruby hash syntax [skip ci] --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 73d1503f5..5d5730ed3 100644 --- a/README.md +++ b/README.md @@ -462,9 +462,10 @@ Employee.search({ salary_gt: 100_000 }, { auth_object: current_user }) ``` If the `true` value is being passed via url params or by some other mechanism -that will convert it to a string (i.e. `"active" => "true"`), the true value -will *not* be passed to the scope. If you want to pass a `'true'` string to the -scope, you should wrap it in an array (i.e. `"active" => ['true']`). +that will convert it to a string (i.e. `active: 'true'` instead of +`active: true`), the true value will *not* be passed to the scope. If you want +to pass a `'true'` string to the scope, you should wrap it in an array (i.e. +`active: ['true']`). Scopes are a recent addition to Ransack and currently have a few caveats: First, a scope involving child associations needs to be defined in the parent @@ -474,7 +475,7 @@ wrapped in an array to function (see [this issue](https://github.com/activerecord-hackery/ransack/issues/404)), which is not compatible with Ransack form helpers. For this use case, it may be better for now to use [ransackers] -(https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers) instead +(https://github.com/activerecord-hackery/ransack/wiki/Using-Ransackers) instead, where feasible. Pull requests with solutions and tests are welcome! ### Grouping queries by OR instead of AND From d890de82073de6ce87f77c64a5ee94ceecd303b4 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sun, 2 Nov 2014 01:11:52 +0100 Subject: [PATCH 055/992] Remove extra space [skip ci] --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 55c9ccfb6..dbe93abe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,8 +7,8 @@ henceforth should be documented here. ### Fixed -* Add support for passing stringy booleans for ransackable scopes ( - [pull request](https://github.com/activerecord-hackery/ransack/pull/460)). +* Add support and tests for passing stringy booleans for ransackable scopes + ([pull request](https://github.com/activerecord-hackery/ransack/pull/460)). *Josh Kovach* From c3a93d6b43faa8b6e5123fc95a1121ffb358e00c Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Sun, 2 Nov 2014 01:15:23 +0100 Subject: [PATCH 056/992] Patch Rails commit c1a118a Fixes #461. It would be preferable to find a better way to solve this. --- lib/ransack/helpers/form_builder.rb | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/ransack/helpers/form_builder.rb b/lib/ransack/helpers/form_builder.rb index def9f00b8..5f86128a2 100644 --- a/lib/ransack/helpers/form_builder.rb +++ b/lib/ransack/helpers/form_builder.rb @@ -1,5 +1,19 @@ require 'action_view' +# This patch is needed since this Rails commit: +# https://github.com/rails/rails/commit/c1a118a +# +# TODO: Find a better a better to solve this. +# +module ActionView::Helpers::Tags + class Base + private + def value(object) + object.send @method_name if object # use send instead of public_send + end + end +end + RANSACK_FORM_BUILDER = 'RANSACK_FORM_BUILDER'.freeze require 'simple_form' if @@ -126,9 +140,8 @@ def predicate_select(options = {}, html_options = {}) end end collection = keys.map { |k| [k, Translate.predicate(k)] } - object.predicate ||= Predicate.named(default) if can_use_default?( - default, :predicate, keys - ) + object.predicate ||= Predicate.named(default) if + can_use_default?(default, :predicate, keys) template_collection_select(:p, collection, options, html_options) end From 6041ec4188a429ff2dfdb622730a8d40a3c2a01f Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sun, 2 Nov 2014 11:14:12 -0800 Subject: [PATCH 057/992] Added task rake mongoid_console --- Rakefile | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Rakefile b/Rakefile index cc76efa8a..9d533d613 100644 --- a/Rakefile +++ b/Rakefile @@ -28,4 +28,16 @@ task :console do require 'console' ARGV.clear IRB.start -end \ No newline at end of file +end + +desc "Open an irb session with Ransack, Mongoid and the sample data used in specs" +task :mongoid_console do + require 'irb' + require 'irb/completion' + require 'pry' + require 'mongoid' + require File.expand_path('../lib/ransack.rb', __FILE__) + require File.expand_path('../spec/mongoid/support/schema.rb', __FILE__) + ARGV.clear + Pry.start +end From 378c7d59dd65219c51bd67487ce8b34141de90c1 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sun, 2 Nov 2014 11:14:37 -0800 Subject: [PATCH 058/992] Fixed mongoid search by id --- lib/ransack/adapters/mongoid/context.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ransack/adapters/mongoid/context.rb b/lib/ransack/adapters/mongoid/context.rb index c9272b0e9..a99985235 100644 --- a/lib/ransack/adapters/mongoid/context.rb +++ b/lib/ransack/adapters/mongoid/context.rb @@ -36,6 +36,8 @@ def type_for(attr) # when :decimal # else # :string + name = '_id' if name == 'id' + t = object.klass.fields[name].type t.to_s.demodulize.underscore.to_sym From 7088c9e537edde2a0c2874c2505d0d3613e3431a Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sun, 2 Nov 2014 11:20:08 -0800 Subject: [PATCH 059/992] added specs: mongoid search by id --- spec/mongoid/adapters/mongoid/base_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/mongoid/adapters/mongoid/base_spec.rb b/spec/mongoid/adapters/mongoid/base_spec.rb index 5d97bceba..b3625a8b9 100644 --- a/spec/mongoid/adapters/mongoid/base_spec.rb +++ b/spec/mongoid/adapters/mongoid/base_spec.rb @@ -198,6 +198,12 @@ def self.sane_adapter? s = Person.search(:only_admin_eq => 'htimS cirA') expect(s.result.selector).to eq({}) end + + it 'searches by id' do + ids = ['some_bson_id', 'another_bson_id'] + s = Person.search(:id_in => ids) + expect(s.result.selector).to eq({ '_id' => { '$in' => ids } }) + end end describe '#ransackable_attributes' do From e90ffd1ed970372b1df93e23e34ff83289478ed4 Mon Sep 17 00:00:00 2001 From: Zhomart Mukhamejanov Date: Sun, 2 Nov 2014 11:35:59 -0800 Subject: [PATCH 060/992] fixed bug after last merging with master. --- .../adapters/active_record/ransack/context.rb | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/lib/ransack/adapters/active_record/ransack/context.rb b/lib/ransack/adapters/active_record/ransack/context.rb index 2300f8a7f..6d7a966fc 100644 --- a/lib/ransack/adapters/active_record/ransack/context.rb +++ b/lib/ransack/adapters/active_record/ransack/context.rb @@ -28,7 +28,7 @@ def initialize(object, options = {}) @join_type = options[:join_type] || Polyamorous::OuterJoin @search_key = options[:search_key] || Ransack.options[:search_key] - if ::ActiveRecord::VERSION::STRING >= "4.1" + if ::ActiveRecord::VERSION::STRING >= "4.1".freeze @base = @join_dependency.join_root @engine = @base.base_klass.arel_engine else @@ -48,30 +48,17 @@ def initialize(object, options = {}) end def klassify(obj) - @object = relation_for(object) - @klass = @object.klass - @join_dependency = join_dependency(@object) - @join_type = options[:join_type] || Polyamorous::OuterJoin - @search_key = options[:search_key] || Ransack.options[:search_key] - - if ::ActiveRecord::VERSION::STRING >= "4.1".freeze - @base = @join_dependency.join_root - @engine = @base.base_klass.arel_engine + if Class === obj && ::ActiveRecord::Base > obj + obj + elsif obj.respond_to? :klass + obj.klass + elsif obj.respond_to? :active_record # Rails 3 + obj.active_record + elsif obj.respond_to? :base_klass # Rails 4 + obj.base_klass else - @base = @join_dependency.join_base - @engine = @base.arel_engine - end - - @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) - if parent && attr_name - hash[key] = [parent, attr_name] - end + raise ArgumentError, "Don't know how to klassify #{obj.inspect}" end end - end end From 4a46098ebd4fab0c9b518d1f22606ff3ce3e6f07 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Mon, 3 Nov 2014 23:56:48 +0100 Subject: [PATCH 061/992] Grammar fix [skip ci] --- lib/ransack/helpers/form_builder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ransack/helpers/form_builder.rb b/lib/ransack/helpers/form_builder.rb index 5f86128a2..c13c6be80 100644 --- a/lib/ransack/helpers/form_builder.rb +++ b/lib/ransack/helpers/form_builder.rb @@ -3,7 +3,7 @@ # This patch is needed since this Rails commit: # https://github.com/rails/rails/commit/c1a118a # -# TODO: Find a better a better to solve this. +# TODO: Find a better way to solve this. # module ActionView::Helpers::Tags class Base From 417b703fd0800348209a5cfb237afefa6b475466 Mon Sep 17 00:00:00 2001 From: Jon Atack Date: Mon, 3 Nov 2014 23:57:15 +0100 Subject: [PATCH 062/992] Maintain Ruby 1.8 compat for now [skip ci] --- spec/ransack/helpers/form_builder_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/ransack/helpers/form_builder_spec.rb b/spec/ransack/helpers/form_builder_spec.rb index b02bdd756..63fe938a3 100644 --- a/spec/ransack/helpers/form_builder_spec.rb +++ b/spec/ransack/helpers/form_builder_spec.rb @@ -132,7 +132,7 @@ module Helpers end end it 'filters predicates with multi-value :only' do - html = @f.predicate_select only: [:eq, :lt] + html = @f.predicate_select :only => [:eq, :lt] Predicate.names.reject { |k| k =~ /^(eq|lt)/ }.each do |key| expect(html).not_to match /