ar-extensions 0.8.2 → 0.9.0
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 +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
|