ar-extensions 0.8.2 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- data/ChangeLog +10 -0
- data/Rakefile +6 -1
- data/db/migrate/generic_schema.rb +26 -1
- data/db/migrate/mysql_schema.rb +1 -1
- data/db/migrate/version.rb +1 -1
- data/init.rb +18 -0
- data/lib/ar-extensions/adapters/abstract_adapter.rb +25 -2
- data/lib/ar-extensions/adapters/mysql.rb +2 -0
- data/lib/ar-extensions/create_and_update.rb +509 -0
- data/lib/ar-extensions/create_and_update/mysql.rb +7 -0
- data/lib/ar-extensions/csv.rb +32 -32
- data/lib/ar-extensions/delete.rb +143 -0
- data/lib/ar-extensions/delete/mysql.rb +3 -0
- data/lib/ar-extensions/extensions.rb +6 -1
- data/lib/ar-extensions/finder_options.rb +275 -0
- data/lib/ar-extensions/finder_options/mysql.rb +6 -0
- data/lib/ar-extensions/finders.rb +7 -1
- data/lib/ar-extensions/import/mysql.rb +8 -1
- data/lib/ar-extensions/insert_select.rb +178 -0
- data/lib/ar-extensions/insert_select/mysql.rb +7 -0
- data/lib/ar-extensions/union.rb +204 -0
- data/lib/ar-extensions/union/mysql.rb +6 -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 +1 -1
- metadata +15 -2
@@ -0,0 +1,7 @@
|
|
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/create_and_update/mysql.rb'
|
6
|
+
|
7
|
+
ActiveRecord::Base.send :include, ActiveRecord::Extensions::CreateAndUpdate
|
data/lib/ar-extensions/csv.rb
CHANGED
@@ -179,14 +179,37 @@ module ActiveRecord::Extensions::FindToCSV
|
|
179
179
|
|
180
180
|
private
|
181
181
|
|
182
|
+
def add_to_csv_association_methods!(association_name)
|
183
|
+
association = self.send association_name
|
184
|
+
association.send( :extend, ArrayInstanceMethods ) if association.is_a?( Array )
|
185
|
+
association
|
186
|
+
end
|
187
|
+
|
188
|
+
def add_to_csv_association_data! data, to
|
189
|
+
if to.empty?
|
190
|
+
to.push( *data )
|
191
|
+
else
|
192
|
+
originals = to.dup
|
193
|
+
to.clear
|
194
|
+
data.each do |assoc_csv|
|
195
|
+
originals.each do |sibling|
|
196
|
+
to.push( sibling + assoc_csv )
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def to_csv_association_is_blank?(association)
|
203
|
+
association.nil? or (association.respond_to?( :empty? ) and association.empty?)
|
204
|
+
end
|
205
|
+
|
182
206
|
def to_csv_data_for_included_associations( includes ) # :nodoc:
|
183
207
|
get_class = proc { |str| Object.const_get( self.class.reflections[ str.to_sym ].class_name ) }
|
184
208
|
|
185
209
|
case includes
|
186
210
|
when Symbol
|
187
|
-
association =
|
188
|
-
|
189
|
-
if association.nil? or (association.respond_to?( :empty? ) and association.empty?)
|
211
|
+
association = add_to_csv_association_methods! includes
|
212
|
+
if to_csv_association_is_blank?(association)
|
190
213
|
[ get_class.call( includes ).columns.map{ '' } ]
|
191
214
|
else
|
192
215
|
[ *association.to_csv_data ]
|
@@ -194,50 +217,27 @@ module ActiveRecord::Extensions::FindToCSV
|
|
194
217
|
when Array
|
195
218
|
siblings = []
|
196
219
|
includes.each do |association_name|
|
197
|
-
association =
|
198
|
-
|
199
|
-
if association.nil? or (association.respond_to?( :empty? ) and association.empty?)
|
220
|
+
association = add_to_csv_association_methods! association_name
|
221
|
+
if to_csv_association_is_blank?(association)
|
200
222
|
association_data = [ get_class.call( association_name ).columns.map{ '' } ]
|
201
223
|
else
|
202
224
|
association_data = association.to_csv_data
|
203
225
|
end
|
204
226
|
|
205
|
-
|
206
|
-
siblings.push( *association_data )
|
207
|
-
else
|
208
|
-
temp = []
|
209
|
-
association_data.each do |assoc_csv|
|
210
|
-
siblings.each do |sibling|
|
211
|
-
temp.push( sibling + assoc_csv )
|
212
|
-
end
|
213
|
-
end
|
214
|
-
siblings = temp
|
215
|
-
end
|
227
|
+
add_to_csv_association_data! association_data, siblings
|
216
228
|
end
|
217
229
|
siblings
|
218
230
|
when Hash
|
219
231
|
sorted_includes = includes.sort_by{ |k| k.to_s }
|
220
232
|
siblings = []
|
221
233
|
sorted_includes.each do |(association_name,options)|
|
222
|
-
association =
|
223
|
-
|
224
|
-
if association.nil? or (association.respond_to?( :empty ) and association.empty?)
|
234
|
+
association = add_to_csv_association_methods! association_name
|
235
|
+
if to_csv_association_is_blank?(association)
|
225
236
|
association_data = [ get_class.call( association_name ).columns.map{ '' } ]
|
226
237
|
else
|
227
238
|
association_data = association.to_csv_data( options )
|
228
239
|
end
|
229
|
-
|
230
|
-
if siblings.empty?
|
231
|
-
siblings.push( *association_data )
|
232
|
-
else
|
233
|
-
temp = []
|
234
|
-
association_data.each do |assoc_csv|
|
235
|
-
siblings.each do |sibling|
|
236
|
-
temp.push( sibling + assoc_csv )
|
237
|
-
end
|
238
|
-
end
|
239
|
-
siblings = temp
|
240
|
-
end
|
240
|
+
add_to_csv_association_data! association_data, siblings
|
241
241
|
end
|
242
242
|
siblings
|
243
243
|
else
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module ActiveRecord::Extensions::Delete#:nodoc:
|
2
|
+
mattr_accessor :delete_batch_size
|
3
|
+
self.delete_batch_size = 15000
|
4
|
+
|
5
|
+
module DeleteSupport #:nodoc:
|
6
|
+
def supports_delete? #:nodoc:
|
7
|
+
true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ActiveRecord::Base
|
13
|
+
supports_extension :delete
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
# Delete all specified records with options
|
18
|
+
#
|
19
|
+
# == Parameters
|
20
|
+
# * +conditions+ - the conditions normally specified to +delete_all+
|
21
|
+
# * +options+ - hash map of additional parameters
|
22
|
+
#
|
23
|
+
# == Options
|
24
|
+
# * <tt>:limit</tt> - the maximum number of records to delete.
|
25
|
+
# * <tt>:batch</tt> - delete in batches specified to avoid database contention
|
26
|
+
# Multiple sql deletions are executed in order to avoid database contention
|
27
|
+
# This has no affect if used inside a transaction
|
28
|
+
#
|
29
|
+
# Delete up to 65 red tags
|
30
|
+
# Tag.delete_all ['name like ?', '%red%'], :limit => 65
|
31
|
+
#
|
32
|
+
# Delete up to 65 red tags in batches of 20. This will execute up to
|
33
|
+
# 4 delete statements: 3 batches of 20 and the final batch of 5.
|
34
|
+
# Tag.delete_all ['name like ?', '%red%'], :limit => 65, :batch => 20
|
35
|
+
def delete_all_with_extension(conditions = nil, options={})
|
36
|
+
|
37
|
+
#raise an error if delete is not supported and options are specified
|
38
|
+
supports_delete! if options.any?
|
39
|
+
|
40
|
+
#call the base method if no options specified
|
41
|
+
return delete_all_without_extension(conditions) unless options.any?
|
42
|
+
|
43
|
+
#batch delete
|
44
|
+
return delete_all_batch(conditions, options[:batch], options[:limit]) if options[:batch]
|
45
|
+
|
46
|
+
#regular delete with limit
|
47
|
+
connection.delete(delete_all_extension_sql(conditions, options), "#{name} Delete All")
|
48
|
+
end
|
49
|
+
|
50
|
+
alias_method_chain :delete_all, :extension
|
51
|
+
|
52
|
+
|
53
|
+
# Utility function to delete all but one of the duplicate records
|
54
|
+
# matching the fields specified. This method will make the records
|
55
|
+
# unique for the specified fields.
|
56
|
+
#
|
57
|
+
# == Options
|
58
|
+
# * <tt>:fields</tt> - the fields to match on
|
59
|
+
# * <tt>:conditions</tt> - additional conditions
|
60
|
+
# * <tt>:winner_clause</tt> - the part of the query specifying what wins. Default winner is that with the greatest id.
|
61
|
+
# * <tt>:query_field</tt> -> the field to use to determine the winner. Defaults to primary_key (id). The tables are aliased
|
62
|
+
# to c1 and c2 respectively
|
63
|
+
# == Examples
|
64
|
+
# Make all the phone numbers of contacts unique by deleting the duplicates with the highest ids
|
65
|
+
# Contacts.delete_duplicates(:fields=>['phone_number_id'])
|
66
|
+
#
|
67
|
+
# Delete all tags that are the same preserving the ones with the highest id
|
68
|
+
# Tag.delete_duplicates :fields => [:name], :winner_clause => "c1.id < c2.id"
|
69
|
+
#
|
70
|
+
# Remove duplicate invitations (those that from the same person and to the same recipient)
|
71
|
+
# preseving the first ones inserted
|
72
|
+
# Invitation.delete_duplicates :fields=>[:event_id, :from_id, :recipient_id]
|
73
|
+
def delete_duplicates(options={})
|
74
|
+
supports_delete!
|
75
|
+
|
76
|
+
options[:query_field]||= primary_key
|
77
|
+
|
78
|
+
query = "DELETE FROM"
|
79
|
+
query << " c1 USING #{quoted_table_name} c1, #{quoted_table_name} c2"
|
80
|
+
query << " WHERE ("
|
81
|
+
query << options[:fields].collect{|field| "c1.#{field} = c2.#{field}" }.join(" and ")
|
82
|
+
query << " and (#{sanitize_sql(options[:conditions])})" unless options[:conditions].blank?
|
83
|
+
query << " and "
|
84
|
+
query << (options[:winner_clause]||"c1.#{options[:query_field]} > c2.#{options[:query_field]}")
|
85
|
+
query << ")"
|
86
|
+
|
87
|
+
self.connection.execute(self.send(:sanitize_sql, query))
|
88
|
+
end
|
89
|
+
|
90
|
+
protected
|
91
|
+
|
92
|
+
|
93
|
+
# Delete all records specified in batches
|
94
|
+
#
|
95
|
+
# == Parameters
|
96
|
+
# * +conditions+ - the conditions normally specified to +delete_all+
|
97
|
+
# * +batch+ - the size of the batches to delete. defaults to 15000
|
98
|
+
# * +limit+ - the maximum number of records to delete
|
99
|
+
#
|
100
|
+
def delete_all_batch(conditions=nil, batch=nil, limit=nil)#:nodoc:
|
101
|
+
|
102
|
+
#update the batch size if batch is nil or true or 0
|
103
|
+
if batch.nil? || !batch.is_a?(Fixnum) || batch.to_i == 0
|
104
|
+
batch = ActiveRecord::Extensions::Delete.delete_batch_size
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
sql = delete_all_extension_sql(conditions, :limit => batch)
|
109
|
+
page_num = total = 0
|
110
|
+
|
111
|
+
loop {
|
112
|
+
page_num += 1
|
113
|
+
|
114
|
+
#if this is the last batch query and limit is set
|
115
|
+
#only delete the remainer
|
116
|
+
if limit && (total + batch > limit)
|
117
|
+
sql = delete_all_extension_sql(conditions, :limit => (limit - total))
|
118
|
+
end
|
119
|
+
|
120
|
+
count = connection.delete(sql, "#{name} Delete All Batch #{page_num}")
|
121
|
+
total += count
|
122
|
+
|
123
|
+
# Return if
|
124
|
+
# * last query did not return the batch size (meaning nothing left to delete)
|
125
|
+
# * we have reached our limit
|
126
|
+
if (count < batch) || (limit && (total >= limit))
|
127
|
+
return total
|
128
|
+
end
|
129
|
+
}
|
130
|
+
end
|
131
|
+
|
132
|
+
#generate the delete SQL with limit
|
133
|
+
def delete_all_extension_sql(conditions, options={})#:nodoc:
|
134
|
+
sql = "DELETE FROM #{quoted_table_name} "
|
135
|
+
add_conditions!(sql, conditions, scope(:find))
|
136
|
+
connection.add_limit_offset!(sql, options)
|
137
|
+
sql
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
@@ -315,7 +315,12 @@ module ActiveRecord::Extensions
|
|
315
315
|
fieldname = caller.connection.quote_column_name( key )
|
316
316
|
min = caller.connection.quote( val.first, caller.columns_hash[ key ] )
|
317
317
|
max = caller.connection.quote( val.last, caller.columns_hash[ key ] )
|
318
|
-
str =
|
318
|
+
str = if val.exclude_end?
|
319
|
+
"#{match_data ? 'NOT ' : '' }(#{caller.quoted_table_name}.#{fieldname} >= #{min} AND #{caller.quoted_table_name}.#{fieldname} < #{max})"
|
320
|
+
else
|
321
|
+
"#{caller.quoted_table_name}.#{fieldname} #{match_data ? 'NOT ' : '' } BETWEEN #{min} AND #{max}"
|
322
|
+
end
|
323
|
+
|
319
324
|
return Result.new( str, nil )
|
320
325
|
end
|
321
326
|
nil
|
@@ -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
|