ghazel-ar-extensions 0.9.3
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 +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
|