searchgasm 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG.rdoc CHANGED
@@ -1,3 +1,11 @@
1
+ == 1.1.1 released 2008-09-19
2
+
3
+ * Fixed typo in "next page" button.
4
+ * Updated valid options for searching and performing calculations, fixed some bugs when searching and performing calculations with advanced options.
5
+ * Fixed bug in ordering where table name was assumed by the hash. Now assumed by the reflection.
6
+ * Added default for per_page, so pagination comes implemented by default
7
+ * On mass assignments blank strings for *any* conditions are ignored. Sometimes blank strings are meaningful for "equals" and "does not equal", those only takes effect if you explicitly call these conditions: search.conditions.name = "". User.new_search(:conditions => {:name => ""}) will be ignored. Also, Searchgasm should never change how ActiveRecord behaves by default. So User.all(:conditions => {:name => ""}) will NOT be ignored.
8
+
1
9
  == 1.1.0 released 2008-09-18
2
10
 
3
11
  * Added the options :inner_spread and :outer_spread to the page_links helper. Also added various config options for setting page_links defaults.
@@ -8,7 +8,7 @@ module Searchgasm
8
8
  # This needs to be implemented because AR doesn't leverage scopes with this method like it probably should
9
9
  def find_with_searchgasm(*args)
10
10
  options = args.extract_options!
11
- args << sanitize_options_with_searchgasm(options)
11
+ args << filter_options_with_searchgasm(options)
12
12
  find_without_searchgasm(*args)
13
13
  end
14
14
 
@@ -36,7 +36,7 @@ module Searchgasm
36
36
  module Shared
37
37
  def count_with_searchgasm(*args)
38
38
  options = args.extract_options!
39
- args << sanitize_options_with_searchgasm(options)
39
+ args << filter_options_with_searchgasm(options)
40
40
  count_without_searchgasm(*args)
41
41
  end
42
42
  end
@@ -6,21 +6,15 @@ module Searchgasm
6
6
  # This is an alias method chain. It hook into ActiveRecord's "calculate" method and checks to see if Searchgasm should get involved.
7
7
  def calculate_with_searchgasm(*args)
8
8
  options = args.extract_options!
9
- options = sanitize_options_with_searchgasm(options)
9
+ options = filter_options_with_searchgasm(options)
10
10
  args << options
11
11
  calculate_without_searchgasm(*args)
12
12
  end
13
13
 
14
- def total(*args)
15
- options = args.extract_options!
16
- searcher = searchgasm_searcher(options)
17
- searcher.total
18
- end
19
-
20
14
  # This is an alias method chain. It hooks into ActiveRecord's "find" method and checks to see if Searchgasm should get involved.
21
15
  def find_with_searchgasm(*args)
22
16
  options = args.extract_options!
23
- options = sanitize_options_with_searchgasm(options)
17
+ options = filter_options_with_searchgasm(options)
24
18
  args << options
25
19
  find_without_searchgasm(*args)
26
20
  end
@@ -45,7 +39,7 @@ module Searchgasm
45
39
  # build_search
46
40
  # end
47
41
  def with_scope_with_searchgasm(method_scoping = {}, action = :merge, &block)
48
- method_scoping[:find] = sanitize_options_with_searchgasm(method_scoping[:find]) if method_scoping[:find]
42
+ method_scoping[:find] = filter_options_with_searchgasm(method_scoping[:find]) if method_scoping[:find]
49
43
  with_scope_without_searchgasm(method_scoping, action, &block)
50
44
  end
51
45
 
@@ -130,10 +124,19 @@ module Searchgasm
130
124
  end
131
125
 
132
126
  private
133
- def sanitize_options_with_searchgasm(options = {})
127
+ def filter_options_with_searchgasm(options = {})
134
128
  return options unless Searchgasm::Search::Base.needed?(self, options)
135
129
  search = Searchgasm::Search::Base.create_virtual_class(self).new # call explicitly to avoid merging the scopes into the searcher
136
130
  search.acting_as_filter = true
131
+ conditions = options.delete(:conditions) || options.delete("conditions") || {}
132
+ if conditions
133
+ case conditions
134
+ when Hash
135
+ conditions.each { |condition, value| search.conditions.send("#{condition}=", value) } # explicitly call to enforce blanks
136
+ else
137
+ search.conditions = conditions
138
+ end
139
+ end
137
140
  search.options = options
138
141
  search.sanitize
139
142
  end
@@ -181,8 +184,12 @@ module ActiveRecord #:nodoc: all
181
184
  alias_method :new_search!, :build_search!
182
185
 
183
186
  def valid_find_options
