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,275 @@
|
|
1
|
+
# ActiveRecord::Extensions::FinderOptions provides additional functionality to the ActiveRecord
|
2
|
+
# ORM library created by DHH for Rails.
|
3
|
+
#
|
4
|
+
# == Using finder_sql_to_string
|
5
|
+
# Expose the finder sql to a string. The options are identical to those accepted by <tt>find(:all, options)</tt>
|
6
|
+
# the find method takes.
|
7
|
+
# === Example:
|
8
|
+
# sql = Contact.finder_sql_to_string(:include => :primary_email_address)
|
9
|
+
# Contact.find_by_sql(sql + 'USE_INDEX(blah)')
|
10
|
+
#
|
11
|
+
# == Enhanced Finder Options
|
12
|
+
# Add index hints, keywords, and pre and post SQL to the query without writing direct SQL
|
13
|
+
# === Parameter options:
|
14
|
+
# * <tt>:pre_sql</tt> appends SQL after the SELECT and before the selected columns
|
15
|
+
#
|
16
|
+
# sql = Contact.find :first, :pre_sql => "HIGH_PRIORITY", :select => 'contacts.name', :conditions => 'id = 5'
|
17
|
+
# SQL> SELECT HIGH_PRIORITY contacts.name FROM `contacts` WHERE id = 5
|
18
|
+
#
|
19
|
+
# * <tt>:post_sql</tt> appends additional SQL to the end of the statement
|
20
|
+
# Contact.find :first, :post_sql => 'FOR UPDATE', :select => 'contacts.name', :conditions => 'id = 5'
|
21
|
+
# SQL> SELECT contacts.name FROM `contacts` where id == 5 FOR UPDATE
|
22
|
+
#
|
23
|
+
# Book.find :all, :post_sql => 'USE_INDEX(blah)'
|
24
|
+
# SQL> SELECT books.* FROM `books` USE_INDEX(blah)
|
25
|
+
#
|
26
|
+
# * <tt>:override_select</tt> is used to override the <tt>SELECT</tt> clause of eager loaded associations
|
27
|
+
# The <tt>:select</tt> option is ignored by the vanilla ActiveRecord when using eager loading with associations (when <tt>:include</tt> is used)
|
28
|
+
# (refer to http://dev.rubyonrails.org/ticket/5371)
|
29
|
+
# The <tt>:override_select</tt> options allows us to directly specify a <tt>SELECT</tt> clause without affecting the operations of legacy code (ignore <tt>:select</tt>)
|
30
|
+
# of the current code. Several plugins are available that enable select with eager loading
|
31
|
+
# Several plugins exist to force <tt>:select</tt> to work with eager loading.
|
32
|
+
# <tt>script/plugin install git://github.com/blythedunham/eload-select.git </tt>
|
33
|
+
#
|
34
|
+
# * <tt>:having</tt> only works when <tt>:group</tt> option is specified
|
35
|
+
# Book.find(:all, :select => 'count(*) as count_all, topic_id', :group => :topic_id, :having => 'count(*) > 1')
|
36
|
+
# SQL>SELECT count(*) as count_all, topic_id FROM `books` GROUP BY topic_id HAVING count(*) > 1
|
37
|
+
#
|
38
|
+
# == Developers
|
39
|
+
# * Blythe Dunham http://blythedunham.com
|
40
|
+
#
|
41
|
+
# == Homepage
|
42
|
+
# * Project Site: http://www.continuousthinking.com/tags/arext
|
43
|
+
# * Rubyforge Project: http://rubyforge.org/projects/arext
|
44
|
+
# * Anonymous SVN: svn checkout svn://rubyforge.org/var/svn/arext
|
45
|
+
#
|
46
|
+
require 'active_record/version'
|
47
|
+
module ActiveRecord::Extensions::FinderOptions
|
48
|
+
def self.included(base)
|
49
|
+
|
50
|
+
#alias and include only if not yet defined
|
51
|
+
unless base.respond_to?(:construct_finder_sql_ext)
|
52
|
+
base.extend ClassMethods
|
53
|
+
base.extend ActiveRecord::Extensions::SqlGeneration
|
54
|
+
base.extend HavingOptionBackCompatibility
|
55
|
+
base.extend ConstructSqlCompatibility
|
56
|
+
|
57
|
+
base.class_eval do
|
58
|
+
class << self
|
59
|
+
VALID_FIND_OPTIONS.concat([:pre_sql, :post_sql, :keywords, :ignore, :rollup, :override_select, :having, :index_hint])
|
60
|
+
alias_method :construct_finder_sql, :construct_finder_sql_ext
|
61
|
+
alias_method_chain :construct_finder_sql_with_included_associations, :ext
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
module ClassMethods
|
68
|
+
# Return a string containing the SQL used with the find(:all)
|
69
|
+
# The options are the same as those with find(:all)
|
70
|
+
#
|
71
|
+
# Additional parameter of
|
72
|
+
# <tt>:force_eager_load</tt> forces eager loading even if the
|
73
|
+
# column is not referenced.
|
74
|
+
#
|
75
|
+
# sql = Contact.finder_sql_to_string(:include => :primary_email_address)
|
76
|
+
# Contact.find_by_sql(sql + 'USE_INDEX(blah)')
|
77
|
+
def finder_sql_to_string(options)
|
78
|
+
select_sql = self.send(
|
79
|
+
(use_eager_loading_sql?(options) ? :finder_sql_with_included_associations : :construct_finder_sql),
|
80
|
+
options.reject{|k,v| k == :force_eager_load}).strip
|
81
|
+
end
|
82
|
+
|
83
|
+
protected
|
84
|
+
|
85
|
+
# use eager loading sql (join associations) if inclu
|
86
|
+
def use_eager_loading_sql?(options)# :nodoc:
|
87
|
+
include_associations = merge_includes(scope(:find, :include), options[:include])
|
88
|
+
return ((include_associations.any?) &&
|
89
|
+
(options[:force_eager_load].is_a?(TrueClass) ||
|
90
|
+
references_eager_loaded_tables?(options)))
|
91
|
+
end
|
92
|
+
|
93
|
+
# construct_finder_sql is called when not using eager loading (:include option is NOT specified)
|
94
|
+
def construct_finder_sql_ext(options) # :nodoc:
|
95
|
+
|
96
|
+
#add piggy back option if plugin is installed
|
97
|
+
add_piggy_back!(options) if self.respond_to? :add_piggy_back!
|
98
|
+
|
99
|
+
scope = scope(:find)
|
100
|
+
sql = pre_sql_statements(options)
|
101
|
+
add_select_column_sql!(sql, options, scope)
|
102
|
+
add_from!(sql, options, scope)
|
103
|
+
|
104
|
+
sql << "#{options[:index_hint]} " if options[:index_hint]
|
105
|
+
|
106
|
+
add_joins!(sql, options[:joins], scope)
|
107
|
+
add_conditions!(sql, options[:conditions], scope)
|
108
|
+
add_group_with_having!(sql, options[:group], options[:having], scope)
|
109
|
+
|
110
|
+
add_order!(sql, options[:order], scope)
|
111
|
+
add_limit!(sql, options, scope)
|
112
|
+
add_lock!(sql, options, scope)
|
113
|
+
|
114
|
+
sql << post_sql_statements(options)
|
115
|
+
sql
|
116
|
+
end
|
117
|
+
|
118
|
+
#override the constructor for use with associations (:include option)
|
119
|
+
#directly use eager select if that plugin is loaded instead of this one
|
120
|
+
def construct_finder_sql_with_included_associations_with_ext(options, join_dependency)#:nodoc
|
121
|
+
scope = scope(:find)
|
122
|
+
sql = pre_sql_statements(options)
|
123
|
+
|
124
|
+
add_eager_selected_column_sql!(sql, options, scope, join_dependency)
|
125
|
+
add_from!(sql, options, scope)
|
126
|
+
|
127
|
+
sql << "#{options[:index_hint]} " if options[:index_hint]
|
128
|
+
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
129
|
+
|
130
|
+
add_joins!(sql, options[:joins], scope)
|
131
|
+
add_conditions!(sql, options[:conditions], scope)
|
132
|
+
|
133
|
+
add_limited_ids_condition!(sql, options_with_group(options), join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
134
|
+
|
135
|
+
add_group_with_having!(sql, options[:group], options[:having], scope)
|
136
|
+
add_order!(sql, options[:order], scope)
|
137
|
+
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
138
|
+
add_lock!(sql, options, scope)
|
139
|
+
|
140
|
+
sql << post_sql_statements(options)
|
141
|
+
|
142
|
+
return sanitize_sql(sql)
|
143
|
+
end
|
144
|
+
|
145
|
+
#generate the finder sql for use with associations (:include => :something)
|
146
|
+
def finder_sql_with_included_associations(options = {})#:nodoc
|
147
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
|
148
|
+
sql = construct_finder_sql_with_included_associations_with_ext(options, join_dependency)
|
149
|
+
end
|
150
|
+
|
151
|
+
#first use :override_select
|
152
|
+
#next use :construct_eload_select_sql if eload-select is loaded
|
153
|
+
#finally use normal column aliases
|
154
|
+
def add_eager_selected_column_sql!(sql, options, scope, join_dependency)#:nodoc:
|
155
|
+
if options[:override_select]
|
156
|
+
sql << options[:override_select]
|
157
|
+
elsif respond_to? :construct_eload_select_sql
|
158
|
+
sql << construct_eload_select_sql((scope && scope[:select]) || options[:select], join_dependency)
|
159
|
+
else
|
160
|
+
sql << column_aliases(join_dependency)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
#simple select sql
|
165
|
+
def add_select_column_sql!(sql, options, scope = :auto)#:nodoc:
|
166
|
+
sql << "#{options[:select] || options[:override_select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))}"
|
167
|
+
end
|
168
|
+
|
169
|
+
#from sql
|
170
|
+
def add_from!(sql, options, scope = :auto)#:nodoc:
|
171
|
+
sql << " FROM #{options[:from] || (scope && scope[:from]) || quoted_table_name} "
|
172
|
+
end
|
173
|
+
|
174
|
+
def options_with_group(options)#:nodoc:
|
175
|
+
options
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
#In Rails 2.0.0 add_joins! signature changed
|
180
|
+
# Pre Rails 2.0.0: add_joins!(sql, options, scope)
|
181
|
+
# After 2.0.0: add_joins!(sql, options[:joins], scope)
|
182
|
+
module ConstructSqlCompatibility
|
183
|
+
def self.extended(base)
|
184
|
+
if ActiveRecord::VERSION::STRING < '2.0.0'
|
185
|
+
base.extend ClassMethods
|
186
|
+
base.class_eval do
|
187
|
+
class << self
|
188
|
+
alias_method_chain :add_joins!, :compatibility
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
module ClassMethods
|
195
|
+
def add_joins_with_compatibility!(sql, options, scope = :auto)#:nodoc:
|
196
|
+
join_param = options.is_a?(Hash) ? options : { :joins => options }
|
197
|
+
add_joins_without_compatibility!(sql, join_param, scope)
|
198
|
+
end
|
199
|
+
|
200
|
+
#aliasing threw errors
|
201
|
+
def quoted_table_name#:nodoc:
|
202
|
+
self.table_name
|
203
|
+
end
|
204
|
+
|
205
|
+
#pre Rails 2.0.0 the order of the scope and options was different
|
206
|
+
def add_from!(sql, options, scope = :auto)#:nodoc:
|
207
|
+
sql << " FROM #{(scope && scope[:from]) || options[:from] || table_name} "
|
208
|
+
end
|
209
|
+
|
210
|
+
def add_select_column_sql!(sql, options, scope = :auto)#:nodoc:
|
211
|
+
sql << "#{options[:override_select] || (scope && scope[:select]) || options[:select] || '*'}"
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Before Version 2.3.0 there was no :having option
|
218
|
+
# Add this option to previous versions by overriding add_group!
|
219
|
+
# to accept a hash with keys :group and :having instead of just group
|
220
|
+
# this avoids having to completely rewrite dependent functions like
|
221
|
+
# construct_finder_sql_for_association_limiting
|
222
|
+
|
223
|
+
module HavingOptionBackCompatibility#:nodoc:
|
224
|
+
def self.extended(base)
|
225
|
+
|
226
|
+
#for previous versions define having
|
227
|
+
if ActiveRecord::VERSION::STRING < '2.3.0'
|
228
|
+
base.extend ClassMethods
|
229
|
+
|
230
|
+
#for 2.3.0+ alias our method to :add_group!
|
231
|
+
else
|
232
|
+
base.class_eval do
|
233
|
+
class << self
|
234
|
+
alias_method :add_group_with_having!, :add_group!
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
module ClassMethods#:nodoc:
|
241
|
+
#add_group! in version 2.3 adds having already
|
242
|
+
#copy that implementation
|
243
|
+
def add_group_with_having!(sql, group, having, scope =:auto)#:nodoc:
|
244
|
+
if group
|
245
|
+
sql << " GROUP BY #{group}"
|
246
|
+
sql << " HAVING #{sanitize_sql(having)}" if having
|
247
|
+
else
|
248
|
+
scope = scope(:find) if :auto == scope
|
249
|
+
if scope && (scoped_group = scope[:group])
|
250
|
+
sql << " GROUP BY #{scoped_group}"
|
251
|
+
sql << " HAVING #{sanitize_sql(scope[:having])}" if scope[:having]
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def add_group!(sql, group_options, scope = :auto)#:nodoc:
|
257
|
+
group, having = if group_options.is_a?(Hash) && group_options.has_key?(:group)
|
258
|
+
[group_options[:group] , group_options[:having]]
|
259
|
+
else
|
260
|
+
[group_options, nil]
|
261
|
+
end
|
262
|
+
add_group_with_having!(sql, group, having, scope)
|
263
|
+
end
|
264
|
+
|
265
|
+
def options_with_group(options)#:nodoc:
|
266
|
+
if options[:group]
|
267
|
+
options.merge(:group => {:group => options[:group], :having => options[:having]})
|
268
|
+
else
|
269
|
+
options
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'active_record/version'
|
2
|
+
|
3
|
+
module ActiveRecord::ConnectionAdapters::Quoting
|
4
|
+
alias :quote_before_arext :quote
|
5
|
+
def quote( value, column=nil ) # :nodoc:
|
6
|
+
if value.is_a?( Regexp )
|
7
|
+
"'#{value.inspect[1...-1]}'"
|
8
|
+
else
|
9
|
+
quote_before_arext( value, column )
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
unless ActiveRecord::VERSION::STRING < '2.0.2'
|
15
|
+
class ActiveRecord::Base
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
alias :sanitize_sql_orig :sanitize_sql
|
22
|
+
def sanitize_sql(arg, table_name = quoted_table_name) # :nodoc:
|
23
|
+
return if arg.blank? # don't process arguments like [], {}, "" or nil
|
24
|
+
if arg.respond_to?( :to_sql )
|
25
|
+
arg = sanitize_sql_by_way_of_duck_typing(arg)
|
26
|
+
elsif arg.is_a?(Hash)
|
27
|
+
arg = sanitize_sql_from_hash(arg, table_name)
|
28
|
+
elsif arg.is_a?( Array ) and arg.size == 2 and arg.first.is_a?( String ) and arg.last.is_a?( Hash )
|
29
|
+
arg = sanitize_sql_from_string_and_hash(arg, table_name)
|
30
|
+
end
|
31
|
+
sanitize_sql_orig(arg)
|
32
|
+
end
|
33
|
+
|
34
|
+
def sanitize_sql_by_way_of_duck_typing(arg) #: nodoc:
|
35
|
+
arg.to_sql( caller )
|
36
|
+
end
|
37
|
+
|
38
|
+
def sanitize_sql_from_string_and_hash(arr, table_name = quoted_table_name) # :nodoc:
|
39
|
+
return arr if arr.first =~ /\:[\w]+/
|
40
|
+
return arr if arr.last.empty? # skip empty hash conditions, ie: :conditions => ["", {}]
|
41
|
+
arr2 = sanitize_sql_from_hash( arr.last, table_name )
|
42
|
+
if arr2.empty?
|
43
|
+
conditions = arr.first
|
44
|
+
else
|
45
|
+
conditions = [ arr.first << " AND (#{arr2.first})" ]
|
46
|
+
conditions.push( *arr2[1..-1] )
|
47
|
+
end
|
48
|
+
conditions
|
49
|
+
end
|
50
|
+
|
51
|
+
def sanitize_sql_from_hash(hsh, table_name = quoted_table_name) #:nodoc:
|
52
|
+
conditions, values = [], []
|
53
|
+
hsh = expand_hash_conditions_for_aggregates(hsh) # introduced in Rails 2.0.2
|
54
|
+
|
55
|
+
hsh.each_pair do |key,val|
|
56
|
+
if val.respond_to?( :to_sql )
|
57
|
+
conditions << sanitize_sql_by_way_of_duck_typing( val )
|
58
|
+
next
|
59
|
+
elsif val.is_a?(Hash) # don't mess with ActiveRecord hash nested hash functionality
|
60
|
+
conditions << sanitize_sql_hash_for_conditions({key => val}, table_name)
|
61
|
+
else
|
62
|
+
sql = nil
|
63
|
+
result = ActiveRecord::Extensions.process( key, val, self )
|
64
|
+
if result
|
65
|
+
conditions << result.sql
|
66
|
+
values.push( result.value ) unless result.value.nil?
|
67
|
+
else
|
68
|
+
# Extract table name from qualified attribute names.
|
69
|
+
attr = key.to_s
|
70
|
+
if attr.include?('.')
|
71
|
+
table_name, attr = attr.split('.', 2)
|
72
|
+
table_name = connection.quote_table_name(table_name)
|
73
|
+
end
|
74
|
+
# ActiveRecord in 2.3.1 changed the method signature for
|
75
|
+
# the method attribute_condition
|
76
|
+
if ActiveRecord::VERSION::STRING < '2.3.1'
|
77
|
+
conditions << "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition( val )} "
|
78
|
+
else
|
79
|
+
conditions << attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", val)
|
80
|
+
end
|
81
|
+
values << val
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
conditions = conditions.join( ' AND ' )
|
87
|
+
return [] if conditions.size == 1 and conditions.first.empty?
|
88
|
+
[ conditions, *values ]
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# Enables support for enabling and disabling foreign keys
|
2
|
+
# for the underlyig database connection for ActiveRecord.
|
3
|
+
#
|
4
|
+
# This can be used with or without block form. This also
|
5
|
+
# uses the connection attached to the model.
|
6
|
+
#
|
7
|
+
# ==== Example 1, without block form
|
8
|
+
# Project.foreign_keys.disable
|
9
|
+
# Project.foreign_keys.enable
|
10
|
+
#
|
11
|
+
# If you use this form you have to manually re-enable the foreign
|
12
|
+
# keys.
|
13
|
+
#
|
14
|
+
# ==== Example 2, with block form
|
15
|
+
# Project.foreign_keys.disable do
|
16
|
+
# # ...
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# Project.foreign_keys.enable do
|
20
|
+
# # ...
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# If you use the block form the foreign keys are automatically
|
24
|
+
# enabled or disabled when the block exits. This currently
|
25
|
+
# does not restore the state of foreign keys to the state before
|
26
|
+
# the block was entered.
|
27
|
+
#
|
28
|
+
# Note: If you use the disable block foreign keys
|
29
|
+
# will be enabled after the block exits. If you use the enable block foreign keys
|
30
|
+
# will be disabled after the block exits.
|
31
|
+
#
|
32
|
+
# TODO: check the external state and restore that state when using block form.
|
33
|
+
module ActiveRecord::Extensions::ForeignKeys
|
34
|
+
|
35
|
+
class ForeignKeyController # :nodoc:
|
36
|
+
attr_reader :clazz
|
37
|
+
|
38
|
+
def initialize( clazz )
|
39
|
+
@clazz = clazz
|
40
|
+
end
|
41
|
+
|
42
|
+
def disable # :nodoc:
|
43
|
+
if block_given?
|
44
|
+
disable
|
45
|
+
yield
|
46
|
+
enable
|
47
|
+
else
|
48
|
+
clazz.connection.execute "set foreign_key_checks = 0"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def enable #:nodoc:
|
53
|
+
if block_given?
|
54
|
+
enable
|
55
|
+
yield
|
56
|
+
disable
|
57
|
+
else
|
58
|
+
clazz.connection.execute "set foreign_key_checks = 1"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end #end ForeignKeyController
|
63
|
+
|
64
|
+
def foreign_keys # :nodoc:
|
65
|
+
ForeignKeyController.new( self )
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
ActiveRecord::Base.extend( ActiveRecord::Extensions::ForeignKeys )
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# This adds FullText searching functionality for the MySQLAdapter.
|
2
|
+
class ActiveRecord::Extensions::FullTextSearching::MySQLFullTextExtension
|
3
|
+
extend Forwardable
|
4
|
+
|
5
|
+
class << self
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def register( fulltext_key, options ) # :nodoc:
|
9
|
+
@fulltext_registry ||= ActiveRecord::Extensions::Registry.new
|
10
|
+
@fulltext_registry.register( fulltext_key, options )
|
11
|
+
end
|
12
|
+
|
13
|
+
def registry # :nodoc:
|
14
|
+
@fulltext_registry
|
15
|
+
end
|
16
|
+
|
17
|
+
def_delegator :@fulltext_registry, :registers?, :registers?
|
18
|
+
end
|
19
|
+
|
20
|
+
RGX = /^match_(.+)/
|
21
|
+
|
22
|
+
def process( key, val, caller ) # :nodoc:
|
23
|
+
match_data = key.to_s.match( RGX )
|
24
|
+
return nil unless match_data
|
25
|
+
fulltext_identifier = match_data.captures[0].to_sym
|
26
|
+
if self.class.registers?( fulltext_identifier )
|
27
|
+
fields = self.class.registry.options( fulltext_identifier )[:fields]
|
28
|
+
str = "MATCH ( #{fields.join( ',' )} ) AGAINST (#{caller.connection.quote(val)})"
|
29
|
+
return ActiveRecord::Extensions::Result.new( str, nil )
|
30
|
+
end
|
31
|
+
nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def_delegator 'ActiveRecord::Extensions::FullTextSupport::MySQLFullTextExtension', :register
|
35
|
+
end
|
36
|
+
ActiveRecord::Extensions.register ActiveRecord::Extensions::FullTextSearching::MySQLFullTextExtension.new, :adapters=>[:mysql]
|
37
|
+
|
38
|
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
|
39
|
+
include ActiveRecord::Extensions::FullTextSearching::FullTextSupport
|
40
|
+
|
41
|
+
def register_fulltext_extension( fulltext_key, options ) # :nodoc:
|
42
|
+
ActiveRecord::Extensions::FullTextSearching::MySQLFullTextExtension.register( fulltext_key, options )
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
# FullTextSearching provides fulltext searching capabilities
|
4
|
+
# if the underlying database adapter supports it. Currently
|
5
|
+
# only MySQL is supported.
|
6
|
+
module ActiveRecord::Extensions::FullTextSearching
|
7
|
+
|
8
|
+
module FullTextSupport # :nodoc:
|
9
|
+
def supports_full_text_searching? #:nodoc:
|
10
|
+
true
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
class ActiveRecord::Base
|
17
|
+
class FullTextSearchingNotSupported < StandardError ; end
|
18
|
+
|
19
|
+
class << self
|
20
|
+
|
21
|
+
# Adds fulltext searching capabilities to the current model
|
22
|
+
# for the given fulltext key and option hash.
|
23
|
+
#
|
24
|
+
# == Parameters
|
25
|
+
# * +fulltext_key+ - the key/attribute to be used to as the fulltext index
|
26
|
+
# * +options+ - the options hash.
|
27
|
+
#
|
28
|
+
# ==== Options
|
29
|
+
# * +fields+ - an array of field names to be used in the fulltext search
|
30
|
+
#
|
31
|
+
# == Example
|
32
|
+
#
|
33
|
+
# class Book < ActiveRecord::Base
|
34
|
+
# fulltext :title, :fields=>%W( title publisher author_name )
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# # To use the fulltext index
|
38
|
+
# Book.find :all, :conditions=>{ :match_title => 'Zach' }
|
39
|
+
#
|
40
|
+
def fulltext( fulltext_key, options )
|
41
|
+
connection.register_fulltext_extension( fulltext_key, options )
|
42
|
+
rescue NoMethodError
|
43
|
+
logger.warn "FullTextSearching is not supported for adapter!"
|
44
|
+
raise FullTextSearchingNotSupported.new
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns true if the current connection adapter supports full
|
48
|
+
# text searching, otherwise returns false.
|
49
|
+
def supports_full_text_searching?
|
50
|
+
connection.supports_full_text_searching?
|
51
|
+
rescue NoMethodError
|
52
|
+
false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module ActiveRecord::Extensions::ConnectionAdapters::MysqlAdapter # :nodoc:
|
2
|
+
|
3
|
+
include ActiveRecord::Extensions::Import::ImportSupport
|
4
|
+
include ActiveRecord::Extensions::Import::OnDuplicateKeyUpdateSupport
|
5
|
+
|
6
|
+
# Returns a generated ON DUPLICATE KEY UPDATE statement given the passed
|
7
|
+
# in +args+.
|
8
|
+
def sql_for_on_duplicate_key_update( table_name, *args ) # :nodoc:
|
9
|
+
sql = ' ON DUPLICATE KEY UPDATE '
|
10
|
+
arg = args.first
|
11
|
+
if arg.is_a?( Array )
|
12
|
+
sql << sql_for_on_duplicate_key_update_as_array( table_name, arg )
|
13
|
+
elsif arg.is_a?( Hash )
|
14
|
+
sql << sql_for_on_duplicate_key_update_as_hash( table_name, arg )
|
15
|
+
elsif arg.is_a?( String )
|
16
|
+
sql << arg
|
17
|
+
else
|
18
|
+
raise ArgumentError.new( "Expected Array or Hash" )
|
19
|
+
end
|
20
|
+
sql
|
21
|
+
end
|
22
|
+
|
23
|
+
def sql_for_on_duplicate_key_update_as_array( table_name, arr ) # :nodoc:
|
24
|
+
results = arr.map do |column|
|
25
|
+
qc = quote_column_name( column )
|
26
|
+
"#{table_name}.#{qc}=VALUES(#{qc})"
|
27
|
+
end
|
28
|
+
results.join( ',' )
|
29
|
+
end
|
30
|
+
|
31
|
+
def sql_for_on_duplicate_key_update_as_hash( table_name, hsh ) # :nodoc:
|
32
|
+
sql = ' ON DUPLICATE KEY UPDATE '
|
33
|
+
results = hsh.map do |column1, column2|
|
34
|
+
qc1 = quote_column_name( column1 )
|
35
|
+
qc2 = quote_column_name( column2 )
|
36
|
+
"#{table_name}.#{qc1}=VALUES( #{qc2} )"
|
37
|
+
end
|
38
|
+
results.join( ',')
|
39
|
+
end
|
40
|
+
|
41
|
+
#return true if the statement is a duplicate key record error
|
42
|
+
def duplicate_key_update_error?(exception)# :nodoc:
|
43
|
+
exception.is_a?(ActiveRecord::StatementInvalid) && exception.to_s.include?('Duplicate entry')
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
|
49
|
+
include ActiveRecord::Extensions::ConnectionAdapters::MysqlAdapter
|
50
|
+
end
|
File without changes
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module ActiveRecord::Extensions::ConnectionAdapters::SQLiteAdapter # :nodoc:
|
2
|
+
include ActiveRecord::Extensions::Import::ImportSupport
|
3
|
+
|
4
|
+
def post_sql_statements( table_name, options )
|
5
|
+
[]
|
6
|
+
end
|
7
|
+
|
8
|
+
def insert_many( sql, values, *args ) # :nodoc:
|
9
|
+
sql2insert = []
|
10
|
+
values.each do |value|
|
11
|
+
sql2insert << "#{sql} #{value};"
|
12
|
+
end
|
13
|
+
|
14
|
+
raw_connection.transaction { |db| db.execute_batch(sql2insert.join("\n")) }
|
15
|
+
number_of_rows_inserted = sql2insert.size
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
ActiveRecord::ConnectionAdapters::SQLiteAdapter.class_eval do
|
21
|
+
include ActiveRecord::Extensions::ConnectionAdapters::SQLiteAdapter
|
22
|
+
end
|