activerecord 4.1.0.beta2 → 4.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +622 -9
- data/MIT-LICENSE +1 -1
- data/lib/active_record.rb +1 -1
- data/lib/active_record/associations.rb +10 -7
- data/lib/active_record/associations/alias_tracker.rb +39 -29
- data/lib/active_record/associations/association.rb +1 -1
- data/lib/active_record/associations/association_scope.rb +56 -31
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -0
- data/lib/active_record/associations/builder/association.rb +6 -0
- data/lib/active_record/associations/builder/belongs_to.rb +1 -1
- data/lib/active_record/associations/collection_association.rb +33 -9
- data/lib/active_record/associations/collection_proxy.rb +53 -5
- data/lib/active_record/associations/has_many_association.rb +1 -1
- data/lib/active_record/associations/join_dependency.rb +5 -5
- data/lib/active_record/associations/join_dependency/join_association.rb +8 -8
- data/lib/active_record/associations/preloader.rb +1 -1
- data/lib/active_record/associations/singular_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +28 -5
- data/lib/active_record/attribute_methods/dirty.rb +27 -4
- data/lib/active_record/attribute_methods/read.rb +1 -1
- data/lib/active_record/attribute_methods/serialization.rb +18 -0
- data/lib/active_record/autosave_association.rb +1 -1
- data/lib/active_record/base.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -8
- data/lib/active_record/connection_adapters/abstract/transaction.rb +4 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +15 -5
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/connection_specification.rb +200 -43
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -1
- data/lib/active_record/connection_adapters/mysql_adapter.rb +8 -2
- data/lib/active_record/connection_adapters/postgresql/array_parser.rb +3 -2
- data/lib/active_record/connection_adapters/postgresql/cast.rb +7 -7
- data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +13 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -17
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +25 -3
- data/lib/active_record/connection_handling.rb +64 -3
- data/lib/active_record/core.rb +28 -24
- data/lib/active_record/dynamic_matchers.rb +6 -2
- data/lib/active_record/enum.rb +111 -17
- data/lib/active_record/errors.rb +12 -0
- data/lib/active_record/fixtures.rb +13 -15
- data/lib/active_record/inheritance.rb +29 -9
- data/lib/active_record/integration.rb +4 -2
- data/lib/active_record/migration.rb +20 -7
- data/lib/active_record/migration/command_recorder.rb +18 -6
- data/lib/active_record/persistence.rb +10 -5
- data/lib/active_record/querying.rb +1 -0
- data/lib/active_record/railtie.rb +11 -8
- data/lib/active_record/railties/databases.rake +24 -38
- data/lib/active_record/relation.rb +3 -2
- data/lib/active_record/relation/batches.rb +24 -9
- data/lib/active_record/relation/finder_methods.rb +100 -11
- data/lib/active_record/relation/query_methods.rb +39 -27
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +7 -5
- data/lib/active_record/scoping.rb +5 -0
- data/lib/active_record/scoping/named.rb +6 -0
- data/lib/active_record/store.rb +1 -1
- data/lib/active_record/tasks/database_tasks.rb +45 -23
- data/lib/active_record/timestamp.rb +2 -2
- data/lib/active_record/transactions.rb +7 -7
- data/lib/active_record/validations/presence.rb +1 -1
- data/lib/active_record/version.rb +1 -1
- metadata +5 -6
- data/lib/active_record/associations/join_helper.rb +0 -36
@@ -43,7 +43,9 @@ module ActiveRecord
|
|
43
43
|
# SQLite does not understand dates, so this method will convert a Date
|
44
44
|
# to a String.
|
45
45
|
def type_cast(value, column)
|
46
|
-
|
46
|
+
if value.respond_to?(:quoted_id) && value.respond_to?(:id)
|
47
|
+
return value.id
|
48
|
+
end
|
47
49
|
|
48
50
|
case value
|
49
51
|
when String, ActiveSupport::Multibyte::Chars
|
@@ -120,9 +120,9 @@ module ActiveRecord
|
|
120
120
|
# The name of the primary key, if one is to be added automatically.
|
121
121
|
# Defaults to +id+. If <tt>:id</tt> is false this option is ignored.
|
122
122
|
#
|
123
|
-
#
|
124
|
-
#
|
125
|
-
#
|
123
|
+
# Note that Active Record models will automatically detect their
|
124
|
+
# primary key. This can be avoided by using +self.primary_key=+ on the model
|
125
|
+
# to define the key explicitly.
|
126
126
|
#
|
127
127
|
# [<tt>:options</tt>]
|
128
128
|
# Any extra options you want appended to the table definition.
|
@@ -714,7 +714,7 @@ module ActiveRecord
|
|
714
714
|
# require the order columns appear in the SELECT.
|
715
715
|
#
|
716
716
|
# columns_for_distinct("posts.id", ["posts.created_at desc"])
|
717
|
-
def columns_for_distinct(columns, orders)
|
717
|
+
def columns_for_distinct(columns, orders) #:nodoc:
|
718
718
|
columns
|
719
719
|
end
|
720
720
|
|
@@ -736,6 +736,10 @@ module ActiveRecord
|
|
736
736
|
remove_column table_name, :created_at
|
737
737
|
end
|
738
738
|
|
739
|
+
def update_table_definition(table_name, base) #:nodoc:
|
740
|
+
Table.new(table_name, base)
|
741
|
+
end
|
742
|
+
|
739
743
|
protected
|
740
744
|
def add_index_sort_order(option_strings, column_names, options = {})
|
741
745
|
if options.is_a?(Hash) && order = options[:order]
|
@@ -848,10 +852,6 @@ module ActiveRecord
|
|
848
852
|
def create_alter_table(name)
|
849
853
|
AlterTable.new create_table_definition(name, false, {})
|
850
854
|
end
|
851
|
-
|
852
|
-
def update_table_definition(table_name, base)
|
853
|
-
Table.new(table_name, base)
|
854
|
-
end
|
855
855
|
end
|
856
856
|
end
|
857
857
|
end
|
@@ -262,6 +262,12 @@ module ActiveRecord
|
|
262
262
|
def active?
|
263
263
|
end
|
264
264
|
|
265
|
+
# Adapter should redefine this if it needs a threadsafe way to approximate
|
266
|
+
# if the connection is active
|
267
|
+
def active_threadsafe?
|
268
|
+
active?
|
269
|
+
end
|
270
|
+
|
265
271
|
# Disconnects from the database if already connected, and establishes a
|
266
272
|
# new connection with the database. Implementors should call super if they
|
267
273
|
# override the default implementation.
|
@@ -349,6 +355,14 @@ module ActiveRecord
|
|
349
355
|
|
350
356
|
protected
|
351
357
|
|
358
|
+
def translate_exception_class(e, sql)
|
359
|
+
message = "#{e.class.name}: #{e.message}: #{sql}"
|
360
|
+
@logger.error message if @logger
|
361
|
+
exception = translate_exception(e, message)
|
362
|
+
exception.set_backtrace e.backtrace
|
363
|
+
exception
|
364
|
+
end
|
365
|
+
|
352
366
|
def log(sql, name = "SQL", binds = [], statement_name = nil)
|
353
367
|
@instrumenter.instrument(
|
354
368
|
"sql.active_record",
|
@@ -358,11 +372,7 @@ module ActiveRecord
|
|
358
372
|
:statement_name => statement_name,
|
359
373
|
:binds => binds) { yield }
|
360
374
|
rescue => e
|
361
|
-
|
362
|
-
@logger.error message if @logger
|
363
|
-
exception = translate_exception(e, message)
|
364
|
-
exception.set_backtrace e.backtrace
|
365
|
-
raise exception
|
375
|
+
raise translate_exception_class(e, sql)
|
366
376
|
end
|
367
377
|
|
368
378
|
def translate_exception(exception, message)
|
@@ -298,11 +298,7 @@ module ActiveRecord
|
|
298
298
|
|
299
299
|
# Executes the SQL statement in the context of this connection.
|
300
300
|
def execute(sql, name = nil)
|
301
|
-
|
302
|
-
@connection.query(sql)
|
303
|
-
else
|
304
|
-
log(sql, name) { @connection.query(sql) }
|
305
|
-
end
|
301
|
+
log(sql, name) { @connection.query(sql) }
|
306
302
|
end
|
307
303
|
|
308
304
|
# MysqlAdapter has to free a result after using it, so we use this method to write
|
@@ -775,7 +771,7 @@ module ActiveRecord
|
|
775
771
|
end.compact.join(', ')
|
776
772
|
|
777
773
|
# ...and send them all in one query
|
778
|
-
|
774
|
+
@connection.query "SET #{encoding} #{variable_assignments}"
|
779
775
|
end
|
780
776
|
end
|
781
777
|
end
|
@@ -13,41 +13,146 @@ module ActiveRecord
|
|
13
13
|
@config = original.config.dup
|
14
14
|
end
|
15
15
|
|
16
|
+
# Expands a connection string into a hash.
|
17
|
+
class ConnectionUrlResolver # :nodoc:
|
18
|
+
|
19
|
+
# == Example
|
20
|
+
#
|
21
|
+
# url = "postgresql://foo:bar@localhost:9000/foo_test?pool=5&timeout=3000"
|
22
|
+
# ConnectionUrlResolver.new(url).to_hash
|
23
|
+
# # => {
|
24
|
+
# "adapter" => "postgresql",
|
25
|
+
# "host" => "localhost",
|
26
|
+
# "port" => 9000,
|
27
|
+
# "database" => "foo_test",
|
28
|
+
# "username" => "foo",
|
29
|
+
# "password" => "bar",
|
30
|
+
# "pool" => "5",
|
31
|
+
# "timeout" => "3000"
|
32
|
+
# }
|
33
|
+
def initialize(url)
|
34
|
+
raise "Database URL cannot be empty" if url.blank?
|
35
|
+
@uri = URI.parse(url)
|
36
|
+
@adapter = @uri.scheme
|
37
|
+
@adapter = "postgresql" if @adapter == "postgres"
|
38
|
+
@query = @uri.query || ''
|
39
|
+
end
|
40
|
+
|
41
|
+
# Converts the given URL to a full connection hash.
|
42
|
+
def to_hash
|
43
|
+
config = raw_config.reject { |_,value| value.blank? }
|
44
|
+
config.map { |key,value| config[key] = uri_parser.unescape(value) if value.is_a? String }
|
45
|
+
config
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def uri
|
51
|
+
@uri
|
52
|
+
end
|
53
|
+
|
54
|
+
def uri_parser
|
55
|
+
@uri_parser ||= URI::Parser.new
|
56
|
+
end
|
57
|
+
|
58
|
+
# Converts the query parameters of the URI into a hash.
|
59
|
+
#
|
60
|
+
# "localhost?pool=5&reap_frequency=2"
|
61
|
+
# # => { "pool" => "5", "reap_frequency" => "2" }
|
62
|
+
#
|
63
|
+
# returns empty hash if no query present.
|
64
|
+
#
|
65
|
+
# "localhost"
|
66
|
+
# # => {}
|
67
|
+
def query_hash
|
68
|
+
Hash[@query.split("&").map { |pair| pair.split("=") }]
|
69
|
+
end
|
70
|
+
|
71
|
+
def raw_config
|
72
|
+
query_hash.merge({
|
73
|
+
"adapter" => @adapter,
|
74
|
+
"username" => uri.user,
|
75
|
+
"password" => uri.password,
|
76
|
+
"port" => uri.port,
|
77
|
+
"database" => database,
|
78
|
+
"host" => uri.host })
|
79
|
+
end
|
80
|
+
|
81
|
+
# Returns name of the database.
|
82
|
+
# Sqlite3 expects this to be a full path or `:memory:`.
|
83
|
+
def database
|
84
|
+
if @adapter == 'sqlite3'
|
85
|
+
if '/:memory:' == uri.path
|
86
|
+
':memory:'
|
87
|
+
else
|
88
|
+
uri.path
|
89
|
+
end
|
90
|
+
else
|
91
|
+
uri.path.sub(%r{^/},"")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
16
96
|
##
|
17
|
-
# Builds a ConnectionSpecification from user input
|
97
|
+
# Builds a ConnectionSpecification from user input.
|
18
98
|
class Resolver # :nodoc:
|
19
|
-
attr_reader :
|
99
|
+
attr_reader :configurations
|
20
100
|
|
21
|
-
|
22
|
-
|
101
|
+
# Accepts a hash two layers deep, keys on the first layer represent
|
102
|
+
# environments such as "production". Keys must be strings.
|
103
|
+
def initialize(configurations)
|
23
104
|
@configurations = configurations
|
24
105
|
end
|
25
106
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
107
|
+
# Returns a hash with database connection information.
|
108
|
+
#
|
109
|
+
# == Examples
|
110
|
+
#
|
111
|
+
# Full hash Configuration.
|
112
|
+
#
|
113
|
+
# configurations = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
|
114
|
+
# Resolver.new(configurations).resolve(:production)
|
115
|
+
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3"}
|
116
|
+
#
|
117
|
+
# Initialized with URL configuration strings.
|
118
|
+
#
|
119
|
+
# configurations = { "production" => "postgresql://localhost/foo" }
|
120
|
+
# Resolver.new(configurations).resolve(:production)
|
121
|
+
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
|
122
|
+
#
|
123
|
+
def resolve(config)
|
124
|
+
if config
|
125
|
+
resolve_connection config
|
126
|
+
elsif env = ActiveRecord::ConnectionHandling::RAILS_ENV.call
|
127
|
+
resolve_env_connection env.to_sym
|
128
|
+
else
|
129
|
+
raise AdapterNotSpecified
|
35
130
|
end
|
36
131
|
end
|
37
132
|
|
38
|
-
|
39
|
-
def
|
40
|
-
|
41
|
-
|
133
|
+
# Expands each key in @configurations hash into fully resolved hash
|
134
|
+
def resolve_all
|
135
|
+
config = configurations.dup
|
136
|
+
config.each do |key, value|
|
137
|
+
config[key] = resolve(value) if value
|
42
138
|
end
|
43
|
-
|
44
|
-
raise(AdapterNotSpecified, "#{spec} database is not configured") unless hash
|
45
|
-
|
46
|
-
resolve_hash_connection hash
|
139
|
+
config
|
47
140
|
end
|
48
141
|
|
49
|
-
|
50
|
-
|
142
|
+
# Returns an instance of ConnectionSpecification for a given adapter.
|
143
|
+
# Accepts a hash one layer deep that contains all connection information.
|
144
|
+
#
|
145
|
+
# == Example
|
146
|
+
#
|
147
|
+
# config = { "production" => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" } }
|
148
|
+
# spec = Resolver.new(config).spec(:production)
|
149
|
+
# spec.adapter_method
|
150
|
+
# # => "sqlite3"
|
151
|
+
# spec.config
|
152
|
+
# # => { "host" => "localhost", "database" => "foo", "adapter" => "sqlite3" }
|
153
|
+
#
|
154
|
+
def spec(config)
|
155
|
+
spec = resolve(config).symbolize_keys
|
51
156
|
|
52
157
|
raise(AdapterNotSpecified, "database configuration does not specify adapter") unless spec.key?(:adapter)
|
53
158
|
|
@@ -61,35 +166,87 @@ module ActiveRecord
|
|
61
166
|
end
|
62
167
|
|
63
168
|
adapter_method = "#{spec[:adapter]}_connection"
|
64
|
-
|
65
169
|
ConnectionSpecification.new(spec, adapter_method)
|
66
170
|
end
|
67
171
|
|
68
|
-
|
69
|
-
config = URI.parse url
|
70
|
-
adapter = config.scheme
|
71
|
-
adapter = "postgresql" if adapter == "postgres"
|
72
|
-
spec = { :adapter => adapter,
|
73
|
-
:username => config.user,
|
74
|
-
:password => config.password,
|
75
|
-
:port => config.port,
|
76
|
-
:database => config.path.sub(%r{^/},""),
|
77
|
-
:host => config.host }
|
78
|
-
|
79
|
-
spec.reject!{ |_,value| value.blank? }
|
80
|
-
|
81
|
-
uri_parser = URI::Parser.new
|
82
|
-
|
83
|
-
spec.map { |key,value| spec[key] = uri_parser.unescape(value) if value.is_a?(String) }
|
172
|
+
private
|
84
173
|
|
85
|
-
|
86
|
-
|
174
|
+
# Returns fully resolved connection, accepts hash, string or symbol.
|
175
|
+
# Always returns a hash.
|
176
|
+
#
|
177
|
+
# == Examples
|
178
|
+
#
|
179
|
+
# Symbol representing current environment.
|
180
|
+
#
|
181
|
+
# Resolver.new("production" => {}).resolve_connection(:production)
|
182
|
+
# # => {}
|
183
|
+
#
|
184
|
+
# One layer deep hash of connection values.
|
185
|
+
#
|
186
|
+
# Resolver.new({}).resolve_connection("adapter" => "sqlite3")
|
187
|
+
# # => { "adapter" => "sqlite3" }
|
188
|
+
#
|
189
|
+
# Connection URL.
|
190
|
+
#
|
191
|
+
# Resolver.new({}).resolve_connection("postgresql://localhost/foo")
|
192
|
+
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
|
193
|
+
#
|
194
|
+
def resolve_connection(spec)
|
195
|
+
case spec
|
196
|
+
when Symbol, String
|
197
|
+
resolve_env_connection spec
|
198
|
+
when Hash
|
199
|
+
resolve_hash_connection spec
|
200
|
+
end
|
201
|
+
end
|
87
202
|
|
88
|
-
|
203
|
+
# Takes the environment such as `:production` or `:development`.
|
204
|
+
# This requires that the @configurations was initialized with a key that
|
205
|
+
# matches.
|
206
|
+
#
|
207
|
+
#
|
208
|
+
# Resolver.new("production" => {}).resolve_env_connection(:production)
|
209
|
+
# # => {}
|
210
|
+
#
|
211
|
+
# Takes a connection URL.
|
212
|
+
#
|
213
|
+
# Resolver.new({}).resolve_env_connection("postgresql://localhost/foo")
|
214
|
+
# # => { "host" => "localhost", "database" => "foo", "adapter" => "postgresql" }
|
215
|
+
#
|
216
|
+
def resolve_env_connection(spec)
|
217
|
+
# Rails has historically accepted a string to mean either
|
218
|
+
# an environment key or a URL spec, so we have deprecated
|
219
|
+
# this ambiguous behaviour and in the future this function
|
220
|
+
# can be removed in favor of resolve_string_connection and
|
221
|
+
# resolve_symbol_connection.
|
222
|
+
if config = configurations[spec.to_s]
|
223
|
+
if spec.is_a?(String)
|
224
|
+
ActiveSupport::Deprecation.warn "Passing a string to ActiveRecord::Base.establish_connection " \
|
225
|
+
"for a configuration lookup is deprecated, please pass a symbol (#{spec.to_sym.inspect}) instead"
|
226
|
+
end
|
227
|
+
resolve_connection(config)
|
228
|
+
elsif spec.is_a?(String)
|
229
|
+
resolve_string_connection(spec)
|
230
|
+
else
|
231
|
+
raise(AdapterNotSpecified, "'#{spec}' database is not configured. Available configuration: #{configurations.inspect}")
|
89
232
|
end
|
233
|
+
end
|
90
234
|
|
235
|
+
# Accepts a hash. Expands the "url" key that contains a
|
236
|
+
# URL database connection to a full connection
|
237
|
+
# hash and merges with the rest of the hash.
|
238
|
+
# Connection details inside of the "url" key win any merge conflicts
|
239
|
+
def resolve_hash_connection(spec)
|
240
|
+
if url = spec.delete("url")
|
241
|
+
connection_hash = resolve_string_connection(url)
|
242
|
+
spec.merge!(connection_hash)
|
243
|
+
end
|
91
244
|
spec
|
92
245
|
end
|
246
|
+
|
247
|
+
def resolve_string_connection(url)
|
248
|
+
ConnectionUrlResolver.new(url).to_hash
|
249
|
+
end
|
93
250
|
end
|
94
251
|
end
|
95
252
|
end
|
@@ -18,6 +18,12 @@ module ActiveRecord
|
|
18
18
|
client = Mysql2::Client.new(config)
|
19
19
|
options = [config[:host], config[:username], config[:password], config[:database], config[:port], config[:socket], 0]
|
20
20
|
ConnectionAdapters::Mysql2Adapter.new(client, logger, options, config)
|
21
|
+
rescue Mysql2::Error => error
|
22
|
+
if error.message.include?("Unknown database")
|
23
|
+
raise ActiveRecord::NoDatabaseError.new(error.message)
|
24
|
+
else
|
25
|
+
raise error
|
26
|
+
end
|
21
27
|
end
|
22
28
|
end
|
23
29
|
|
@@ -207,7 +213,7 @@ module ActiveRecord
|
|
207
213
|
|
208
214
|
# Returns an array of arrays containing the field values.
|
209
215
|
# Order is the same as that returned by +columns+.
|
210
|
-
def select_rows(sql, name = nil)
|
216
|
+
def select_rows(sql, name = nil, binds = [])
|
211
217
|
execute(sql, name).to_a
|
212
218
|
end
|
213
219
|
|
@@ -34,6 +34,12 @@ module ActiveRecord
|
|
34
34
|
default_flags |= Mysql::CLIENT_FOUND_ROWS if Mysql.const_defined?(:CLIENT_FOUND_ROWS)
|
35
35
|
options = [host, username, password, database, port, socket, default_flags]
|
36
36
|
ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config)
|
37
|
+
rescue Mysql::Error => error
|
38
|
+
if error.message.include?("Unknown database")
|
39
|
+
raise ActiveRecord::NoDatabaseError.new(error.message)
|
40
|
+
else
|
41
|
+
raise error
|
42
|
+
end
|
37
43
|
end
|
38
44
|
end
|
39
45
|
|
@@ -207,9 +213,9 @@ module ActiveRecord
|
|
207
213
|
|
208
214
|
# DATABASE STATEMENTS ======================================
|
209
215
|
|
210
|
-
def select_rows(sql, name = nil)
|
216
|
+
def select_rows(sql, name = nil, binds = [])
|
211
217
|
@connection.query_with_result = true
|
212
|
-
rows = exec_query(sql, name).rows
|
218
|
+
rows = exec_query(sql, name, binds).rows
|
213
219
|
@connection.more_results && @connection.next_result # invoking stored procedures with CLIENT_MULTI_RESULTS requires this to tidy up else connection will be dropped
|
214
220
|
rows
|
215
221
|
end
|