Permalink
Browse files

Released v1.0.3 (see changelog)

  • Loading branch information...
binarylogic committed Sep 18, 2008
1 parent 4fa5012 commit c06d174f9b5c93415c3fc6d2fd9a3d522d792819
View
@@ -1,7 +1,12 @@
-== 1.0.3 released 2008-09-14
+== 1.0.3 released 2008-09-18
* Updated inspect to show the current options for your search. Plays nicer in the console.
* Made sure protection state is persistent among relationship conditions.
+* Fixed bug with backwards compatibility of rails. concat requires a proc in older version.
+* Defaulted remote control types to use GET requests instead of POST.
+* Completely reengineered integration with ActiveRecord. Searchgasm is properly using scopes letting you do use serachgasm where scope are implemented. @current_users.orders.new_search, etc. If your search is scoped and you want a search object, that search object will represent a new search in the context of those scopes, meaning the scopes get merged into Searchgasm as options.
+* Dropped support for Searchgasm functionality when defining relationships: has_many :order, :conditions => {:total_gt => 100}, will not work anymore. It's a chicken and the egg thing. Searchgasm needs AR constants, some models get loaded before others, therefore the Order model may not have been loaded yet, causing an unknown constant error.
+* Clean up redundant code and moved it into the Searchgasm::Shared namespace.
== 1.0.2 released 2008-09-12
View
@@ -1,6 +1,3 @@
-benchmarks/benchmark.rb
-benchmarks/benchmark_helper.rb
-benchmarks/profile.rb
CHANGELOG.rdoc
examples/README.rdoc
init.rb
@@ -42,7 +39,11 @@ lib/searchgasm/search/conditions.rb
lib/searchgasm/search/ordering.rb
lib/searchgasm/search/pagination.rb
lib/searchgasm/search/protection.rb
-lib/searchgasm/utilities.rb
+lib/searchgasm/search/scoping.rb
+lib/searchgasm/search.rb
+lib/searchgasm/shared/searching.rb
+lib/searchgasm/shared/utilities.rb
+lib/searchgasm/shared/virtual_classes.rb
lib/searchgasm/version.rb
lib/searchgasm.rb
Manifest
View
@@ -219,14 +219,23 @@ For tree data structures you get a few nifty methods. Let's assume Users is a tr
User.all(:conditions => {:inclusive_descendant_of => User.roots.first.id})
-== Available anywhere (relationships & scopes)
+== Scope support
-Not only can you use searchgasm when searching, but you can use it when setting up relationships or scopes. Anywhere you specify conditions in ActiveRecord.
+Not only can you use searchgasm when searching, but you can use it when using scopes.
class User < ActiveRecord::Base
- has_many :expensive_pending_orders, :conditions => {:total_greater_than => 1_000_000, :state => :pending}, :per_page => 20
named_scope :sexy, :conditions => {:first_name => "Ben", email_ends_with => "binarylogic.com"}, :per_page => 20
end
+
+or
+
+ class User < ActiveRecord::Base
+ def self.find_sexy
+ with_scope(:find => {:conditions => {:first_name => "Ben", email_ends_with => "binarylogic.com"}, :per_page => 20}) do
+ all
+ end
+ end
+ end
== Always use protection...against SQL injections
@@ -328,7 +337,11 @@ Pretty nifty, huh? You can create any condition ultimately creating any SQL you
== Under the hood
-I'm a big fan of understanding what I'm using, so here's a quick explanation: The design behind this plugin is pretty simple. The search object "sanitizes" down into the options passed into ActiveRecord::Base.find(). It serves as a transparent filter between you and ActiveRecord::Base.find(). This filter provides "enhancements" that get translated into options that ActiveRecord::Base.find() can understand. It doesn't dig into the ActiveRecord internals, it only uses what is publicly available. It jumps in and helps out <em>only</em> when needed, otherwise it sits back and lets ActiveRecord do all of the work. Between that and the extensive tests, this is a solid and fast plugin.
+I'm a big fan of understanding what I'm using, so here's a quick explanation: The design behind this plugin is pretty simple and I had 1 main rule when developing this:
+
+ActiveRecord should never know about Searchgasm
+
+What that rule means is that any options you pass when searching get "sanitized" down into options ActiveRecord can understand. Searchgasm serves as a transparent filter between you and ActiveRecord. It doesn't dig into the ActiveRecord internals, it only uses what is publicly available. It jumps in and helps out <em>only</em> when needed, otherwise it sits back and stays completely out of the way. Between that and the extensive tests, this is a solid and fast plugin.
== Reporting problems / bugs
View
@@ -6,10 +6,14 @@
# Core Ext
require "searchgasm/core_ext/hash"
-# Utilties
+# Shared
+require "searchgasm/shared/utilities"
+require "searchgasm/shared/searching"
+require "searchgasm/shared/virtual_classes"
+
+# Base classes
require "searchgasm/version"
require "searchgasm/config"
-require "searchgasm/utilities"
# ActiveRecord
require "searchgasm/active_record/base"
@@ -5,68 +5,56 @@ module ActiveRecord
# These methods hook into ActiveRecords association methods and add in searchgasm functionality.
module Associations
module AssociationCollection
- # This is an alias method chain. It hook into ActiveRecord's "find" method for associations and checks to see if Searchgasm should get involved.
+ # This needs to be implemented because AR doesn't leverage scopes with this method like it probably should
def find_with_searchgasm(*args)
options = args.extract_options!
args << sanitize_options_with_searchgasm(options)
find_without_searchgasm(*args)
end
-
+
# See build_conditions under Searchgasm::ActiveRecord::Base. This is the same thing but for associations.
def build_conditions(options = {}, &block)
- conditions = @reflection.klass.build_conditions(options, &block)
- conditions.scope = scope(:find)[:conditions]
- conditions
+ @reflection.klass.send(:with_scope, :find => construct_scope[:find]) { @reflection.klass.build_conditions(options, &block) }
end
-
+
# See build_conditions! under Searchgasm::ActiveRecord::Base. This is the same thing but for associations.
def build_conditions!(options = {}, &block)
- conditions = @reflection.klass.build_conditions!(options, &block)
- conditions.scope = scope(:find)[:conditions]
- conditions
+ @reflection.klass.send(:with_scope, :find => construct_scope[:find]) { @reflection.klass.build_conditions!(options, &block) }
end
-
+
# See build_search under Searchgasm::ActiveRecord::Base. This is the same thing but for associations.
def build_search(options = {}, &block)
- conditions = @reflection.klass.build_search(options, &block)
- conditions.scope = scope(:find)[:conditions]
- conditions
+ @reflection.klass.send(:with_scope, :find => construct_scope[:find]) { @reflection.klass.build_search(options, &block) }
end
-
+
# See build_conditions! under Searchgasm::ActiveRecord::Base. This is the same thing but for associations.
def build_search!(options = {}, &block)
- conditions = @reflection.klass.build_search!(options, &block)
- conditions.scope = scope(:find)[:conditions]
- conditions
+ @reflection.klass.send(:with_scope, :find => construct_scope[:find]) { @reflection.klass.build_search!(options, &block) }
end
end
-
- module HasManyAssociation
- # This is an alias method chain. It hook into ActiveRecord's "calculate" method for has many associations and checks to see if Searchgasm should get involved.
+
+ module Shared
def count_with_searchgasm(*args)
- column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
- count_without_searchgasm(column_name, sanitize_options_with_searchgasm(options))
+ options = args.extract_options!
+ args << sanitize_options_with_searchgasm(options)
+ count_without_searchgasm(*args)
end
end
end
end
end
-ActiveRecord::Associations::AssociationCollection.send(:include, Searchgasm::ActiveRecord::Associations::AssociationCollection)
-
module ActiveRecord
module Associations
class AssociationCollection
+ include Searchgasm::ActiveRecord::Associations::AssociationCollection
+
alias_method_chain :find, :searchgasm
end
- end
-end
-
-ActiveRecord::Associations::HasManyAssociation.send(:include, Searchgasm::ActiveRecord::Associations::HasManyAssociation)
-
-module ActiveRecord
- module Associations
+
class HasManyAssociation
+ include Searchgasm::ActiveRecord::Associations::Shared
+
alias_method_chain :count, :searchgasm
end
end
@@ -18,21 +18,29 @@ def find_with_searchgasm(*args)
args << options
find_without_searchgasm(*args)
end
-
+
# This is an alias method chain. It hooks into ActiveRecord's scopes and checks to see if Searchgasm should get involved. Allowing you to use all of Searchgasms conditions and tools
# in scopes as well.
#
# === Examples
#
+ # Named scopes:
+ #
# named_scope :top_expensive, :conditions => {:total_gt => 1_000_000}, :per_page => 10
+ # named_scope :top_expensive_ordered, :conditions => {:total_gt => 1_000_000}, :per_page => 10, :order_by => {:user => :first_name}
+ #
+ # Good ole' regular scopes:
#
# with_scope(:find => {:conditions => {:total_gt => 1_000_000}, :per_page => 10}) do
# find(:all)
# end
- def scope_with_searchgasm(method, key = nil)
- scope = scope_without_searchgasm(method, key)
- return sanitize_options_with_searchgasm(scope) if key.nil? && method == :find && !scope.blank?
- scope
+ #
+ # with_scope(:find => {:conditions => {:total_gt => 1_000_000}, :per_page => 10}) do
+ # build_search
+ # end
+ def with_scope_with_searchgasm(method_scoping = {}, action = :merge, &block)
+ method_scoping[:find] = sanitize_options_with_searchgasm(method_scoping[:find]) if method_scoping[:find]
+ with_scope_without_searchgasm(method_scoping, action, &block)
end
# This is a special method that Searchgasm adds in. It returns a new conditions object on the model. So you can search by conditions *only*.
@@ -118,17 +126,40 @@ def accessible_conditions # :nodoc:
private
def sanitize_options_with_searchgasm(options = {})
return options unless Searchgasm::Search::Base.needed?(self, options)
- search = searchgasm_searcher(options)
+ search = Searchgasm::Search::Base.create_virtual_class(self).new(options) # call explicitly to avoid merging the scopes into the searcher
search.acting_as_filter = true
search.sanitize
end
def searchgasm_conditions(options = {})
- Searchgasm::Conditions::Base.create_virtual_class(self).new(options)
+ conditions = Searchgasm::Conditions::Base.create_virtual_class(self).new(options)
+ conditions.conditions = (scope(:find) || {})[:conditions]
+ conditions
end
def searchgasm_searcher(options = {})
- Searchgasm::Search::Base.create_virtual_class(self).new(options)
+ search = Searchgasm::Search::Base.create_virtual_class(self).new(options)
+ options_from_scope_for_searchgasm(options).each { |option, value| search.send("#{option}=", value) }
+ search
+ end
+
+ def options_from_scope_for_searchgasm(options)
+ # The goal here is to mimic how scope work. Merge what scopes would and don't what they wouldn't.
+ scope = scope(:find) || {}
+ scope_options = {}
+ [:group, :include, :select, :readonly].each { |option| scope_options[option] = scope[option] if !options.has_key?(option) && scope.has_key?(option) }
+
+ if scope[:joins] || options[:joins]
+ scope_options[:joins] = []
+ scope_options[:joins] += scope[:joins].is_a?(Array) ? scope[:joins] : [scope[:joins]] unless scope[:joins].blank?
+ scope_options[:joins] += options[:joins].is_a?(Array) ? options[:joins] : [options[:joins]] unless options[:joins].blank?
+ end
+
+ scope_options[:limit] = scope[:limit] if !options.has_key?(:per_page) && !options.has_key?(:limit) && scope.has_key?(:per_page)
+ scope_options[:offset] = scope[:offset] if !options.has_key?(:page) && !options.has_key?(:offset) && scope.has_key?(:offset)
+ scope_options[:order] = scope[:order] if !options.has_key?(:order_by) && !options.has_key?(:order) && scope.has_key?(:order)
+ scope_options[:conditions] = scope[:conditions] if scope.has_key?(:conditions)
+ scope_options
end
end
end
@@ -141,7 +172,7 @@ class Base
class << self
alias_method_chain :calculate, :searchgasm
alias_method_chain :find, :searchgasm
- alias_method_chain :scope, :searchgasm
+ alias_method_chain :with_scope, :searchgasm
alias_method :new_conditions, :build_conditions
alias_method :new_conditions!, :build_conditions!
alias_method :new_search, :build_search
@@ -5,7 +5,7 @@ module Condition # :nodoc:
# The base class for creating a condition. Your custom conditions should extend this class.
# See Searchgasm::Conditions::Base.register_condition on how to write your own condition.
class Base
- include Utilities
+ include Shared::Utilities
attr_accessor :column, :klass
attr_reader :value
@@ -1,8 +1,6 @@
module Searchgasm
module Condition
class InclusiveDescendantOf < Tree
- include Searchgasm::Utilities
-
def to_conditions(value)
condition = DescendantOf.new(klass, column)
condition.value = value
@@ -1,8 +1,6 @@
module Searchgasm
module Condition
class SiblingOf < Tree
- include Searchgasm::Utilities
-
def to_conditions(value)
parent_association = klass.reflect_on_association(:parent)
foreign_key_name = (parent_association && parent_association.options[:foreign_key]) || "parent_id"
@@ -5,9 +5,11 @@ module Conditions # :nodoc:
# Represents a collection of conditions and performs various tasks on that collection. For information on each condition see Searchgasm::Condition.
# Each condition has its own file and class and the source for each condition is pretty self explanatory.
class Base
- include Utilities
+ include Searchgasm::Shared::Utilities
+ include Searchgasm::Shared::Searching
+ include Searchgasm::Shared::VirtualClasses
- attr_accessor :relationship_name, :scope
+ attr_accessor :relationship_name, :sql
class << self
attr_accessor :added_klass_conditions, :added_column_conditions, :added_associations
@@ -64,37 +66,14 @@ def condition_names
def needed?(model_class, conditions) # :nodoc:
if conditions.is_a?(Hash)
+ column_names = model_class.column_names
conditions.stringify_keys.keys.each do |condition|
- return true unless model_class.column_names.include?(condition)
+ return true unless column_names.include?(condition)
end
end
false
end
-
- # Creates virtual classes for the class passed to it. This is a neccesity for keeping dynamically created method
- # names specific to models. It provides caching and helps a lot with performance.
- def create_virtual_class(model_class)
- class_search_name = "::Searchgasm::Cache::#{model_class.name}Conditions"
-
- begin
- eval(class_search_name)
- rescue NameError
- eval <<-end_eval
- class #{class_search_name} < ::Searchgasm::Conditions::Base
- def self.klass
- #{model_class.name}
- end
-
- def klass
- #{model_class.name}
- end
- end
-
- #{class_search_name}
- end_eval
- end
- end
end
def initialize(init_conditions = {})
@@ -127,26 +106,27 @@ def includes
def inspect
conditions_hash = conditions
+ conditions_hash[:sql] = sql if sql
conditions_hash[:protected] = true if protected?
conditions_hash.inspect
end
# Sanitizes the conditions down into conditions that ActiveRecord::Base.find can understand.
- def sanitize
+ def sanitize(for_method = nil)
conditions = merge_conditions(*objects.collect { |object| object.sanitize })
- return scope if conditions.blank?
- merge_conditions(conditions, scope)
+ return sql if conditions.blank?
+ merged_conditions = merge_conditions(conditions, sql)
+ for_method.blank? ? merged_conditions : {:conditions => merged_conditions}
end
- # Allows you to set the conditions via a hash. If you do not pass a hash it will set scope instead, so that you can continue to add conditions and ultimately
- # merge it all together at the end.
+ # Allows you to set the conditions via a hash.
def conditions=(conditions)
case conditions
when Hash
assert_valid_conditions(conditions)
remove_conditions_from_protected_assignement(conditions).each { |condition, value| send("#{condition}=", value) }
else
- self.scope = conditions
+ self.sql = conditions
end
end
@@ -15,7 +15,7 @@ def conditions_with_protection=(conditions)
unless conditions.is_a?(Hash)
if protect?
return if conditions.blank?
- raise(ArgumentError, "You can not set a scope or pass SQL while the search is being protected")
+ raise(ArgumentError, "You can not pass SQL as conditions while the search is being protected, you can only pass a hash")
end
end
@@ -30,6 +30,7 @@ def protect=(value)
def protect?
protect == true
end
+ alias_method :protected?, :protect?
end
end
end
Oops, something went wrong.

0 comments on commit c06d174

Please sign in to comment.