184
- VALID_FIND_OPTIONS
185
- end
187
+ VALID_FIND_OPTIONS
188
+ end
189
+
190
+ def valid_calculations_options
191
+ Calculations::CALCULATIONS_OPTIONS
192
+ end
186
193
  end
187
194
  end
188
195
  end
@@ -135,7 +135,10 @@ module Searchgasm
135
135
  case conditions
136
136
  when Hash
137
137
  assert_valid_conditions(conditions)
138
- remove_conditions_from_protected_assignement(conditions).each { |condition, value| send("#{condition}=", value) }
138
+ remove_conditions_from_protected_assignement(conditions).each do |condition, value|
139
+ next if value.blank? # ignore blanks on mass assignments
140
+ send("#{condition}=", value)
141
+ end
139
142
  else
140
143
  self.sql = conditions
141
144
  end
@@ -92,12 +92,12 @@ module Searchgasm
92
92
  end
93
93
 
94
94
  def page_links_next # :nodoc:
95
- @page_links_next ||= "< Next"
95
+ @page_links_next ||= "Next >"
96
96
  end
97
97
 
98
98
  # The default for the :next option for the page_links helper.
99
99
  #
100
- # * <tt>Default:</tt> "< Next"
100
+ # * <tt>Default:</tt> "Next >"
101
101
  # * <tt>Accepts:</tt> Anything you want, text, html, etc. nil to disable
102
102
  def page_links_next=(value)
103
103
  @page_links_next = value
@@ -116,14 +116,14 @@ module Searchgasm
116
116
  end
117
117
 
118
118
  def per_page # :nodoc:
119
- @per_page
119
+ @per_page ||= per_page_choices[2]
120
120
  end
121
121
 
122
122
  # The default for per page. This is only applicaple for protected searches. Meaning you start the search with new_search or new_conditions.
123
123
  # The reason for this not to disturb regular queries such as Whatever.find(:all). You would not expect that to be limited.
124
124
  #
125
- # * <tt>Default:</tt> nil, nil means "show all"
126
- # * <tt>Accepts:</tt> Any value in your per_page choices
125
+ # * <tt>Default:</tt> The 3rd option in your per_page_choices, default of 50
126
+ # * <tt>Accepts:</tt> Any value in your per_page choices, nil means "show all"
127
127
  def per_page=(value)
128
128
  @per_page = value
129
129
  end
@@ -9,13 +9,21 @@ module Searchgasm #:nodoc:
9
9
  include Searchgasm::Shared::Searching
10
10
  include Searchgasm::Shared::VirtualClasses
11
11
 
12
+ # Options ActiveRecord allows when searching
13
+ AR_FIND_OPTIONS = ::ActiveRecord::Base.valid_find_options
14
+
15
+ # Options ActiveRecord allows when performing calculations
16
+ AR_CALCULATIONS_OPTIONS = ::ActiveRecord::Base.valid_calculations_options
17
+
18
+ AR_OPTIONS = (AR_FIND_OPTIONS + AR_CALCULATIONS_OPTIONS).uniq
19
+
12
20
  # Options that ActiveRecord doesn't suppport, but Searchgasm does
13
21
  SPECIAL_FIND_OPTIONS = [:order_by, :order_as, :page, :per_page]
14
22
 
15
23
  # Valid options you can use when searching
16
- VALID_FIND_OPTIONS = SPECIAL_FIND_OPTIONS + ::ActiveRecord::Base.valid_find_options # the order is very important, these options get set in this order
24
+ OPTIONS = SPECIAL_FIND_OPTIONS + AR_OPTIONS # the order is very important, these options get set in this order
17
25
 
18
- attr_accessor *::ActiveRecord::Base.valid_find_options
26
+ attr_accessor *AR_OPTIONS
19
27
 
20
28
  class << self
21
29
  # Used in the ActiveRecord methods to determine if Searchgasm should get involved or not.
@@ -47,7 +55,7 @@ module Searchgasm #:nodoc:
47
55
  # Makes using searchgasm in the console less annoying and keeps the output meaningful and useful
48
56
  def inspect
49
57
  current_find_options = {}
50
- ::ActiveRecord::Base.valid_find_options.each do |option|
58
+ AR_OPTIONS.each do |option|
51
59
  value = send(option)
52
60
  next if value.nil?
53
61
  current_find_options[option] = value
@@ -72,10 +80,9 @@ module Searchgasm #:nodoc:
72
80
 
73
81
  def options=(values)
74
82
  return unless values.is_a?(Hash)
75
- values.symbolize_keys.fast_assert_valid_keys(VALID_FIND_OPTIONS)
83
+ values.symbolize_keys.fast_assert_valid_keys(OPTIONS)
76
84
 
