wunderbread-ar-extensions 0.8.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 +131 -0
- data/README +156 -0
- data/Rakefile +99 -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 +87 -0
- data/db/migrate/mysql_schema.rb +31 -0
- data/db/migrate/oracle_schema.rb +5 -0
- data/db/migrate/version.rb +4 -0
- data/init.rb +29 -0
- data/lib/ar-extensions.rb +5 -0
- data/lib/ar-extensions/adapters/abstract_adapter.rb +146 -0
- data/lib/ar-extensions/adapters/jdbcmysql.rb +10 -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/csv.rb +309 -0
- data/lib/ar-extensions/extensions.rb +506 -0
- data/lib/ar-extensions/finder_options.rb +208 -0
- data/lib/ar-extensions/finder_options/mysql.rb +6 -0
- data/lib/ar-extensions/finders.rb +95 -0
- data/lib/ar-extensions/foreign_keys.rb +70 -0
- data/lib/ar-extensions/fulltext.rb +62 -0
- data/lib/ar-extensions/fulltext/mysql.rb +44 -0
- data/lib/ar-extensions/import.rb +348 -0
- data/lib/ar-extensions/import/jdbcmysql.rb +5 -0
- data/lib/ar-extensions/import/mysql.rb +5 -0
- data/lib/ar-extensions/import/mysql_generic.rb +41 -0
- data/lib/ar-extensions/import/postgresql.rb +0 -0
- data/lib/ar-extensions/import/sqlite.rb +22 -0
- data/lib/ar-extensions/insert_select.rb +184 -0
- data/lib/ar-extensions/insert_select/mysql.rb +6 -0
- data/lib/ar-extensions/synchronize.rb +30 -0
- data/lib/ar-extensions/temporary_table.rb +124 -0
- data/lib/ar-extensions/temporary_table/mysql.rb +3 -0
- data/lib/ar-extensions/util/sql_generation.rb +27 -0
- data/lib/ar-extensions/util/support_methods.rb +43 -0
- data/lib/ar-extensions/version.rb +9 -0
- metadata +107 -0
|
@@ -0,0 +1,208 @@
|
|
|
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
|
+
#
|
|
33
|
+
# <tt>script/plugin install http://arperftoolkit.rubyforge.org/svn/trunk/eload_select/ </tt>
|
|
34
|
+
#
|
|
35
|
+
# * <tt>:having</tt> only works when <tt>:group</tt> option is specified
|
|
36
|
+
# Book.find(:all, :select => 'count(*) as count_all, topic_id', :group => :topic_id, :having => 'count(*) > 1')
|
|
37
|
+
# SQL>SELECT count(*) as count_all, topic_id FROM `books` GROUP BY topic_id HAVING count(*) > 1
|
|
38
|
+
#
|
|
39
|
+
# == Developers
|
|
40
|
+
# * Blythe Dunham http://blythedunham.com
|
|
41
|
+
#
|
|
42
|
+
# == Homepage
|
|
43
|
+
# * Project Site: http://www.continuousthinking.com/tags/arext
|
|
44
|
+
# * Rubyforge Project: http://rubyforge.org/projects/arext
|
|
45
|
+
# * Anonymous SVN: svn checkout svn://rubyforge.org/var/svn/arext
|
|
46
|
+
#
|
|
47
|
+
require 'active_record/version'
|
|
48
|
+
module ActiveRecord::Extensions::FinderOptions
|
|
49
|
+
def self.included(base)
|
|
50
|
+
|
|
51
|
+
#alias and include only if not yet defined
|
|
52
|
+
unless base.respond_to?(:construct_finder_sql_ext)
|
|
53
|
+
base.extend ClassMethods
|
|
54
|
+
base.extend ActiveRecord::Extensions::SqlGeneration
|
|
55
|
+
base.class_eval do
|
|
56
|
+
class << self
|
|
57
|
+
VALID_FIND_OPTIONS.concat([:pre_sql, :post_sql, :keywords, :ignore, :rollup, :override_select, :having, :index_hint])
|
|
58
|
+
alias_method :construct_finder_sql, :construct_finder_sql_ext
|
|
59
|
+
alias_method_chain :construct_finder_sql_with_included_associations, :ext
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
module ClassMethods
|
|
66
|
+
# Return a string containing the SQL used with the find(:all)
|
|
67
|
+
# The options are the same as those with find(:all)
|
|
68
|
+
#
|
|
69
|
+
# Additional parameter of
|
|
70
|
+
# <tt>:force_eager_load</tt> forces eager loading even if the
|
|
71
|
+
# column is not referenced.
|
|
72
|
+
#
|
|
73
|
+
# sql = Contact.finder_sql_to_string(:include => :primary_email_address)
|
|
74
|
+
# Contact.find_by_sql(sql + 'USE_INDEX(blah)')
|
|
75
|
+
def finder_sql_to_string(options)
|
|
76
|
+
|
|
77
|
+
select_sql = self.send(
|
|
78
|
+
(use_eager_loading_sql?(options) ? :finder_sql_with_included_associations : :construct_finder_sql),
|
|
79
|
+
options.reject{|k,v| k == :force_eager_load}).strip
|
|
80
|
+
|
|
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
|
+
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# construct_finder_sql is called when not using eager loading (:include option is NOT specified)
|
|
95
|
+
def construct_finder_sql_ext(options) # :nodoc:
|
|
96
|
+
|
|
97
|
+
#add piggy back option if plugin is installed
|
|
98
|
+
add_piggy_back!(options) if self.respond_to? :add_piggy_back!
|
|
99
|
+
|
|
100
|
+
scope = scope(:find)
|
|
101
|
+
sql = pre_sql_statements(options)
|
|
102
|
+
sql << "#{options[:select] || options[:override_select] || (scope && scope[:select]) || default_select(options[:joins] || (scope && scope[:joins]))} "
|
|
103
|
+
sql << "FROM #{options[:from] || (scope && scope[:from]) || quoted_table_name} "
|
|
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
|
+
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
#override the constructor for use with associations (:include option)
|
|
120
|
+
#directly use eager select if that plugin is loaded instead of this one
|
|
121
|
+
def construct_finder_sql_with_included_associations_with_ext(options, join_dependency)#:nodoc
|
|
122
|
+
if respond_to?(:construct_finder_sql_with_included_associations_with_eager_select)
|
|
123
|
+
return construct_finder_sql_with_included_associations_with_eager_select(options, join_dependency)
|
|
124
|
+
else
|
|
125
|
+
scope = scope(:find)
|
|
126
|
+
sql = pre_sql_statements(options)
|
|
127
|
+
sql << "#{options[:override_select]||column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
|
|
128
|
+
sql << "#{options[:index_hint]} " if options[:index_hint]
|
|
129
|
+
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
add_joins!(sql, options[:joins], scope)
|
|
133
|
+
add_conditions!(sql, options[:conditions], scope)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
add_limited_ids_condition!(sql, options_with_group(options), join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
|
|
137
|
+
|
|
138
|
+
add_group_with_having!(sql, options[:group], options[:having], scope)
|
|
139
|
+
add_order!(sql, options[:order], scope)
|
|
140
|
+
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
|
|
141
|
+
add_lock!(sql, options, scope)
|
|
142
|
+
|
|
143
|
+
sql << post_sql_statements(options)
|
|
144
|
+
|
|
145
|
+
return sanitize_sql(sql)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
#generate the finder sql for use with associations (:include => :something)
|
|
150
|
+
def finder_sql_with_included_associations(options = {})#:nodoc
|
|
151
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
|
|
152
|
+
sql = construct_finder_sql_with_included_associations_with_ext(options, join_dependency)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
# Before Version 2.3.0 there was no :having option
|
|
157
|
+
# Add this option to previous versions by overriding add_group!
|
|
158
|
+
# to accept a hash with keys :group and :having instead of just group
|
|
159
|
+
# this avoids having to completely rewrite dependent functions like
|
|
160
|
+
# construct_finder_sql_for_association_limiting
|
|
161
|
+
|
|
162
|
+
if ActiveRecord::VERSION::STRING < '2.3.1'
|
|
163
|
+
#add_group! in version 2.3 adds having already
|
|
164
|
+
#copy that implementation
|
|
165
|
+
|
|
166
|
+
def add_group_with_having!(sql, group, having, scope =:auto)
|
|
167
|
+
if group
|
|
168
|
+
sql << " GROUP BY #{group}"
|
|
169
|
+
sql << " HAVING #{sanitize_sql(having)}" if having
|
|
170
|
+
else
|
|
171
|
+
scope = scope(:find) if :auto == scope
|
|
172
|
+
if scope && (scoped_group = scope[:group])
|
|
173
|
+
sql << " GROUP BY #{scoped_group}"
|
|
174
|
+
sql << " HAVING #{sanitize_sql(scope[:having])}" if scope[:having]
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def add_group!(sql, group_options, scope = :auto)#:nodoc:
|
|
180
|
+
group, having = if group_options.is_a?(Hash) && group_options.has_key?(:group)
|
|
181
|
+
[group_options[:group] , group_options[:having]]
|
|
182
|
+
else
|
|
183
|
+
[group_options, nil]
|
|
184
|
+
end
|
|
185
|
+
add_group_with_having!(sql, group, having, scope)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def options_with_group(options)#:nodoc:
|
|
189
|
+
if options[:group]
|
|
190
|
+
options.merge(:group => {:group => options[:group], :having => options[:having]})
|
|
191
|
+
else
|
|
192
|
+
options
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
#for Version 2.3 and greater, define options_with_group to pass itself
|
|
197
|
+
else
|
|
198
|
+
def options_with_group(options)#:nodoc:
|
|
199
|
+
options
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def add_group_with_having!(sql, group, having, scope =:auto)#:nodoc:
|
|
203
|
+
add_group!(sql, group, having, scope)
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -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
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require 'active_record/version'
|
|
2
|
+
|
|
3
|
+
module ActiveRecord::ConnectionAdapters::Quoting
|
|
4
|
+
|
|
5
|
+
alias :quote_before_arext :quote
|
|
6
|
+
def quote( value, column=nil ) # :nodoc:
|
|
7
|
+
if value.is_a?( Regexp )
|
|
8
|
+
"'#{value.inspect[1...-1]}'"
|
|
9
|
+
else
|
|
10
|
+
quote_before_arext( value, column )
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
class ActiveRecord::Base
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
alias :sanitize_sql_orig :sanitize_sql
|
|
22
|
+
def sanitize_sql( arg ) # :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 )
|
|
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 )
|
|
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 ) # :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 )
|
|
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 ) #: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)
|
|
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
|
+
else
|
|
74
|
+
table_name = quoted_table_name
|
|
75
|
+
end
|
|
76
|
+
# ActiveRecord in 2.3.1 changed the method signature for
|
|
77
|
+
# the method attribute_condition
|
|
78
|
+
if ActiveRecord::VERSION::STRING < '2.3.1'
|
|
79
|
+
conditions << "#{table_name}.#{connection.quote_column_name(attr)} #{attribute_condition( val )} "
|
|
80
|
+
else
|
|
81
|
+
conditions << attribute_condition("#{table_name}.#{connection.quote_column_name(attr)}", val)
|
|
82
|
+
end
|
|
83
|
+
values << val
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
conditions = conditions.join( ' AND ' )
|
|
89
|
+
return [] if conditions.size == 1 and conditions.first.empty?
|
|
90
|
+
[ conditions, *values ]
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
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,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,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
|