Empact-ar-extensions 0.9.2

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.
Files changed (45) hide show
  1. data/ChangeLog +145 -0
  2. data/README +167 -0
  3. data/Rakefile +61 -0
  4. data/config/database.yml +7 -0
  5. data/config/database.yml.template +7 -0
  6. data/config/mysql.schema +72 -0
  7. data/config/postgresql.schema +39 -0
  8. data/db/migrate/generic_schema.rb +97 -0
  9. data/db/migrate/mysql_schema.rb +32 -0
  10. data/db/migrate/oracle_schema.rb +5 -0
  11. data/db/migrate/version.rb +4 -0
  12. data/init.rb +31 -0
  13. data/lib/ar-extensions.rb +5 -0
  14. data/lib/ar-extensions/adapters/abstract_adapter.rb +146 -0
  15. data/lib/ar-extensions/adapters/mysql.rb +10 -0
  16. data/lib/ar-extensions/adapters/oracle.rb +14 -0
  17. data/lib/ar-extensions/adapters/postgresql.rb +9 -0
  18. data/lib/ar-extensions/adapters/sqlite.rb +7 -0
  19. data/lib/ar-extensions/create_and_update.rb +508 -0
  20. data/lib/ar-extensions/create_and_update/mysql.rb +7 -0
  21. data/lib/ar-extensions/csv.rb +309 -0
  22. data/lib/ar-extensions/delete.rb +143 -0
  23. data/lib/ar-extensions/delete/mysql.rb +3 -0
  24. data/lib/ar-extensions/extensions.rb +509 -0
  25. data/lib/ar-extensions/finder_options.rb +275 -0
  26. data/lib/ar-extensions/finder_options/mysql.rb +6 -0
  27. data/lib/ar-extensions/finders.rb +96 -0
  28. data/lib/ar-extensions/foreign_keys.rb +70 -0
  29. data/lib/ar-extensions/fulltext.rb +62 -0
  30. data/lib/ar-extensions/fulltext/mysql.rb +44 -0
  31. data/lib/ar-extensions/import.rb +354 -0
  32. data/lib/ar-extensions/import/mysql.rb +50 -0
  33. data/lib/ar-extensions/import/postgresql.rb +0 -0
  34. data/lib/ar-extensions/import/sqlite.rb +22 -0
  35. data/lib/ar-extensions/insert_select.rb +178 -0
  36. data/lib/ar-extensions/insert_select/mysql.rb +7 -0
  37. data/lib/ar-extensions/synchronize.rb +30 -0
  38. data/lib/ar-extensions/temporary_table.rb +131 -0
  39. data/lib/ar-extensions/temporary_table/mysql.rb +3 -0
  40. data/lib/ar-extensions/union.rb +204 -0
  41. data/lib/ar-extensions/union/mysql.rb +6 -0
  42. data/lib/ar-extensions/util/sql_generation.rb +27 -0
  43. data/lib/ar-extensions/util/support_methods.rb +32 -0
  44. data/lib/ar-extensions/version.rb +9 -0
  45. metadata +128 -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,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,96 @@
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
+ Array(result.value).each do |value|
67
+ values.push(value)
68
+ end
69
+ else
70
+ # Extract table name from qualified attribute names.
71
+ attr = key.to_s
72
+ if attr.include?('.')
73
+ table_name, attr = attr.split('.', 2)
74
+ table_name = connection.quote_table_name(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
96
+ 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 )