77
- # Do the special options first, and then the core options last, since the core options take precendence
78
- VALID_FIND_OPTIONS.each do |option|
85
+ OPTIONS.each do |option|
79
86
  next unless values.has_key?(option)
80
87
  send("#{option}=", values[option])
81
88
  end
@@ -84,9 +91,9 @@ module Searchgasm #:nodoc:
84
91
  end
85
92
 
86
93
  # Sanitizes everything down into options ActiveRecord::Base.find can understand
87
- def sanitize
94
+ def sanitize(searching = true)
88
95
  find_options = {}
89
- ::ActiveRecord::Base.valid_find_options.each do |find_option|
96
+ (searching ? AR_FIND_OPTIONS : AR_CALCULATIONS_OPTIONS).each do |find_option|
90
97
  value = send(find_option)
91
98
  next if value.blank?
92
99
  find_options[find_option] = value
@@ -51,8 +51,8 @@ module Searchgasm
51
51
  includes.blank? ? nil : (includes.size == 1 ? includes.first : includes)
52
52
  end
53
53
 
54
- def sanitize_with_conditions # :nodoc:
55
- find_options = sanitize_without_conditions
54
+ def sanitize_with_conditions(searching = true) # :nodoc:
55
+ find_options = sanitize_without_conditions(searching)
56
56
  if conditions_obj = find_options.delete(:conditions)
57
57
  new_conditions = conditions_obj.sanitize
58
58
  find_options[:conditions] = new_conditions unless new_conditions.blank?
@@ -115,7 +115,8 @@ module Searchgasm
115
115
 
116
116
  private
117
117
  def order_by_to_order(order_by, order_as, alt_klass = nil, new_includes = [])
118
- table_name = (alt_klass || klass).table_name
118
+ k = alt_klass || klass
119
+ table_name = k.table_name
119
120
  sql_parts = []
120
121
 
121
122
  case order_by
@@ -123,10 +124,11 @@ module Searchgasm
123
124
  order_by.each { |part| sql_parts << order_by_to_order(part, order_as) }
124
125
  when Hash
125
126
  raise(ArgumentError, "when passing a hash to order_by you must only have 1 key: {:user_group => :name} not {:user_group => :name, :user_group => :id}. The latter should be [{:user_group => :name}, {:user_group => :id}]") if order_by.keys.size != 1
126
- k = order_by.keys.first
127
- v = order_by.values.first
128
- new_includes << k.to_sym
129
- sql_parts << order_by_to_order(v, order_as, eval(k.to_s.classify), new_includes) # using eval, better performance, protection makes sure nothing fishy goes on here
127
+ key = order_by.keys.first
128
+ reflection = k.reflect_on_association(key.to_sym)
129
+ value = order_by.values.first
130
+ new_includes << key.to_sym
131
+ sql_parts << order_by_to_order(value, order_as, reflection.klass, new_includes) # using eval, better performance, protection makes sure nothing fishy goes on here
130
132
  when Symbol, String
131
133
  new_include = build_order_by_includes(new_includes)
132
134
  self.order_by_includes << new_include if new_include
@@ -24,8 +24,12 @@ module Searchgasm
24
24
  # Options that are allowed when protecting against SQL injections (still checked though)
25
25
  SAFE_OPTIONS = Base::SPECIAL_FIND_OPTIONS + [:conditions, :limit, :offset]
26
26
 
27
+ VULNERABLE_FIND_OPTIONS = Base::AR_FIND_OPTIONS - SAFE_OPTIONS
28
+
29
+ VULNERABLE_CALCULATIONS_OPTIONS = Base::AR_CALCULATIONS_OPTIONS - SAFE_OPTIONS
30
+
27
31
  # Options that are not allowed, at all, when protecting against SQL injections
28
- VULNERABLE_OPTIONS = Base::VALID_FIND_OPTIONS - SAFE_OPTIONS
32
+ VULNERABLE_OPTIONS = Base::OPTIONS - SAFE_OPTIONS
29
33
 
30
34
  def self.included(klass)
31
35
  klass.class_eval do
@@ -59,12 +63,13 @@ module Searchgasm
59
63
 
60
64
  k = alt_klass || klass
61
65
  column_names = k.column_names
62
-
66
+
63
67
  [order_by].flatten.each do |column|
64
68
  case column
65
69
  when Hash
66
- return false unless k.reflect_on_association(column.keys.first.to_sym)
67
- return false unless order_by_safe?(column.values.first, column.keys.first.to_s.classify.constantize)
70
+ reflection = k.reflect_on_association(column.keys.first.to_sym)
71
+ return false unless reflection
72
+ return false unless order_by_safe?(column.values.first, reflection.klass)
68
73
  when Array
