Permalink
Browse files

Released v1.2.0

  • Loading branch information...
1 parent 10d1eea commit 6a8e5f76a1bb3ff2ffdb4405e0eb72db063f3117 @binarylogic committed Sep 24, 2008
View
@@ -1,9 +1,11 @@
-== 1.1.4 released 2008-09-24
+== 1.2.0 released 2008-09-24
* Added searchgasm_params and searchgasm_url helper to use outside of the control type helpers.
* Added dup and clone methods that work properly for searchgasm objects
* Fixed bug to remove nil scope values, HABTM likes to add :limit => nil
* Removed unnecessary build_search methods for associations
+* Removed support for searching with conditions only. This just made things much more complicated when you can accomplish the same thing by starting a new search and only setting conditions.
+* Fixed bug when searching with *any* conditions to use left outer joins instead of inner joins.
== 1.1.3 released 2008-09-23
View
@@ -177,19 +177,6 @@ Any of the options used in the above example can be used in these, but for the s
search.conditions.first_name_contains = "Ben"
search.per_page = 20
search.all
-
-== Search with conditions only
-
-Don't need pagination, ordering, or any of the other options? Search with conditions only.
-
- conditions = User.new_conditions(:age_gt => 18)
- conditions.first_name_contains = "Ben"
- conditions.all
- # ... all operations above are available
-
-Pass a conditions object right into ActiveRecord:
-
- User.all(:conditions => conditions)
== Match ANY or ALL of the conditions
@@ -250,24 +237,22 @@ or
== Always use protection...against SQL injections
-If there is one thing we all know, it's to always use protection against SQL injections. That's why searchgasm protects you by default. The new\_search and new\_conditions methods protect mass assignments by default (instantiation and search.options = {}). This means that various checks are done to ensure it is not possible to perform any type of SQL injection during mass assignments. But this also limits how you can search, meaning you can't write raw SQL. If you want to be daring and search without protection, all that you have to do is add ! to the end of the methods: new\_search! and new\_conditions!.
+If there is one thing we all know, it's to always use protection against SQL injections. That's why searchgasm protects you by default. The new\_search methods protect mass assignments by default (instantiation and search.options = {}). This means that various checks are done to ensure it is not possible to perform any type of SQL injection during mass assignments. But this also limits how you can search, meaning you can't write raw SQL. If you want to be daring and search without protection, all that you have to do is add ! to the end of the method: new\_search!.
=== Protected from SQL injections
search = Account.new_search(params[:search])
- conditions = Account.new_conditions(params[:conditions])
=== *NOT* protected from SQL injections
accounts = Account.find(params[:search])
accounts = Account.all(params[:search])
account = Account.first(params[:search])
search = Account.new_search!(params[:search])
- conditions = Account.new_conditions!(params[:conditions])
I'm sure you already knew this, but it's tempting to do this when you can pass the params hash right into these methods.
-Lesson learned: use new\_search and new\_conditions when passing in params as *ANY* of the options.
+Lesson learned: use new\_search when passing in params as *ANY* of the options.
== Available Conditions
View
@@ -8,7 +8,6 @@
# Shared
require "searchgasm/shared/utilities"
-require "searchgasm/shared/searching"
require "searchgasm/shared/virtual_classes"
# Base classes
@@ -23,6 +22,7 @@
require "searchgasm/search/ordering"
require "searchgasm/search/pagination"
require "searchgasm/search/conditions"
+require "searchgasm/search/searching"
require "searchgasm/search/base"
require "searchgasm/search/protection"
@@ -68,6 +68,7 @@ class Base
include Ordering
include Protection
include Pagination
+ include Searching
end
end
@@ -11,6 +11,7 @@ module Base
def calculate_with_searchgasm(*args)
options = args.extract_options!
options = filter_options_with_searchgasm(options, false)
+ args[1] = primary_key if options[:distinct] && [nil, :all].include?(args[1]) # quick fix for adding a column name if distinct is true and no specific column is provided
args << options
calculate_without_searchgasm(*args)
end
@@ -47,30 +48,6 @@ def with_scope_with_searchgasm(method_scoping = {}, action = :merge, &block)
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*.
- #
- # <b>This method is "protected". Meaning it checks the passed options for SQL injections. So trying to write raw SQL in *any* of the option will result in a raised exception. It's safe to pass a params object when instantiating.</b>
- #
- # === Examples
- #
- # conditions = User.new_conditions
- # conditions.first_name_contains = "Ben"
- # conditions.all # can call any search method: first, find(:all), find(:first), sum("id"), etc...
- def build_conditions(values = {}, &block)
- conditions = searchgasm_conditions
- conditions.protect = true
- conditions.conditions = values
- yield conditions if block_given?
- conditions
- end
-
- # See build_conditions. This is the same method but *without* protection. Do *NOT* pass in a params object to this method.
- def build_conditions!(values = {}, &block)
- conditions = searchgasm_conditions(values)
- yield conditions if block_given?
- conditions
- end
-
# This is a special method that Searchgasm adds in. It returns a new search object on the model. So you can search via an object.
#
# <b>This method is "protected". Meaning it checks the passed options for SQL injections. So trying to write raw SQL in *any* of the option will result in a raised exception. It's safe to pass a params object when instantiating.</b>
@@ -86,7 +63,7 @@ def build_conditions!(values = {}, &block)
# search.order_by = {:user_group => :name}
# search.all # can call any search method: first, find(:all), find(:first), sum("id"), etc...
def build_search(options = {}, &block)
- search = searchgasm_searcher
+ search = searchgasm_search
search.protect = true
search.options = options
yield search if block_given?
@@ -97,7 +74,7 @@ def build_search(options = {}, &block)
#
# This also has an alias "new_search!"
def build_search!(options = {}, &block)
- search = searchgasm_searcher(options)
+ search = searchgasm_search(options)
yield search if block_given?
search
end
@@ -120,7 +97,7 @@ def protected_conditions # :nodoc:
# This is the reverse of conditions_protected. You can specify conditions here and *only* these conditions will be allowed in mass assignment. Any condition not specified here will be blocked.
def conditions_accessible(*conditions)
- write_inheritable_attribute(:conditions_accessible, Set.new(conditions.map(&:to_s)) + (protected_conditions || []))
+ write_inheritable_attribute(:conditions_accessible, Set.new(conditions.map(&:to_s)) + (accessible_conditions || []))
end
def accessible_conditions # :nodoc:
@@ -130,7 +107,7 @@ def accessible_conditions # :nodoc:
private
def filter_options_with_searchgasm(options = {}, searching = true)
return options unless Searchgasm::Search::Base.needed?(self, options)
- search = Searchgasm::Search::Base.create_virtual_class(self).new # call explicitly to avoid merging the scopes into the searcher
+ search = Searchgasm::Search::Base.create_virtual_class(self).new # call explicitly to avoid merging the scopes into the search
search.acting_as_filter = true
conditions = options.delete(:conditions) || options.delete("conditions") || {}
if conditions
@@ -145,15 +122,7 @@ def filter_options_with_searchgasm(options = {}, searching = true)
search.sanitize(searching)
end
- def searchgasm_conditions(options = {})
- searcher = Searchgasm::Conditions::Base.create_virtual_class(self).new
- conditions = scope(:find) && scope(:find)[:conditions]
- searcher.scope = {:conditions => conditions} if conditions
- searcher.conditions = options
- searcher
- end
-
- def searchgasm_searcher(options = {})
+ def searchgasm_search(options = {})
scope = {}
current_scope = scope(:find) && scope(:find).deep_dup
if current_scope
@@ -168,11 +137,11 @@ def searchgasm_searcher(options = {})
current_scope.each { |k, v| new_scope[k] = v unless v.nil? }
current_scope = new_scope
end
- searcher = Searchgasm::Search::Base.create_virtual_class(self).new
- searcher.scope = scope
- searcher.options = current_scope
- searcher.options = options
- searcher
+ search = Searchgasm::Search::Base.create_virtual_class(self).new
+ search.scope = scope
+ search.options = current_scope
+ search.options = options
+ search
end
end
end
@@ -186,8 +155,6 @@ class << self
alias_method_chain :calculate, :searchgasm
alias_method_chain :find, :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
alias_method :new_search!, :build_search!
@@ -6,7 +6,6 @@ module Conditions # :nodoc:
# Each condition has its own file and class and the source for each condition is pretty self explanatory.
class Base
include Shared::Utilities
- include Shared::Searching
include Shared::VirtualClasses
attr_accessor :any, :relationship_name
@@ -65,6 +64,8 @@ def condition_names
end
def needed?(model_class, conditions) # :nodoc:
+ return false if conditions.blank?
+
if conditions.is_a?(Hash)
return true if conditions[:any]
column_names = model_class.column_names
@@ -105,11 +106,11 @@ def any?
end
# A list of joins to use when searching, includes relationships
- def joins
+ def auto_joins
j = []
associations.each do |association|
next if association.conditions.blank?
- association_joins = association.joins
+ association_joins = association.auto_joins
j << (association_joins.blank? ? association.relationship_name.to_sym : {association.relationship_name.to_sym => association_joins})
end
j.blank? ? nil : (j.size == 1 ? j.first : j)
@@ -262,7 +263,7 @@ def objects
end
def reset_objects!
- objects.each { |object| eval("@#{object.name} = nil") }
+ objects.each { |object| object.class < ::Searchgasm::Conditions::Base ? eval("@#{object.relationship_name} = nil") : eval("@#{object.name} = nil") }
objects.clear
end
@@ -3,10 +3,8 @@ module Search #:nodoc:
# = Searchgasm
#
# Please refer the README.rdoc for usage, examples, and installation.
-
class Base
include Searchgasm::Shared::Utilities
- include Searchgasm::Shared::Searching
include Searchgasm::Shared::VirtualClasses
# Options ActiveRecord allows when searching
@@ -24,11 +22,14 @@ class Base
OPTIONS = SPECIAL_FIND_OPTIONS + AR_OPTIONS # the order is very important, these options get set in this order
attr_accessor *AR_OPTIONS
+ attr_reader :auto_joins
class << self
# Used in the ActiveRecord methods to determine if Searchgasm should get involved or not.
# This keeps Searchgasm out of the way unless it is needed.
def needed?(model_class, options)
+ return false if options.blank?
+
SPECIAL_FIND_OPTIONS.each do |option|
return true if options.symbolize_keys.keys.include?(option)
end
@@ -78,13 +79,8 @@ def inspect
"#<#{klass}Search #{current_find_options.inspect}>"
end
- def joins=(value)
- @memoized_joins = nil
- @joins = value
- end
-
def joins
- @memoized_joins ||= @joins
+ merge_joins(@joins, auto_joins)
end
def limit=(value)
@@ -128,9 +124,7 @@ def sanitize(searching = true)
if searching
find_options[:group] ||= "#{quote_table_name(klass.table_name)}.#{quote_column_name(klass.primary_key)}"
else
- # If we are calculating use includes because they use joins that grab uniq records. When calculating, includes don't have the
- # performance hit that they have when searching. Plus it's cleaner.
- find_options[:include] = merge_joins(find_options[:include], find_options.delete(:joins))
+ find_options[:distinct] = true
end
end
@@ -10,6 +10,7 @@ def self.included(klass)
alias_method_chain :initialize, :conditions
alias_method_chain :conditions=, :conditions
alias_method_chain :conditions, :conditions
+ alias_method_chain :auto_joins, :conditions
alias_method_chain :joins, :conditions
alias_method_chain :sanitize, :conditions
end
@@ -34,7 +35,7 @@ def initialize_with_conditions(init_options = {})
# now you can create the rest of your search and your "scope" will get merged into your final SQL.
# What this does is determine if the value a hash or a conditions object, if not it sets it up as a scope.
def conditions_with_conditions=(values)
- @memoized_joins = nil
+ @memoized_auto_joins = nil
case values
when Searchgasm::Conditions::Base
@conditions = values
@@ -44,16 +45,26 @@ def conditions_with_conditions=(values)
end
def conditions_with_conditions
- @memoized_joins = nil # have to assume they are calling a condition on a relationship
+ @memoized_auto_joins = nil # have to assume they are calling a condition on a relationship
conditions_without_conditions
end
# Tells searchgasm what relationships to join during the search. This is based on what conditions you set.
#
# <b>Be careful!</b>
# ActiveRecord associations can be an SQL train wreck. Make sure you think about what you are searching and that you aren't joining a table with a million records.
+ def auto_joins_with_conditions
+ @memoized_auto_joins ||= merge_joins(auto_joins_without_conditions, conditions.auto_joins)
+ end
+
+ # Changes joins to use left outer joins if conditions.any == true.
def joins_with_conditions
- @memoized_joins ||= merge_joins(joins_without_conditions, conditions.joins)
+ if conditions.any?
+ join_dependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(klass, joins_without_conditions, nil)
+ join_dependency.join_associations.collect { |assoc| assoc.association_join }.join
+ else
+ joins_without_conditions
+ end
end
def sanitize_with_conditions(searching = true) # :nodoc:
Oops, something went wrong.

0 comments on commit 6a8e5f7

Please sign in to comment.