From 24e0f1c974fe26caffed8147c300d9672e3172f6 Mon Sep 17 00:00:00 2001 From: Greg Molnar Date: Mon, 25 Jun 2018 20:50:14 +0200 Subject: [PATCH] move mongoid support to a separate gem --- Gemfile | 8 - README.md | 1 + Rakefile | 23 +- lib/ransack/adapters/mongoid.rb | 15 - lib/ransack/adapters/mongoid/3.2/.gitkeep | 0 .../adapters/mongoid/attributes/attribute.rb | 37 -- .../mongoid/attributes/order_predications.rb | 17 - .../mongoid/attributes/predications.rb | 141 ------ lib/ransack/adapters/mongoid/base.rb | 134 ------ lib/ransack/adapters/mongoid/context.rb | 212 --------- lib/ransack/adapters/mongoid/inquiry_hash.rb | 23 - .../adapters/mongoid/ransack/constants.rb | 88 ---- .../adapters/mongoid/ransack/context.rb | 59 --- .../mongoid/ransack/nodes/condition.rb | 22 - .../adapters/mongoid/ransack/translate.rb | 13 - .../adapters/mongoid/ransack/visitor.rb | 18 - lib/ransack/adapters/mongoid/table.rb | 35 -- spec/mongoid/adapters/mongoid/base_spec.rb | 314 ------------ spec/mongoid/adapters/mongoid/context_spec.rb | 56 --- spec/mongoid/configuration_spec.rb | 162 ------- spec/mongoid/dependencies_spec.rb | 8 - spec/mongoid/helpers/ransack_helper.rb | 11 - spec/mongoid/nodes/condition_spec.rb | 49 -- spec/mongoid/nodes/grouping_spec.rb | 13 - spec/mongoid/predicate_spec.rb | 155 ------ spec/mongoid/search_spec.rb | 445 ------------------ spec/mongoid/support/mongoid.yml | 11 - spec/mongoid/support/schema.rb | 135 ------ spec/mongoid/translate_spec.rb | 14 - spec/mongoid_spec_helper.rb | 63 --- 30 files changed, 2 insertions(+), 2280 deletions(-) delete mode 100644 lib/ransack/adapters/mongoid.rb delete mode 100644 lib/ransack/adapters/mongoid/3.2/.gitkeep delete mode 100644 lib/ransack/adapters/mongoid/attributes/attribute.rb delete mode 100644 lib/ransack/adapters/mongoid/attributes/order_predications.rb delete mode 100644 lib/ransack/adapters/mongoid/attributes/predications.rb delete mode 100644 lib/ransack/adapters/mongoid/base.rb delete mode 100644 lib/ransack/adapters/mongoid/context.rb delete mode 100644 lib/ransack/adapters/mongoid/inquiry_hash.rb delete mode 100644 lib/ransack/adapters/mongoid/ransack/constants.rb delete mode 100644 lib/ransack/adapters/mongoid/ransack/context.rb delete mode 100644 lib/ransack/adapters/mongoid/ransack/nodes/condition.rb delete mode 100644 lib/ransack/adapters/mongoid/ransack/translate.rb delete mode 100644 lib/ransack/adapters/mongoid/ransack/visitor.rb delete mode 100644 lib/ransack/adapters/mongoid/table.rb delete mode 100644 spec/mongoid/adapters/mongoid/base_spec.rb delete mode 100644 spec/mongoid/adapters/mongoid/context_spec.rb delete mode 100644 spec/mongoid/configuration_spec.rb delete mode 100644 spec/mongoid/dependencies_spec.rb delete mode 100644 spec/mongoid/helpers/ransack_helper.rb delete mode 100644 spec/mongoid/nodes/condition_spec.rb delete mode 100644 spec/mongoid/nodes/grouping_spec.rb delete mode 100644 spec/mongoid/predicate_spec.rb delete mode 100644 spec/mongoid/search_spec.rb delete mode 100644 spec/mongoid/support/mongoid.yml delete mode 100644 spec/mongoid/support/schema.rb delete mode 100644 spec/mongoid/translate_spec.rb delete mode 100644 spec/mongoid_spec_helper.rb diff --git a/Gemfile b/Gemfile index 21d6035..2118fca 100644 --- a/Gemfile +++ b/Gemfile @@ -40,14 +40,6 @@ else end end -if ENV['DB'] =~ /mongoid4/ - gem 'mongoid', '~> 4.0.0', require: false -end - -if ENV['DB'] =~ /mongoid5/ - gem 'mongoid', '~> 5.0.0', require: false -end - group :test do # TestUnit was removed from Ruby 2.2 but still needed for testing Rails 3.x. gem 'test-unit', '~> 3.0' if RUBY_VERSION >= '2.2' diff --git a/README.md b/README.md index 468debb..53e9d59 100644 --- a/README.md +++ b/README.md @@ -875,6 +875,7 @@ en: ## Mongoid +Mongoid support has been moved to its own gem at [ransack-mongoid](github.com/activerecord-hackery/ransack-mongoid). Ransack works with Mongoid in the same way as Active Record, except that with Mongoid, associations are not currently supported. Demo source code may be found [here](https://github.com/Zhomart/ransack-mongodb-demo). A `result` method diff --git a/Rakefile b/Rakefile index b05a526..45bcb17 100644 --- a/Rakefile +++ b/Rakefile @@ -11,17 +11,8 @@ RSpec::Core::RakeTask.new(:spec) do |rspec| # rspec.rspec_opts = ['--backtrace'] end -RSpec::Core::RakeTask.new(:mongoid) do |rspec| - ENV['SPEC'] = 'spec/mongoid/**/*_spec.rb' - rspec.rspec_opts = ['--backtrace'] -end - task :default do - if ENV['DB'] =~ /mongoid/ - Rake::Task["mongoid"].invoke - else - Rake::Task["spec"].invoke - end + Rake::Task["spec"].invoke end desc "Open an irb session with Ransack and the sample data used in specs" @@ -31,15 +22,3 @@ task :console do ARGV.clear Pry.start 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 diff --git a/lib/ransack/adapters/mongoid.rb b/lib/ransack/adapters/mongoid.rb deleted file mode 100644 index 84e24ba..0000000 --- a/lib/ransack/adapters/mongoid.rb +++ /dev/null @@ -1,15 +0,0 @@ -require '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 -when /^3\.2\./ - require 'ransack/adapters/mongoid/3.2/context' -else - require 'ransack/adapters/mongoid/context' -end - -Ransack::SUPPORTS_ATTRIBUTE_ALIAS = false diff --git a/lib/ransack/adapters/mongoid/3.2/.gitkeep b/lib/ransack/adapters/mongoid/3.2/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/lib/ransack/adapters/mongoid/attributes/attribute.rb b/lib/ransack/adapters/mongoid/attributes/attribute.rb deleted file mode 100644 index dee2991..0000000 --- a/lib/ransack/adapters/mongoid/attributes/attribute.rb +++ /dev/null @@ -1,37 +0,0 @@ -require 'ransack/adapters/mongoid/attributes/predications' -require 'ransack/adapters/mongoid/attributes/order_predications' - -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 - - 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 - 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/attributes/order_predications.rb b/lib/ransack/adapters/mongoid/attributes/order_predications.rb deleted file mode 100644 index 5639c31..0000000 --- a/lib/ransack/adapters/mongoid/attributes/order_predications.rb +++ /dev/null @@ -1,17 +0,0 @@ -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 diff --git a/lib/ransack/adapters/mongoid/attributes/predications.rb b/lib/ransack/adapters/mongoid/attributes/predications.rb deleted file mode 100644 index d7f503c..0000000 --- a/lib/ransack/adapters/mongoid/attributes/predications.rb +++ /dev/null @@ -1,141 +0,0 @@ -module Ransack - 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) - { name => other }.to_inquiry - end - - def eq_any(others) - grouping_any :eq, others - end - - def eq_all(others) - grouping_all :eq, others - end - - def in(other) - { name => { "$in" => other } }.to_inquiry - end - - def in_any(others) - grouping_any :in, others - end - - def in_all(others) - grouping_all :in, others - end - - def not_in(other) - { "$not" => { name => { "$in" => other } } }.to_inquiry - 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 => /#{other}/i }.to_inquiry - end - - def matches_any(others) - grouping_any :matches, others - end - - def matches_all(others) - grouping_all :matches, others - end - - def does_not_match(other) - { "$not" => { name => /#{other}/i } }.to_inquiry - end - - def does_not_match_any(others) - grouping_any :does_not_match, others - end - - def does_not_match_all(others) - grouping_all :does_not_match, 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 } }.to_inquiry - 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 } }.to_inquiry - 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/lib/ransack/adapters/mongoid/base.rb b/lib/ransack/adapters/mongoid/base.rb deleted file mode 100644 index 50d4353..0000000 --- a/lib/ransack/adapters/mongoid/base.rb +++ /dev/null @@ -1,134 +0,0 @@ -require 'delegate' - -module Ransack - module Adapters - module Mongoid - module Base - - extend ActiveSupport::Concern - - included do - class_attribute :_ransackers - class_attribute :_ransack_aliases - self._ransackers ||= {} - self._ransack_aliases ||= {} - end - - class ColumnWrapper < SimpleDelegator - def type - _super = super - case _super - when BSON::ObjectId, Object - :string - else - _super.name.underscore.to_sym - end - end - end - - class Connection - def initialize model - @model = model - end - - def quote_column_name name - name - end - end - - module ClassMethods - 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 ransack_alias(new_name, old_name) - self._ransack_aliases = _ransack_aliases.merge new_name.to_s => - old_name.to_s - end - - def ransacker(name, opts = {}, &block) - self._ransackers = _ransackers.merge name.to_s => Ransacker - .new(self, name, opts, &block) - end - - def all_ransackable_attributes - ['id'] + column_names.select { |c| c != '_id' } + _ransackers.keys + - _ransack_aliases.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 - ransackable_attributes(auth_object) - end - - def ransackable_associations(auth_object = nil) - 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, :embeds_many, :embedded_in - ) - end - - # For overriding with a whitelist of symbols - def ransackable_scopes(auth_object = nil) - [] - end - - # imitating active record - - def joins_values *args - [] - end - - def custom_join_ast *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 - - 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 - - def table - name = ::Ransack::Adapters::Mongoid::Attributes::Attribute.new( - self.criteria, :name - ) - { :name => name } - end - - end - - end # Base - end - end -end diff --git a/lib/ransack/adapters/mongoid/context.rb b/lib/ransack/adapters/mongoid/context.rb deleted file mode 100644 index 3ca2ebe..0000000 --- a/lib/ransack/adapters/mongoid/context.rb +++ /dev/null @@ -1,212 +0,0 @@ -require 'ransack/context' -require 'polyamorous' - -module Ransack - module Adapters - module Mongoid - class Context < ::Ransack::Context - - 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.split('.').last - # 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 - - # when :date - # when :datetime, :timestamp, :time - # when :boolean - # when :integer - # when :float - # when :decimal - # else # :string - - name = '_id' if name == 'id' - - t = object.klass.fields[name].try(:type) || bind_pair_for(attr.name).first.fields[name].type - - t.to_s.demodulize.underscore.to_sym - end - - def evaluate(search, opts = {}) - viz = Visitor.new - relation = @object.where(viz.accept(search.base)) - if search.sorts.any? - 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 - # -- mongoid has different distinct method - # opts[:distinct] ? relation.distinct : relation - relation - end - - def attribute_method?(str, klass = @klass) - exists = false - if ransackable_attribute?(str, klass) - exists = true - elsif (segments = str.split(Constants::UNDERSCORE)).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 - Ransack::Adapters::Mongoid::Table.new(parent) - end - - def klassify(obj) - if Class === obj && obj.ancestors.include?(::Mongoid::Document) - 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 - - def lock_association(association) - warn "lock_association is not implemented for Ransack mongoid adapter" if $DEBUG - end - - def remove_association(association) - warn "remove_association is not implemented for Ransack mongoid adapter" if $DEBUG - 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(Constants::UNDERSCORE)).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) - parent, attr_name = get_parent_and_attribute_name( - remainder.join('_'), found_assoc.klass - ) - attr_name = "#{segments.join('_')}.#{attr_name}" - 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_all.detect { |a| a.name.to_s == str } - end - - def join_dependency(relation) - if relation.respond_to?(:join_dependency) # Polyamorous enables this - relation.join_dependency - else - build_join_dependency(relation) - end - end - - # 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| - case join - when String - Constants::STRING_JOIN - when Hash, Symbol, Array - Constants::ASSOCIATION_JOIN - when JoinDependency, JoinDependency::JoinAssociation - Constants::STASHED_JOIN - when Arel::Nodes::Join - Constants::JOIN_NODE - else - raise 'unknown class: %s' % join.class.name - end - end - - association_joins = buckets[Constants::ASSOCIATION_JOIN] || [] - - stashed_association_joins = buckets[Constants::STASHED_JOIN] || [] - - join_nodes = buckets[Constants::JOIN_NODE] || [] - - string_joins = (buckets[Constants::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 - - join_dependency # ActiveRecord::Associations::JoinDependency - end - - # 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 - unless found_association - @join_dependency.send( - :build, - Polyamorous::Join.new(name, @join_type, klass), - parent - ) - found_association = @join_dependency.join_associations.last - # Leverage the stashed association functionality in AR - @object = @object.joins(found_association) - end - found_association - end - - end - end - end -end diff --git a/lib/ransack/adapters/mongoid/inquiry_hash.rb b/lib/ransack/adapters/mongoid/inquiry_hash.rb deleted file mode 100644 index 8d7ec02..0000000 --- a/lib/ransack/adapters/mongoid/inquiry_hash.rb +++ /dev/null @@ -1,23 +0,0 @@ -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/constants.rb b/lib/ransack/adapters/mongoid/ransack/constants.rb deleted file mode 100644 index 305dee2..0000000 --- a/lib/ransack/adapters/mongoid/ransack/constants.rb +++ /dev/null @@ -1,88 +0,0 @@ -module Ransack - module Constants - DERIVED_PREDICATES = [ - [CONT, { - :arel_predicate => 'matches', - :formatter => proc { |v| "#{escape_regex(v)}" } - } - ], - ['not_cont', { - :arel_predicate => 'does_not_match', - :formatter => proc { |v| "#{escape_regex(v)}" } - } - ], - ['start', { - :arel_predicate => 'matches', - :formatter => proc { |v| "\\A#{escape_regex(v)}" } - } - ], - ['not_start', { - :arel_predicate => 'does_not_match', - :formatter => proc { |v| "\\A#{escape_regex(v)}" } - } - ], - ['end', { - :arel_predicate => 'matches', - :formatter => proc { |v| "#{escape_regex(v)}\\Z" } - } - ], - ['not_end', { - :arel_predicate => 'does_not_match', - :formatter => proc { |v| "#{escape_regex(v)}\\Z" } - } - ], - ['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_regex(unescaped) - Regexp.escape(unescaped) - end - end -end diff --git a/lib/ransack/adapters/mongoid/ransack/context.rb b/lib/ransack/adapters/mongoid/ransack/context.rb deleted file mode 100644 index 4767dcb..0000000 --- a/lib/ransack/adapters/mongoid/ransack/context.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'ransack/visitor' - -module Ransack - class Context - # attr_reader :arel_visitor - - class << self - - def for_class(klass, options = {}) - if klass.ancestors.include?(::Mongoid::Document) - Adapters::Mongoid::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] - - @base = @object.klass - # @engine = @base.arel_engine - end - - def bind_pair_for(key) - @bind_pairs ||= {} - - @bind_pairs[key] ||= begin - parent, attr_name = get_parent_and_attribute_name(key.to_s) - [parent, attr_name] if parent && attr_name - 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 deleted file mode 100644 index 6e51016..0000000 --- a/lib/ransack/adapters/mongoid/ransack/nodes/condition.rb +++ /dev/null @@ -1,22 +0,0 @@ -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 && combinator == 'and' - Arel::Nodes::Grouping.new(Arel::Nodes::And.new(predicates)) - else - predicates.inject(&:or) - end - end - - end # Condition - end -end diff --git a/lib/ransack/adapters/mongoid/ransack/translate.rb b/lib/ransack/adapters/mongoid/ransack/translate.rb deleted file mode 100644 index f321927..0000000 --- a/lib/ransack/adapters/mongoid/ransack/translate.rb +++ /dev/null @@ -1,13 +0,0 @@ -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 - klass.model_name.i18n_key.to_s - end - end -end diff --git a/lib/ransack/adapters/mongoid/ransack/visitor.rb b/lib/ransack/adapters/mongoid/ransack/visitor.rb deleted file mode 100644 index a6a507c..0000000 --- a/lib/ransack/adapters/mongoid/ransack/visitor.rb +++ /dev/null @@ -1,18 +0,0 @@ -module Ransack - class Visitor - def visit_and(object) - nodes = object.values.map { |o| accept(o) }.compact - nodes.inject(&:and) - end - - def quoted?(object) - case object - when Arel::Nodes::SqlLiteral, Bignum, Fixnum - false - else - true - end - end - - end -end diff --git a/lib/ransack/adapters/mongoid/table.rb b/lib/ransack/adapters/mongoid/table.rb deleted file mode 100644 index 3c82fa7..0000000 --- a/lib/ransack/adapters/mongoid/table.rb +++ /dev/null @@ -1,35 +0,0 @@ -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/spec/mongoid/adapters/mongoid/base_spec.rb b/spec/mongoid/adapters/mongoid/base_spec.rb deleted file mode 100644 index b2f7a6a..0000000 --- a/spec/mongoid/adapters/mongoid/base_spec.rb +++ /dev/null @@ -1,314 +0,0 @@ -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 - allow(Person) - .to receive(:ransackable_scopes) - .and_return([: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 '#ransack_alias' do - it 'translates an alias to the correct attributes' do - p = Person.create!(name: 'Meatloaf', email: 'babies@example.com') - - s = Person.ransack(term_cont: 'atlo') - expect(s.result.to_a).to eq [p] - - s = Person.ransack(term_cont: 'babi') - expect(s.result.to_a).to eq [p] - - s = Person.ransack(term_cont: 'nomatch') - expect(s.result.to_a).to eq [] - end - - it 'makes aliases available to subclasses' do - yngwie = Musician.create!(name: 'Yngwie Malmsteen') - - musicians = Musician.ransack(term_cont: 'ngw').result - expect(musicians).to eq([yngwie]) - end - - it 'handles naming collisions gracefully' do - frank = Person.create!(name: 'Frank Stallone') - - people = Person.ransack(term_cont: 'allon').result - expect(people).to eq([frank]) - - Class.new(Article) do - ransack_alias :term, :title - end - - people = Person.ransack(term_cont: 'allon').result - expect(people).to eq([frank]) - 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 - - 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 - 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 'term' } - 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 'term' } - 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 - 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 deleted file mode 100644 index 4902549..0000000 --- a/spec/mongoid/adapters/mongoid/context_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'mongoid_spec_helper' - -module Ransack - module Adapters - 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 - 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 ::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) - - 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 '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' - 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' - expect(attribute.relation.name).to eq 'articles' - expect(attribute.relation.table_alias).to be_nil - end - - end - end - end -end diff --git a/spec/mongoid/configuration_spec.rb b/spec/mongoid/configuration_spec.rb deleted file mode 100644 index 7ed3a2c..0000000 --- a/spec/mongoid/configuration_spec.rb +++ /dev/null @@ -1,162 +0,0 @@ -require 'mongoid_spec_helper' - -module Ransack - describe Configuration do - it 'yields Ransack on configure' do - Ransack.configure { |config| expect(config).to eq Ransack } - 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 - default = Ransack.options.clone - - Ransack.configure { |c| c.search_key = :query } - - expect(Ransack.options[:search_key]).to eq :query - - Ransack.options = default - end - - it 'should have default values for arrows' do - expect(Ransack.options[:up_arrow]).to eq '▼' - expect(Ransack.options[:down_arrow]).to eq '▲' - end - - it 'changes the default value for the up arrow only' do - default, new_up_arrow = Ransack.options.clone, 'U+02191' - - Ransack.configure { |c| c.custom_arrows = { up_arrow: new_up_arrow } } - - expect(Ransack.options[:down_arrow]).to eq default[:down_arrow] - expect(Ransack.options[:up_arrow]).to eq new_up_arrow - - Ransack.options = default - end - - it 'changes the default value for the down arrow only' do - default, new_down_arrow = Ransack.options.clone, '' - - Ransack.configure { |c| c.custom_arrows = { down_arrow: new_down_arrow } } - - expect(Ransack.options[:up_arrow]).to eq default[:up_arrow] - expect(Ransack.options[:down_arrow]).to eq new_down_arrow - - Ransack.options = default - end - - it 'changes the default value for both arrows' do - default = Ransack.options.clone - new_up_arrow = '' - new_down_arrow = 'U+02193' - - Ransack.configure do |c| - c.custom_arrows = { up_arrow: new_up_arrow, down_arrow: new_down_arrow } - end - - expect(Ransack.options[:up_arrow]).to eq new_up_arrow - expect(Ransack.options[:down_arrow]).to eq new_down_arrow - - Ransack.options = default - end - - it 'consecutive arrow customizations respect previous customizations' do - default = Ransack.options.clone - - Ransack.configure { |c| c.custom_arrows = { up_arrow: 'up' } } - expect(Ransack.options[:down_arrow]).to eq default[:down_arrow] - - Ransack.configure { |c| c.custom_arrows = { down_arrow: 'DOWN' } } - expect(Ransack.options[:up_arrow]).to eq 'up' - - Ransack.configure { |c| c.custom_arrows = { up_arrow: 'U-Arrow' } } - expect(Ransack.options[:down_arrow]).to eq 'DOWN' - - Ransack.configure { |c| c.custom_arrows = { down_arrow: 'down arrow-2' } } - expect(Ransack.options[:up_arrow]).to eq 'U-Arrow' - - Ransack.options = default - 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 - - describe '`wants_array` option takes precedence over Arel predicate' do - it 'implicitly wants an array for in/not in predicates' do - Ransack.configure do |config| - config.add_predicate( - :test_in_predicate, - :arel_predicate => 'in' - ) - config.add_predicate( - :test_not_in_predicate, - :arel_predicate => 'not_in' - ) - end - - expect(Ransack.predicates['test_in_predicate'].wants_array) - .to eq true - expect(Ransack.predicates['test_not_in_predicate'].wants_array) - .to eq true - end - - it 'explicitly does not want array for in/not_in predicates' do - Ransack.configure do |config| - config.add_predicate( - :test_in_predicate_no_array, - :arel_predicate => 'in', - :wants_array => false - ) - config.add_predicate( - :test_not_in_predicate_no_array, - :arel_predicate => 'not_in', - :wants_array => false - ) - end - - expect(Ransack.predicates['test_in_predicate_no_array'].wants_array) - .to eq false - expect(Ransack.predicates['test_not_in_predicate_no_array'].wants_array) - .to eq false - end - end - end -end diff --git a/spec/mongoid/dependencies_spec.rb b/spec/mongoid/dependencies_spec.rb deleted file mode 100644 index 71653b8..0000000 --- a/spec/mongoid/dependencies_spec.rb +++ /dev/null @@ -1,8 +0,0 @@ -unless ::ActiveSupport::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 diff --git a/spec/mongoid/helpers/ransack_helper.rb b/spec/mongoid/helpers/ransack_helper.rb deleted file mode 100644 index 1cc73ea..0000000 --- a/spec/mongoid/helpers/ransack_helper.rb +++ /dev/null @@ -1,11 +0,0 @@ -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/nodes/condition_spec.rb b/spec/mongoid/nodes/condition_spec.rb deleted file mode 100644 index 8829ea8..0000000 --- a/spec/mongoid/nodes/condition_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'mongoid_spec_helper' - -module Ransack - module Nodes - describe Condition do - - context 'with an alias' do - subject { - Condition.extract( - Context.for(Person), 'term_start', Person.first(2).map(&:name) - ) - } - - specify { expect(subject.combinator).to eq 'or' } - specify { expect(subject.predicate.name).to eq 'start' } - - it 'converts the alias to the correct attributes' do - expect(subject.attributes.map(&:name)).to eq(['name', 'email']) - end - end - - 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 { expect(subject).to be_nil } - end - end - end - end -end diff --git a/spec/mongoid/nodes/grouping_spec.rb b/spec/mongoid/nodes/grouping_spec.rb deleted file mode 100644 index a814ed1..0000000 --- a/spec/mongoid/nodes/grouping_spec.rb +++ /dev/null @@ -1,13 +0,0 @@ -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/predicate_spec.rb b/spec/mongoid/predicate_spec.rb deleted file mode 100644 index b73e38d..0000000 --- a/spec/mongoid/predicate_spec.rb +++ /dev/null @@ -1,155 +0,0 @@ -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 - - 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 - - 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 diff --git a/spec/mongoid/search_spec.rb b/spec/mongoid/search_spec.rb deleted file mode 100644 index 163f515..0000000 --- a/spec/mongoid/search_spec.rb +++ /dev/null @@ -1,445 +0,0 @@ -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] - 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 { |c| c.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 { |c| c.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 - 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/mongoid.yml b/spec/mongoid/support/mongoid.yml deleted file mode 100644 index dd169b0..0000000 --- a/spec/mongoid/support/mongoid.yml +++ /dev/null @@ -1,11 +0,0 @@ -test: - clients: - default: - database: ransack_mongoid_test - hosts: - - localhost:27017 - sessions: - default: - database: ransack_mongoid_test - hosts: - - localhost:27017 diff --git a/spec/mongoid/support/schema.rb b/spec/mongoid/support/schema.rb deleted file mode 100644 index 5f6f0e9..0000000 --- a/spec/mongoid/support/schema.rb +++ /dev/null @@ -1,135 +0,0 @@ -require 'mongoid' - -Mongoid.load!(File.expand_path("../mongoid.yml", __FILE__), :test) -Mongo::Logger.logger.level = Logger::WARN if defined?(Mongo) -Mongoid.purge! - -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 - - ransack_alias :term, :name_or_email - - # 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.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] - # ) - parent.table[:name] - end - - def self.ransackable_attributes(auth_object = nil) - if auth_object == :admin - all_ransackable_attributes - ['only_sort'] - else - all_ransackable_attributes - ['only_sort', 'only_admin'] - end - end - - def self.ransortable_attributes(auth_object = nil) - if auth_object == :admin - all_ransackable_attributes - ['only_search'] - else - all_ransackable_attributes - ['only_search', 'only_admin'] - end - end -end - -class Musician < Person -end - -class Article - include Mongoid::Document - - 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 :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 :note, type: String - - belongs_to :notable, :polymorphic => true -end - -module Schema - def self.create - 10.times do - person = Person.make.save! - Note.make.save!(:notable => person) - 3.times do - article = Article.create!(:person => person) - 3.times do - # article.tags = [Tag.make.save!, Tag.make.save!, Tag.make.save!] - end - Note.create.save!(:notable => article) - 10.times do - Comment.create.save!(: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/translate_spec.rb b/spec/mongoid/translate_spec.rb deleted file mode 100644 index 2ebfdf4..0000000 --- a/spec/mongoid/translate_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -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 diff --git a/spec/mongoid_spec_helper.rb b/spec/mongoid_spec_helper.rb deleted file mode 100644 index faffc0a..0000000 --- a/spec/mongoid_spec_helper.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'machinist/object' -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('../{mongoid/helpers,mongoid/support,blueprints}/*.rb', - __FILE__)] -.each { |f| require f } - -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 - if ENV['DB'] == 'mongoid4' - message = "Running Ransack specs with #{Mongoid.default_session.inspect - }, Mongoid #{Mongoid::VERSION}, Moped #{Moped::VERSION - }, Origin #{Origin::VERSION} and Ruby #{RUBY_VERSION}" - else - message = "Running Ransack specs with #{Mongoid.default_client.inspect - }, Mongoid #{Mongoid::VERSION}, Mongo driver #{Mongo::VERSION}" - end - line = '=' * message.length - puts line, message, line - 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