69
74
  return false unless order_by_safe?(column)
70
75
  else
@@ -21,17 +21,15 @@ module Searchgasm
21
21
  end
22
22
  end_eval
23
23
  end
24
-
24
+
25
25
  # Setup methods for calculating
26
26
  CALCULATION_METHODS.each do |method|
27
27
  class_eval <<-"end_eval", __FILE__, __LINE__
28
28
  def #{method}(*args)
29
29
  options = args.extract_options!
30
30
  klass.send(:with_scope, :find => options) do
31
- find_options = (self.class < Searchgasm::Conditions::Base ? {:conditions => sanitize} : sanitize)
32
- find_options.delete(:select)
33
- find_options.delete(:limit)
34
- find_options.delete(:offset)
31
+ find_options = (self.class < Searchgasm::Conditions::Base ? {:conditions => sanitize} : sanitize(false))
32
+ [:select, :limit, :offset].each { |option| find_options.delete(option) }
35
33
  args << find_options
36
34
  klass.#{method}(*args)
37
35
  end
@@ -67,7 +67,7 @@ module Searchgasm
67
67
 
68
68
  MAJOR = 1
69
69
  MINOR = 1
70
- TINY = 0
70
+ TINY = 1
71
71
 
72
72
  # The current version as a Version instance
73
73
  CURRENT = new(MAJOR, MINOR, TINY)
data/searchgasm.gemspec CHANGED
@@ -1,18 +1,18 @@
1
1
 
2
- # Gem::Specification for Searchgasm-1.1.0
2
+ # Gem::Specification for Searchgasm-1.1.1
3
3
  # Originally generated by Echoe
4
4
 
5
5
  --- !ruby/object:Gem::Specification
6
6
  name: searchgasm
7
7
  version: !ruby/object:Gem::Version
8
- version: 1.1.0
8
+ version: 1.1.1
9
9
  platform: ruby
10
10
  authors:
11
11
  - Ben Johnson of Binary Logic
12
12
  autorequire:
13
13
  bindir: bin
14
14
 
15
- date: 2008-09-18 00:00:00 -04:00
15
+ date: 2008-09-19 00:00:00 -04:00
16
16
  default_executable:
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
@@ -28,8 +28,9 @@ class TestActiveRecordBase < Test::Unit::TestCase
28
28
  assert_nothing_raised { Account.sum("id", {}) }
29
29
  end
30
30
 
31
- def test_valid_find_options
31
+ def test_ar_options
32
32
  assert_equal [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :from, :lock ], ActiveRecord::Base.valid_find_options
33
+ assert_equal [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include, :from], ActiveRecord::Base.valid_calculations_options
33
34
  end
34
35
 
35
36
  def test_build_search
@@ -185,4 +185,13 @@ class TestConditionsBase < Test::Unit::TestCase
185
185
  conditions.any = false
186
186
  assert_equal [Account.find(1)], conditions.all
187
187
  end
188
+
189
+ def test_ignoring_blanks
190
+ conditions = Account.new_conditions(:name => "", :created_after => nil)
191
+ assert_equal({}, conditions.conditions)
192
+ conditions.name = ""
193
+ assert_equal({:name_equals => ""}, conditions.conditions)
194
+ conditions.created_after = ""
195
+ assert_equal({:name_equals => ""}, conditions.conditions)
196
+ end
188
197
  end
@@ -20,5 +20,8 @@ class TestConditionsProtection < Test::Unit::TestCase
20
20
 
21
21
  assert_raise(ArgumentError) { account.users.build_conditions("(DELETE FROM users)") }
22
22
  assert_nothing_raised { account.users.build_conditions!("(DELETE FROM users)") }
23
+
24
+ #search = Account.new_search
25
+ #assert_nothing_raised { search.conditions = "(DELETE FROM users)" }
23
26
  end
24
27
  end
data/test/test_helper.rb CHANGED
@@ -9,6 +9,7 @@ require File.dirname(__FILE__) + '/../lib/searchgasm'
9
9
  ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
10
10
 
11
11
  class Account < ActiveRecord::Base
12
+ has_one :admin, :class_name => "User", :conditions => {:first_name => "Ben"}
12
13
  has_many :users, :dependent => :destroy
13
14
  has_many :orders, :through => :users
14
15
  end
@@ -17,6 +18,7 @@ class User < ActiveRecord::Base
17
18
  acts_as_tree
18
19
  belongs_to :account
19
20
  has_many :orders, :dependent => :destroy
