composite_primary_keys 0.7.5 → 0.8.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/Manifest.txt +75 -0
- data/Rakefile +83 -101
- data/lib/composite_primary_keys.rb +8 -0
- data/lib/composite_primary_keys/associations.rb +66 -18
- data/lib/composite_primary_keys/base.rb +169 -159
- data/lib/composite_primary_keys/calculations.rb +63 -0
- data/lib/composite_primary_keys/connection_adapters/postgresql_adapter.rb +11 -0
- data/lib/composite_primary_keys/version.rb +2 -2
- data/scripts/txt2html +4 -2
- data/scripts/txt2js +3 -2
- data/test/abstract_unit.rb +9 -2
- data/test/connections/native_mysql/connection.rb +5 -2
- data/test/connections/native_postgresql/connection.rb +15 -0
- data/test/connections/native_sqlite/connection.rb +10 -0
- data/test/fixtures/db_definitions/mysql.sql +20 -0
- data/test/fixtures/db_definitions/postgresql.sql +100 -0
- data/test/fixtures/db_definitions/sqlite.sql +84 -0
- data/test/fixtures/group.rb +3 -0
- data/test/fixtures/groups.yml +3 -0
- data/test/fixtures/membership.rb +7 -0
- data/test/fixtures/membership_status.rb +3 -0
- data/test/fixtures/membership_statuses.yml +8 -0
- data/test/fixtures/memberships.yml +6 -0
- data/test/{associations_test.rb → test_associations.rb} +22 -12
- data/test/{attributes_test.rb → test_attributes.rb} +4 -5
- data/test/{clone_test.rb → test_clone.rb} +2 -3
- data/test/{create_test.rb → test_create.rb} +2 -3
- data/test/{delete_test.rb → test_delete.rb} +2 -3
- data/test/{dummy_test.rb → test_dummy.rb} +4 -5
- data/test/{find_test.rb → test_find.rb} +3 -4
- data/test/{ids_test.rb → test_ids.rb} +4 -4
- data/test/{miscellaneous_test.rb → test_miscellaneous.rb} +2 -3
- data/test/{pagination_test.rb → test_pagination.rb} +4 -3
- data/test/{santiago_test.rb → test_santiago.rb} +5 -3
- data/test/test_tutorial_examle.rb +29 -0
- data/test/{update_test.rb → test_update.rb} +2 -3
- data/website/index.html +267 -201
- data/website/index.txt +74 -70
- data/website/stylesheets/screen.css +33 -3
- data/website/version-raw.js +1 -1
- data/website/version.js +1 -1
- metadata +80 -66
- data/scripts/http-access2-2.0.6.gem +0 -0
- data/scripts/rubyforge +0 -217
- data/scripts/rubyforge-orig +0 -217
- data/test/fixtures/db_definitions/mysql.drop.sql +0 -10
@@ -25,6 +25,7 @@ module CompositePrimaryKeys
|
|
25
25
|
extend CompositePrimaryKeys::ActiveRecord::Base::CompositeClassMethods
|
26
26
|
include CompositePrimaryKeys::ActiveRecord::Base::CompositeInstanceMethods
|
27
27
|
include CompositePrimaryKeys::ActiveRecord::Associations
|
28
|
+
extend CompositePrimaryKeys::ActiveRecord::Calculations::ClassMethods
|
28
29
|
EOV
|
29
30
|
end
|
30
31
|
|
@@ -44,7 +45,7 @@ module CompositePrimaryKeys
|
|
44
45
|
def id
|
45
46
|
attr_names = self.class.primary_keys
|
46
47
|
CompositeIds.new(
|
47
|
-
|
48
|
+
attr_names.map {|attr_name| read_attribute(attr_name)}
|
48
49
|
)
|
49
50
|
end
|
50
51
|
alias_method :ids, :id
|
@@ -59,9 +60,9 @@ module CompositePrimaryKeys
|
|
59
60
|
|
60
61
|
def quoted_id #:nodoc:
|
61
62
|
[self.class.primary_keys, ids].
|
62
|
-
|
63
|
-
|
64
|
-
|
63
|
+
transpose.
|
64
|
+
map {|attr_name,id| quote_value(id, column_for_attribute(attr_name))}.
|
65
|
+
to_composite_ids
|
65
66
|
end
|
66
67
|
|
67
68
|
# Sets the primary ID.
|
@@ -132,15 +133,19 @@ module CompositePrimaryKeys
|
|
132
133
|
|
133
134
|
# Creates a new record with values matching those of the instance attributes.
|
134
135
|
def create_without_callbacks
|
135
|
-
unless self.id
|
136
|
+
unless self.id
|
137
|
+
raise CompositeKeyError, "Composite keys do not generated ids from sequences, you must provide id values"
|
138
|
+
end
|
136
139
|
attributes_minus_pks = attributes_with_quotes(false)
|
137
140
|
cols = quoted_column_names(attributes_minus_pks) << self.class.primary_key
|
138
141
|
vals = attributes_minus_pks.values << quoted_id
|
139
142
|
connection.insert(
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
143
|
+
"INSERT INTO #{self.class.table_name} " +
|
144
|
+
"(#{cols.join(', ')}) " +
|
145
|
+
"VALUES (#{vals.join(', ')})",
|
146
|
+
"#{self.class.name} Create",
|
147
|
+
self.class.primary_key,
|
148
|
+
self.id
|
144
149
|
)
|
145
150
|
@new_record = false
|
146
151
|
return true
|
@@ -164,178 +169,183 @@ module CompositePrimaryKeys
|
|
164
169
|
def destroy_without_callbacks
|
165
170
|
where_class = [self.class.primary_key, quoted_id].transpose.map do |pair|
|
166
171
|
"(#{pair[0]} = #{pair[1]})"
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
end
|
175
|
-
freeze
|
172
|
+
end.join(" AND ")
|
173
|
+
unless new_record?
|
174
|
+
connection.delete(
|
175
|
+
"DELETE FROM #{self.class.table_name} " +
|
176
|
+
"WHERE #{where_class}",
|
177
|
+
"#{self.class.name} Destroy"
|
178
|
+
)
|
176
179
|
end
|
180
|
+
freeze
|
181
|
+
end
|
177
182
|
|
183
|
+
end
|
184
|
+
|
185
|
+
module CompositeClassMethods
|
186
|
+
def primary_key; primary_keys; end
|
187
|
+
def primary_key=(keys); primary_keys = keys; end
|
188
|
+
|
189
|
+
def composite?
|
190
|
+
true
|
178
191
|
end
|
179
192
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
193
|
+
#ids_to_s([[1,2],[7,3]]) -> "(1,2),(7,3)"
|
194
|
+
#ids_to_s([[1,2],[7,3]], ',', ';') -> "1,2;7,3"
|
195
|
+
def ids_to_s(many_ids, id_sep = CompositePrimaryKeys::ID_SEP, list_sep = ',', left_bracket = '(', right_bracket = ')')
|
196
|
+
many_ids.map {|ids| "#{left_bracket}#{ids}#{right_bracket}"}.join(list_sep)
|
197
|
+
end
|
198
|
+
|
199
|
+
# Returns true if the given +ids+ represents the primary keys of a record in the database, false otherwise.
|
200
|
+
# Example:
|
201
|
+
# Person.exists?(5,7)
|
202
|
+
def exists?(ids)
|
203
|
+
obj = find(ids) rescue false
|
204
|
+
!obj.nil? and obj.is_a?(self)
|
205
|
+
end
|
206
|
+
|
207
|
+
# Deletes the record with the given +ids+ without instantiating an object first, e.g. delete(1,2)
|
208
|
+
# If an array of ids is provided (e.g. delete([1,2], [3,4]), all of them
|
209
|
+
# are deleted.
|
210
|
+
def delete(*ids)
|
211
|
+
unless ids.is_a?(Array); raise "*ids must be an Array"; end
|
212
|
+
ids = [ids.to_composite_ids] if not ids.first.is_a?(Array)
|
213
|
+
where_class = ids.map do |id_set|
|
214
|
+
[primary_keys, id_set].transpose.map do |key, id|
|
215
|
+
"#{table_name}.#{key.to_s}=#{sanitize(id)}"
|
216
|
+
end.join(" AND ")
|
217
|
+
end.join(") OR (")
|
218
|
+
delete_all([ "(#{where_class})" ])
|
219
|
+
end
|
220
|
+
|
221
|
+
# Destroys the record with the given +ids+ by instantiating the object and calling #destroy (all the callbacks are the triggered).
|
222
|
+
# If an array of ids is provided, all of them are destroyed.
|
223
|
+
def destroy(*ids)
|
224
|
+
unless ids.is_a?(Array); raise "*ids must be an Array"; end
|
225
|
+
if ids.first.is_a?(Array)
|
226
|
+
ids = ids.map{|compids| compids.to_composite_ids}
|
227
|
+
else
|
228
|
+
ids = ids.to_composite_ids
|
192
229
|
end
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
230
|
+
ids.first.is_a?(CompositeIds) ? ids.each { |id_set| find(id_set).destroy } : find(ids).destroy
|
231
|
+
end
|
232
|
+
|
233
|
+
# Returns an array of column objects for the table associated with this class.
|
234
|
+
# Each column that matches to one of the primary keys has its
|
235
|
+
# primary attribute set to true
|
236
|
+
def columns
|
237
|
+
unless @columns
|
238
|
+
@columns = connection.columns(table_name, "#{name} Columns")
|
239
|
+
@columns.each {|column| column.primary = primary_keys.include?(column.name.to_sym)}
|
200
240
|
end
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
241
|
+
@columns
|
242
|
+
end
|
243
|
+
|
244
|
+
## DEACTIVATED METHODS ##
|
245
|
+
public
|
246
|
+
# Lazy-set the sequence name to the connection's default. This method
|
247
|
+
# is only ever called once since set_sequence_name overrides it.
|
248
|
+
def sequence_name #:nodoc:
|
249
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
250
|
+
end
|
251
|
+
|
252
|
+
def reset_sequence_name #:nodoc:
|
253
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
254
|
+
end
|
255
|
+
|
256
|
+
def set_primary_key(value = nil, &block)
|
257
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
258
|
+
end
|
259
|
+
|
260
|
+
private
|
261
|
+
def find_one(id, options)
|
262
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
263
|
+
end
|
264
|
+
|
265
|
+
def find_some(ids, options)
|
266
|
+
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
267
|
+
end
|
268
|
+
|
269
|
+
def find_from_ids(ids, options)
|
270
|
+
ids = ids.first if ids.last == nil
|
271
|
+
conditions = " AND (#{sanitize_sql(options[:conditions])})" if options[:conditions]
|
272
|
+
# if ids is just a flat list, then its size must = primary_key.length (one id per primary key, in order)
|
273
|
+
# if ids is list of lists, then each inner list must follow rule above
|
274
|
+
if ids.first.is_a? String
|
275
|
+
# find '2,1' -> ids = ['2,1']
|
276
|
+
# find '2,1;7,3' -> ids = ['2,1;7,3']
|
277
|
+
ids = ids.first.split(ID_SET_SEP).map {|id_set| id_set.split(ID_SEP).to_composite_ids}
|
278
|
+
# find '2,1;7,3' -> ids = [['2','1'],['7','3']], inner [] are CompositeIds
|
209
279
|
end
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
unless ids.is_a?(Array); raise "*ids must be an Array"; end
|
215
|
-
if ids.first.is_a?(Array)
|
216
|
-
ids = ids.map{|compids| compids.to_composite_ids}
|
217
|
-
else
|
218
|
-
ids = ids.to_composite_ids
|
280
|
+
ids = [ids.to_composite_ids] if not ids.first.kind_of?(Array)
|
281
|
+
ids.each do |id_set|
|
282
|
+
unless id_set.is_a?(Array)
|
283
|
+
raise "Ids must be in an Array, instead received: #{id_set.inspect}"
|
219
284
|
end
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
# Returns an array of column objects for the table associated with this class.
|
224
|
-
# Each column that matches to one of the primary keys has its
|
225
|
-
# primary attribute set to true
|
226
|
-
def columns
|
227
|
-
unless @columns
|
228
|
-
@columns = connection.columns(table_name, "#{name} Columns")
|
229
|
-
@columns.each {|column| column.primary = primary_keys.include?(column.name.to_sym)}
|
285
|
+
unless id_set.length == primary_keys.length
|
286
|
+
raise "#{id_set.inspect}: Incorrect number of primary keys for #{class_name}: #{primary_keys.inspect}"
|
230
287
|
end
|
231
|
-
@columns
|
232
|
-
end
|
233
|
-
|
234
|
-
## DEACTIVATED METHODS ##
|
235
|
-
public
|
236
|
-
# Lazy-set the sequence name to the connection's default. This method
|
237
|
-
# is only ever called once since set_sequence_name overrides it.
|
238
|
-
def sequence_name #:nodoc:
|
239
|
-
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
240
|
-
end
|
241
|
-
|
242
|
-
def reset_sequence_name #:nodoc:
|
243
|
-
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
244
|
-
end
|
245
|
-
|
246
|
-
def set_primary_key(value = nil, &block)
|
247
|
-
raise CompositeKeyError, CompositePrimaryKeys::ActiveRecord::Base::INVALID_FOR_COMPOSITE_KEYS
|
248
288
|
end
|
249
289
|
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
end
|
290
|
+
# Let keys = [:a, :b]
|
291
|
+
# If ids = [[10, 50], [11, 51]], then :conditions =>
|
292
|
+
# "(#{table_name}.a, #{table_name}.b) IN ((10, 50), (11, 51))"
|
254
293
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
# find '2,1;7,3' -> ids = [['2','1'],['7','3']], inner [] are CompositeIds
|
269
|
-
end
|
270
|
-
ids = [ids.to_composite_ids] if not ids.first.kind_of?(Array)
|
271
|
-
ids.each do |id_set|
|
272
|
-
unless id_set.is_a?(Array)
|
273
|
-
raise "Ids must be in an Array, instead received: #{id_set.inspect}"
|
274
|
-
end
|
275
|
-
unless id_set.length == primary_keys.length
|
276
|
-
raise "#{id_set.inspect}: Incorrect number of primary keys for #{class_name}: #{primary_keys.inspect}"
|
294
|
+
conditions = ids.map do |id_set|
|
295
|
+
[primary_keys, id_set].transpose.map do |key, id|
|
296
|
+
"#{table_name}.#{key.to_s}=#{sanitize(id)}"
|
297
|
+
end.join(" AND ")
|
298
|
+
end.join(") OR (")
|
299
|
+
options.update :conditions => "(#{conditions})"
|
300
|
+
|
301
|
+
result = find_every(options)
|
302
|
+
|
303
|
+
if result.size == ids.size
|
304
|
+
ids.size == 1 ? result[0] : result
|
305
|
+
else
|
306
|
+
raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions}"
|
277
307
|
end
|
278
308
|
end
|
279
309
|
|
280
|
-
# Let keys = [:a, :b]
|
281
|
-
# If ids = [[10, 50], [11, 51]], then :conditions =>
|
282
|
-
# "(#{table_name}.a, #{table_name}.b) IN ((10, 50), (11, 51))"
|
283
|
-
|
284
|
-
conditions = ids.map do |id_set|
|
285
|
-
[primary_keys, id_set].transpose.map do |key, id|
|
286
|
-
"#{table_name}.#{key.to_s}=#{sanitize(id)}"
|
287
|
-
end.join(" AND ")
|
288
|
-
end.join(") OR (")
|
289
|
-
options.update :conditions => "(#{conditions})"
|
290
|
-
|
291
|
-
result = find_every(options)
|
292
|
-
|
293
|
-
if result.size == ids.size
|
294
|
-
ids.size == 1 ? result[0] : result
|
295
|
-
else
|
296
|
-
raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions}"
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
end
|
301
310
|
end
|
302
311
|
end
|
303
312
|
end
|
313
|
+
end
|
314
|
+
|
315
|
+
module ActiveRecord
|
316
|
+
ID_SEP = ','
|
317
|
+
ID_SET_SEP = ';'
|
304
318
|
|
305
|
-
|
306
|
-
|
307
|
-
|
319
|
+
class Base
|
320
|
+
# Allows +attr_name+ to be the list of primary_keys, and returns the id
|
321
|
+
# of the object
|
322
|
+
# e.g. @object[@object.class.primary_key] => [1,1]
|
323
|
+
def [](attr_name)
|
324
|
+
if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first
|
325
|
+
attr_name = attr_name.split(ID_SEP)
|
326
|
+
end
|
327
|
+
attr_name.is_a?(Array) ?
|
328
|
+
attr_name.map {|name| read_attribute(name)} :
|
329
|
+
read_attribute(attr_name)
|
330
|
+
end
|
308
331
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first
|
315
|
-
attr_name = attr_name.split(ID_SEP)
|
316
|
-
end
|
317
|
-
attr_name.is_a?(Array) ?
|
318
|
-
attr_name.map {|name| read_attribute(name)} :
|
319
|
-
read_attribute(attr_name)
|
332
|
+
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
|
333
|
+
# (Alias for the protected write_attribute method).
|
334
|
+
def []=(attr_name, value)
|
335
|
+
if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first
|
336
|
+
attr_name = attr_name.split(ID_SEP)
|
320
337
|
end
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
if attr_name.is_a?(String) and attr_name != attr_name.split(ID_SEP).first
|
326
|
-
attr_name = attr_name.split(ID_SEP)
|
327
|
-
end
|
328
|
-
if attr_name.is_a? Array
|
329
|
-
value = value.split(ID_SEP) if value.is_a? String
|
330
|
-
unless value.length == attr_name.length
|
331
|
-
raise "Number of attr_names and values do not match"
|
332
|
-
end
|
333
|
-
#breakpoint
|
334
|
-
[attr_name, value].transpose.map {|name,val| write_attribute(name.to_s, val)}
|
335
|
-
else
|
336
|
-
write_attribute(attr_name, value)
|
338
|
+
if attr_name.is_a? Array
|
339
|
+
value = value.split(ID_SEP) if value.is_a? String
|
340
|
+
unless value.length == attr_name.length
|
341
|
+
raise "Number of attr_names and values do not match"
|
337
342
|
end
|
343
|
+
#breakpoint
|
344
|
+
[attr_name, value].transpose.map {|name,val| write_attribute(name.to_s, val)}
|
345
|
+
else
|
346
|
+
write_attribute(attr_name, value)
|
338
347
|
end
|
339
|
-
|
340
348
|
end
|
349
|
+
|
341
350
|
end
|
351
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module CompositePrimaryKeys
|
2
|
+
module ActiveRecord
|
3
|
+
module Calculations
|
4
|
+
module ClassMethods
|
5
|
+
def construct_calculation_sql(operation, column_name, options) #:nodoc:
|
6
|
+
operation = operation.to_s.downcase
|
7
|
+
options = options.symbolize_keys
|
8
|
+
|
9
|
+
scope = scope(:find)
|
10
|
+
merged_includes = merge_includes(scope ? scope[:include] : [], options[:include])
|
11
|
+
aggregate_alias = column_alias_for(operation, column_name)
|
12
|
+
use_workaround = !connection.supports_count_distinct? && options[:distinct] && operation.to_s.downcase == 'count'
|
13
|
+
join_dependency = nil
|
14
|
+
|
15
|
+
if merged_includes.any? && operation.to_s.downcase == 'count'
|
16
|
+
options[:distinct] = true
|
17
|
+
use_workaround = !connection.supports_count_distinct?
|
18
|
+
column_name = options[:select] || primary_key.map{ |part| "#{table_name}.#{part}"}.join(',')
|
19
|
+
end
|
20
|
+
|
21
|
+
sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}"
|
22
|
+
|
23
|
+
# A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
|
24
|
+
sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
|
25
|
+
|
26
|
+
sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
|
27
|
+
sql << " FROM (SELECT DISTINCT #{column_name}" if use_workaround
|
28
|
+
sql << " FROM #{table_name} "
|
29
|
+
if merged_includes.any?
|
30
|
+
join_dependency = ::ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins])
|
31
|
+
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
|
32
|
+
end
|
33
|
+
add_joins!(sql, options, scope)
|
34
|
+
add_conditions!(sql, options[:conditions], scope)
|
35
|
+
add_limited_ids_condition!(sql, options, join_dependency) if \
|
36
|
+
join_dependency &&
|
37
|
+
!using_limitable_reflections?(join_dependency.reflections) &&
|
38
|
+
((scope && scope[:limit]) || options[:limit])
|
39
|
+
|
40
|
+
if options[:group]
|
41
|
+
group_key = Base.connection.adapter_name == 'FrontBase' ? :group_alias : :group_field
|
42
|
+
sql << " GROUP BY #{options[group_key]} "
|
43
|
+
end
|
44
|
+
|
45
|
+
if options[:group] && options[:having]
|
46
|
+
# FrontBase requires identifiers in the HAVING clause and chokes on function calls
|
47
|
+
if Base.connection.adapter_name == 'FrontBase'
|
48
|
+
options[:having].downcase!
|
49
|
+
options[:having].gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
|
50
|
+
end
|
51
|
+
|
52
|
+
sql << " HAVING #{options[:having]} "
|
53
|
+
end
|
54
|
+
|
55
|
+
sql << " ORDER BY #{options[:order]} " if options[:order]
|
56
|
+
add_limit!(sql, options, scope)
|
57
|
+
sql << ') as w1' if use_workaround
|
58
|
+
sql
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|