activerecord 3.1.12 → 3.2.0.rc1
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.
- data/CHANGELOG.md +6263 -103
- data/README.rdoc +2 -2
- data/examples/performance.rb +55 -31
- data/lib/active_record.rb +28 -2
- data/lib/active_record/aggregations.rb +2 -2
- data/lib/active_record/associations.rb +82 -69
- data/lib/active_record/associations/association.rb +2 -37
- data/lib/active_record/associations/association_scope.rb +3 -30
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +3 -3
- 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 +55 -28
- data/lib/active_record/associations/collection_proxy.rb +1 -35
- data/lib/active_record/associations/has_many_association.rb +5 -1
- data/lib/active_record/associations/has_many_through_association.rb +11 -8
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations/preloader/association.rb +3 -1
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +212 -32
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +3 -3
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +69 -80
- data/lib/active_record/attribute_methods/serialization.rb +89 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
- data/lib/active_record/attribute_methods/write.rb +27 -5
- data/lib/active_record/autosave_association.rb +23 -8
- data/lib/active_record/base.rb +223 -1712
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -13
- data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +83 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +31 -76
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +1 -7
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locking/optimistic.rb +19 -11
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +3 -3
- data/lib/active_record/migration.rb +38 -29
- data/lib/active_record/migration/command_recorder.rb +7 -7
- data/lib/active_record/model_schema.rb +362 -0
- data/lib/active_record/nested_attributes.rb +3 -2
- data/lib/active_record/persistence.rb +51 -1
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +24 -28
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +133 -77
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation.rb +78 -35
- data/lib/active_record/relation/batches.rb +5 -2
- data/lib/active_record/relation/calculations.rb +27 -6
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +5 -4
- data/lib/active_record/relation/predicate_builder.rb +13 -16
- data/lib/active_record/relation/query_methods.rb +59 -4
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +5 -2
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +140 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +2 -44
- data/lib/active_record/session_store.rb +11 -11
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +16 -3
- data/lib/active_record/transactions.rb +5 -5
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/version.rb +3 -3
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +48 -38
- checksums.yaml +0 -7
- data/lib/active_record/named_scope.rb +0 -200
@@ -1,47 +1,44 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
class PredicateBuilder # :nodoc:
|
3
|
-
def self.build_from_hash(engine, attributes, default_table
|
3
|
+
def self.build_from_hash(engine, attributes, default_table)
|
4
4
|
predicates = attributes.map do |column, value|
|
5
5
|
table = default_table
|
6
6
|
|
7
|
-
if
|
7
|
+
if value.is_a?(Hash)
|
8
8
|
table = Arel::Table.new(column, engine)
|
9
|
-
|
10
|
-
if value.empty?
|
11
|
-
'1 = 2'
|
12
|
-
else
|
13
|
-
build_from_hash(engine, value, table, false)
|
14
|
-
end
|
9
|
+
build_from_hash(engine, value, table)
|
15
10
|
else
|
16
11
|
column = column.to_s
|
17
12
|
|
18
|
-
if
|
13
|
+
if column.include?('.')
|
19
14
|
table_name, column = column.split('.', 2)
|
20
15
|
table = Arel::Table.new(table_name, engine)
|
21
16
|
end
|
22
17
|
|
23
|
-
attribute = table[column]
|
18
|
+
attribute = table[column.to_sym]
|
24
19
|
|
25
20
|
case value
|
26
21
|
when ActiveRecord::Relation
|
27
22
|
value = value.select(value.klass.arel_table[value.klass.primary_key]) if value.select_values.empty?
|
28
23
|
attribute.in(value.arel.ast)
|
29
24
|
when Array, ActiveRecord::Associations::CollectionProxy
|
30
|
-
values = value.to_a.map {
|
31
|
-
|
32
|
-
|
25
|
+
values = value.to_a.map {|x| x.is_a?(ActiveRecord::Base) ? x.id : x}
|
26
|
+
ranges, values = values.partition {|v| v.is_a?(Range) || v.is_a?(Arel::Relation)}
|
27
|
+
|
28
|
+
array_predicates = ranges.map {|range| attribute.in(range)}
|
33
29
|
|
34
30
|
if values.include?(nil)
|
35
31
|
values = values.compact
|
36
32
|
if values.empty?
|
37
|
-
attribute.eq
|
33
|
+
array_predicates << attribute.eq(nil)
|
38
34
|
else
|
39
|
-
attribute.in(values.compact).or
|
35
|
+
array_predicates << attribute.in(values.compact).or(attribute.eq(nil))
|
40
36
|
end
|
41
37
|
else
|
42
|
-
attribute.in(values)
|
38
|
+
array_predicates << attribute.in(values)
|
43
39
|
end
|
44
40
|
|
41
|
+
array_predicates.inject {|composite, predicate| composite.or(predicate)}
|
45
42
|
when Range, Arel::Relation
|
46
43
|
attribute.in(value)
|
47
44
|
when ActiveRecord::Base
|
@@ -9,7 +9,8 @@ module ActiveRecord
|
|
9
9
|
:select_values, :group_values, :order_values, :joins_values,
|
10
10
|
:where_values, :having_values, :bind_values,
|
11
11
|
:limit_value, :offset_value, :lock_value, :readonly_value, :create_with_value,
|
12
|
-
:from_value, :reorder_value, :reverse_order_value
|
12
|
+
:from_value, :reorder_value, :reverse_order_value,
|
13
|
+
:uniq_value
|
13
14
|
|
14
15
|
def includes(*args)
|
15
16
|
args.reject! {|a| a.blank? }
|
@@ -38,7 +39,7 @@ module ActiveRecord
|
|
38
39
|
end
|
39
40
|
|
40
41
|
# Works in two unique ways.
|
41
|
-
#
|
42
|
+
#
|
42
43
|
# First: takes a block so it can be used just like Array#select.
|
43
44
|
#
|
44
45
|
# Model.scoped.select { |m| m.field == value }
|
@@ -176,6 +177,58 @@ module ActiveRecord
|
|
176
177
|
relation
|
177
178
|
end
|
178
179
|
|
180
|
+
# Specifies whether the records should be unique or not. For example:
|
181
|
+
#
|
182
|
+
# User.select(:name)
|
183
|
+
# # => Might return two records with the same name
|
184
|
+
#
|
185
|
+
# User.select(:name).uniq
|
186
|
+
# # => Returns 1 record per unique name
|
187
|
+
#
|
188
|
+
# User.select(:name).uniq.uniq(false)
|
189
|
+
# # => You can also remove the uniqueness
|
190
|
+
def uniq(value = true)
|
191
|
+
relation = clone
|
192
|
+
relation.uniq_value = value
|
193
|
+
relation
|
194
|
+
end
|
195
|
+
|
196
|
+
# Used to extend a scope with additional methods, either through
|
197
|
+
# a module or through a block provided.
|
198
|
+
#
|
199
|
+
# The object returned is a relation, which can be further extended.
|
200
|
+
#
|
201
|
+
# === Using a module
|
202
|
+
#
|
203
|
+
# module Pagination
|
204
|
+
# def page(number)
|
205
|
+
# # pagination code goes here
|
206
|
+
# end
|
207
|
+
# end
|
208
|
+
#
|
209
|
+
# scope = Model.scoped.extending(Pagination)
|
210
|
+
# scope.page(params[:page])
|
211
|
+
#
|
212
|
+
# You can also pass a list of modules:
|
213
|
+
#
|
214
|
+
# scope = Model.scoped.extending(Pagination, SomethingElse)
|
215
|
+
#
|
216
|
+
# === Using a block
|
217
|
+
#
|
218
|
+
# scope = Model.scoped.extending do
|
219
|
+
# def page(number)
|
220
|
+
# # pagination code goes here
|
221
|
+
# end
|
222
|
+
# end
|
223
|
+
# scope.page(params[:page])
|
224
|
+
#
|
225
|
+
# You can also use a block and a module list:
|
226
|
+
#
|
227
|
+
# scope = Model.scoped.extending(Pagination) do
|
228
|
+
# def per_page(number)
|
229
|
+
# # pagination code goes here
|
230
|
+
# end
|
231
|
+
# end
|
179
232
|
def extending(*modules)
|
180
233
|
modules << Module.new(&Proc.new) if block_given?
|
181
234
|
|
@@ -206,7 +259,7 @@ module ActiveRecord
|
|
206
259
|
arel.having(*@having_values.uniq.reject{|h| h.blank?}) unless @having_values.empty?
|
207
260
|
|
208
261
|
arel.take(connection.sanitize_limit(@limit_value)) if @limit_value
|
209
|
-
arel.skip(@offset_value
|
262
|
+
arel.skip(@offset_value) if @offset_value
|
210
263
|
|
211
264
|
arel.group(*@group_values.uniq.reject{|g| g.blank?}) unless @group_values.empty?
|
212
265
|
|
@@ -216,6 +269,7 @@ module ActiveRecord
|
|
216
269
|
|
217
270
|
build_select(arel, @select_values.uniq)
|
218
271
|
|
272
|
+
arel.distinct(@uniq_value)
|
219
273
|
arel.from(@from_value) if @from_value
|
220
274
|
arel.lock(@lock_value) if @lock_value
|
221
275
|
|
@@ -331,10 +385,11 @@ module ActiveRecord
|
|
331
385
|
|
332
386
|
order_query.map do |o|
|
333
387
|
case o
|
334
|
-
when Arel::Nodes::
|
388
|
+
when Arel::Nodes::Ordering
|
335
389
|
o.reverse
|
336
390
|
when String, Symbol
|
337
391
|
o.to_s.split(',').collect do |s|
|
392
|
+
s.strip!
|
338
393
|
s.gsub!(/\sasc\Z/i, ' DESC') || s.gsub!(/\sdesc\Z/i, ' ASC') || s.concat(' DESC')
|
339
394
|
end
|
340
395
|
else
|
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>
|
@@ -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
|
@@ -40,7 +40,7 @@ module ActiveRecord
|
|
40
40
|
def header(stream)
|
41
41
|
define_params = @version ? ":version => #{@version}" : ""
|
42
42
|
|
43
|
-
if stream.respond_to?(:external_encoding)
|
43
|
+
if stream.respond_to?(:external_encoding)
|
44
44
|
stream.puts "# encoding: #{stream.external_encoding.name}"
|
45
45
|
end
|
46
46
|
|
@@ -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'
|
@@ -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
|
|
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Scoping
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
include Default
|
9
|
+
include Named
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
# with_scope lets you apply options to inner block incrementally. It takes a hash and the keys must be
|
14
|
+
# <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameter is <tt>Relation</tt> while
|
15
|
+
# <tt>:create</tt> parameters are an attributes hash.
|
16
|
+
#
|
17
|
+
# class Article < ActiveRecord::Base
|
18
|
+
# def self.create_with_scope
|
19
|
+
# with_scope(:find => where(:blog_id => 1), :create => { :blog_id => 1 }) do
|
20
|
+
# find(1) # => SELECT * from articles WHERE blog_id = 1 AND id = 1
|
21
|
+
# a = create(1)
|
22
|
+
# a.blog_id # => 1
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
|
28
|
+
# <tt>where</tt>, <tt>includes</tt>, and <tt>joins</tt> operations in <tt>Relation</tt>, which are merged.
|
29
|
+
#
|
30
|
+
# <tt>joins</tt> operations are uniqued so multiple scopes can join in the same table without table aliasing
|
31
|
+
# problems. If you need to join multiple tables, but still want one of the tables to be uniqued, use the
|
32
|
+
# array of strings format for your joins.
|
33
|
+
#
|
34
|
+
# class Article < ActiveRecord::Base
|
35
|
+
# def self.find_with_scope
|
36
|
+
# with_scope(:find => where(:blog_id => 1).limit(1), :create => { :blog_id => 1 }) do
|
37
|
+
# with_scope(:find => limit(10)) do
|
38
|
+
# all # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
|
39
|
+
# end
|
40
|
+
# with_scope(:find => where(:author_id => 3)) do
|
41
|
+
# all # => SELECT * from articles WHERE blog_id = 1 AND author_id = 3 LIMIT 1
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# You can ignore any previous scopings by using the <tt>with_exclusive_scope</tt> method.
|
48
|
+
#
|
49
|
+
# class Article < ActiveRecord::Base
|
50
|
+
# def self.find_with_exclusive_scope
|
51
|
+
# with_scope(:find => where(:blog_id => 1).limit(1)) do
|
52
|
+
# with_exclusive_scope(:find => limit(10)) do
|
53
|
+
# all # => SELECT * from articles LIMIT 10
|
54
|
+
# end
|
55
|
+
# end
|
56
|
+
# end
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# *Note*: the +:find+ scope also has effect on update and deletion methods, like +update_all+ and +delete_all+.
|
60
|
+
def with_scope(scope = {}, action = :merge, &block)
|
61
|
+
# If another Active Record class has been passed in, get its current scope
|
62
|
+
scope = scope.current_scope if !scope.is_a?(Relation) && scope.respond_to?(:current_scope)
|
63
|
+
|
64
|
+
previous_scope = self.current_scope
|
65
|
+
|
66
|
+
if scope.is_a?(Hash)
|
67
|
+
# Dup first and second level of hash (method and params).
|
68
|
+
scope = scope.dup
|
69
|
+
scope.each do |method, params|
|
70
|
+
scope[method] = params.dup unless params == true
|
71
|
+
end
|
72
|
+
|
73
|
+
scope.assert_valid_keys([ :find, :create ])
|
74
|
+
relation = construct_finder_arel(scope[:find] || {})
|
75
|
+
relation.default_scoped = true unless action == :overwrite
|
76
|
+
|
77
|
+
if previous_scope && previous_scope.create_with_value && scope[:create]
|
78
|
+
scope_for_create = if action == :merge
|
79
|
+
previous_scope.create_with_value.merge(scope[:create])
|
80
|
+
else
|
81
|
+
scope[:create]
|
82
|
+
end
|
83
|
+
|
84
|
+
relation = relation.create_with(scope_for_create)
|
85
|
+
else
|
86
|
+
scope_for_create = scope[:create]
|
87
|
+
scope_for_create ||= previous_scope.create_with_value if previous_scope
|
88
|
+
relation = relation.create_with(scope_for_create) if scope_for_create
|
89
|
+
end
|
90
|
+
|
91
|
+
scope = relation
|
92
|
+
end
|
93
|
+
|
94
|
+
scope = previous_scope.merge(scope) if previous_scope && action == :merge
|
95
|
+
|
96
|
+
self.current_scope = scope
|
97
|
+
begin
|
98
|
+
yield
|
99
|
+
ensure
|
100
|
+
self.current_scope = previous_scope
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
protected
|
105
|
+
|
106
|
+
# Works like with_scope, but discards any nested properties.
|
107
|
+
def with_exclusive_scope(method_scoping = {}, &block)
|
108
|
+
if method_scoping.values.any? { |e| e.is_a?(ActiveRecord::Relation) }
|
109
|
+
raise ArgumentError, <<-MSG
|
110
|
+
New finder API can not be used with_exclusive_scope. You can either call unscoped to get an anonymous scope not bound to the default_scope:
|
111
|
+
|
112
|
+
User.unscoped.where(:active => true)
|
113
|
+
|
114
|
+
Or call unscoped with a block:
|
115
|
+
|
116
|
+
User.unscoped do
|
117
|
+
User.where(:active => true).all
|
118
|
+
end
|
119
|
+
|
120
|
+
MSG
|
121
|
+
end
|
122
|
+
with_scope(method_scoping, :overwrite, &block)
|
123
|
+
end
|
124
|
+
|
125
|
+
def current_scope #:nodoc:
|
126
|
+
Thread.current["#{self}_current_scope"]
|
127
|
+
end
|
128
|
+
|
129
|
+
def current_scope=(scope) #:nodoc:
|
130
|
+
Thread.current["#{self}_current_scope"] = scope
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
def construct_finder_arel(options = {}, scope = nil)
|
136
|
+
relation = options.is_a?(Hash) ? unscoped.apply_finder_options(options) : options
|
137
|
+
relation = scope.merge(relation) if scope
|
138
|
+
relation
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
def populate_with_current_scope_attributes
|
144
|
+
return unless self.class.scope_attributes?
|
145
|
+
|
146
|
+
self.class.scope_attributes.each do |att,value|
|
147
|
+
send("#{att}=", value) if respond_to?("#{att}=")
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|