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.

Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +622 -9
  3. data/MIT-LICENSE +1 -1
  4. data/lib/active_record.rb +1 -1
  5. data/lib/active_record/associations.rb +10 -7
  6. data/lib/active_record/associations/alias_tracker.rb +39 -29
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/association_scope.rb +56 -31
  9. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -0
  10. data/lib/active_record/associations/builder/association.rb +6 -0
  11. data/lib/active_record/associations/builder/belongs_to.rb +1 -1
  12. data/lib/active_record/associations/collection_association.rb +33 -9
  13. data/lib/active_record/associations/collection_proxy.rb +53 -5
  14. data/lib/active_record/associations/has_many_association.rb +1 -1
  15. data/lib/active_record/associations/join_dependency.rb +5 -5
  16. data/lib/active_record/associations/join_dependency/join_association.rb +8 -8
  17. data/lib/active_record/associations/preloader.rb +1 -1
  18. data/lib/active_record/associations/singular_association.rb +1 -1
  19. data/lib/active_record/attribute_methods.rb +28 -5
  20. data/lib/active_record/attribute_methods/dirty.rb +27 -4
  21. data/lib/active_record/attribute_methods/read.rb +1 -1
  22. data/lib/active_record/attribute_methods/serialization.rb +18 -0
  23. data/lib/active_record/autosave_association.rb +1 -1
  24. data/lib/active_record/base.rb +1 -1
  25. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +2 -2
  26. data/lib/active_record/connection_adapters/abstract/database_statements.rb +16 -9
  27. data/lib/active_record/connection_adapters/abstract/quoting.rb +3 -1
  28. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +8 -8
  29. data/lib/active_record/connection_adapters/abstract/transaction.rb +4 -0
  30. data/lib/active_record/connection_adapters/abstract_adapter.rb +15 -5
  31. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +2 -6
  32. data/lib/active_record/connection_adapters/connection_specification.rb +200 -43
  33. data/lib/active_record/connection_adapters/mysql2_adapter.rb +7 -1
  34. data/lib/active_record/connection_adapters/mysql_adapter.rb +8 -2
  35. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +3 -2
  36. data/lib/active_record/connection_adapters/postgresql/cast.rb +7 -7
  37. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +2 -2
  38. data/lib/active_record/connection_adapters/postgresql/quoting.rb +1 -1
  39. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +13 -0
  40. data/lib/active_record/connection_adapters/postgresql_adapter.rb +32 -17
  41. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +25 -3
  42. data/lib/active_record/connection_handling.rb +64 -3
  43. data/lib/active_record/core.rb +28 -24
  44. data/lib/active_record/dynamic_matchers.rb +6 -2
  45. data/lib/active_record/enum.rb +111 -17
  46. data/lib/active_record/errors.rb +12 -0
  47. data/lib/active_record/fixtures.rb +13 -15
  48. data/lib/active_record/inheritance.rb +29 -9
  49. data/lib/active_record/integration.rb +4 -2
  50. data/lib/active_record/migration.rb +20 -7
  51. data/lib/active_record/migration/command_recorder.rb +18 -6
  52. data/lib/active_record/persistence.rb +10 -5
  53. data/lib/active_record/querying.rb +1 -0
  54. data/lib/active_record/railtie.rb +11 -8
  55. data/lib/active_record/railties/databases.rake +24 -38
  56. data/lib/active_record/relation.rb +3 -2
  57. data/lib/active_record/relation/batches.rb +24 -9
  58. data/lib/active_record/relation/finder_methods.rb +100 -11
  59. data/lib/active_record/relation/query_methods.rb +39 -27
  60. data/lib/active_record/result.rb +1 -1
  61. data/lib/active_record/sanitization.rb +7 -5
  62. data/lib/active_record/scoping.rb +5 -0
  63. data/lib/active_record/scoping/named.rb +6 -0
  64. data/lib/active_record/store.rb +1 -1
  65. data/lib/active_record/tasks/database_tasks.rb +45 -23
  66. data/lib/active_record/timestamp.rb +2 -2
  67. data/lib/active_record/transactions.rb +7 -7
  68. data/lib/active_record/validations/presence.rb +1 -1
  69. data/lib/active_record/version.rb +1 -1
  70. metadata +5 -6
  71. 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
- return value.id if value.respond_to?(:quoted_id)
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
- # Also note that this just sets the primary key in the table. You additionally
124
- # need to configure the primary key in the model via +self.primary_key=+.
125
- # Models do NOT auto-detect the primary key from their table definition.
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) # :nodoc:
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
@@ -23,6 +23,10 @@ module ActiveRecord
23
23
  @parent = nil
24
24
  end
25
25
 
26
+ def finalized?
27
+ @state
28
+ end
29
+
26
30
  def committed?
27
31
  @state == :committed
28
32
  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
- message = "#{e.class.name}: #{e.message}: #{sql}"
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
- if name == :skip_logging
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
- execute("SET #{encoding} #{variable_assignments}", :skip_logging)
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 :config, :klass, :configurations
99
+ attr_reader :configurations
20
100
 
21
- def initialize(config, configurations)
22
- @config = config
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
- def spec
27
- case config
28
- when nil
29
- raise AdapterNotSpecified unless defined?(Rails.env)
30
- resolve_string_connection Rails.env
31
- when Symbol, String
32
- resolve_string_connection config.to_s
33
- when Hash
34
- resolve_hash_connection config
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
- private
39
- def resolve_string_connection(spec) # :nodoc:
40
- hash = configurations.fetch(spec) do |k|
41
- connection_url_to_hash(k)
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
- def resolve_hash_connection(spec) # :nodoc:
50
- spec = spec.symbolize_keys
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
- def connection_url_to_hash(url) # :nodoc:
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
- if config.query
86
- options = Hash[config.query.split("&").map{ |pair| pair.split("=") }].symbolize_keys
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
- spec.merge!(options)
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