activerecord 3.1.12 → 3.2.22.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 +804 -338
- data/README.rdoc +3 -3
- data/examples/performance.rb +20 -1
- data/lib/active_record/aggregations.rb +1 -1
- data/lib/active_record/associations/alias_tracker.rb +3 -6
- data/lib/active_record/associations/association.rb +13 -45
- data/lib/active_record/associations/association_scope.rb +3 -15
- data/lib/active_record/associations/belongs_to_association.rb +1 -1
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +2 -1
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +7 -4
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +5 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -16
- data/lib/active_record/associations/collection_association.rb +65 -32
- data/lib/active_record/associations/collection_proxy.rb +8 -41
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +1 -0
- data/lib/active_record/associations/has_many_association.rb +11 -7
- data/lib/active_record/associations/has_many_through_association.rb +19 -9
- data/lib/active_record/associations/has_one_association.rb +23 -13
- data/lib/active_record/associations/join_dependency/join_association.rb +6 -1
- data/lib/active_record/associations/join_dependency.rb +3 -3
- data/lib/active_record/associations/preloader/through_association.rb +3 -3
- data/lib/active_record/associations/preloader.rb +14 -10
- data/lib/active_record/associations/through_association.rb +8 -4
- data/lib/active_record/associations.rb +92 -76
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +21 -11
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +73 -83
- data/lib/active_record/attribute_methods/serialization.rb +120 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +12 -14
- data/lib/active_record/attribute_methods/write.rb +32 -6
- data/lib/active_record/attribute_methods.rb +231 -30
- data/lib/active_record/autosave_association.rb +44 -26
- data/lib/active_record/base.rb +227 -1708
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +150 -148
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +85 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +7 -34
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +10 -2
- data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +39 -28
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +48 -19
- data/lib/active_record/connection_adapters/abstract_adapter.rb +77 -42
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +676 -0
- data/lib/active_record/connection_adapters/column.rb +37 -11
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +133 -581
- data/lib/active_record/connection_adapters/mysql_adapter.rb +136 -693
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +209 -97
- data/lib/active_record/connection_adapters/schema_cache.rb +69 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +62 -35
- data/lib/active_record/counter_cache.rb +9 -4
- data/lib/active_record/dynamic_finder_match.rb +12 -0
- data/lib/active_record/dynamic_matchers.rb +84 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +86 -0
- data/lib/active_record/explain_subscriber.rb +25 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/fixtures.rb +57 -86
- data/lib/active_record/identity_map.rb +3 -4
- data/lib/active_record/inheritance.rb +174 -0
- data/lib/active_record/integration.rb +60 -0
- data/lib/active_record/locking/optimistic.rb +33 -26
- data/lib/active_record/locking/pessimistic.rb +23 -1
- data/lib/active_record/log_subscriber.rb +8 -4
- data/lib/active_record/migration/command_recorder.rb +8 -8
- data/lib/active_record/migration.rb +68 -35
- data/lib/active_record/model_schema.rb +368 -0
- data/lib/active_record/nested_attributes.rb +60 -24
- data/lib/active_record/persistence.rb +57 -11
- data/lib/active_record/query_cache.rb +6 -6
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +37 -29
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +213 -117
- data/lib/active_record/railties/jdbcmysql_error.rb +1 -1
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation/batches.rb +7 -4
- data/lib/active_record/relation/calculations.rb +55 -16
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +16 -11
- data/lib/active_record/relation/predicate_builder.rb +8 -6
- data/lib/active_record/relation/query_methods.rb +75 -9
- data/lib/active_record/relation/spawn_methods.rb +48 -7
- data/lib/active_record/relation.rb +78 -32
- data/lib/active_record/result.rb +10 -4
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +12 -5
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +200 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +4 -45
- data/lib/active_record/session_store.rb +18 -16
- data/lib/active_record/store.rb +52 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +17 -3
- data/lib/active_record/transactions.rb +27 -6
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +8 -8
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/version.rb +3 -3
- data/lib/active_record.rb +38 -3
- data/lib/rails/generators/active_record/migration/migration_generator.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -3
- data/lib/rails/generators/active_record/model/model_generator.rb +9 -1
- data/lib/rails/generators/active_record/model/templates/migration.rb +3 -5
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +49 -28
- data/lib/active_record/named_scope.rb +0 -200
@@ -1,5 +1,5 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
1
2
|
require 'active_support/core_ext/object/blank'
|
2
|
-
require 'active_support/core_ext/module/delegation'
|
3
3
|
|
4
4
|
module ActiveRecord
|
5
5
|
# = Active Record Relation
|
@@ -7,13 +7,9 @@ module ActiveRecord
|
|
7
7
|
JoinOperation = Struct.new(:relation, :join_class, :on)
|
8
8
|
ASSOCIATION_METHODS = [:includes, :eager_load, :preload]
|
9
9
|
MULTI_VALUE_METHODS = [:select, :group, :order, :joins, :where, :having, :bind]
|
10
|
-
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :
|
10
|
+
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering, :reverse_order, :uniq]
|
11
11
|
|
12
|
-
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches
|
13
|
-
|
14
|
-
# These are explicitly delegated to improve performance (avoids method_missing)
|
15
|
-
delegate :to_xml, :to_yaml, :length, :collect, :map, :each, :all?, :include?, :to => :to_a
|
16
|
-
delegate :table_name, :quoted_table_name, :primary_key, :quoted_primary_key, :to => :klass
|
12
|
+
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
|
17
13
|
|
18
14
|
attr_reader :table, :klass, :loaded
|
19
15
|
attr_accessor :extensions, :default_scoped
|
@@ -95,14 +91,77 @@ module ActiveRecord
|
|
95
91
|
scoping { @klass.create!(*args, &block) }
|
96
92
|
end
|
97
93
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
94
|
+
# Tries to load the first record; if it fails, then <tt>create</tt> is called with the same arguments as this method.
|
95
|
+
#
|
96
|
+
# Expects arguments in the same format as <tt>Base.create</tt>.
|
97
|
+
#
|
98
|
+
# ==== Examples
|
99
|
+
# # Find the first user named Penélope or create a new one.
|
100
|
+
# User.where(:first_name => 'Penélope').first_or_create
|
101
|
+
# # => <User id: 1, first_name: 'Penélope', last_name: nil>
|
102
|
+
#
|
103
|
+
# # Find the first user named Penélope or create a new one.
|
104
|
+
# # We already have one so the existing record will be returned.
|
105
|
+
# User.where(:first_name => 'Penélope').first_or_create
|
106
|
+
# # => <User id: 1, first_name: 'Penélope', last_name: nil>
|
107
|
+
#
|
108
|
+
# # Find the first user named Scarlett or create a new one with a particular last name.
|
109
|
+
# User.where(:first_name => 'Scarlett').first_or_create(:last_name => 'Johansson')
|
110
|
+
# # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
|
111
|
+
#
|
112
|
+
# # Find the first user named Scarlett or create a new one with a different last name.
|
113
|
+
# # We already have one so the existing record will be returned.
|
114
|
+
# User.where(:first_name => 'Scarlett').first_or_create do |user|
|
115
|
+
# user.last_name = "O'Hara"
|
116
|
+
# end
|
117
|
+
# # => <User id: 2, first_name: 'Scarlett', last_name: 'Johansson'>
|
118
|
+
def first_or_create(attributes = nil, options = {}, &block)
|
119
|
+
first || create(attributes, options, &block)
|
120
|
+
end
|
121
|
+
|
122
|
+
# Like <tt>first_or_create</tt> but calls <tt>create!</tt> so an exception is raised if the created record is invalid.
|
123
|
+
#
|
124
|
+
# Expects arguments in the same format as <tt>Base.create!</tt>.
|
125
|
+
def first_or_create!(attributes = nil, options = {}, &block)
|
126
|
+
first || create!(attributes, options, &block)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Like <tt>first_or_create</tt> but calls <tt>new</tt> instead of <tt>create</tt>.
|
130
|
+
#
|
131
|
+
# Expects arguments in the same format as <tt>Base.new</tt>.
|
132
|
+
def first_or_initialize(attributes = nil, options = {}, &block)
|
133
|
+
first || new(attributes, options, &block)
|
134
|
+
end
|
135
|
+
|
136
|
+
# Runs EXPLAIN on the query or queries triggered by this relation and
|
137
|
+
# returns the result as a string. The string is formatted imitating the
|
138
|
+
# ones printed by the database shell.
|
139
|
+
#
|
140
|
+
# Note that this method actually runs the queries, since the results of some
|
141
|
+
# are needed by the next ones when eager loading is going on.
|
142
|
+
#
|
143
|
+
# Please see further details in the
|
144
|
+
# {Active Record Query Interface guide}[http://edgeguides.rubyonrails.org/active_record_querying.html#running-explain].
|
145
|
+
def explain
|
146
|
+
_, queries = collecting_queries_for_explain { exec_queries }
|
147
|
+
exec_explain(queries)
|
103
148
|
end
|
104
149
|
|
105
150
|
def to_a
|
151
|
+
# We monitor here the entire execution rather than individual SELECTs
|
152
|
+
# because from the point of view of the user fetching the records of a
|
153
|
+
# relation is a single unit of work. You want to know if this call takes
|
154
|
+
# too long, not if the individual queries take too long.
|
155
|
+
#
|
156
|
+
# It could be the case that none of the queries involved surpass the
|
157
|
+
# threshold, and at the same time the sum of them all does. The user
|
158
|
+
# should get a query plan logged in that case.
|
159
|
+
logging_query_plan do
|
160
|
+
exec_queries
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def exec_queries
|
106
165
|
return @records if loaded?
|
107
166
|
|
108
167
|
default_scoped = with_default_scope
|
@@ -133,6 +192,7 @@ module ActiveRecord
|
|
133
192
|
@loaded = true
|
134
193
|
@records
|
135
194
|
end
|
195
|
+
private :exec_queries
|
136
196
|
|
137
197
|
def as_json(options = nil) #:nodoc:
|
138
198
|
to_a.as_json(options)
|
@@ -178,7 +238,7 @@ module ActiveRecord
|
|
178
238
|
# Please check unscoped if you want to remove all previous scopes (including
|
179
239
|
# the default_scope) during the execution of a block.
|
180
240
|
def scoping
|
181
|
-
@klass.
|
241
|
+
@klass.with_scope(self, :overwrite) { yield }
|
182
242
|
end
|
183
243
|
|
184
244
|
# Updates all records with details given if they match a set of conditions supplied, limits and order can
|
@@ -253,8 +313,7 @@ module ActiveRecord
|
|
253
313
|
# Person.update(people.keys, people.values)
|
254
314
|
def update(id, attributes)
|
255
315
|
if id.is_a?(Array)
|
256
|
-
idx
|
257
|
-
id.collect { |one_id| idx += 1; update(one_id, attributes[idx]) }
|
316
|
+
id.each.with_index.map {|one_id, idx| update(one_id, attributes[idx])}
|
258
317
|
else
|
259
318
|
object = find(id)
|
260
319
|
object.update_attributes(attributes)
|
@@ -298,7 +357,7 @@ module ActiveRecord
|
|
298
357
|
end
|
299
358
|
|
300
359
|
# Destroy an object (or multiple objects) that has the given id, the object is instantiated first,
|
301
|
-
# therefore all callbacks and filters are fired off before the object is deleted.
|
360
|
+
# therefore all callbacks and filters are fired off before the object is deleted. This method is
|
302
361
|
# less efficient than ActiveRecord#delete but allows cleanup methods and other actions to be run.
|
303
362
|
#
|
304
363
|
# This essentially finds the object (or multiple objects) with the given id, creates a new object
|
@@ -327,7 +386,7 @@ module ActiveRecord
|
|
327
386
|
# Deletes the records matching +conditions+ without instantiating the records first, and hence not
|
328
387
|
# calling the +destroy+ method nor invoking callbacks. This is a single SQL DELETE statement that
|
329
388
|
# goes straight to the database, much more efficient than +destroy_all+. Be careful with relations
|
330
|
-
# though, in particular <tt>:dependent</tt> rules defined on associations are not honored.
|
389
|
+
# though, in particular <tt>:dependent</tt> rules defined on associations are not honored. Returns
|
331
390
|
# the number of rows affected.
|
332
391
|
#
|
333
392
|
# ==== Parameters
|
@@ -344,6 +403,8 @@ module ActiveRecord
|
|
344
403
|
# If you need to destroy dependent associations or call your <tt>before_*</tt> or
|
345
404
|
# +after_destroy+ callbacks, use the +destroy_all+ method instead.
|
346
405
|
def delete_all(conditions = nil)
|
406
|
+
raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
|
407
|
+
|
347
408
|
IdentityMap.repository[symbolized_base_class] = {} if IdentityMap.enabled?
|
348
409
|
if conditions
|
349
410
|
where(conditions).delete_all
|
@@ -447,20 +508,6 @@ module ActiveRecord
|
|
447
508
|
end
|
448
509
|
end
|
449
510
|
|
450
|
-
protected
|
451
|
-
|
452
|
-
def method_missing(method, *args, &block)
|
453
|
-
if Array.method_defined?(method)
|
454
|
-
to_a.send(method, *args, &block)
|
455
|
-
elsif @klass.respond_to?(method)
|
456
|
-
scoping { @klass.send(method, *args, &block) }
|
457
|
-
elsif arel.respond_to?(method)
|
458
|
-
arel.send(method, *args, &block)
|
459
|
-
else
|
460
|
-
super
|
461
|
-
end
|
462
|
-
end
|
463
|
-
|
464
511
|
private
|
465
512
|
|
466
513
|
def references_eager_loaded_tables?
|
@@ -486,6 +533,5 @@ module ActiveRecord
|
|
486
533
|
# ignore raw_sql_ that is used by Oracle adapter as alias for limit/offset subqueries
|
487
534
|
string.scan(/([a-zA-Z_][.\w]+).?\./).flatten.map{ |s| s.downcase }.uniq - ['raw_sql_']
|
488
535
|
end
|
489
|
-
|
490
536
|
end
|
491
537
|
end
|
data/lib/active_record/result.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
###
|
3
3
|
# This class encapsulates a Result returned from calling +exec_query+ on any
|
4
|
-
# database connection adapter.
|
4
|
+
# database connection adapter. For example:
|
5
5
|
#
|
6
6
|
# x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo')
|
7
7
|
# x # => #<ActiveRecord::Result:0xdeadbeef>
|
@@ -26,9 +26,15 @@ module ActiveRecord
|
|
26
26
|
|
27
27
|
private
|
28
28
|
def hash_rows
|
29
|
-
@hash_rows ||=
|
30
|
-
|
31
|
-
|
29
|
+
@hash_rows ||=
|
30
|
+
begin
|
31
|
+
# We freeze the strings to prevent them getting duped when
|
32
|
+
# used as keys in ActiveRecord::Model's @attributes hash
|
33
|
+
columns = @columns.map { |c| c.dup.freeze }
|
34
|
+
@rows.map { |row|
|
35
|
+
Hash[columns.zip(row)]
|
36
|
+
}
|
37
|
+
end
|
32
38
|
end
|
33
39
|
end
|
34
40
|
end
|
@@ -0,0 +1,194 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Sanitization
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def quote_value(value, column = nil) #:nodoc:
|
9
|
+
connection.quote(value,column)
|
10
|
+
end
|
11
|
+
|
12
|
+
# Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
|
13
|
+
def sanitize(object) #:nodoc:
|
14
|
+
connection.quote(object)
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
20
|
+
# them into a valid SQL fragment for a WHERE clause.
|
21
|
+
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
22
|
+
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
|
23
|
+
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
|
24
|
+
def sanitize_sql_for_conditions(condition, table_name = self.table_name)
|
25
|
+
return nil if condition.blank?
|
26
|
+
|
27
|
+
case condition
|
28
|
+
when Array; sanitize_sql_array(condition)
|
29
|
+
when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
|
30
|
+
else condition
|
31
|
+
end
|
32
|
+
end
|
33
|
+
alias_method :sanitize_sql, :sanitize_sql_for_conditions
|
34
|
+
|
35
|
+
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
36
|
+
# them into a valid SQL fragment for a SET clause.
|
37
|
+
# { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
|
38
|
+
def sanitize_sql_for_assignment(assignments)
|
39
|
+
case assignments
|
40
|
+
when Array; sanitize_sql_array(assignments)
|
41
|
+
when Hash; sanitize_sql_hash_for_assignment(assignments)
|
42
|
+
else assignments
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Accepts a hash of SQL conditions and replaces those attributes
|
47
|
+
# that correspond to a +composed_of+ relationship with their expanded
|
48
|
+
# aggregate attribute values.
|
49
|
+
# Given:
|
50
|
+
# class Person < ActiveRecord::Base
|
51
|
+
# composed_of :address, :class_name => "Address",
|
52
|
+
# :mapping => [%w(address_street street), %w(address_city city)]
|
53
|
+
# end
|
54
|
+
# Then:
|
55
|
+
# { :address => Address.new("813 abc st.", "chicago") }
|
56
|
+
# # => { :address_street => "813 abc st.", :address_city => "chicago" }
|
57
|
+
def expand_hash_conditions_for_aggregates(attrs)
|
58
|
+
expanded_attrs = {}
|
59
|
+
attrs.each do |attr, value|
|
60
|
+
unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
|
61
|
+
mapping = aggregate_mapping(aggregation)
|
62
|
+
mapping.each do |field_attr, aggregate_attr|
|
63
|
+
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
|
64
|
+
expanded_attrs[field_attr] = value
|
65
|
+
else
|
66
|
+
expanded_attrs[field_attr] = value.send(aggregate_attr)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
else
|
70
|
+
expanded_attrs[attr] = value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
expanded_attrs
|
74
|
+
end
|
75
|
+
|
76
|
+
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
|
77
|
+
# { :name => "foo'bar", :group_id => 4 }
|
78
|
+
# # => "name='foo''bar' and group_id= 4"
|
79
|
+
# { :status => nil, :group_id => [1,2,3] }
|
80
|
+
# # => "status IS NULL and group_id IN (1,2,3)"
|
81
|
+
# { :age => 13..18 }
|
82
|
+
# # => "age BETWEEN 13 AND 18"
|
83
|
+
# { 'other_records.id' => 7 }
|
84
|
+
# # => "`other_records`.`id` = 7"
|
85
|
+
# { :other_records => { :id => 7 } }
|
86
|
+
# # => "`other_records`.`id` = 7"
|
87
|
+
# And for value objects on a composed_of relationship:
|
88
|
+
# { :address => Address.new("123 abc st.", "chicago") }
|
89
|
+
# # => "address_street='123 abc st.' and address_city='chicago'"
|
90
|
+
def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
|
91
|
+
attrs = expand_hash_conditions_for_aggregates(attrs)
|
92
|
+
|
93
|
+
table = Arel::Table.new(table_name).alias(default_table_name)
|
94
|
+
PredicateBuilder.build_from_hash(arel_engine, attrs, table).map { |b|
|
95
|
+
connection.visitor.accept b
|
96
|
+
}.join(' AND ')
|
97
|
+
end
|
98
|
+
alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
|
99
|
+
|
100
|
+
# Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
|
101
|
+
# { :status => nil, :group_id => 1 }
|
102
|
+
# # => "status = NULL , group_id = 1"
|
103
|
+
def sanitize_sql_hash_for_assignment(attrs)
|
104
|
+
attrs.map do |attr, value|
|
105
|
+
"#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
|
106
|
+
end.join(', ')
|
107
|
+
end
|
108
|
+
|
109
|
+
# Accepts an array of conditions. The array has each value
|
110
|
+
# sanitized and interpolated into the SQL statement.
|
111
|
+
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
112
|
+
def sanitize_sql_array(ary)
|
113
|
+
statement, *values = ary
|
114
|
+
if values.first.is_a?(Hash) && statement =~ /:\w+/
|
115
|
+
replace_named_bind_variables(statement, values.first)
|
116
|
+
elsif statement.include?('?')
|
117
|
+
replace_bind_variables(statement, values)
|
118
|
+
elsif statement.blank?
|
119
|
+
statement
|
120
|
+
else
|
121
|
+
statement % values.collect { |value| connection.quote_string(value.to_s) }
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
alias_method :sanitize_conditions, :sanitize_sql
|
126
|
+
|
127
|
+
def replace_bind_variables(statement, values) #:nodoc:
|
128
|
+
raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
|
129
|
+
bound = values.dup
|
130
|
+
c = connection
|
131
|
+
statement.gsub('?') { quote_bound_value(bound.shift, c) }
|
132
|
+
end
|
133
|
+
|
134
|
+
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
|
135
|
+
statement.gsub(/(:?):([a-zA-Z]\w*)/) do
|
136
|
+
if $1 == ':' # skip postgresql casts
|
137
|
+
$& # return the whole match
|
138
|
+
elsif bind_vars.include?(match = $2.to_sym)
|
139
|
+
quote_bound_value(bind_vars[match])
|
140
|
+
else
|
141
|
+
raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def expand_range_bind_variables(bind_vars) #:nodoc:
|
147
|
+
expanded = []
|
148
|
+
|
149
|
+
bind_vars.each do |var|
|
150
|
+
next if var.is_a?(Hash)
|
151
|
+
|
152
|
+
if var.is_a?(Range)
|
153
|
+
expanded << var.first
|
154
|
+
expanded << var.last
|
155
|
+
else
|
156
|
+
expanded << var
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
expanded
|
161
|
+
end
|
162
|
+
|
163
|
+
def quote_bound_value(value, c = connection) #:nodoc:
|
164
|
+
if value.respond_to?(:map) && !value.acts_like?(:string)
|
165
|
+
if value.respond_to?(:empty?) && value.empty?
|
166
|
+
c.quote(nil)
|
167
|
+
else
|
168
|
+
value.map { |v| c.quote(v) }.join(',')
|
169
|
+
end
|
170
|
+
else
|
171
|
+
c.quote(value)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
|
176
|
+
unless expected == provided
|
177
|
+
raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# TODO: Deprecate this
|
183
|
+
def quoted_id #:nodoc:
|
184
|
+
quote_value(id, column_for_attribute(self.class.primary_key))
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
# Quote strings appropriately for SQL statements.
|
190
|
+
def quote_value(value, column = nil)
|
191
|
+
self.class.connection.quote(value, column)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
@@ -70,8 +70,8 @@ HEADER
|
|
70
70
|
@connection.tables.sort.each do |tbl|
|
71
71
|
next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
|
72
72
|
case ignored
|
73
|
-
when String; tbl == ignored
|
74
|
-
when Regexp; tbl =~ ignored
|
73
|
+
when String; remove_prefix_and_suffix(tbl) == ignored
|
74
|
+
when Regexp; remove_prefix_and_suffix(tbl) =~ ignored
|
75
75
|
else
|
76
76
|
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
|
77
77
|
end
|
@@ -92,7 +92,7 @@ HEADER
|
|
92
92
|
pk = @connection.primary_key(table)
|
93
93
|
end
|
94
94
|
|
95
|
-
tbl.print " create_table #{table.inspect}"
|
95
|
+
tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
|
96
96
|
if columns.detect { |c| c.name == pk }
|
97
97
|
if pk != 'id'
|
98
98
|
tbl.print %Q(, :primary_key => "#{pk}")
|
@@ -110,7 +110,7 @@ HEADER
|
|
110
110
|
spec = {}
|
111
111
|
spec[:name] = column.name.inspect
|
112
112
|
|
113
|
-
# AR has an
|
113
|
+
# AR has an optimization which handles zero-scale decimals as integers. This
|
114
114
|
# code ensures that the dumper still dumps the column as a decimal.
|
115
115
|
spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
|
116
116
|
'decimal'
|
@@ -181,7 +181,7 @@ HEADER
|
|
181
181
|
if (indexes = @connection.indexes(table)).any?
|
182
182
|
add_index_statements = indexes.map do |index|
|
183
183
|
statement_parts = [
|
184
|
-
('add_index ' + index.table.inspect),
|
184
|
+
('add_index ' + remove_prefix_and_suffix(index.table).inspect),
|
185
185
|
index.columns.inspect,
|
186
186
|
(':name => ' + index.name.inspect),
|
187
187
|
]
|
@@ -190,6 +190,9 @@ HEADER
|
|
190
190
|
index_lengths = (index.lengths || []).compact
|
191
191
|
statement_parts << (':length => ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
|
192
192
|
|
193
|
+
index_orders = (index.orders || {})
|
194
|
+
statement_parts << (':order => ' + index.orders.inspect) unless index_orders.empty?
|
195
|
+
|
193
196
|
' ' + statement_parts.join(', ')
|
194
197
|
end
|
195
198
|
|
@@ -197,5 +200,9 @@ HEADER
|
|
197
200
|
stream.puts
|
198
201
|
end
|
199
202
|
end
|
203
|
+
|
204
|
+
def remove_prefix_and_suffix(table)
|
205
|
+
table.gsub(/^(#{ActiveRecord::Base.table_name_prefix})(.+)(#{ActiveRecord::Base.table_name_suffix})$/, "\\2")
|
206
|
+
end
|
200
207
|
end
|
201
208
|
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Scoping
|
5
|
+
module Default
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
# Stores the default scope for the class
|
10
|
+
class_attribute :default_scopes, :instance_writer => false
|
11
|
+
self.default_scopes = []
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
# Returns a scope for the model without the default_scope.
|
16
|
+
#
|
17
|
+
# class Post < ActiveRecord::Base
|
18
|
+
# def self.default_scope
|
19
|
+
# where :published => true
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# Post.all # Fires "SELECT * FROM posts WHERE published = true"
|
24
|
+
# Post.unscoped.all # Fires "SELECT * FROM posts"
|
25
|
+
#
|
26
|
+
# This method also accepts a block. All queries inside the block will
|
27
|
+
# not use the default_scope:
|
28
|
+
#
|
29
|
+
# Post.unscoped {
|
30
|
+
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
31
|
+
# }
|
32
|
+
#
|
33
|
+
# It is recommended to use the block form of unscoped because chaining
|
34
|
+
# unscoped with <tt>scope</tt> does not work. Assuming that
|
35
|
+
# <tt>published</tt> is a <tt>scope</tt>, the following two statements
|
36
|
+
# are equal: the default_scope is applied on both.
|
37
|
+
#
|
38
|
+
# Post.unscoped.published
|
39
|
+
# Post.published
|
40
|
+
def unscoped #:nodoc:
|
41
|
+
block_given? ? relation.scoping { yield } : relation
|
42
|
+
end
|
43
|
+
|
44
|
+
def before_remove_const #:nodoc:
|
45
|
+
self.current_scope = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
protected
|
49
|
+
|
50
|
+
# Use this macro in your model to set a default scope for all operations on
|
51
|
+
# the model.
|
52
|
+
#
|
53
|
+
# class Article < ActiveRecord::Base
|
54
|
+
# default_scope where(:published => true)
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# Article.all # => SELECT * FROM articles WHERE published = true
|
58
|
+
#
|
59
|
+
# The <tt>default_scope</tt> is also applied while creating/building a record. It is not
|
60
|
+
# applied while updating a record.
|
61
|
+
#
|
62
|
+
# Article.new.published # => true
|
63
|
+
# Article.create.published # => true
|
64
|
+
#
|
65
|
+
# You can also use <tt>default_scope</tt> with a block, in order to have it lazily evaluated:
|
66
|
+
#
|
67
|
+
# class Article < ActiveRecord::Base
|
68
|
+
# default_scope { where(:published_at => Time.now - 1.week) }
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# (You can also pass any object which responds to <tt>call</tt> to the <tt>default_scope</tt>
|
72
|
+
# macro, and it will be called when building the default scope.)
|
73
|
+
#
|
74
|
+
# If you use multiple <tt>default_scope</tt> declarations in your model then they will
|
75
|
+
# be merged together:
|
76
|
+
#
|
77
|
+
# class Article < ActiveRecord::Base
|
78
|
+
# default_scope where(:published => true)
|
79
|
+
# default_scope where(:rating => 'G')
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# Article.all # => SELECT * FROM articles WHERE published = true AND rating = 'G'
|
83
|
+
#
|
84
|
+
# This is also the case with inheritance and module includes where the parent or module
|
85
|
+
# defines a <tt>default_scope</tt> and the child or including class defines a second one.
|
86
|
+
#
|
87
|
+
# If you need to do more complex things with a default scope, you can alternatively
|
88
|
+
# define it as a class method:
|
89
|
+
#
|
90
|
+
# class Article < ActiveRecord::Base
|
91
|
+
# def self.default_scope
|
92
|
+
# # Should return a scope, you can call 'super' here etc.
|
93
|
+
# end
|
94
|
+
# end
|
95
|
+
def default_scope(scope = {})
|
96
|
+
scope = Proc.new if block_given?
|
97
|
+
self.default_scopes = default_scopes + [scope]
|
98
|
+
end
|
99
|
+
|
100
|
+
def build_default_scope #:nodoc:
|
101
|
+
if method(:default_scope).owner != ActiveRecord::Scoping::Default::ClassMethods
|
102
|
+
evaluate_default_scope { default_scope }
|
103
|
+
elsif default_scopes.any?
|
104
|
+
evaluate_default_scope do
|
105
|
+
default_scopes.inject(relation) do |default_scope, scope|
|
106
|
+
if scope.is_a?(Hash)
|
107
|
+
default_scope.apply_finder_options(scope)
|
108
|
+
elsif !scope.is_a?(Relation) && scope.respond_to?(:call)
|
109
|
+
default_scope.merge(scope.call)
|
110
|
+
else
|
111
|
+
default_scope.merge(scope)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def ignore_default_scope? #:nodoc:
|
119
|
+
Thread.current["#{self}_ignore_default_scope"]
|
120
|
+
end
|
121
|
+
|
122
|
+
def ignore_default_scope=(ignore) #:nodoc:
|
123
|
+
Thread.current["#{self}_ignore_default_scope"] = ignore
|
124
|
+
end
|
125
|
+
|
126
|
+
# The ignore_default_scope flag is used to prevent an infinite recursion situation where
|
127
|
+
# a default scope references a scope which has a default scope which references a scope...
|
128
|
+
def evaluate_default_scope
|
129
|
+
return if ignore_default_scope?
|
130
|
+
|
131
|
+
begin
|
132
|
+
self.ignore_default_scope = true
|
133
|
+
yield
|
134
|
+
ensure
|
135
|
+
self.ignore_default_scope = false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|