composite_primary_keys 0.7.5 → 0.8.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|