railswhere 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (4) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/lib/search_builder.rb +134 -0
  3. data/lib/where.rb +207 -0
  4. metadata +129 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ All portions Copyright (c) 2007 Tim Harper
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,134 @@
1
+ require 'ostruct'
2
+ class SearchBuilder
3
+ attr_reader :object
4
+ attr_accessor :where
5
+
6
+ def object=(value)
7
+ value=OpenStruct.new(value) if Hash===value
8
+ @object = value
9
+ end
10
+
11
+ def initialize(target_object, options={})
12
+ self.object = target_object.is_a?(Hash) ? OpenStruct.new(target_object) : target_object
13
+ self.where = options[:append_to] || Where.new
14
+ @table_prefix = ""
15
+ end
16
+
17
+ def self.delegate_to(object_name, methods = [])
18
+ for method_name in methods
19
+ class_eval <<-EOF, __FILE__, __LINE__ +1
20
+ def #{method_name}(*params, &block)
21
+ #{object_name} && #{object_name}.#{method_name}(*params, &block)
22
+ end
23
+ EOF
24
+ end
25
+ end
26
+
27
+ delegate_to "@where", %w[and or to_sql to_s empty?]
28
+
29
+ def range_on(field, options={})
30
+ options = options.clone
31
+ min_param = options[:min_param] || "#{options[:param] || field}_min"
32
+ max_param = options[:max_param] || "#{options[:param] || field}_max"
33
+ cast = options[:cast] || :string
34
+
35
+ process_clause(field, ">= ?", options.merge(:param => min_param))
36
+ process_clause(field, "<= ?", options.merge(:param => max_param))
37
+
38
+ self
39
+ end
40
+
41
+ def like_on(field, options={})
42
+ options = options.clone
43
+ options[:suffix] ||= "%"
44
+
45
+ process_clause(field, "like ?", options)
46
+ end
47
+
48
+ def for_table(table, &block)
49
+ if block_given?
50
+ last_table_prefix = @table_prefix
51
+ end
52
+
53
+ @table_prefix = "#{table}."
54
+
55
+ if block_given?
56
+ yield
57
+
58
+ @table_prefix = last_table_prefix
59
+ end
60
+
61
+ end
62
+
63
+ def equal_on(field, options={})
64
+ options = options.clone
65
+ options[:cast] = :string
66
+ process_clause(field, "= ?", options)
67
+ end
68
+
69
+ def in_on(field, options={})
70
+ options = options.clone
71
+ options[:cast] ||= :array
72
+ process_clause(field, "in (?)", options)
73
+ end
74
+
75
+ def process_clause(field, operator_clause, options={})
76
+ param = options[:param] || field
77
+ self.and_unless_blank("#{@table_prefix}#{field} #{operator_clause}", value_for(param, options[:cast]), options)
78
+
79
+ self
80
+ end
81
+
82
+ def and_unless_blank(condition, value, options={})
83
+ value = value.compact if (Array === value)
84
+
85
+ # if value is an empty array or a blank string, don't filter on it
86
+ return self if value.blank?
87
+
88
+ prefix = options[:prefix]
89
+ suffix = options[:suffix]
90
+ if prefix || suffix
91
+ @where.and(condition, [prefix, value, suffix].compact.to_s )
92
+ else
93
+ @where.and(condition, value)
94
+ end
95
+
96
+ self
97
+ end
98
+
99
+ def value_for(param, cast=:string)
100
+ param = param.to_s
101
+ if param.include?(".")
102
+ param=param.split(".").last
103
+ end
104
+ cast_to( object.send(param), cast)
105
+ end
106
+
107
+ def cast_to(value, type)
108
+ self.class.cast_to(value, type)
109
+ end
110
+
111
+ def self.cast_to(value, type)
112
+ return value if value.nil?
113
+
114
+ case type
115
+ when nil
116
+ value
117
+ when :array
118
+ value = Array===value ? value : [value]
119
+ when :time
120
+ Time.parse(value)
121
+ when :date
122
+ Time.parse(value).to_date
123
+ when :i, :int, :integer
124
+ value.to_i
125
+ when :f, :float
126
+ value.to_f
127
+ when :string
128
+ value.to_s
129
+ else
130
+ raise "unknown cast type: #{type}"
131
+ end
132
+
133
+ end
134
+ end
data/lib/where.rb ADDED
@@ -0,0 +1,207 @@
1
+ # = Where clause generator
2
+ # == Author: Tim Harper ( "timseeharperATgmail.seeom".gsub("see", "c").gsub("AT", "@") )
3
+ #
4
+ # <b>Usage example</b>
5
+ # === Returning SQL
6
+ #
7
+ # sql = Where.new('x=?',5).and( Where.new('x=?',6).or('x=?',7)).to_s
8
+ # # returns (x=5) and ( ( x=6 ) or ( x=7 ) )
9
+ #
10
+ # === Building a complicated where clause made easy
11
+ #
12
+ # def get_search_query_string
13
+ #
14
+ # where = Where.new
15
+ # where.and('users.first_name like ?', params[:search_first_name] + '%') unless params[:search_first_name].blank?
16
+ # where.and('users.last_name like ?', params[:search_last_name] + '%') unless params[:search_last_name].blank?
17
+ #
18
+ # status_where = Where.new
19
+ # for status in params[search_statuses].split(',')
20
+ # status_where.or 'status=?', status
21
+ # end
22
+ # where.and status_where unless status_where.blank?
23
+ #
24
+ # where.to_s
25
+ # end
26
+ #
27
+ # User.find(:all, :conditions => get_search_query_string)
28
+ #
29
+ # === Inline
30
+ #
31
+ # User.find(:all, :conditions => Where.new('first_name like ?', 'Tim').and('last_name like ?', 'Harper') )
32
+ # # Sweet chaining action!
33
+
34
+
35
+ class Where
36
+ attr_accessor :default_params
37
+ attr_reader :clauses, :target
38
+ # Constructs a new where clause
39
+ #
40
+ # optionally, you can provide a criteria, like the following:
41
+ #
42
+ # Where.initialize "joke_title = ?", "He says, 'Under there', to which I reply, 'under where?'"
43
+ def initialize(criteria_or_options=nil, *params, &block)
44
+ @clauses=Array.new
45
+ @target = self
46
+ if criteria_or_options.is_a?(Hash)
47
+ criteria = nil
48
+ options = criteria_or_options
49
+ else
50
+ criteria = criteria_or_options
51
+ options = {}
52
+ end
53
+ self.and(criteria, *params) unless criteria.nil?
54
+ self.default_params = options[:default_params] || {}
55
+
56
+ yield(self) if block_given?
57
+ end
58
+
59
+ def initialize_copy(from)
60
+ @clauses = from.instance_variable_get("@clauses").clone
61
+ end
62
+
63
+ # Appends an <b>and</b> expression to your where clause
64
+ #
65
+ # Example:
66
+ #
67
+ # where = Where.new
68
+ # where.and("name = ?", "Tim O'brien")
69
+ # where.to_s
70
+ #
71
+ # # => "(name = 'Tim O''brien')
72
+ def and(*params, &block)
73
+ @target.append_clause(params, "AND", &block)
74
+ end
75
+
76
+ alias << and
77
+
78
+ # Appends an <b>or</b> expression to your where clause
79
+ #
80
+ # Example:
81
+ #
82
+ # where = Where.new
83
+ # where.or("name = ?", "Tim O'brien")
84
+ # where.or("name = ?", "Tim O'neal")
85
+ # where.to_s
86
+ #
87
+ # # => "(name = 'Tim O''brien') or (name = 'Tim O''neal')"
88
+ def or(*params, &block)
89
+ @target.append_clause(params, "OR", &block)
90
+ end
91
+
92
+ # Same as or, but negates the whole expression
93
+ def or_not(*params, &block)
94
+ @target.append_clause(params, "OR NOT", &block)
95
+ end
96
+
97
+ # Same as and, but negates the whole expression
98
+ def and_not(*params, &block)
99
+ @target.append_clause(params, "AND NOT", &block)
100
+ end
101
+
102
+ def &(params)
103
+ self.and(*params)
104
+ end
105
+
106
+ def |(params)
107
+ self.or(*params)
108
+ end
109
+
110
+ def self.&(params)
111
+ Where.new(*params)
112
+ end
113
+
114
+ def self.|(params)
115
+ Where.new.or(*params)
116
+ end
117
+
118
+ # Converts the where clause to a SQL string.
119
+ def to_s(format=nil)
120
+ output=""
121
+
122
+ @clauses.each_index{|index|
123
+ omit_conjuction = (index==0)
124
+ output << @clauses[index].to_s(omit_conjuction) # Omit the clause if index=0
125
+ }
126
+ case format
127
+ when :where
128
+ output.empty? ? "" : " WHERE #{output}"
129
+ else
130
+ output.empty? ? "(true)" : output
131
+ end
132
+ end
133
+
134
+ alias :to_sql :to_s
135
+
136
+ # Determines if any clauses have been added.
137
+ #
138
+ # where = Where.new
139
+ # where.blank?
140
+ # # => true
141
+ #
142
+ # where.and(nil)
143
+ # where.blank?
144
+ # # => true
145
+ #
146
+ # where.and(Where.new(nil))
147
+ # where.blank?
148
+ # # => true
149
+ #
150
+ # where.and("name=1")
151
+ # where.blank?
152
+ # # => false
153
+ def blank?
154
+ @clauses.empty?
155
+ end
156
+
157
+ alias :empty? :blank?
158
+
159
+ protected
160
+ def append_clause(params, conjuction = "AND", &block) # :nodoc:
161
+ if block_given?
162
+ previous_target = @target
163
+ @target = Where.new(:default_params => default_params)
164
+ yield
165
+ previous_target.clauses << Clause.new(@target, conjuction)
166
+ @target = previous_target
167
+ else
168
+ params = params + [default_params] if params.length == 1 && params.first.is_a?(String)
169
+ @target.clauses << Clause.new(params, conjuction) unless params.first.blank?
170
+ end
171
+ self
172
+ end
173
+
174
+ # Used internally to +Where+. You shouldn't have any reason to interact with this class.
175
+ class Clause
176
+
177
+ def initialize(criteria, conjuction = "AND") # :nodoc:
178
+ @conjuction=conjuction.upcase
179
+ criteria = criteria.first if criteria.class==Array && criteria.length==1
180
+
181
+ case criteria
182
+ when Array # if it's an array, sanitize it
183
+ @criteria = ActiveRecord::Base.send(:sanitize_sql_array, criteria)
184
+ when Hash
185
+ return nil if criteria.empty?
186
+ @criteria = criteria.keys.sort_by { |v| v.to_s }.map do |field|
187
+ ActiveRecord::Base.send(:sanitize_sql_array, ["#{field} = ?", criteria[field]])
188
+ end.join(' AND ')
189
+ else
190
+ @criteria = criteria.to_s # otherwise, run to_s. If it's a recursive Where clause, it will return the sql we need
191
+ end
192
+ end
193
+
194
+ def to_s(omit_conjuction=false) # :nodoc:
195
+ if omit_conjuction
196
+ output = @conjuction.include?("NOT") ? "NOT " : ""
197
+ output << "(#{@criteria})"
198
+ else
199
+ " #{@conjuction} (#{@criteria})"
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ def Where(*params, &block)
206
+ Where.new(*params, &block)
207
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: railswhere
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ version: "0.1"
10
+ platform: ruby
11
+ authors:
12
+ - Tim Harper
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2011-07-21 00:00:00 -06:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: activerecord
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">"
27
+ - !ruby/object:Gem::Version
28
+ hash: 7
29
+ segments:
30
+ - 3
31
+ - 0
32
+ version: "3.0"
33
+ type: :runtime
34
+ version_requirements: *id001
35
+ - !ruby/object:Gem::Dependency
36
+ name: sqlite3-ruby
37
+ prerelease: false
38
+ requirement: &id002 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ hash: 3
44
+ segments:
45
+ - 0
46
+ version: "0"
47
+ type: :development
48
+ version_requirements: *id002
49
+ - !ruby/object:Gem::Dependency
50
+ name: rspec
51
+ prerelease: false
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - "="
56
+ - !ruby/object:Gem::Version
57
+ hash: 27
58
+ segments:
59
+ - 2
60
+ - 5
61
+ - 0
62
+ version: 2.5.0
63
+ type: :development
64
+ version_requirements: *id003
65
+ - !ruby/object:Gem::Dependency
66
+ name: ruby-debug
67
+ prerelease: false
68
+ requirement: &id004 !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ hash: 3
74
+ segments:
75
+ - 0
76
+ version: "0"
77
+ type: :development
78
+ version_requirements: *id004
79
+ description: Obligatory description when the summary suits.
80
+ email:
81
+ - timcharper@gmail.com
82
+ executables: []
83
+
84
+ extensions: []
85
+
86
+ extra_rdoc_files: []
87
+
88
+ files:
89
+ - lib/search_builder.rb
90
+ - lib/where.rb
91
+ - MIT-LICENSE
92
+ has_rdoc: true
93
+ homepage: http://tim.theenchanter.com/
94
+ licenses: []
95
+
96
+ post_install_message:
97
+ rdoc_options: []
98
+
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ hash: 3
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ hash: 23
116
+ segments:
117
+ - 1
118
+ - 3
119
+ - 6
120
+ version: 1.3.6
121
+ requirements: []
122
+
123
+ rubyforge_project:
124
+ rubygems_version: 1.5.0
125
+ signing_key:
126
+ specification_version: 3
127
+ summary: Easily generate SQL statements
128
+ test_files: []
129
+