ar-extensions 0.5.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 +97 -0
- data/README +123 -0
- data/Rakefile +97 -0
- data/config/database.yml +7 -0
- data/config/database.yml.template +7 -0
- data/config/mysql.schema +72 -0
- data/config/postgresql.schema +39 -0
- data/db/migrate/generic_schema.rb +59 -0
- data/db/migrate/mysql_schema.rb +26 -0
- data/db/migrate/version.rb +4 -0
- data/init.rb +32 -0
- data/lib/ar-extensions.rb +4 -0
- data/lib/ar-extensions/adapters/abstract_adapter.rb +83 -0
- data/lib/ar-extensions/adapters/mysql_adapter.rb +14 -0
- data/lib/ar-extensions/adapters/postgresql.rb +7 -0
- data/lib/ar-extensions/csv.rb +302 -0
- data/lib/ar-extensions/extensions.rb +474 -0
- data/lib/ar-extensions/finders.rb +76 -0
- data/lib/ar-extensions/foreign_keys.rb +70 -0
- data/lib/ar-extensions/fulltext.rb +63 -0
- data/lib/ar-extensions/fulltext/mysql.rb +44 -0
- data/lib/ar-extensions/import.rb +254 -0
- data/lib/ar-extensions/import/mysql.rb +70 -0
- data/lib/ar-extensions/import/postgresql.rb +0 -0
- data/lib/ar-extensions/temporary_table.rb +124 -0
- data/lib/ar-extensions/temporary_table/mysql.rb +3 -0
- data/lib/ar-extensions/version.rb +8 -0
- metadata +83 -0
@@ -0,0 +1,474 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
# ActiveRecord::Extensions provides additional functionality to the ActiveRecord
|
4
|
+
# ORM library created by DHH for Rails.
|
5
|
+
#
|
6
|
+
# It's main features include:
|
7
|
+
# * better finder support using a :conditions Hash for ActiveRecord::Base#find
|
8
|
+
# * better finder support using any object that responds to the to_sql method
|
9
|
+
# * mass data import functionality
|
10
|
+
# * a more modular design to extending ActiveRecord
|
11
|
+
#
|
12
|
+
#
|
13
|
+
# == Using Better Finder Hash Support
|
14
|
+
# Here are a few examples, please refer to the class documentation for each
|
15
|
+
# extensions:
|
16
|
+
#
|
17
|
+
# class Post < ActiveRecord::Base ; end
|
18
|
+
#
|
19
|
+
# Post.find( :all, :conditions=>{
|
20
|
+
# :title => "Title", # title='Title'
|
21
|
+
# :author_contains => "Zach", # author like '%Zach%'
|
22
|
+
# :author_starts_with => "Zach", # author like 'Zach%'
|
23
|
+
# :author_ends_with => "Dennis", # author like '%Zach'
|
24
|
+
# :published_at => (Date.now-30 .. Date.now), # published_at BETWEEN xxx AND xxx
|
25
|
+
# :rating => [ 4, 5, 6 ], # rating IN ( 4, 5, 6 )
|
26
|
+
# :rating_not_in => [ 7, 8, 9 ] # rating NOT IN( 4, 5, 6 )
|
27
|
+
# :rating_ne => 4, # rating != 4
|
28
|
+
# :rating_gt => 4, # rating > 4
|
29
|
+
# :rating_lt => 4, # rating < 4
|
30
|
+
# :content => /(a|b|c)/ # REGEXP '(a|b|c)'
|
31
|
+
# )
|
32
|
+
#
|
33
|
+
#
|
34
|
+
# == Create Your Own Finder Extension Example
|
35
|
+
# The following example shows you how-to create a robust and reliable
|
36
|
+
# finder extension which allows you to use Ranges in your :conditions Hash. This
|
37
|
+
# is the actual implementation in ActiveRecord::Extensions.
|
38
|
+
#
|
39
|
+
# class RangeExt
|
40
|
+
# NOT_IN_RGX = /^(.+)_(ne|not|not_in|not_between)$/
|
41
|
+
#
|
42
|
+
# def self.process( key, val, caller )
|
43
|
+
# return nil unless val.is_a?( Range )
|
44
|
+
# match_data = key.to_s.match( NOT_IN_RGX )
|
45
|
+
# key = match_data.captures[0] if match_data
|
46
|
+
# fieldname = caller.connection.quote_column_name( key )
|
47
|
+
# min = caller.connection.quote( val.first, caller.columns_hash[ key ] )
|
48
|
+
# max = caller.connection.quote( val.last, caller.columns_hash[ key ] )
|
49
|
+
# str = "#{caller.table_name}.#{fieldname} #{match_data ? 'NOT ' : '' } BETWEEN #{min} AND #{max}"
|
50
|
+
# Result.new( str, nil )
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
#
|
54
|
+
# == Using to_sql Ducks In Your Find Methods!
|
55
|
+
# The below example shows you how-to utilize objects that respond_to the method +to_sql+ in
|
56
|
+
# your finds:
|
57
|
+
#
|
58
|
+
# class InsuranceClaim < ActiveRecord::Base ; end
|
59
|
+
#
|
60
|
+
# class InsuranceClaimAgeAndTypeQuery
|
61
|
+
# def to_sql
|
62
|
+
# "age_in_days BETWEEN 1 AND 60 AND claim_type IN( 'typea', 'typeb' )"
|
63
|
+
# end
|
64
|
+
# end
|
65
|
+
#
|
66
|
+
# claims = InsuranceClaim.find( :all, InsuranceClaimAgeAndTypeQuery.new )
|
67
|
+
#
|
68
|
+
# claims = InsuranceClaim.find( :all, :conditions=>{
|
69
|
+
# :claim_amount_gt => 30000,
|
70
|
+
# :age_and_type => InsuranceClaimAgeAndTypeQuery.new }
|
71
|
+
# )
|
72
|
+
#
|
73
|
+
# == Importing Lots of Data
|
74
|
+
#
|
75
|
+
# ActiveRecord executes a single INSERT statement for every cal to 'create'
|
76
|
+
# and for every call to 'save' on a new model object. When you have only
|
77
|
+
# a handful of records to create or save this is not a big deal, but when
|
78
|
+
# you have hundreds, thousands or hundreds of thousands of records
|
79
|
+
# you need to have better performance.
|
80
|
+
#
|
81
|
+
# Below is an example of how to import the least amount of INSERT statements
|
82
|
+
# using mechanisms provided by your database vendor:
|
83
|
+
#
|
84
|
+
# class Student < ActiveRecord::Base ; end
|
85
|
+
#
|
86
|
+
# column_names = Student.columns.map{ |column| column.name }
|
87
|
+
# value_sets = some_method_to_load_data_from_csv_file( 'students.csv' )
|
88
|
+
# options = { :valudate => true }
|
89
|
+
#
|
90
|
+
# Student.import( column_names, value_sets, options )
|
91
|
+
#
|
92
|
+
# The +import+ functionality can be used even if there is not specific
|
93
|
+
# support for you vendor. This happens when a particular database vendor
|
94
|
+
# specific enhancement hasn't been added to ActiveRecord::Extensions.
|
95
|
+
# You can still use +import+ though because the +import+ functionality has
|
96
|
+
# been created with backwards compatibility. You may still get better
|
97
|
+
# performance using +import+, but you will definitely get no worse then
|
98
|
+
# ActiveRecord's create or save methods.
|
99
|
+
#
|
100
|
+
# See ActiveRecord::Base.import for more information and other ways to use
|
101
|
+
# this functionality.
|
102
|
+
#
|
103
|
+
# == Developers
|
104
|
+
# * Zach Dennis
|
105
|
+
# * Mark Van Holsytn
|
106
|
+
#
|
107
|
+
# == Homepage
|
108
|
+
# * Project Site: http://www.continuousthinking.com/tags/arext
|
109
|
+
# * Rubyforge Project: http://rubyforge.org/projects/arext
|
110
|
+
# * Anonymous SVN: svn checkout svn://rubyforge.org/var/svn/arext
|
111
|
+
#
|
112
|
+
module ActiveRecord::Extensions
|
113
|
+
|
114
|
+
Result = Struct.new( :sql, :value )
|
115
|
+
|
116
|
+
# ActiveRecored::Extensions::Registry is used to register finder extensions.
|
117
|
+
# Extensions are processed in last in first out order, like a stack.
|
118
|
+
class Registry # :nodoc:
|
119
|
+
|
120
|
+
def options( extension )
|
121
|
+
extension_arr = @registry.detect{ |arr| arr.first == extension }
|
122
|
+
return unless extension_arr
|
123
|
+
extension_arr.last
|
124
|
+
end
|
125
|
+
|
126
|
+
def registers?( extension ) # :nodoc:
|
127
|
+
@registry.detect{ |arr| arr.first == extension }
|
128
|
+
end
|
129
|
+
|
130
|
+
def register( extension, options ) # :nodoc:
|
131
|
+
@registry << [ extension, options ]
|
132
|
+
end
|
133
|
+
|
134
|
+
def initialize # :nodoc:
|
135
|
+
@registry = []
|
136
|
+
end
|
137
|
+
|
138
|
+
def process( field, value, caller ) # :nodoc:
|
139
|
+
current_adapter = caller.connection.adapter_name.downcase
|
140
|
+
@registry.reverse.each do |(extension,options)|
|
141
|
+
adapters = options[:adapters]
|
142
|
+
adapters.map!{ |e| e.to_s } unless adapters == :all
|
143
|
+
next if options[:adapters] != :all and adapters.grep( /#{current_adapter}/ ).empty?
|
144
|
+
if result=extension.process( field, value, caller )
|
145
|
+
return result
|
146
|
+
end
|
147
|
+
end
|
148
|
+
nil
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
class << self
|
154
|
+
extend Forwardable
|
155
|
+
|
156
|
+
def register( extension, options ) # :nodoc:
|
157
|
+
@registry ||= Registry.new
|
158
|
+
@registry.register( extension, options )
|
159
|
+
end
|
160
|
+
|
161
|
+
def_delegator :@registry, :process, :process
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
# ActiveRecord::Extension to translate an Array of values
|
166
|
+
# into the approriate IN( ... ) or NOT IN( ... ) SQL.
|
167
|
+
#
|
168
|
+
# == Examples
|
169
|
+
# Model.find :all, :conditions=>{ :id => [ 1,2,3 ] }
|
170
|
+
#
|
171
|
+
# # the following three calls are equivalent
|
172
|
+
# Model.find :all, :conditions=>{ :id_ne => [ 4,5,6 ] }
|
173
|
+
# Model.find :all, :conditions=>{ :id_not => [ 4,5,6 ] }
|
174
|
+
# Model.find :all, :conditions=>{ :id_not_in => [ 4,5,6 ] }
|
175
|
+
class ArrayExt
|
176
|
+
|
177
|
+
NOT_EQUAL_RGX = /(.+)_(ne|not|not_in)/
|
178
|
+
|
179
|
+
def self.process( key, val, caller )
|
180
|
+
if val.is_a?( Array )
|
181
|
+
match_data = key.to_s.match( NOT_EQUAL_RGX )
|
182
|
+
key = match_data.captures[0] if match_data
|
183
|
+
str = "#{caller.table_name}.#{caller.connection.quote_column_name( key )} " +
|
184
|
+
(match_data ? 'NOT ' : '') + "IN( ? )"
|
185
|
+
return Result.new( str, val )
|
186
|
+
end
|
187
|
+
nil
|
188
|
+
end
|
189
|
+
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
# ActiveRecord::Extension to translate Hash keys which end in
|
194
|
+
# +_lt+, +_lte+, +_gt+, or +_gte+ with the approriate <, <=, >,
|
195
|
+
# or >= symbols.
|
196
|
+
# * +_lt+ - denotes less than
|
197
|
+
# * +_gt+ - denotes greater than
|
198
|
+
# * +_lte+ - denotes less than or equal to
|
199
|
+
# * +_gte+ - denotes greater than or equal to
|
200
|
+
#
|
201
|
+
# == Examples
|
202
|
+
# Model.find :all, :conditions=>{ 'number_gt'=>100 }
|
203
|
+
# Model.find :all, :conditions=>{ 'number_lt'=>100 }
|
204
|
+
# Model.find :all, :conditions=>{ 'number_gte'=>100 }
|
205
|
+
# Model.find :all, :conditions=>{ 'number_lte'=>100 }
|
206
|
+
class Comparison
|
207
|
+
|
208
|
+
SUFFIX_MAP = { 'eq'=>'=', 'lt'=>'<', 'lte'=>'<=', 'gt'=>'>', 'gte'=>'>=', 'ne'=>'!=', 'not'=>'!=' }
|
209
|
+
|
210
|
+
def self.process( key, val, caller )
|
211
|
+
process_without_suffix( key, val, caller ) || process_with_suffix( key, val, caller )
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.process_without_suffix( key, val, caller )
|
215
|
+
return nil unless caller.columns_hash.has_key?( key )
|
216
|
+
if val.nil?
|
217
|
+
str = "#{caller.table_name}.#{caller.connection.quote_column_name( key )} IS NULL"
|
218
|
+
else
|
219
|
+
str = "#{caller.table_name}.#{caller.connection.quote_column_name( key )}=" +
|
220
|
+
"#{caller.connection.quote( val, caller.columns_hash[ key ] )} "
|
221
|
+
end
|
222
|
+
Result.new( str, nil )
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.process_with_suffix( key, val, caller )
|
226
|
+
return nil unless val.is_a?( String ) or val.is_a?( Numeric )
|
227
|
+
SUFFIX_MAP.each_pair do |k,v|
|
228
|
+
match_data = key.to_s.match( /(.+)_#{k}$/ )
|
229
|
+
if match_data
|
230
|
+
fieldname = match_data.captures[0]
|
231
|
+
return nil unless caller.columns_hash.has_key?( fieldname )
|
232
|
+
str = "#{caller.table_name}.#{caller.connection.quote_column_name( fieldname )} " +
|
233
|
+
"#{v} #{caller.connection.quote( val, caller.columns_hash[ fieldname ] )} "
|
234
|
+
return Result.new( str, nil )
|
235
|
+
end
|
236
|
+
end
|
237
|
+
nil
|
238
|
+
end
|
239
|
+
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
# ActiveRecord::Extension to translate Hash keys which end in
|
244
|
+
# +_like+ or +_contains+ with the approriate LIKE keyword
|
245
|
+
# used in SQL.
|
246
|
+
#
|
247
|
+
# == Examples
|
248
|
+
# # the below two examples are equivalent
|
249
|
+
# Model.find :all, :conditions=>{ :name_like => 'John' }
|
250
|
+
# Model.find :all, :conditions=>{ :name_contains => 'John' }
|
251
|
+
#
|
252
|
+
# Model.find :all, :conditions=>{ :name_starts_with => 'J' }
|
253
|
+
# Model.find :all, :conditions=>{ :name_ends_with => 'n' }
|
254
|
+
class Like
|
255
|
+
LIKE_RGX = /(.+)_(like|contains)$/
|
256
|
+
STARTS_WITH_RGX = /(.+)_starts_with$/
|
257
|
+
ENDS_WITH_RGX = /(.+)_ends_with$/
|
258
|
+
|
259
|
+
def self.process( key, val, caller )
|
260
|
+
if match_data=key.to_s.match( LIKE_RGX )
|
261
|
+
fieldname = match_data.captures[0]
|
262
|
+
str = "#{caller.table_name}.#{caller.connection.quote_column_name( fieldname )} LIKE ?"
|
263
|
+
return Result.new( str, "%#{val}%" )
|
264
|
+
elsif match_data=key.to_s.match( STARTS_WITH_RGX )
|
265
|
+
fieldname = match_data.captures[0]
|
266
|
+
str = "#{caller.table_name}.#{caller.connection.quote_column_name( fieldname )} LIKE ?"
|
267
|
+
return Result.new( str, "#{val}%" )
|
268
|
+
elsif match_data=key.to_s.match( ENDS_WITH_RGX )
|
269
|
+
fieldname = match_data.captures[0]
|
270
|
+
str = "#{caller.table_name}.#{caller.connection.quote_column_name( fieldname )} LIKE ?"
|
271
|
+
return Result.new( str, "%#{val}" )
|
272
|
+
end
|
273
|
+
nil
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
|
279
|
+
# ActiveRecord::Extension to translate a ruby Range object into SQL's BETWEEN ... AND ...
|
280
|
+
# or NOT BETWEEN ... AND ... . This works on Ranges of Numbers, Dates, Times, etc.
|
281
|
+
#
|
282
|
+
# == Examples
|
283
|
+
# # the following two statements are identical because of how Ranges treat .. and ...
|
284
|
+
# Model.find :all, :conditions=>{ :id => ( 1 .. 2 ) }
|
285
|
+
# Model.find :all, :conditions=>{ :id => ( 1 ... 2 ) }
|
286
|
+
#
|
287
|
+
# # the following four statements are identical, this finds NOT BETWEEN matches
|
288
|
+
# Model.find :all, :conditions=>{ :id_ne => ( 4 .. 6 ) }
|
289
|
+
# Model.find :all, :conditions=>{ :id_not => ( 4 .. 6 ) }
|
290
|
+
# Model.find :all, :conditions=>{ :id_not_in => ( 4 ..6 ) }
|
291
|
+
# Model.find :all, :conditions=>{ :id_not_between => ( 4 .. 6 ) }
|
292
|
+
#
|
293
|
+
# # a little more creative, working with date ranges
|
294
|
+
# Model.find :all, :conditions=>{ :created_on => (Date.now-30 .. Date.now) }
|
295
|
+
class RangeExt
|
296
|
+
NOT_IN_RGX = /^(.+)_(ne|not|not_in|not_between)$/
|
297
|
+
|
298
|
+
def self.process( key, val, caller )
|
299
|
+
if val.is_a?( Range )
|
300
|
+
match_data = key.to_s.match( NOT_IN_RGX )
|
301
|
+
key = match_data.captures[0] if match_data
|
302
|
+
fieldname = caller.connection.quote_column_name( key )
|
303
|
+
min = caller.connection.quote( val.first, caller.columns_hash[ key ] )
|
304
|
+
max = caller.connection.quote( val.last, caller.columns_hash[ key ] )
|
305
|
+
str = "#{caller.table_name}.#{fieldname} #{match_data ? 'NOT ' : '' } BETWEEN #{min} AND #{max}"
|
306
|
+
return Result.new( str, nil )
|
307
|
+
end
|
308
|
+
nil
|
309
|
+
end
|
310
|
+
|
311
|
+
end
|
312
|
+
|
313
|
+
# A base class for database vendor specific Regexp implementations. This is meant to be
|
314
|
+
# subclassed only because of the helper method(s) it provides.
|
315
|
+
class RegexpBase
|
316
|
+
|
317
|
+
NOT_EQUAL_RGX = /^(.+)_(ne|not|does_not_match)$/
|
318
|
+
|
319
|
+
# A result class which provides an easy interface.
|
320
|
+
class RegexpResult
|
321
|
+
attr_reader :fieldname, :negate
|
322
|
+
|
323
|
+
def initialize( fieldname, negate=false )
|
324
|
+
@fieldname, @negate = fieldname, negate
|
325
|
+
end
|
326
|
+
|
327
|
+
def negate?
|
328
|
+
negate ? true : false
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
# Given the passed in +str+ and +caller+ this will return a RegexpResult object
|
333
|
+
# which gives the database quoted fieldname/column and can tell you whether or not
|
334
|
+
# the original +str+ is indicating a negated regular expression.
|
335
|
+
#
|
336
|
+
# == Examples
|
337
|
+
# r = RegexpBase.field_result( 'id' )
|
338
|
+
# r.fieldname => # 'id'
|
339
|
+
# r.negate? => # false
|
340
|
+
#
|
341
|
+
# r = RegexpBase.field_result( 'id_ne' )
|
342
|
+
# r.fieldname => # 'id'
|
343
|
+
# r.negate? => # true
|
344
|
+
#
|
345
|
+
# r = RegexpBase.field_result( 'id_not' )
|
346
|
+
# r.fieldname => # 'id'
|
347
|
+
# r.negate? => # true
|
348
|
+
#
|
349
|
+
# r = RegexpBase.field_result( 'id_does_not_match' )
|
350
|
+
# r.fieldname => # 'id'
|
351
|
+
# r.negate? => # true
|
352
|
+
def self.field_result( str, caller )
|
353
|
+
negate = false
|
354
|
+
if match_data=str.to_s.match( NOT_EQUAL_RGX )
|
355
|
+
negate = true
|
356
|
+
str = match_data.captures[0]
|
357
|
+
end
|
358
|
+
fieldname = caller.connection.quote_column_name( str )
|
359
|
+
RegexpResult.new( fieldname, negate )
|
360
|
+
end
|
361
|
+
|
362
|
+
end
|
363
|
+
|
364
|
+
|
365
|
+
# ActiveRecord::Extension for implementing Regexp implementation for MySQL.
|
366
|
+
# See documention for RegexpBase.
|
367
|
+
class MySQLRegexp < RegexpBase
|
368
|
+
|
369
|
+
def self.process( key, val, caller )
|
370
|
+
return nil unless val.is_a?( Regexp )
|
371
|
+
r = field_result( key, caller )
|
372
|
+
Result.new( "#{caller.table_name}.#{r.fieldname} #{r.negate? ? 'NOT ':''} REGEXP ?", val )
|
373
|
+
end
|
374
|
+
|
375
|
+
end
|
376
|
+
|
377
|
+
|
378
|
+
# ActiveRecord::Extension for implementing Regexp implementation for PostgreSQL.
|
379
|
+
# See documention for RegexpBase.
|
380
|
+
#
|
381
|
+
# Note: this doesn't support case insensitive matches.
|
382
|
+
class PostgreSQLRegexp < RegexpBase
|
383
|
+
|
384
|
+
def self.process( key, val, caller )
|
385
|
+
return nil unless val.is_a?( Regexp )
|
386
|
+
r = field_result( key, caller )
|
387
|
+
return Result.new( "#{caller.table_name}.#{r.fieldname} #{r.negate? ? '!~ ':'~'} ?", val )
|
388
|
+
end
|
389
|
+
|
390
|
+
end
|
391
|
+
|
392
|
+
|
393
|
+
# ActiveRecord::Extension for implementing Regexp implementation for MySQL.
|
394
|
+
# See documention for RegexpBase.
|
395
|
+
class SqliteRegexp < RegexpBase
|
396
|
+
class_inheritable_accessor :connections
|
397
|
+
self.connections = []
|
398
|
+
|
399
|
+
def self.add_rlike_function( connection )
|
400
|
+
self.connections << connection
|
401
|
+
unless connection.respond_to?( 'sqlite_regexp_support?' )
|
402
|
+
class << connection
|
403
|
+
def sqlite_regexp_support? ; true ; end
|
404
|
+
end
|
405
|
+
connection.instance_eval( '@connection' ).create_function( 'rlike', 3 ) do |func, a, b, negate|
|
406
|
+
if negate =~ /true/
|
407
|
+
func.set_result 1 if a.to_s !~ /#{b}/
|
408
|
+
else
|
409
|
+
func.set_result 1 if a.to_s =~ /#{b}/
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
end
|
414
|
+
|
415
|
+
def self.process( key, val, caller )
|
416
|
+
return nil unless val.is_a?( Regexp )
|
417
|
+
r = field_result( key, caller )
|
418
|
+
unless self.connections.include?( caller.connection )
|
419
|
+
add_rlike_function( caller.connection )
|
420
|
+
end
|
421
|
+
Result.new( "rlike( #{r.fieldname}, ?, '#{r.negate?}' )", val )
|
422
|
+
end
|
423
|
+
|
424
|
+
end
|
425
|
+
|
426
|
+
class DatetimeSupport
|
427
|
+
SUFFIX_MAP = { 'eq'=>'=', 'lt'=>'<', 'lte'=>'<=', 'gt'=>'>', 'gte'=>'>=', 'ne'=>'!=', 'not'=>'!=' }
|
428
|
+
|
429
|
+
def self.process( key, val, caller )
|
430
|
+
return unless val.is_a?( Time )
|
431
|
+
process_without_suffix( key, val, caller ) || process_with_suffix( key, val, caller )
|
432
|
+
end
|
433
|
+
|
434
|
+
def self.process_without_suffix( key, val, caller )
|
435
|
+
return nil unless caller.columns_hash.has_key?( key )
|
436
|
+
if val.nil?
|
437
|
+
str = "#{caller.table_name}.#{caller.connection.quote_column_name( key )} IS NULL"
|
438
|
+
else
|
439
|
+
str = "#{caller.table_name}.#{caller.connection.quote_column_name( key )}=" +
|
440
|
+
"#{caller.connection.quote( val.to_s(:db), caller.columns_hash[ key ] )} "
|
441
|
+
end
|
442
|
+
Result.new( str, nil )
|
443
|
+
end
|
444
|
+
|
445
|
+
def self.process_with_suffix( key, val, caller )
|
446
|
+
SUFFIX_MAP.each_pair do |k,v|
|
447
|
+
match_data = key.to_s.match( /(.+)_#{k}$/ )
|
448
|
+
if match_data
|
449
|
+
fieldname = match_data.captures[0]
|
450
|
+
return nil unless caller.columns_hash.has_key?( fieldname )
|
451
|
+
str = "#{caller.table_name}.#{caller.connection.quote_column_name( fieldname )} " +
|
452
|
+
"#{v} #{caller.connection.quote( val.to_s(:db), caller.columns_hash[ fieldname ] )} "
|
453
|
+
return Result.new( str, nil )
|
454
|
+
end
|
455
|
+
end
|
456
|
+
nil
|
457
|
+
end
|
458
|
+
|
459
|
+
|
460
|
+
end
|
461
|
+
|
462
|
+
|
463
|
+
register Comparison, :adapters=>:all
|
464
|
+
register Like, :adapters=>:all
|
465
|
+
register ArrayExt, :adapters=>:all
|
466
|
+
register RangeExt, :adapters=>:all
|
467
|
+
register MySQLRegexp, :adapters=>[ :mysql ]
|
468
|
+
register PostgreSQLRegexp, :adapters=>[ :postgresql ]
|
469
|
+
register SqliteRegexp, :adapters =>[ :sqlite ]
|
470
|
+
register DatetimeSupport, :adapters =>[ :mysql, :sqlite ]
|
471
|
+
end
|
472
|
+
|
473
|
+
|
474
|
+
|