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.
- data/CHANGELOG.md +6749 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +177 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +147 -0
- data/lib/active_record/aggregations.rb +255 -0
- data/lib/active_record/associations.rb +1604 -0
- data/lib/active_record/associations/alias_tracker.rb +79 -0
- data/lib/active_record/associations/association.rb +239 -0
- data/lib/active_record/associations/association_scope.rb +119 -0
- data/lib/active_record/associations/belongs_to_association.rb +79 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
- data/lib/active_record/associations/builder/association.rb +55 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
- data/lib/active_record/associations/builder/has_many.rb +71 -0
- data/lib/active_record/associations/builder/has_one.rb +62 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +574 -0
- data/lib/active_record/associations/collection_proxy.rb +132 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
- data/lib/active_record/associations/has_many_association.rb +108 -0
- data/lib/active_record/associations/has_many_through_association.rb +180 -0
- data/lib/active_record/associations/has_one_association.rb +73 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +214 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_helper.rb +55 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +127 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +67 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +83 -0
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +272 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +101 -0
- data/lib/active_record/attribute_methods/primary_key.rb +114 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +135 -0
- data/lib/active_record/attribute_methods/serialization.rb +93 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
- data/lib/active_record/attribute_methods/write.rb +69 -0
- data/lib/active_record/autosave_association.rb +422 -0
- data/lib/active_record/base.rb +716 -0
- data/lib/active_record/callbacks.rb +275 -0
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/column.rb +270 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/counter_cache.rb +119 -0
- data/lib/active_record/dynamic_finder_match.rb +56 -0
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/dynamic_scope_match.rb +23 -0
- data/lib/active_record/errors.rb +195 -0
- data/lib/active_record/explain.rb +85 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +906 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +156 -0
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +183 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +68 -0
- data/lib/active_record/migration.rb +765 -0
- data/lib/active_record/migration/command_recorder.rb +105 -0
- data/lib/active_record/model_schema.rb +366 -0
- data/lib/active_record/nested_attributes.rb +469 -0
- data/lib/active_record/observer.rb +121 -0
- data/lib/active_record/persistence.rb +372 -0
- data/lib/active_record/query_cache.rb +74 -0
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +119 -0
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/controller_runtime.rb +49 -0
- data/lib/active_record/railties/databases.rake +620 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +534 -0
- data/lib/active_record/relation.rb +534 -0
- data/lib/active_record/relation/batches.rb +90 -0
- data/lib/active_record/relation/calculations.rb +354 -0
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +398 -0
- data/lib/active_record/relation/predicate_builder.rb +58 -0
- data/lib/active_record/relation/query_methods.rb +417 -0
- data/lib/active_record/relation/spawn_methods.rb +148 -0
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +18 -0
- data/lib/active_record/serializers/xml_serializer.rb +202 -0
- data/lib/active_record/session_store.rb +358 -0
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +73 -0
- data/lib/active_record/timestamp.rb +113 -0
- data/lib/active_record/transactions.rb +360 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +83 -0
- data/lib/active_record/validations/associated.rb +43 -0
- data/lib/active_record/validations/uniqueness.rb +180 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +25 -0
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
- 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
|