ghazel-ar-extensions 0.9.3
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +145 -0
- data/README +169 -0
- data/Rakefile +61 -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 +97 -0
- data/db/migrate/mysql_schema.rb +32 -0
- data/db/migrate/oracle_schema.rb +5 -0
- data/db/migrate/version.rb +4 -0
- data/init.rb +31 -0
- data/lib/ar-extensions/adapters/abstract_adapter.rb +146 -0
- data/lib/ar-extensions/adapters/mysql.rb +10 -0
- data/lib/ar-extensions/adapters/oracle.rb +14 -0
- data/lib/ar-extensions/adapters/postgresql.rb +9 -0
- data/lib/ar-extensions/adapters/sqlite.rb +7 -0
- data/lib/ar-extensions/create_and_update/mysql.rb +7 -0
- data/lib/ar-extensions/create_and_update.rb +509 -0
- data/lib/ar-extensions/csv.rb +309 -0
- data/lib/ar-extensions/delete/mysql.rb +3 -0
- data/lib/ar-extensions/delete.rb +143 -0
- data/lib/ar-extensions/extensions.rb +513 -0
- data/lib/ar-extensions/finder_options/mysql.rb +6 -0
- data/lib/ar-extensions/finder_options.rb +275 -0
- data/lib/ar-extensions/finders.rb +94 -0
- data/lib/ar-extensions/foreign_keys.rb +70 -0
- data/lib/ar-extensions/fulltext/mysql.rb +44 -0
- data/lib/ar-extensions/fulltext.rb +62 -0
- data/lib/ar-extensions/import/mysql.rb +50 -0
- data/lib/ar-extensions/import/postgresql.rb +0 -0
- data/lib/ar-extensions/import/sqlite.rb +22 -0
- data/lib/ar-extensions/import.rb +348 -0
- data/lib/ar-extensions/insert_select/mysql.rb +7 -0
- data/lib/ar-extensions/insert_select.rb +178 -0
- data/lib/ar-extensions/synchronize.rb +30 -0
- data/lib/ar-extensions/temporary_table/mysql.rb +3 -0
- data/lib/ar-extensions/temporary_table.rb +131 -0
- data/lib/ar-extensions/union/mysql.rb +6 -0
- data/lib/ar-extensions/union.rb +204 -0
- data/lib/ar-extensions/util/sql_generation.rb +27 -0
- data/lib/ar-extensions/util/support_methods.rb +32 -0
- data/lib/ar-extensions/version.rb +9 -0
- data/lib/ar-extensions.rb +5 -0
- metadata +110 -0
@@ -0,0 +1,513 @@
|
|
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.quoted_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 call 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.quoted_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
|
+
ACCEPTABLE_COMPARISONS = [ String, Numeric, Time, DateTime, Date ]
|
210
|
+
|
211
|
+
def self.process( key, val, caller )
|
212
|
+
process_without_suffix( key, val, caller ) || process_with_suffix( key, val, caller )
|
213
|
+
end
|
214
|
+
|
215
|
+
def self.process_without_suffix( key, val, caller )
|
216
|
+
return nil unless caller.columns_hash.has_key?( key )
|
217
|
+
if val.nil?
|
218
|
+
str = "#{caller.quoted_table_name}.#{caller.connection.quote_column_name( key )} is ?"
|
219
|
+
else
|
220
|
+
str = "#{caller.quoted_table_name}.#{caller.connection.quote_column_name( key )}=?"
|
221
|
+
end
|
222
|
+
Result.new( str, val )
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.process_with_suffix( key, val, caller )
|
226
|
+
return nil unless ACCEPTABLE_COMPARISONS.find{ |klass| val.is_a?(klass) }
|
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.quoted_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
|
+
def self.process( key, val, caller )
|
259
|
+
values = case val
|
260
|
+
when NilClass
|
261
|
+
[]
|
262
|
+
when Array
|
263
|
+
val
|
264
|
+
else
|
265
|
+
[val]
|
266
|
+
end
|
267
|
+
case key.to_s
|
268
|
+
when LIKE_RGX
|
269
|
+
str = values.collect do |v|
|
270
|
+
"#{caller.quoted_table_name}.#{caller.connection.quote_column_name( $1 )} LIKE " +
|
271
|
+
"#{caller.connection.quote( '%%' + v + '%%', caller.columns_hash[ $1 ] )} "
|
272
|
+
end
|
273
|
+
when STARTS_WITH_RGX
|
274
|
+
str = values.collect do |v|
|
275
|
+
"#{caller.quoted_table_name}.#{caller.connection.quote_column_name( $1 )} LIKE " +
|
276
|
+
"#{caller.connection.quote( v + '%%', caller.columns_hash[ $1 ] )} "
|
277
|
+
end
|
278
|
+
when ENDS_WITH_RGX
|
279
|
+
str = values.collect do |v|
|
280
|
+
"#{caller.quoted_table_name}.#{caller.connection.quote_column_name( $1 )} LIKE " +
|
281
|
+
"#{caller.connection.quote( '%%' + v, caller.columns_hash[ $1 ] )} "
|
282
|
+
end
|
283
|
+
else
|
284
|
+
return nil
|
285
|
+
end
|
286
|
+
|
287
|
+
str = str.join(' OR ')
|
288
|
+
result_values = []
|
289
|
+
str.gsub!(/'((%%)?([^\?]*\?[^%]*|[^%]*%[^%]*)(%%)?)'/) do |match|
|
290
|
+
result_values << $2
|
291
|
+
'?'
|
292
|
+
end
|
293
|
+
result_values = nil if result_values.empty?
|
294
|
+
return Result.new(str , result_values)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
|
298
|
+
|
299
|
+
# ActiveRecord::Extension to translate a ruby Range object into SQL's BETWEEN ... AND ...
|
300
|
+
# or NOT BETWEEN ... AND ... . This works on Ranges of Numbers, Dates, Times, etc.
|
301
|
+
#
|
302
|
+
# == Examples
|
303
|
+
# # the following two statements are identical because of how Ranges treat .. and ...
|
304
|
+
# Model.find :all, :conditions=>{ :id => ( 1 .. 2 ) }
|
305
|
+
# Model.find :all, :conditions=>{ :id => ( 1 ... 2 ) }
|
306
|
+
#
|
307
|
+
# # the following four statements are identical, this finds NOT BETWEEN matches
|
308
|
+
# Model.find :all, :conditions=>{ :id_ne => ( 4 .. 6 ) }
|
309
|
+
# Model.find :all, :conditions=>{ :id_not => ( 4 .. 6 ) }
|
310
|
+
# Model.find :all, :conditions=>{ :id_not_in => ( 4 ..6 ) }
|
311
|
+
# Model.find :all, :conditions=>{ :id_not_between => ( 4 .. 6 ) }
|
312
|
+
#
|
313
|
+
# # a little more creative, working with date ranges
|
314
|
+
# Model.find :all, :conditions=>{ :created_on => (Date.now-30 .. Date.now) }
|
315
|
+
class RangeExt
|
316
|
+
NOT_IN_RGX = /^(.+)_(ne|not|not_in|not_between)$/
|
317
|
+
|
318
|
+
def self.process( key, val, caller )
|
319
|
+
if val.is_a?( Range )
|
320
|
+
match_data = key.to_s.match( NOT_IN_RGX )
|
321
|
+
key = match_data.captures[0] if match_data
|
322
|
+
fieldname = caller.connection.quote_column_name( key )
|
323
|
+
min = caller.connection.quote( val.first, caller.columns_hash[ key ] )
|
324
|
+
max = caller.connection.quote( val.last, caller.columns_hash[ key ] )
|
325
|
+
str = if val.exclude_end?
|
326
|
+
"#{match_data ? 'NOT ' : '' }(#{caller.quoted_table_name}.#{fieldname} >= #{min} AND #{caller.quoted_table_name}.#{fieldname} < #{max})"
|
327
|
+
else
|
328
|
+
"#{caller.quoted_table_name}.#{fieldname} #{match_data ? 'NOT ' : '' } BETWEEN #{min} AND #{max}"
|
329
|
+
end
|
330
|
+
|
331
|
+
return Result.new( str, nil )
|
332
|
+
end
|
333
|
+
nil
|
334
|
+
end
|
335
|
+
|
336
|
+
end
|
337
|
+
|
338
|
+
# A base class for database vendor specific Regexp implementations. This is meant to be
|
339
|
+
# subclassed only because of the helper method(s) it provides.
|
340
|
+
class RegexpBase
|
341
|
+
|
342
|
+
NOT_EQUAL_RGX = /^(.+)_(ne|not|does_not_match)$/
|
343
|
+
|
344
|
+
# A result class which provides an easy interface.
|
345
|
+
class RegexpResult
|
346
|
+
attr_reader :fieldname, :negate
|
347
|
+
|
348
|
+
def initialize( fieldname, negate=false )
|
349
|
+
@fieldname, @negate = fieldname, negate
|
350
|
+
end
|
351
|
+
|
352
|
+
def negate?
|
353
|
+
negate ? true : false
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
# Given the passed in +str+ and +caller+ this will return a RegexpResult object
|
358
|
+
# which gives the database quoted fieldname/column and can tell you whether or not
|
359
|
+
# the original +str+ is indicating a negated regular expression.
|
360
|
+
#
|
361
|
+
# == Examples
|
362
|
+
# r = RegexpBase.field_result( 'id' )
|
363
|
+
# r.fieldname => # 'id'
|
364
|
+
# r.negate? => # false
|
365
|
+
#
|
366
|
+
# r = RegexpBase.field_result( 'id_ne' )
|
367
|
+
# r.fieldname => # 'id'
|
368
|
+
# r.negate? => # true
|
369
|
+
#
|
370
|
+
# r = RegexpBase.field_result( 'id_not' )
|
371
|
+
# r.fieldname => # 'id'
|
372
|
+
# r.negate? => # true
|
373
|
+
#
|
374
|
+
# r = RegexpBase.field_result( 'id_does_not_match' )
|
375
|
+
# r.fieldname => # 'id'
|
376
|
+
# r.negate? => # true
|
377
|
+
def self.field_result( str, caller )
|
378
|
+
negate = false
|
379
|
+
if match_data=str.to_s.match( NOT_EQUAL_RGX )
|
380
|
+
negate = true
|
381
|
+
str = match_data.captures[0]
|
382
|
+
end
|
383
|
+
fieldname = caller.connection.quote_column_name( str )
|
384
|
+
RegexpResult.new( fieldname, negate )
|
385
|
+
end
|
386
|
+
|
387
|
+
end
|
388
|
+
|
389
|
+
|
390
|
+
# ActiveRecord::Extension for implementing Regexp implementation for MySQL.
|
391
|
+
# See documention for RegexpBase.
|
392
|
+
class MySQLRegexp < RegexpBase
|
393
|
+
|
394
|
+
def self.process( key, val, caller )
|
395
|
+
return nil unless val.is_a?( Regexp )
|
396
|
+
r = field_result( key, caller )
|
397
|
+
Result.new( "#{caller.quoted_table_name}.#{r.fieldname} #{r.negate? ? 'NOT ':''} REGEXP ?", val )
|
398
|
+
end
|
399
|
+
|
400
|
+
end
|
401
|
+
|
402
|
+
|
403
|
+
# ActiveRecord::Extension for implementing Regexp implementation for PostgreSQL.
|
404
|
+
# See documention for RegexpBase.
|
405
|
+
#
|
406
|
+
# Note: this doesn't support case insensitive matches.
|
407
|
+
class PostgreSQLRegexp < RegexpBase
|
408
|
+
|
409
|
+
def self.process( key, val, caller )
|
410
|
+
return nil unless val.is_a?( Regexp )
|
411
|
+
r = field_result( key, caller )
|
412
|
+
return Result.new( "#{caller.quoted_table_name}.#{r.fieldname} #{r.negate? ? '!~ ':'~'} ?", val )
|
413
|
+
end
|
414
|
+
|
415
|
+
end
|
416
|
+
|
417
|
+
# ActiveRecord::Extension for implementing Regexp implementation for Oracle.
|
418
|
+
# See documention for RegexpBase.
|
419
|
+
#
|
420
|
+
class OracleRegexp < RegexpBase
|
421
|
+
|
422
|
+
def self.process( key, val, caller )
|
423
|
+
return nil unless val.is_a?( Regexp )
|
424
|
+
r = field_result( key, caller )
|
425
|
+
return Result.new( "#{r.negate? ? ' NOT ':''} REGEXP_LIKE(#{caller.quoted_table_name}.#{r.fieldname} , ?)", val )
|
426
|
+
end
|
427
|
+
|
428
|
+
end
|
429
|
+
|
430
|
+
|
431
|
+
# ActiveRecord::Extension for implementing Regexp implementation for MySQL.
|
432
|
+
# See documention for RegexpBase.
|
433
|
+
class SqliteRegexp < RegexpBase
|
434
|
+
class_inheritable_accessor :connections
|
435
|
+
self.connections = []
|
436
|
+
|
437
|
+
def self.add_rlike_function( connection )
|
438
|
+
self.connections << connection
|
439
|
+
unless connection.respond_to?( 'sqlite_regexp_support?' )
|
440
|
+
class << connection
|
441
|
+
def sqlite_regexp_support? ; true ; end
|
442
|
+
end
|
443
|
+
connection.instance_eval( '@connection' ).create_function( 'rlike', 3 ) do |func, a, b, negate|
|
444
|
+
if negate =~ /true/
|
445
|
+
func.set_result 1 if a.to_s !~ /#{b}/
|
446
|
+
else
|
447
|
+
func.set_result 1 if a.to_s =~ /#{b}/
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
end
|
452
|
+
|
453
|
+
def self.process( key, val, caller )
|
454
|
+
return nil unless val.is_a?( Regexp )
|
455
|
+
r = field_result( key, caller )
|
456
|
+
unless self.connections.include?( caller.connection )
|
457
|
+
add_rlike_function( caller.connection )
|
458
|
+
end
|
459
|
+
Result.new( "rlike( #{r.fieldname}, ?, '#{r.negate?}' )", val )
|
460
|
+
end
|
461
|
+
|
462
|
+
end
|
463
|
+
|
464
|
+
class DatetimeSupport
|
465
|
+
SUFFIX_MAP = { 'eq'=>'=', 'lt'=>'<', 'lte'=>'<=', 'gt'=>'>', 'gte'=>'>=', 'ne'=>'!=', 'not'=>'!=' }
|
466
|
+
|
467
|
+
def self.process( key, val, caller )
|
468
|
+
return unless val.is_a?( Time )
|
469
|
+
process_without_suffix( key, val, caller ) || process_with_suffix( key, val, caller )
|
470
|
+
end
|
471
|
+
|
472
|
+
def self.process_without_suffix( key, val, caller )
|
473
|
+
return nil unless caller.columns_hash.has_key?( key )
|
474
|
+
if val.nil?
|
475
|
+
str = "#{caller.quoted_table_name}.#{caller.connection.quote_column_name( key )} IS NULL"
|
476
|
+
else
|
477
|
+
str = "#{caller.quoted_table_name}.#{caller.connection.quote_column_name( key )}=" +
|
478
|
+
"#{caller.connection.quote( val.to_s(:db), caller.columns_hash[ key ] )} "
|
479
|
+
end
|
480
|
+
Result.new( str, nil )
|
481
|
+
end
|
482
|
+
|
483
|
+
def self.process_with_suffix( key, val, caller )
|
484
|
+
SUFFIX_MAP.each_pair do |k,v|
|
485
|
+
match_data = key.to_s.match( /(.+)_#{k}$/ )
|
486
|
+
if match_data
|
487
|
+
fieldname = match_data.captures[0]
|
488
|
+
return nil unless caller.columns_hash.has_key?( fieldname )
|
489
|
+
str = "#{caller.quoted_table_name}.#{caller.connection.quote_column_name( fieldname )} " +
|
490
|
+
"#{v} #{caller.connection.quote( val.to_s(:db), caller.columns_hash[ fieldname ] )} "
|
491
|
+
return Result.new( str, nil )
|
492
|
+
end
|
493
|
+
end
|
494
|
+
nil
|
495
|
+
end
|
496
|
+
|
497
|
+
|
498
|
+
end
|
499
|
+
|
500
|
+
|
501
|
+
register Comparison, :adapters=>:all
|
502
|
+
register ArrayExt, :adapters=>:all
|
503
|
+
register Like, :adapters=>:all
|
504
|
+
register RangeExt, :adapters=>:all
|
505
|
+
register MySQLRegexp, :adapters=>[ :mysql ]
|
506
|
+
register PostgreSQLRegexp, :adapters=>[ :postgresql ]
|
507
|
+
register SqliteRegexp, :adapters =>[ :sqlite ]
|
508
|
+
register OracleRegexp, :adapters =>[ :oracle ]
|
509
|
+
register DatetimeSupport, :adapters =>[ :mysql, :sqlite, :oracle ]
|
510
|
+
end
|
511
|
+
|
512
|
+
|
513
|
+
|
@@ -0,0 +1,6 @@
|
|
1
|
+
# Although the finder options actually override ActiveRecord::Base functionality instead of
|
2
|
+
# connector functionality, the methods are included here to keep the syntax of 0.8.0 intact
|
3
|
+
#
|
4
|
+
# To include finder options, use:
|
5
|
+
# require 'ar-extensions/finder_options/mysql.rb'
|
6
|
+
ActiveRecord::Base.send :include, ActiveRecord::Extensions::FinderOptions
|