activerecord 4.0.0 → 4.0.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +326 -3
- data/README.rdoc +1 -1
- data/lib/active_record/association_relation.rb +18 -0
- data/lib/active_record/associations/association.rb +11 -3
- data/lib/active_record/associations/builder/belongs_to.rb +4 -2
- data/lib/active_record/associations/collection_association.rb +8 -8
- data/lib/active_record/associations/collection_proxy.rb +2 -4
- data/lib/active_record/associations/has_many_association.rb +2 -2
- data/lib/active_record/associations/has_one_association.rb +3 -1
- data/lib/active_record/associations/join_dependency/join_association.rb +2 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +14 -1
- data/lib/active_record/associations/preloader.rb +3 -2
- data/lib/active_record/attribute_methods/read.rb +15 -9
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +1 -1
- data/lib/active_record/attribute_methods/write.rb +2 -0
- data/lib/active_record/attribute_methods.rb +26 -2
- data/lib/active_record/autosave_association.rb +8 -8
- data/lib/active_record/callbacks.rb +4 -1
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -7
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +12 -3
- data/lib/active_record/connection_adapters/abstract_adapter.rb +16 -2
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +3 -1
- data/lib/active_record/connection_adapters/column.rb +12 -11
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +9 -3
- data/lib/active_record/connection_adapters/mysql_adapter.rb +19 -9
- data/lib/active_record/connection_adapters/postgresql/cast.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +7 -6
- data/lib/active_record/connection_adapters/postgresql/oid.rb +5 -0
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +2 -0
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +7 -10
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +19 -13
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +8 -5
- data/lib/active_record/core.rb +18 -18
- data/lib/active_record/dynamic_matchers.rb +1 -1
- data/lib/active_record/fixtures.rb +2 -2
- data/lib/active_record/locking/optimistic.rb +1 -1
- data/lib/active_record/migration/command_recorder.rb +4 -1
- data/lib/active_record/migration.rb +1 -1
- data/lib/active_record/model_schema.rb +27 -17
- data/lib/active_record/null_relation.rb +1 -5
- data/lib/active_record/persistence.rb +16 -5
- data/lib/active_record/railtie.rb +1 -0
- data/lib/active_record/railties/databases.rake +4 -1
- data/lib/active_record/reflection.rb +1 -1
- data/lib/active_record/relation/batches.rb +2 -2
- data/lib/active_record/relation/calculations.rb +1 -0
- data/lib/active_record/relation/finder_methods.rb +10 -10
- data/lib/active_record/relation/merger.rb +31 -28
- data/lib/active_record/relation/query_methods.rb +20 -11
- data/lib/active_record/relation.rb +1 -1
- data/lib/active_record/result.rb +15 -1
- data/lib/active_record/sanitization.rb +13 -3
- data/lib/active_record/schema_dumper.rb +5 -1
- data/lib/active_record/tasks/database_tasks.rb +2 -1
- data/lib/active_record/tasks/sqlite_database_tasks.rb +1 -1
- data/lib/active_record/transactions.rb +5 -5
- data/lib/active_record/validations/uniqueness.rb +2 -2
- data/lib/active_record/version.rb +1 -1
- data/lib/active_record.rb +1 -0
- metadata +8 -7
data/lib/active_record/core.rb
CHANGED
@@ -91,20 +91,8 @@ module ActiveRecord
|
|
91
91
|
end
|
92
92
|
|
93
93
|
module ClassMethods
|
94
|
-
def inherited(child_class) #:nodoc:
|
95
|
-
child_class.initialize_generated_modules
|
96
|
-
super
|
97
|
-
end
|
98
|
-
|
99
94
|
def initialize_generated_modules
|
100
|
-
|
101
|
-
|
102
|
-
# force attribute methods to be higher in inheritance hierarchy than other generated methods
|
103
|
-
generated_attribute_methods.const_set(:AttrNames, Module.new {
|
104
|
-
def self.const_missing(name)
|
105
|
-
const_set(name, [name.to_s.sub(/ATTR_/, '')].pack('h*').freeze)
|
106
|
-
end
|
107
|
-
})
|
95
|
+
super
|
108
96
|
|
109
97
|
generated_feature_methods
|
110
98
|
end
|
@@ -123,6 +111,8 @@ module ActiveRecord
|
|
123
111
|
super
|
124
112
|
elsif abstract_class?
|
125
113
|
"#{super}(abstract)"
|
114
|
+
elsif !connected?
|
115
|
+
"#{super}(no database connection)"
|
126
116
|
elsif table_exists?
|
127
117
|
attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
|
128
118
|
"#{super}(#{attr_list})"
|
@@ -153,7 +143,7 @@ module ActiveRecord
|
|
153
143
|
else
|
154
144
|
superclass.arel_engine
|
155
145
|
end
|
156
|
-
|
146
|
+
end
|
157
147
|
end
|
158
148
|
|
159
149
|
private
|
@@ -177,19 +167,22 @@ module ActiveRecord
|
|
177
167
|
# ==== Example:
|
178
168
|
# # Instantiates a single new object
|
179
169
|
# User.new(first_name: 'Jamie')
|
180
|
-
def initialize(attributes = nil)
|
170
|
+
def initialize(attributes = nil, options = {})
|
181
171
|
defaults = self.class.column_defaults.dup
|
182
172
|
defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
|
183
173
|
|
184
174
|
@attributes = self.class.initialize_attributes(defaults)
|
185
|
-
@
|
175
|
+
@column_types_override = nil
|
176
|
+
@column_types = self.class.column_types
|
186
177
|
|
187
178
|
init_internals
|
188
179
|
init_changed_attributes
|
189
180
|
ensure_proper_type
|
190
181
|
populate_with_current_scope_attributes
|
191
182
|
|
192
|
-
|
183
|
+
# +options+ argument is only needed to make protected_attributes gem easier to hook.
|
184
|
+
# Remove it when we drop support to this gem.
|
185
|
+
init_attributes(attributes, options) if attributes
|
193
186
|
|
194
187
|
yield self if block_given?
|
195
188
|
run_callbacks :initialize unless _initialize_callbacks.empty?
|
@@ -207,7 +200,8 @@ module ActiveRecord
|
|
207
200
|
# post.title # => 'hello world'
|
208
201
|
def init_with(coder)
|
209
202
|
@attributes = self.class.initialize_attributes(coder['attributes'])
|
210
|
-
@
|
203
|
+
@column_types_override = coder['column_types']
|
204
|
+
@column_types = self.class.column_types
|
211
205
|
|
212
206
|
init_internals
|
213
207
|
|
@@ -459,5 +453,11 @@ module ActiveRecord
|
|
459
453
|
@changed_attributes[attr] = orig_value if _field_changed?(attr, orig_value, @attributes[attr])
|
460
454
|
end
|
461
455
|
end
|
456
|
+
|
457
|
+
# This method is needed to make protected_attributes gem easier to hook.
|
458
|
+
# Remove it when we drop support to this gem.
|
459
|
+
def init_attributes(attributes, options)
|
460
|
+
assign_attributes(attributes)
|
461
|
+
end
|
462
462
|
end
|
463
463
|
end
|
@@ -639,7 +639,7 @@ module ActiveRecord
|
|
639
639
|
end
|
640
640
|
|
641
641
|
def read_fixture_files
|
642
|
-
yaml_files = Dir["#{@path}
|
642
|
+
yaml_files = Dir["#{@path}/{**,*}/*.yml"].select { |f|
|
643
643
|
::File.file?(f)
|
644
644
|
} + [yaml_file_path]
|
645
645
|
|
@@ -758,7 +758,7 @@ module ActiveRecord
|
|
758
758
|
|
759
759
|
def fixtures(*fixture_set_names)
|
760
760
|
if fixture_set_names.first == :all
|
761
|
-
fixture_set_names = Dir["#{fixture_path}
|
761
|
+
fixture_set_names = Dir["#{fixture_path}/{**,*}/*.{yml}"]
|
762
762
|
fixture_set_names.map! { |f| f[(fixture_path.to_s.size + 1)..-5] }
|
763
763
|
else
|
764
764
|
fixture_set_names = fixture_set_names.flatten.map { |n| n.to_s }
|
@@ -82,7 +82,7 @@ module ActiveRecord
|
|
82
82
|
|
83
83
|
stmt = relation.where(
|
84
84
|
relation.table[self.class.primary_key].eq(id).and(
|
85
|
-
relation.table[lock_col].eq(self.class.quote_value(previous_lock_value))
|
85
|
+
relation.table[lock_col].eq(self.class.quote_value(previous_lock_value, column_for_attribute(lock_col)))
|
86
86
|
)
|
87
87
|
).arel.compile_update(arel_attributes_with_values_for_update(attribute_names))
|
88
88
|
|
@@ -144,7 +144,10 @@ module ActiveRecord
|
|
144
144
|
|
145
145
|
def invert_remove_index(args)
|
146
146
|
table, options = *args
|
147
|
-
|
147
|
+
|
148
|
+
unless options && options.is_a?(Hash) && options[:column]
|
149
|
+
raise ActiveRecord::IrreversibleMigration, "remove_index is only reversible if given a :column option."
|
150
|
+
end
|
148
151
|
|
149
152
|
options = options.dup
|
150
153
|
[:add_index, [table, options.delete(:column), options]]
|
@@ -32,7 +32,7 @@ module ActiveRecord
|
|
32
32
|
|
33
33
|
class PendingMigrationError < ActiveRecordError#:nodoc:
|
34
34
|
def initialize
|
35
|
-
super("Migrations are pending; run 'rake db:migrate RAILS_ENV=#{Rails.env}' to resolve this issue.")
|
35
|
+
super("Migrations are pending; run 'bin/rake db:migrate RAILS_ENV=#{Rails.env}' to resolve this issue.")
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
@@ -224,13 +224,20 @@ module ActiveRecord
|
|
224
224
|
def decorate_columns(columns_hash) # :nodoc:
|
225
225
|
return if columns_hash.empty?
|
226
226
|
|
227
|
-
columns_hash.
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
227
|
+
@serialized_column_names ||= self.columns_hash.keys.find_all do |name|
|
228
|
+
serialized_attributes.key?(name)
|
229
|
+
end
|
230
|
+
|
231
|
+
@serialized_column_names.each do |name|
|
232
|
+
columns_hash[name] = AttributeMethods::Serialization::Type.new(columns_hash[name])
|
233
|
+
end
|
234
|
+
|
235
|
+
@time_zone_column_names ||= self.columns_hash.find_all do |name, col|
|
236
|
+
create_time_zone_conversion_attribute?(name, col)
|
237
|
+
end.map!(&:first)
|
238
|
+
|
239
|
+
@time_zone_column_names.each do |name|
|
240
|
+
columns_hash[name] = AttributeMethods::TimeZoneConversion::Type.new(columns_hash[name])
|
234
241
|
end
|
235
242
|
|
236
243
|
columns_hash
|
@@ -297,16 +304,19 @@ module ActiveRecord
|
|
297
304
|
undefine_attribute_methods
|
298
305
|
connection.schema_cache.clear_table_cache!(table_name) if table_exists?
|
299
306
|
|
300
|
-
@arel_engine
|
301
|
-
@column_defaults
|
302
|
-
@column_names
|
303
|
-
@columns
|
304
|
-
@columns_hash
|
305
|
-
@column_types
|
306
|
-
@content_columns
|
307
|
-
@dynamic_methods_hash
|
308
|
-
@inheritance_column
|
309
|
-
@relation
|
307
|
+
@arel_engine = nil
|
308
|
+
@column_defaults = nil
|
309
|
+
@column_names = nil
|
310
|
+
@columns = nil
|
311
|
+
@columns_hash = nil
|
312
|
+
@column_types = nil
|
313
|
+
@content_columns = nil
|
314
|
+
@dynamic_methods_hash = nil
|
315
|
+
@inheritance_column = nil unless defined?(@explicit_inheritance_column) && @explicit_inheritance_column
|
316
|
+
@relation = nil
|
317
|
+
@serialized_column_names = nil
|
318
|
+
@time_zone_column_names = nil
|
319
|
+
@cached_time_zone = nil
|
310
320
|
end
|
311
321
|
|
312
322
|
# This is a hook for use by modules that need to do extra stuff to
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
6
6
|
@records = []
|
7
7
|
end
|
8
8
|
|
9
|
-
def pluck(
|
9
|
+
def pluck(*column_names)
|
10
10
|
[]
|
11
11
|
end
|
12
12
|
|
@@ -42,10 +42,6 @@ module ActiveRecord
|
|
42
42
|
@to_sql ||= ""
|
43
43
|
end
|
44
44
|
|
45
|
-
def where_values_hash
|
46
|
-
{}
|
47
|
-
end
|
48
|
-
|
49
45
|
def count(*)
|
50
46
|
0
|
51
47
|
end
|
@@ -335,8 +335,17 @@ module ActiveRecord
|
|
335
335
|
|
336
336
|
# Reloads the record from the database.
|
337
337
|
#
|
338
|
-
# This method
|
339
|
-
#
|
338
|
+
# This method finds record by its primary key (which could be assigned manually) and
|
339
|
+
# modifies the receiver in-place:
|
340
|
+
#
|
341
|
+
# account = Account.new
|
342
|
+
# # => #<Account id: nil, email: nil>
|
343
|
+
# account.id = 1
|
344
|
+
# account.reload
|
345
|
+
# # Account Load (1.2ms) SELECT "accounts".* FROM "accounts" WHERE "accounts"."id" = $1 LIMIT 1 [["id", 1]]
|
346
|
+
# # => #<Account id: 1, email: 'account@example.com'>
|
347
|
+
#
|
348
|
+
# Attributes are updated, and caches busted, in particular the associations cache.
|
340
349
|
#
|
341
350
|
# If the record no longer exists in the database <tt>ActiveRecord::RecordNotFound</tt>
|
342
351
|
# is raised. Otherwise, in addition to the in-place modification the method
|
@@ -383,14 +392,16 @@ module ActiveRecord
|
|
383
392
|
end
|
384
393
|
|
385
394
|
@attributes.update(fresh_object.instance_variable_get('@attributes'))
|
386
|
-
@columns_hash = fresh_object.instance_variable_get('@columns_hash')
|
387
395
|
|
388
|
-
@
|
396
|
+
@column_types = self.class.column_types
|
397
|
+
@column_types_override = fresh_object.instance_variable_get('@column_types_override')
|
398
|
+
@attributes_cache = {}
|
389
399
|
self
|
390
400
|
end
|
391
401
|
|
392
402
|
# Saves the record with the updated_at/on attributes set to the current time.
|
393
|
-
# Please note that no validation is performed and
|
403
|
+
# Please note that no validation is performed and only the +after_touch+
|
404
|
+
# callback is executed.
|
394
405
|
# If an attribute name is passed, that attribute is updated along with
|
395
406
|
# updated_at/on attributes.
|
396
407
|
#
|
@@ -46,6 +46,7 @@ module ActiveRecord
|
|
46
46
|
ActiveRecord::Tasks::DatabaseTasks.database_configuration = Rails.application.config.database_configuration
|
47
47
|
ActiveRecord::Tasks::DatabaseTasks.migrations_paths = Rails.application.paths['db/migrate'].to_a
|
48
48
|
ActiveRecord::Tasks::DatabaseTasks.fixtures_path = File.join Rails.root, 'test', 'fixtures'
|
49
|
+
ActiveRecord::Tasks::DatabaseTasks.root = Rails.root
|
49
50
|
|
50
51
|
if defined?(ENGINE_PATH) && engine = Rails::Engine.find(ENGINE_PATH)
|
51
52
|
if engine.paths['db/migrate'].existent
|
@@ -322,11 +322,14 @@ db_namespace = namespace :db do
|
|
322
322
|
# desc "Recreate the test database from an existent schema.rb file"
|
323
323
|
task :load_schema => 'db:test:purge' do
|
324
324
|
begin
|
325
|
+
should_reconnect = ActiveRecord::Base.connection_pool.active_connection?
|
325
326
|
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
|
326
327
|
ActiveRecord::Schema.verbose = false
|
327
328
|
db_namespace["schema:load"].invoke
|
328
329
|
ensure
|
329
|
-
|
330
|
+
if should_reconnect
|
331
|
+
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations[ActiveRecord::Tasks::DatabaseTasks.env])
|
332
|
+
end
|
330
333
|
end
|
331
334
|
end
|
332
335
|
|
@@ -486,7 +486,7 @@ module ActiveRecord
|
|
486
486
|
# Add to it the scope from this reflection (if any)
|
487
487
|
scope_chain.first << scope if scope
|
488
488
|
|
489
|
-
through_scope_chain = through_reflection.scope_chain
|
489
|
+
through_scope_chain = through_reflection.scope_chain.map(&:dup)
|
490
490
|
|
491
491
|
if options[:source_type]
|
492
492
|
through_scope_chain.first <<
|
@@ -58,8 +58,8 @@ module ActiveRecord
|
|
58
58
|
|
59
59
|
relation = self
|
60
60
|
|
61
|
-
|
62
|
-
|
61
|
+
if logger && (arel.orders.present? || arel.taken.present?)
|
62
|
+
logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
|
63
63
|
end
|
64
64
|
|
65
65
|
start = options.delete(:start)
|
@@ -130,21 +130,21 @@ module ActiveRecord
|
|
130
130
|
last or raise RecordNotFound
|
131
131
|
end
|
132
132
|
|
133
|
-
# Returns
|
134
|
-
# conditions given, or
|
133
|
+
# Returns +true+ if a record exists in the table that matches the +id+ or
|
134
|
+
# conditions given, or +false+ otherwise. The argument can take six forms:
|
135
135
|
#
|
136
136
|
# * Integer - Finds the record with this primary key.
|
137
137
|
# * String - Finds the record with a primary key corresponding to this
|
138
138
|
# string (such as <tt>'5'</tt>).
|
139
139
|
# * Array - Finds the record that matches these +find+-style conditions
|
140
|
-
# (such as <tt>['
|
140
|
+
# (such as <tt>['name LIKE ?', "%#{query}%"]</tt>).
|
141
141
|
# * Hash - Finds the record that matches these +find+-style conditions
|
142
|
-
# (such as <tt>{
|
142
|
+
# (such as <tt>{name: 'David'}</tt>).
|
143
143
|
# * +false+ - Returns always +false+.
|
144
144
|
# * No args - Returns +false+ if the table is empty, +true+ otherwise.
|
145
145
|
#
|
146
|
-
# For more information about specifying conditions as a
|
147
|
-
# see the Conditions section in the introduction to ActiveRecord::Base
|
146
|
+
# For more information about specifying conditions as a hash or array,
|
147
|
+
# see the Conditions section in the introduction to <tt>ActiveRecord::Base</tt>.
|
148
148
|
#
|
149
149
|
# Note: You can't pass in a condition as a string (like <tt>name =
|
150
150
|
# 'Jamie'</tt>), since it would be sanitized and then queried against
|
@@ -171,7 +171,7 @@ module ActiveRecord
|
|
171
171
|
relation = relation.where(table[primary_key].eq(conditions)) if conditions != :none
|
172
172
|
end
|
173
173
|
|
174
|
-
connection.select_value(relation, "#{name} Exists", relation.bind_values)
|
174
|
+
connection.select_value(relation, "#{name} Exists", relation.bind_values) ? true : false
|
175
175
|
rescue ThrowResult
|
176
176
|
false
|
177
177
|
end
|
@@ -222,7 +222,7 @@ module ActiveRecord
|
|
222
222
|
end
|
223
223
|
|
224
224
|
def construct_relation_for_association_find(join_dependency)
|
225
|
-
relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns)
|
225
|
+
relation = except(:includes, :eager_load, :preload, :select).select(join_dependency.columns + select_values)
|
226
226
|
apply_join_dependency(relation, join_dependency)
|
227
227
|
end
|
228
228
|
|
@@ -245,9 +245,9 @@ module ActiveRecord
|
|
245
245
|
|
246
246
|
def construct_limited_ids_condition(relation)
|
247
247
|
orders = relation.order_values.map { |val| val.presence }.compact
|
248
|
-
values = @klass.connection.
|
248
|
+
values = @klass.connection.columns_for_distinct("#{quoted_table_name}.#{quoted_primary_key}", orders)
|
249
249
|
|
250
|
-
relation = relation.dup.select(values)
|
250
|
+
relation = relation.dup.select(values).distinct!
|
251
251
|
|
252
252
|
id_rows = @klass.connection.select_all(relation.arel, 'SQL', relation.bind_values)
|
253
253
|
ids_array = id_rows.map {|row| row[primary_key]}
|
@@ -62,7 +62,11 @@ module ActiveRecord
|
|
62
62
|
def merge
|
63
63
|
normal_values.each do |name|
|
64
64
|
value = values[name]
|
65
|
-
|
65
|
+
# The unless clause is here mostly for performance reasons (since the `send` call might be moderately
|
66
|
+
# expensive), most of the time the value is going to be `nil` or `.blank?`, the only catch is that
|
67
|
+
# `false.blank?` returns `true`, so there needs to be an extra check so that explicit `false` values
|
68
|
+
# don't fall through the cracks.
|
69
|
+
relation.send("#{name}!", *value) unless value.nil? || (value.blank? && false != value)
|
66
70
|
end
|
67
71
|
|
68
72
|
merge_multi_values
|
@@ -101,8 +105,15 @@ module ActiveRecord
|
|
101
105
|
end
|
102
106
|
|
103
107
|
def merge_multi_values
|
104
|
-
relation.where_values
|
105
|
-
|
108
|
+
lhs_wheres = relation.where_values
|
109
|
+
rhs_wheres = values[:where] || []
|
110
|
+
lhs_binds = relation.bind_values
|
111
|
+
rhs_binds = values[:bind] || []
|
112
|
+
|
113
|
+
removed, kept = partition_overwrites(lhs_wheres, rhs_wheres)
|
114
|
+
|
115
|
+
relation.where_values = kept + rhs_wheres
|
116
|
+
relation.bind_values = filter_binds(lhs_binds, removed) + rhs_binds
|
106
117
|
|
107
118
|
if values[:reordering]
|
108
119
|
# override any order specified in the original relation
|
@@ -125,37 +136,29 @@ module ActiveRecord
|
|
125
136
|
end
|
126
137
|
end
|
127
138
|
|
128
|
-
def
|
129
|
-
|
130
|
-
|
131
|
-
else
|
132
|
-
relation.bind_values
|
133
|
-
end
|
139
|
+
def filter_binds(lhs_binds, removed_wheres)
|
140
|
+
set = Set.new removed_wheres.map { |x| x.left.name }
|
141
|
+
lhs_binds.dup.delete_if { |col,_| set.include? col.name }
|
134
142
|
end
|
135
143
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
# present in the relation being merged in.
|
144
|
+
# Remove equalities from the existing relation with a LHS which is
|
145
|
+
# present in the relation being merged in.
|
146
|
+
# returns [things_to_remove, things_to_keep]
|
147
|
+
def partition_overwrites(lhs_wheres, rhs_wheres)
|
148
|
+
if lhs_wheres.empty? || rhs_wheres.empty?
|
149
|
+
return [[], lhs_wheres]
|
150
|
+
end
|
144
151
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
end
|
150
|
-
}
|
152
|
+
nodes = rhs_wheres.find_all do |w|
|
153
|
+
w.respond_to?(:operator) && w.operator == :==
|
154
|
+
end
|
155
|
+
seen = Set.new(nodes) { |node| node.left }
|
151
156
|
|
152
|
-
|
153
|
-
|
154
|
-
w.operator == :== &&
|
155
|
-
seen.include?(w.left)
|
156
|
-
} + values[:where]
|
157
|
+
lhs_wheres.partition do |w|
|
158
|
+
w.respond_to?(:operator) && w.operator == :== && seen.include?(w.left)
|
157
159
|
end
|
158
160
|
end
|
161
|
+
|
159
162
|
end
|
160
163
|
end
|
161
164
|
end
|
@@ -285,11 +285,13 @@ module ActiveRecord
|
|
285
285
|
references!(references) if references.any?
|
286
286
|
|
287
287
|
# if a symbol is given we prepend the quoted table name
|
288
|
-
args = args.map { |arg|
|
289
|
-
arg.is_a?(Symbol) ?
|
288
|
+
args = args.map! { |arg|
|
289
|
+
arg.is_a?(Symbol) ?
|
290
|
+
Arel::Nodes::Ascending.new(klass.arel_table[arg]) :
|
291
|
+
arg
|
290
292
|
}
|
291
293
|
|
292
|
-
self.order_values
|
294
|
+
self.order_values += args
|
293
295
|
self
|
294
296
|
end
|
295
297
|
|
@@ -301,7 +303,7 @@ module ActiveRecord
|
|
301
303
|
#
|
302
304
|
# User.order('email DESC').reorder('id ASC').order('name ASC')
|
303
305
|
#
|
304
|
-
# generates a query with 'ORDER BY
|
306
|
+
# generates a query with 'ORDER BY id ASC, name ASC'.
|
305
307
|
def reorder(*args)
|
306
308
|
check_if_method_has_arguments!("reorder", args)
|
307
309
|
spawn.reorder!(*args)
|
@@ -799,7 +801,7 @@ module ActiveRecord
|
|
799
801
|
def build_arel
|
800
802
|
arel = Arel::SelectManager.new(table.engine, table)
|
801
803
|
|
802
|
-
build_joins(arel, joins_values) unless joins_values.empty?
|
804
|
+
build_joins(arel, joins_values.flatten) unless joins_values.empty?
|
803
805
|
|
804
806
|
collapse_wheres(arel, (where_values - ['']).uniq)
|
805
807
|
|
@@ -875,19 +877,25 @@ module ActiveRecord
|
|
875
877
|
end
|
876
878
|
|
877
879
|
def collapse_wheres(arel, wheres)
|
878
|
-
|
879
|
-
|
880
|
-
arel.where(Arel::Nodes::And.new(equalities)) unless equalities.empty?
|
881
|
-
|
882
|
-
(wheres - equalities).each do |where|
|
880
|
+
predicates = wheres.map do |where|
|
881
|
+
next where if ::Arel::Nodes::Equality === where
|
883
882
|
where = Arel.sql(where) if String === where
|
884
|
-
|
883
|
+
Arel::Nodes::Grouping.new(where)
|
885
884
|
end
|
885
|
+
|
886
|
+
arel.where(Arel::Nodes::And.new(predicates)) if predicates.present?
|
886
887
|
end
|
887
888
|
|
888
889
|
def build_where(opts, other = [])
|
889
890
|
case opts
|
890
891
|
when String, Array
|
892
|
+
#TODO: Remove duplication with: /activerecord/lib/active_record/sanitization.rb:113
|
893
|
+
values = Hash === other.first ? other.first.values : other
|
894
|
+
|
895
|
+
values.grep(ActiveRecord::Relation) do |rel|
|
896
|
+
self.bind_values += rel.bind_values
|
897
|
+
end
|
898
|
+
|
891
899
|
[@klass.send(:sanitize_sql, other.empty? ? opts : ([opts] + other))]
|
892
900
|
when Hash
|
893
901
|
attributes = @klass.send(:expand_hash_conditions_for_aggregates, opts)
|
@@ -907,6 +915,7 @@ module ActiveRecord
|
|
907
915
|
case opts
|
908
916
|
when Relation
|
909
917
|
name ||= 'subquery'
|
918
|
+
self.bind_values = opts.bind_values + self.bind_values
|
910
919
|
opts.arel.as(name.to_s)
|
911
920
|
else
|
912
921
|
opts
|
@@ -17,7 +17,7 @@ module ActiveRecord
|
|
17
17
|
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
|
18
18
|
|
19
19
|
attr_reader :table, :klass, :loaded
|
20
|
-
attr_accessor :default_scoped
|
20
|
+
attr_accessor :default_scoped
|
21
21
|
alias :model :klass
|
22
22
|
alias :loaded? :loaded
|
23
23
|
alias :default_scoped? :default_scoped
|
data/lib/active_record/result.rb
CHANGED
@@ -59,7 +59,21 @@ module ActiveRecord
|
|
59
59
|
# used as keys in ActiveRecord::Base's @attributes hash
|
60
60
|
columns = @columns.map { |c| c.dup.freeze }
|
61
61
|
@rows.map { |row|
|
62
|
-
Hash[columns.zip(row)]
|
62
|
+
# In the past we used Hash[columns.zip(row)]
|
63
|
+
# though elegant, the verbose way is much more efficient
|
64
|
+
# both time and memory wise cause it avoids a big array allocation
|
65
|
+
# this method is called a lot and needs to be micro optimised
|
66
|
+
hash = {}
|
67
|
+
|
68
|
+
index = 0
|
69
|
+
length = columns.length
|
70
|
+
|
71
|
+
while index < length
|
72
|
+
hash[columns[index]] = row[index]
|
73
|
+
index += 1
|
74
|
+
end
|
75
|
+
|
76
|
+
hash
|
63
77
|
}
|
64
78
|
end
|
65
79
|
end
|
@@ -89,7 +89,7 @@ module ActiveRecord
|
|
89
89
|
attrs = expand_hash_conditions_for_aggregates(attrs)
|
90
90
|
|
91
91
|
table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
|
92
|
-
PredicateBuilder.build_from_hash(self
|
92
|
+
PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
|
93
93
|
connection.visitor.accept b
|
94
94
|
}.join(' AND ')
|
95
95
|
end
|
@@ -126,7 +126,17 @@ module ActiveRecord
|
|
126
126
|
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
|
127
127
|
bound = values.dup
|
128
128
|
c = connection
|
129
|
-
statement.gsub('?')
|
129
|
+
statement.gsub('?') do
|
130
|
+
replace_bind_variable(bound.shift, c)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def replace_bind_variable(value, c = connection) #:nodoc:
|
135
|
+
if ActiveRecord::Relation === value
|
136
|
+
value.to_sql
|
137
|
+
else
|
138
|
+
quote_bound_value(value, c)
|
139
|
+
end
|
130
140
|
end
|
131
141
|
|
132
142
|
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
|
@@ -134,7 +144,7 @@ module ActiveRecord
|
|
134
144
|
if $1 == ':' # skip postgresql casts
|
135
145
|
$& # return the whole match
|
136
146
|
elsif bind_vars.include?(match = $2.to_sym)
|
137
|
-
|
147
|
+
replace_bind_variable(bind_vars[match])
|
138
148
|
else
|
139
149
|
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
140
150
|
end
|
@@ -106,9 +106,13 @@ HEADER
|
|
106
106
|
end
|
107
107
|
|
108
108
|
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
109
|
-
|
109
|
+
pkcol = columns.detect { |c| c.name == pk }
|
110
|
+
if pkcol
|
110
111
|
if pk != 'id'
|
111
112
|
tbl.print %Q(, primary_key: "#{pk}")
|
113
|
+
elsif pkcol.sql_type == 'uuid'
|
114
|
+
tbl.print ", id: :uuid"
|
115
|
+
tbl.print %Q(, default: "#{pkcol.default_function}") if pkcol.default_function
|
112
116
|
end
|
113
117
|
else
|
114
118
|
tbl.print ", id: false"
|
@@ -23,6 +23,7 @@ module ActiveRecord
|
|
23
23
|
# * +fixtures_path+: a path to fixtures directory.
|
24
24
|
# * +migrations_paths+: a list of paths to directories with migrations.
|
25
25
|
# * +seed_loader+: an object which will load seeds, it needs to respond to the +load_seed+ method.
|
26
|
+
# * +root+: a path to the root of the application.
|
26
27
|
#
|
27
28
|
# Example usage of +DatabaseTasks+ outside Rails could look as such:
|
28
29
|
#
|
@@ -37,7 +38,7 @@ module ActiveRecord
|
|
37
38
|
|
38
39
|
attr_writer :current_config
|
39
40
|
attr_accessor :database_configuration, :migrations_paths, :seed_loader, :db_dir,
|
40
|
-
:fixtures_path, :env
|
41
|
+
:fixtures_path, :env, :root
|
41
42
|
|
42
43
|
LOCAL_HOSTS = ['127.0.0.1', 'localhost']
|
43
44
|
|
@@ -3,7 +3,7 @@ module ActiveRecord
|
|
3
3
|
class SQLiteDatabaseTasks # :nodoc:
|
4
4
|
delegate :connection, :establish_connection, to: ActiveRecord::Base
|
5
5
|
|
6
|
-
def initialize(configuration, root =
|
6
|
+
def initialize(configuration, root = ActiveRecord::Tasks::DatabaseTasks.root)
|
7
7
|
@configuration, @root = configuration, root
|
8
8
|
end
|
9
9
|
|