railswhere 0.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.
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
+