21
+ has_one :cool_order, :class_name => "Order", :conditions => {:total => 100}
20
22
  end
21
23
 
22
24
  class Order < ActiveRecord::Base
@@ -39,7 +39,19 @@ class TestSearchBase < Test::Unit::TestCase
39
39
  end
40
40
 
41
41
  def test_setting_first_level_options
42
- search = Account.new_search
42
+ search = Account.new_search!(:include => :users, :joins => :test, :offset => 5, :limit => 20, :order => "name ASC", :select => "name", :readonly => true, :group => "name", :from => "accounts", :lock => true)
43
+ assert_equal :users, search.include
44
+ assert_equal :test, search.joins
45
+ assert_equal 5, search.offset
46
+ assert_equal 20, search.limit
47
+ assert_equal "name ASC", search.order
48
+ assert_equal "name", search.select
49
+ assert_equal true, search.readonly
50
+ assert_equal "name", search.group
51
+ assert_equal "accounts", search.from
52
+ assert_equal true, search.lock
53
+
54
+ search = Account.new_search(:per_page => nil)
43
55
 
44
56
  search.include = :users
45
57
  assert_equal :users, search.include
@@ -190,6 +202,9 @@ class TestSearchBase < Test::Unit::TestCase
190
202
  assert_equal 3, search.maximum('id')
191
203
  assert_equal 1, search.minimum('id')
192
204
  assert_equal 4, search.sum('id')
205
+
206
+ search.readonly = true
207
+ assert_equal 4, search.sum('id')
193
208
  end
194
209
 
195
210
  def test_method_creation_in_scope
@@ -29,7 +29,7 @@ class TestSearchPagination < Test::Unit::TestCase
29
29
  end
30
30
 
31
31
  def test_page
32
- search = Account.new_search
32
+ search = Account.new_search(:per_page => nil)
33
33
  search.page = 2
34
34
  assert_equal 1, search.page
35
35
  search.per_page = 20
@@ -14,18 +14,18 @@ class TestSearchProtection < Test::Unit::TestCase
14
14
 
15
15
  def test_protection
16
16
  assert_raise(ArgumentError) { Account.build_search(:conditions => "(DELETE FROM users)", :page => 2, :per_page => 15) }
17
- Searchgasm::Search::Base::VULNERABLE_OPTIONS.each { |option| assert_raise(ArgumentError) { Account.build_search(option => "(DELETE FROM users)") } }
17
+ Searchgasm::Search::Base::VULNERABLE_FIND_OPTIONS.each { |option| assert_raise(ArgumentError) { Account.build_search(option => "(DELETE FROM users)") } }
18
18
 
19
19
  assert_nothing_raised { Account.build_search!(:conditions => "(DELETE FROM users)", :page => 2, :per_page => 15) }
20
- Searchgasm::Search::Base::VULNERABLE_OPTIONS.each { |option| assert_nothing_raised { Account.build_search!(option => "(DELETE FROM users)") } }
20
+ Searchgasm::Search::Base::VULNERABLE_FIND_OPTIONS.each { |option| assert_nothing_raised { Account.build_search!(option => "(DELETE FROM users)") } }
21
21
 
22
22
  account = Account.first
23
23
 
24
24
  assert_raise(ArgumentError) { account.users.build_search(:conditions => "(DELETE FROM users)", :page => 2, :per_page => 15) }
25
- Searchgasm::Search::Base::VULNERABLE_OPTIONS.each { |option| assert_raise(ArgumentError) { account.users.build_search(option => "(DELETE FROM users)") } }
25
+ Searchgasm::Search::Base::VULNERABLE_FIND_OPTIONS.each { |option| assert_raise(ArgumentError) { account.users.build_search(option => "(DELETE FROM users)") } }
26
26
 
27
27
  assert_nothing_raised { account.users.build_search!(:conditions => "(DELETE FROM users)", :page => 2, :per_page => 15) }
28
- Searchgasm::Search::Base::VULNERABLE_OPTIONS.each { |option| assert_nothing_raised { account.users.build_search!(option => "(DELETE FROM users)") } }
28
+ Searchgasm::Search::Base::VULNERABLE_FIND_OPTIONS.each { |option| assert_nothing_raised { account.users.build_search!(option => "(DELETE FROM users)") } }
29
29
 
30
30
  assert_raise(ArgumentError) { Account.build_search(:order_by => "unknown_column") }
31
31
  assert_nothing_raised { Account.build_search!(:order_by => "unknown_column") }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: searchgasm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Johnson of Binary Logic
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-09-18 00:00:00 -04:00
12
+ date: 2008-09-19 00:00:00 -04:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency