sskirby-activerecord 3.2.1

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 (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,417 @@
1
+ require 'active_support/core_ext/array/wrap'
2
+ require 'active_support/core_ext/object/blank'
3
+
4
+ module ActiveRecord
5
+ module QueryMethods
6
+ extend ActiveSupport::Concern
7
+
8
+ attr_accessor :includes_values, :eager_load_values, :preload_values,
9
+ :select_values, :group_values, :order_values, :joins_values,
10
+ :where_values, :having_values, :bind_values,
11
+ :limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
12
+ :from_value, :reordering_value, :reverse_order_value,
13
+ :uniq_value
14
+
15
+ def includes(*args)
16
+ args.reject! {|a| a.blank? }
17
+
18
+ return self if args.empty?
19
+
20
+ relation = clone
21
+ relation.includes_values = (relation.includes_values + args).flatten.uniq
22
+ relation
23
+ end
24
+
25
+ def eager_load(*args)
26
+ return self if args.blank?
27
+
28
+ relation = clone
29
+ relation.eager_load_values += args
30
+ relation
31
+ end
32
+
33
+ def preload(*args)
34
+ return self if args.blank?
35
+
36
+ relation = clone
37
+ relation.preload_values += args
38
+ relation
39
+ end
40
+
41
+ # Works in two unique ways.
42
+ #
43
+ # First: takes a block so it can be used just like Array#select.
44
+ #
45
+ # Model.scoped.select { |m| m.field == value }
46
+ #
47
+ # This will build an array of objects from the database for the scope,
48
+ # converting them into an array and iterating through them using Array#select.
49
+ #
50
+ # Second: Modifies the SELECT statement for the query so that only certain
51
+ # fields are retrieved:
52
+ #
53
+ # >> Model.select(:field)
54
+ # => [#<Model field:value>]
55
+ #
56
+ # Although in the above example it looks as though this method returns an
57
+ # array, it actually returns a relation object and can have other query
58
+ # methods appended to it, such as the other methods in ActiveRecord::QueryMethods.
59
+ #
60
+ # The argument to the method can also be an array of fields.
61
+ #
62
+ # >> Model.select([:field, :other_field, :and_one_more])
63
+ # => [#<Model field: "value", other_field: "value", and_one_more: "value">]
64
+ #
65
+ # Any attributes that do not have fields retrieved by a select
66
+ # will raise a ActiveModel::MissingAttributeError when the getter method for that attribute is used:
67
+ #
68
+ # >> Model.select(:field).first.other_field
69
+ # => ActiveModel::MissingAttributeError: missing attribute: other_field
70
+ def select(value = Proc.new)
71
+ if block_given?
72
+ to_a.select {|*block_args| value.call(*block_args) }
73
+ else
74
+ relation = clone
75
+ relation.select_values += Array.wrap(value)
76
+ relation
77
+ end
78
+ end
79
+
80
+ def group(*args)
81
+ return self if args.blank?
82
+
83
+ relation = clone
84
+ relation.group_values += args.flatten
85
+ relation
86
+ end
87
+
88
+ def order(*args)
89
+ return self if args.blank?
90
+
91
+ relation = clone
92
+ relation.order_values += args.flatten
93
+ relation
94
+ end
95
+
96
+ # Replaces any existing order defined on the relation with the specified order.
97
+ #
98
+ # User.order('email DESC').reorder('id ASC') # generated SQL has 'ORDER BY id ASC'
99
+ #
100
+ # Subsequent calls to order on the same relation will be appended. For example:
101
+ #
102
+ # User.order('email DESC').reorder('id ASC').order('name ASC')
103
+ #
104
+ # generates a query with 'ORDER BY id ASC, name ASC'.
105
+ #
106
+ def reorder(*args)
107
+ return self if args.blank?
108
+
109
+ relation = clone
110
+ relation.reordering_value = true
111
+ relation.order_values = args.flatten
112
+ relation
113
+ end
114
+
115
+ def joins(*args)
116
+ return self if args.compact.blank?
117
+
118
+ relation = clone
119
+
120
+ args.flatten!
121
+ relation.joins_values += args
122
+
123
+ relation
124
+ end
125
+
126
+ def bind(value)
127
+ relation = clone
128
+ relation.bind_values += [value]
129
+ relation
130
+ end
131
+
132
+ def where(opts, *rest)
133
+ return self if opts.blank?
134
+
135
+ relation = clone
136
+ relation.where_values += build_where(opts, rest)
137
+ relation
138
+ end
139
+
140
+ def having(opts, *rest)
141
+ return self if opts.blank?
142
+
143
+ relation = clone
144
+ relation.having_values += build_where(opts, rest)
145
+ relation
146
+ end
147
+
148
+ def limit(value)
149
+ relation = clone
150
+ relation.limit_value = value
151
+ relation
152
+ end
153
+
154
+ def offset(value)
155
+ relation = clone
156
+ relation.offset_value = value
157
+ relation
158
+ end
159
+
160
+ def lock(locks = true)
161
+ relation = clone
162
+
163
+ case locks
164
+ when String, TrueClass, NilClass
165
+ relation.lock_value = locks || true
166
+ else
167
+ relation.lock_value = false
168
+ end
169
+
170
+ relation
171
+ end
172
+
173
+ def readonly(value = true)
174
+ relation = clone
175
+ relation.readonly_value = value
176
+ relation
177
+ end
178
+
179
+ def create_with(value)
180
+ relation = clone
181
+ relation.create_with_value = value ? create_with_value.merge(value) : {}
182
+ relation
183
+ end
184
+
185
+ def from(value)
186
+ relation = clone
187
+ relation.from_value = value
188
+ relation
189
+ end
190
+
191
+ # Specifies whether the records should be unique or not. For example:
192
+ #
193
+ # User.select(:name)
194
+ # # => Might return two records with the same name
195
+ #
196
+ # User.select(:name).uniq
197
+ # # => Returns 1 record per unique name
198
+ #
199
+ # User.select(:name).uniq.uniq(false)
200
+ # # => You can also remove the uniqueness
201
+ def uniq(value = true)
202
+ relation = clone
203
+ relation.uniq_value = value
204
+ relation
205
+ end
206
+
207
+ # Used to extend a scope with additional methods, either through
208
+ # a module or through a block provided.
209
+ #
210
+ # The object returned is a relation, which can be further extended.
211
+ #
212
+ # === Using a module
213
+ #
214
+ # module Pagination
215
+ # def page(number)
216
+ # # pagination code goes here
217
+ # end
218
+ # end
219
+ #
220
+ # scope = Model.scoped.extending(Pagination)
221
+ # scope.page(params[:page])
222
+ #
223
+ # You can also pass a list of modules:
224
+ #
225
+ # scope = Model.scoped.extending(Pagination, SomethingElse)
226
+ #
227
+ # === Using a block
228
+ #
229
+ # scope = Model.scoped.extending do
230
+ # def page(number)
231
+ # # pagination code goes here
232
+ # end
233
+ # end
234
+ # scope.page(params[:page])
235
+ #
236
+ # You can also use a block and a module list:
237
+ #
238
+ # scope = Model.scoped.extending(Pagination) do
239
+ # def per_page(number)
240
+ # # pagination code goes here
241
+ # end
242
+ # end
243
+ def extending(*modules)
244
+ modules << Module.new(&Proc.new) if block_given?
245
+
246
+ return self if modules.empty?
247
+
248
+ relation = clone
249
+ relation.send(:apply_modules, modules.flatten)
250
+ relation
251
+ end
252
+
253
+ def reverse_order
254
+ relation = clone
255
+ relation.reverse_order_value = !relation.reverse_order_value
256
+ relation
257
+ end
258
+
259
+ def arel
260
+ @arel ||= with_default_scope.build_arel
261
+ end
262
+
263
+ def build_arel
264
+ arel = table.from table
265
+
266
+ build_joins(arel, @joins_values) unless @joins_values.empty?
267
+
268
+ collapse_wheres(arel, (@where_values - ['']).uniq)
269
+
270
+ arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
271
+
272
+ arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
273
+ arel.skip(@offset_value) if @offset_value
274
+
275
+ arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
276
+
277
+ order = @order_values
278
+ order = reverse_sql_order(order) if @reverse_order_value
279
+ arel.order(*order.uniq.reject{|o| o.blank?}) unless order.empty?
280
+
281
+ build_select(arel, @select_values.uniq)
282
+
283
+ arel.distinct(@uniq_value)
284
+ arel.from(@from_value) if @from_value
285
+ arel.lock(@lock_value) if @lock_value
286
+
287
+ arel
288
+ end
289
+
290
+ private
291
+
292
+ def custom_join_ast(table, joins)
293
+ joins = joins.reject { |join| join.blank? }
294
+
295
+ return [] if joins.empty?
296
+
297
+ @implicit_readonly = true
298
+
299
+ joins.map do |join|
300
+ case join
301
+ when Array
302
+ join = Arel.sql(join.join(' ')) if array_of_strings?(join)
303
+ when String
304
+ join = Arel.sql(join)
305
+ end
306
+ table.create_string_join(join)
307
+ end
308
+ end
309
+
310
+ def collapse_wheres(arel, wheres)
311
+ equalities = wheres.grep(Arel::Nodes::Equality)
312
+
313
+ arel.where(Arel::Nodes::And.new(equalities)) unless equalities.empty?
314
+
315
+ (wheres - equalities).each do |where|
316
+ where = Arel.sql(where) if String === where
317
+ arel.where(Arel::Nodes::Grouping.new(where))
318
+ end
319
+ end
320
+
321
+ def build_where(opts, other = [])
322
+ case opts
323
+ when String, Array
324
+ [@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
325
+ when Hash
326
+ attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
327
+ PredicateBuilder.build_from_hash(table.engine, attributes, table)
328
+ else
329
+ [opts]
330
+ end
331
+ end
332
+
333
+ def build_joins(manager, joins)
334
+ buckets = joins.group_by do |join|
335
+ case join
336
+ when String
337
+ 'string_join'
338
+ when Hash, Symbol, Array
339
+ 'association_join'
340
+ when ActiveRecord::Associations::JoinDependency::JoinAssociation
341
+ 'stashed_join'
342
+ when Arel::Nodes::Join
343
+ 'join_node'
344
+ else
345
+ raise 'unknown class: %s' % join.class.name
346
+ end
347
+ end
348
+
349
+ association_joins = buckets['association_join'] || []
350
+ stashed_association_joins = buckets['stashed_join'] || []
351
+ join_nodes = (buckets['join_node'] || []).uniq
352
+ string_joins = (buckets['string_join'] || []).map { |x|
353
+ x.strip
354
+ }.uniq
355
+
356
+ join_list = join_nodes + custom_join_ast(manager, string_joins)
357
+
358
+ join_dependency = ActiveRecord::Associations::JoinDependency.new(
359
+ @klass,
360
+ association_joins,
361
+ join_list
362
+ )
363
+
364
+ join_dependency.graft(*stashed_association_joins)
365
+
366
+ @implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty?
367
+
368
+ # FIXME: refactor this to build an AST
369
+ join_dependency.join_associations.each do |association|
370
+ association.join_to(manager)
371
+ end
372
+
373
+ manager.join_sources.concat join_list
374
+
375
+ manager
376
+ end
377
+
378
+ def build_select(arel, selects)
379
+ unless selects.empty?
380
+ @implicit_readonly = false
381
+ arel.project(*selects)
382
+ else
383
+ arel.project(@klass.arel_table[Arel.star])
384
+ end
385
+ end
386
+
387
+ def apply_modules(modules)
388
+ unless modules.empty?
389
+ @extensions += modules
390
+ modules.each {|extension| extend(extension) }
391
+ end
392
+ end
393
+
394
+ def reverse_sql_order(order_query)
395
+ order_query = ["#{quoted_table_name}.#{quoted_primary_key} ASC"] if order_query.empty?
396
+
397
+ order_query.map do |o|
398
+ case o
399
+ when Arel::Nodes::Ordering
400
+ o.reverse
401
+ when String, Symbol
402
+ o.to_s.split(',').collect do |s|
403
+ s.strip!
404
+ s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
405
+ end
406
+ else
407
+ o
408
+ end
409
+ end.flatten
410
+ end
411
+
412
+ def array_of_strings?(o)
413
+ o.is_a?(Array) && o.all?{|obj| obj.is_a?(String)}
414
+ end
415
+
416
+ end
417
+ end
@@ -0,0 +1,148 @@
1
+ require 'active_support/core_ext/object/blank'
2
+
3
+ module ActiveRecord
4
+ module SpawnMethods
5
+ def merge(r)
6
+ return self unless r
7
+ return to_a & r if r.is_a?(Array)
8
+
9
+ merged_relation = clone
10
+
11
+ r = r.with_default_scope if r.default_scoped? && r.klass != klass
12
+
13
+ Relation::ASSOCIATION_METHODS.each do |method|
14
+ value = r.send(:"#{method}_values")
15
+
16
+ unless value.empty?
17
+ if method == :includes
18
+ merged_relation = merged_relation.includes(value)
19
+ else
20
+ merged_relation.send(:"#{method}_values=", value)
21
+ end
22
+ end
23
+ end
24
+
25
+ (Relation::MULTI_VALUE_METHODS - [:joins, :where, :order]).each do |method|
26
+ value = r.send(:"#{method}_values")
27
+ merged_relation.send(:"#{method}_values=", merged_relation.send(:"#{method}_values") + value) if value.present?
28
+ end
29
+
30
+ merged_relation.joins_values += r.joins_values
31
+
32
+ merged_wheres = @where_values + r.where_values
33
+
34
+ unless @where_values.empty?
35
+ # Remove duplicates, last one wins.
36
+ seen = Hash.new { |h,table| h[table] = {} }
37
+ merged_wheres = merged_wheres.reverse.reject { |w|
38
+ nuke = false
39
+ if w.respond_to?(:operator) && w.operator == :==
40
+ name = w.left.name
41
+ table = w.left.relation.name
42
+ nuke = seen[table][name]
43
+ seen[table][name] = true
44
+ end
45
+ nuke
46
+ }.reverse
47
+ end
48
+
49
+ merged_relation.where_values = merged_wheres
50
+
51
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method|
52
+ value = r.send(:"#{method}_value")
53
+ merged_relation.send(:"#{method}_value=", value) unless value.nil?
54
+ end
55
+
56
+ merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
57
+
58
+ merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty?
59
+
60
+ if (r.reordering_value)
61
+ # override any order specified in the original relation
62
+ merged_relation.reordering_value = true
63
+ merged_relation.order_values = r.order_values
64
+ else
65
+ # merge in order_values from r
66
+ merged_relation.order_values += r.order_values
67
+ end
68
+
69
+ # Apply scope extension modules
70
+ merged_relation.send :apply_modules, r.extensions
71
+
72
+ merged_relation
73
+ end
74
+
75
+ # Removes from the query the condition(s) specified in +skips+.
76
+ #
77
+ # Example:
78
+ #
79
+ # Post.order('id asc').except(:order) # discards the order condition
80
+ # Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
81
+ #
82
+ def except(*skips)
83
+ result = self.class.new(@klass, table)
84
+ result.default_scoped = default_scoped
85
+
86
+ ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method|
87
+ result.send(:"#{method}_values=", send(:"#{method}_values"))
88
+ end
89
+
90
+ (Relation::SINGLE_VALUE_METHODS - skips).each do |method|
91
+ result.send(:"#{method}_value=", send(:"#{method}_value"))
92
+ end
93
+
94
+ # Apply scope extension modules
95
+ result.send(:apply_modules, extensions)
96
+
97
+ result
98
+ end
99
+
100
+ # Removes any condition from the query other than the one(s) specified in +onlies+.
101
+ #
102
+ # Example:
103
+ #
104
+ # Post.order('id asc').only(:where) # discards the order condition
105
+ # Post.order('id asc').only(:where, :order) # uses the specified order
106
+ #
107
+ def only(*onlies)
108
+ result = self.class.new(@klass, table)
109
+ result.default_scoped = default_scoped
110
+
111
+ ((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) & onlies).each do |method|
112
+ result.send(:"#{method}_values=", send(:"#{method}_values"))
113
+ end
114
+
115
+ (Relation::SINGLE_VALUE_METHODS & onlies).each do |method|
116
+ result.send(:"#{method}_value=", send(:"#{method}_value"))
117
+ end
118
+
119
+ # Apply scope extension modules
120
+ result.send(:apply_modules, extensions)
121
+
122
+ result
123
+ end
124
+
125
+ VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :extend,
126
+ :order, :select, :readonly, :group, :having, :from, :lock ]
127
+
128
+ def apply_finder_options(options)
129
+ relation = clone
130
+ return relation unless options
131
+
132
+ options.assert_valid_keys(VALID_FIND_OPTIONS)
133
+ finders = options.dup
134
+ finders.delete_if { |key, value| value.nil? && key != :limit }
135
+
136
+ ([:joins, :select, :group, :order, :having, :limit, :offset, :from, :lock, :readonly] & finders.keys).each do |finder|
137
+ relation = relation.send(finder, finders[finder])
138
+ end
139
+
140
+ relation = relation.where(finders[:conditions]) if options.has_key?(:conditions)
141
+ relation = relation.includes(finders[:include]) if options.has_key?(:include)
142
+ relation = relation.extending(finders[:extend]) if options.has_key?(:extend)
143
+
144
+ relation
145
+ end
146
+
147
+ end
148
+ end