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.
Files changed (42) hide show
  1. data/ChangeLog +131 -0
  2. data/README +156 -0
  3. data/Rakefile +99 -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 +87 -0
  9. data/db/migrate/mysql_schema.rb +31 -0
  10. data/db/migrate/oracle_schema.rb +5 -0
  11. data/db/migrate/version.rb +4 -0
  12. data/init.rb +29 -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/jdbcmysql.rb +10 -0
  16. data/lib/ar-extensions/adapters/mysql.rb +10 -0
  17. data/lib/ar-extensions/adapters/oracle.rb +14 -0
  18. data/lib/ar-extensions/adapters/postgresql.rb +9 -0
  19. data/lib/ar-extensions/adapters/sqlite.rb +7 -0
  20. data/lib/ar-extensions/csv.rb +309 -0
  21. data/lib/ar-extensions/extensions.rb +506 -0
  22. data/lib/ar-extensions/finder_options.rb +208 -0
  23. data/lib/ar-extensions/finder_options/mysql.rb +6 -0
  24. data/lib/ar-extensions/finders.rb +95 -0
  25. data/lib/ar-extensions/foreign_keys.rb +70 -0
  26. data/lib/ar-extensions/fulltext.rb +62 -0
  27. data/lib/ar-extensions/fulltext/mysql.rb +44 -0
  28. data/lib/ar-extensions/import.rb +348 -0
  29. data/lib/ar-extensions/import/jdbcmysql.rb +5 -0
  30. data/lib/ar-extensions/import/mysql.rb +5 -0
  31. data/lib/ar-extensions/import/mysql_generic.rb +41 -0
  32. data/lib/ar-extensions/import/postgresql.rb +0 -0
  33. data/lib/ar-extensions/import/sqlite.rb +22 -0
  34. data/lib/ar-extensions/insert_select.rb +184 -0
  35. data/lib/ar-extensions/insert_select/mysql.rb +6 -0
  36. data/lib/ar-extensions/synchronize.rb +30 -0
  37. data/lib/ar-extensions/temporary_table.rb +124 -0
  38. data/lib/ar-extensions/temporary_table/mysql.rb +3 -0
  39. data/lib/ar-extensions/util/sql_generation.rb +27 -0
  40. data/lib/ar-extensions/util/support_methods.rb +43 -0
  41. data/lib/ar-extensions/version.rb +9 -0
  42. 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