activerecord 4.1.16 → 4.2.0.beta1

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +634 -2185
  3. data/README.rdoc +15 -10
  4. data/lib/active_record.rb +2 -1
  5. data/lib/active_record/aggregations.rb +12 -8
  6. data/lib/active_record/associations.rb +58 -33
  7. data/lib/active_record/associations/association.rb +1 -1
  8. data/lib/active_record/associations/association_scope.rb +53 -21
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +16 -5
  11. data/lib/active_record/associations/builder/belongs_to.rb +7 -29
  12. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +2 -11
  13. data/lib/active_record/associations/builder/has_one.rb +2 -2
  14. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  15. data/lib/active_record/associations/collection_association.rb +32 -44
  16. data/lib/active_record/associations/collection_proxy.rb +1 -10
  17. data/lib/active_record/associations/has_many_association.rb +60 -14
  18. data/lib/active_record/associations/has_many_through_association.rb +34 -23
  19. data/lib/active_record/associations/has_one_association.rb +0 -1
  20. data/lib/active_record/associations/join_dependency.rb +7 -9
  21. data/lib/active_record/associations/join_dependency/join_association.rb +18 -14
  22. data/lib/active_record/associations/preloader.rb +2 -2
  23. data/lib/active_record/associations/preloader/association.rb +9 -5
  24. data/lib/active_record/associations/preloader/through_association.rb +3 -3
  25. data/lib/active_record/associations/singular_association.rb +16 -1
  26. data/lib/active_record/associations/through_association.rb +6 -22
  27. data/lib/active_record/attribute.rb +131 -0
  28. data/lib/active_record/attribute_assignment.rb +19 -11
  29. data/lib/active_record/attribute_decorators.rb +66 -0
  30. data/lib/active_record/attribute_methods.rb +53 -90
  31. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  32. data/lib/active_record/attribute_methods/dirty.rb +85 -42
  33. data/lib/active_record/attribute_methods/primary_key.rb +6 -8
  34. data/lib/active_record/attribute_methods/read.rb +14 -57
  35. data/lib/active_record/attribute_methods/serialization.rb +12 -146
  36. data/lib/active_record/attribute_methods/time_zone_conversion.rb +32 -40
  37. data/lib/active_record/attribute_methods/write.rb +8 -23
  38. data/lib/active_record/attribute_set.rb +77 -0
  39. data/lib/active_record/attribute_set/builder.rb +32 -0
  40. data/lib/active_record/attributes.rb +122 -0
  41. data/lib/active_record/autosave_association.rb +11 -21
  42. data/lib/active_record/base.rb +9 -19
  43. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +69 -45
  44. data/lib/active_record/connection_adapters/abstract/database_statements.rb +22 -42
  45. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -60
  46. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +37 -2
  47. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +102 -21
  48. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +9 -33
  49. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +178 -55
  50. data/lib/active_record/connection_adapters/abstract/transaction.rb +120 -115
  51. data/lib/active_record/connection_adapters/abstract_adapter.rb +143 -57
  52. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +156 -107
  53. data/lib/active_record/connection_adapters/column.rb +13 -244
  54. data/lib/active_record/connection_adapters/connection_specification.rb +6 -20
  55. data/lib/active_record/connection_adapters/mysql2_adapter.rb +12 -15
  56. data/lib/active_record/connection_adapters/mysql_adapter.rb +55 -143
  57. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  58. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  59. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -20
  60. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -388
  61. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +96 -0
  62. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  63. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  64. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  65. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  66. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  67. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +76 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +85 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +26 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  86. data/lib/active_record/connection_adapters/postgresql/quoting.rb +42 -122
  87. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  88. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +154 -0
  89. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +86 -34
  90. data/lib/active_record/connection_adapters/postgresql/utils.rb +66 -0
  91. data/lib/active_record/connection_adapters/postgresql_adapter.rb +188 -452
  92. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  93. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +54 -47
  94. data/lib/active_record/connection_handling.rb +1 -1
  95. data/lib/active_record/core.rb +119 -22
  96. data/lib/active_record/counter_cache.rb +60 -6
  97. data/lib/active_record/enum.rb +9 -10
  98. data/lib/active_record/errors.rb +27 -26
  99. data/lib/active_record/explain.rb +1 -1
  100. data/lib/active_record/fixtures.rb +52 -45
  101. data/lib/active_record/gem_version.rb +3 -3
  102. data/lib/active_record/inheritance.rb +33 -8
  103. data/lib/active_record/integration.rb +4 -4
  104. data/lib/active_record/locking/optimistic.rb +34 -16
  105. data/lib/active_record/migration.rb +22 -32
  106. data/lib/active_record/migration/command_recorder.rb +19 -2
  107. data/lib/active_record/migration/join_table.rb +1 -1
  108. data/lib/active_record/model_schema.rb +39 -48
  109. data/lib/active_record/nested_attributes.rb +8 -18
  110. data/lib/active_record/persistence.rb +39 -22
  111. data/lib/active_record/query_cache.rb +3 -3
  112. data/lib/active_record/querying.rb +1 -8
  113. data/lib/active_record/railtie.rb +17 -10
  114. data/lib/active_record/railties/databases.rake +47 -42
  115. data/lib/active_record/readonly_attributes.rb +0 -1
  116. data/lib/active_record/reflection.rb +225 -92
  117. data/lib/active_record/relation.rb +35 -11
  118. data/lib/active_record/relation/batches.rb +0 -2
  119. data/lib/active_record/relation/calculations.rb +28 -32
  120. data/lib/active_record/relation/delegation.rb +1 -1
  121. data/lib/active_record/relation/finder_methods.rb +42 -20
  122. data/lib/active_record/relation/merger.rb +0 -1
  123. data/lib/active_record/relation/predicate_builder.rb +1 -22
  124. data/lib/active_record/relation/predicate_builder/array_handler.rb +16 -11
  125. data/lib/active_record/relation/predicate_builder/relation_handler.rb +0 -4
  126. data/lib/active_record/relation/query_methods.rb +98 -62
  127. data/lib/active_record/relation/spawn_methods.rb +6 -7
  128. data/lib/active_record/result.rb +16 -9
  129. data/lib/active_record/sanitization.rb +8 -1
  130. data/lib/active_record/schema.rb +0 -1
  131. data/lib/active_record/schema_dumper.rb +51 -9
  132. data/lib/active_record/schema_migration.rb +4 -0
  133. data/lib/active_record/scoping/default.rb +5 -4
  134. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  135. data/lib/active_record/statement_cache.rb +79 -5
  136. data/lib/active_record/store.rb +5 -5
  137. data/lib/active_record/tasks/database_tasks.rb +37 -5
  138. data/lib/active_record/tasks/mysql_database_tasks.rb +10 -16
  139. data/lib/active_record/tasks/postgresql_database_tasks.rb +2 -2
  140. data/lib/active_record/timestamp.rb +9 -7
  141. data/lib/active_record/transactions.rb +35 -21
  142. data/lib/active_record/type.rb +20 -0
  143. data/lib/active_record/type/binary.rb +40 -0
  144. data/lib/active_record/type/boolean.rb +19 -0
  145. data/lib/active_record/type/date.rb +46 -0
  146. data/lib/active_record/type/date_time.rb +43 -0
  147. data/lib/active_record/type/decimal.rb +40 -0
  148. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  149. data/lib/active_record/type/float.rb +19 -0
  150. data/lib/active_record/type/hash_lookup_type_map.rb +19 -0
  151. data/lib/active_record/type/integer.rb +23 -0
  152. data/lib/active_record/type/mutable.rb +16 -0
  153. data/lib/active_record/type/numeric.rb +36 -0
  154. data/lib/active_record/type/serialized.rb +51 -0
  155. data/lib/active_record/type/string.rb +36 -0
  156. data/lib/active_record/type/text.rb +11 -0
  157. data/lib/active_record/type/time.rb +26 -0
  158. data/lib/active_record/type/time_value.rb +38 -0
  159. data/lib/active_record/type/type_map.rb +48 -0
  160. data/lib/active_record/type/value.rb +101 -0
  161. data/lib/active_record/validations.rb +21 -16
  162. data/lib/active_record/validations/uniqueness.rb +9 -23
  163. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  164. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  165. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  166. metadata +71 -14
  167. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,4 +1,3 @@
1
-
2
1
  module ActiveRecord
3
2
  module ConnectionAdapters
4
3
  class SchemaCache
@@ -12,15 +11,15 @@ module ActiveRecord
12
11
  @columns_hash = {}
13
12
  @primary_keys = {}
14
13
  @tables = {}
15
- prepare_default_proc
16
14
  end
17
15
 
18
16
  def primary_keys(table_name)
19
- @primary_keys[table_name]
17
+ @primary_keys[table_name] ||= table_exists?(table_name) ? connection.primary_key(table_name) : nil
20
18
  end
21
19
 
22
20
  # A cached lookup for table existence.
23
21
  def table_exists?(name)
22
+ prepare_tables if @tables.empty?
24
23
  return @tables[name] if @tables.key? name
25
24
 
26
25
  @tables[name] = connection.table_exists?(name)
@@ -29,9 +28,9 @@ module ActiveRecord
29
28
  # Add internal cache for table with +table_name+.
30
29
  def add(table_name)
31
30
  if table_exists?(table_name)
32
- @primary_keys[table_name]
33
- @columns[table_name]
34
- @columns_hash[table_name]
31
+ primary_keys(table_name)
32
+ columns(table_name)
33
+ columns_hash(table_name)
35
34
  end
36
35
  end
37
36
 
@@ -40,14 +39,16 @@ module ActiveRecord
40
39
  end
41
40
 
42
41
  # Get the columns for a table
43
- def columns(table)
44
- @columns[table]
42
+ def columns(table_name)
43
+ @columns[table_name] ||= connection.columns(table_name)
45
44
  end
46
45
 
47
46
  # Get the columns for a table as a hash, key is the column name
48
47
  # value is the column object.
49
- def columns_hash(table)
50
- @columns_hash[table]
48
+ def columns_hash(table_name)
49
+ @columns_hash[table_name] ||= Hash[columns(table_name).map { |col|
50
+ [col.name, col]
51
+ }]
51
52
  end
52
53
 
53
54
  # Clears out internal caches
@@ -76,33 +77,18 @@ module ActiveRecord
76
77
  def marshal_dump
77
78
  # if we get current version during initialization, it happens stack over flow.
78
79
  @version = ActiveRecord::Migrator.current_version
79
- [@version] + [@columns, @columns_hash, @primary_keys, @tables].map { |val|
80
- Hash[val]
81
- }
80
+ [@version, @columns, @columns_hash, @primary_keys, @tables]
82
81
  end
83
82
 
84
83
  def marshal_load(array)
85
84
  @version, @columns, @columns_hash, @primary_keys, @tables = array
86
- prepare_default_proc
87
85
  end
88
86
 
89
87
  private
90
88
 
91
- def prepare_default_proc
92
- @columns.default_proc = Proc.new do |h, table_name|
93
- h[table_name] = connection.columns(table_name)
94
- end
95
-
96
- @columns_hash.default_proc = Proc.new do |h, table_name|
97
- h[table_name] = Hash[columns(table_name).map { |col|
98
- [col.name, col]
99
- }]
89
+ def prepare_tables
90
+ connection.tables.each { |table| @tables[table] = true }
100
91
  end
101
-
102
- @primary_keys.default_proc = Proc.new do |h, table_name|
103
- h[table_name] = table_exists?(table_name) ? connection.primary_key(table_name) : nil
104
- end
105
- end
106
92
  end
107
93
  end
108
94
  end
@@ -14,9 +14,9 @@ module ActiveRecord
14
14
  raise ArgumentError, "No database file specified. Missing argument: database"
15
15
  end
16
16
 
17
- # Allow database path relative to Rails.root, but only if
18
- # the database path is not the special path that tells
19
- # Sqlite to build a database only in memory.
17
+ # Allow database path relative to Rails.root, but only if the database
18
+ # path is not the special path that tells sqlite to build a database only
19
+ # in memory.
20
20
  if ':memory:' != config[:database]
21
21
  config[:database] = File.expand_path(config[:database], Rails.root) if defined?(Rails.root)
22
22
  dirname = File.dirname(config[:database])
@@ -30,24 +30,32 @@ module ActiveRecord
30
30
 
31
31
  db.busy_timeout(ConnectionAdapters::SQLite3Adapter.type_cast_config_to_integer(config[:timeout])) if config[:timeout]
32
32
 
33
- ConnectionAdapters::SQLite3Adapter.new(db, logger, config)
33
+ ConnectionAdapters::SQLite3Adapter.new(db, logger, nil, config)
34
34
  rescue Errno::ENOENT => error
35
35
  if error.message.include?("No such file or directory")
36
- raise ActiveRecord::NoDatabaseError.new(error.message)
36
+ raise ActiveRecord::NoDatabaseError.new(error.message, error)
37
37
  else
38
- raise error
38
+ raise
39
39
  end
40
40
  end
41
41
  end
42
42
 
43
43
  module ConnectionAdapters #:nodoc:
44
- class SQLite3Column < Column #:nodoc:
45
- class << self
46
- def binary_to_string(value)
47
- if value.encoding != Encoding::ASCII_8BIT
48
- value = value.force_encoding(Encoding::ASCII_8BIT)
49
- end
50
- value
44
+ class SQLite3Binary < Type::Binary # :nodoc:
45
+ def cast_value(value)
46
+ if value.encoding != Encoding::ASCII_8BIT
47
+ value = value.force_encoding(Encoding::ASCII_8BIT)
48
+ end
49
+ value
50
+ end
51
+ end
52
+
53
+ class SQLite3String < Type::String # :nodoc:
54
+ def type_cast_for_database(value)
55
+ if value.is_a?(::String) && value.encoding == Encoding::ASCII_8BIT
56
+ value.encode(Encoding::UTF_8)
57
+ else
58
+ super
51
59
  end
52
60
  end
53
61
  end
@@ -63,13 +71,12 @@ module ActiveRecord
63
71
 
64
72
  NATIVE_DATABASE_TYPES = {
65
73
  primary_key: 'INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL',
66
- string: { name: "varchar", limit: 255 },
74
+ string: { name: "varchar" },
67
75
  text: { name: "text" },
68
76
  integer: { name: "integer" },
69
77
  float: { name: "float" },
70
78
  decimal: { name: "decimal" },
71
79
  datetime: { name: "datetime" },
72
- timestamp: { name: "datetime" },
73
80
  time: { name: "time" },
74
81
  date: { name: "date" },
75
82
  binary: { name: "blob" },
@@ -123,11 +130,7 @@ module ActiveRecord
123
130
  end
124
131
  end
125
132
 
126
- class BindSubstitution < Arel::Visitors::SQLite # :nodoc:
127
- include Arel::Visitors::BindVisitor
128
- end
129
-
130
- def initialize(connection, logger, config)
133
+ def initialize(connection, logger, connection_options, config)
131
134
  super(connection, logger)
132
135
 
133
136
  @active = nil
@@ -135,11 +138,12 @@ module ActiveRecord
135
138
  self.class.type_cast_config_to_integer(config.fetch(:statement_limit) { 1000 }))
136
139
  @config = config
137
140
 
141
+ @visitor = Arel::Visitors::SQLite.new self
142
+
138
143
  if self.class.type_cast_config_to_boolean(config.fetch(:prepared_statements) { true })
139
144
  @prepared_statements = true
140
- @visitor = Arel::Visitors::SQLite.new self
141
145
  else
142
- @visitor = unprepared_visitor
146
+ @prepared_statements = false
143
147
  end
144
148
  end
145
149
 
@@ -225,10 +229,19 @@ module ActiveRecord
225
229
 
226
230
  # QUOTING ==================================================
227
231
 
228
- def quote(value, column = nil)
229
- if value.kind_of?(String) && column && column.type == :binary
230
- s = value.unpack("H*")[0]
231
- "x'#{s}'"
232
+ def _quote(value) # :nodoc:
233
+ case value
234
+ when Type::Binary::Data
235
+ "x'#{value.hex}'"
236
+ else
237
+ super
238
+ end
239
+ end
240
+
241
+ def _type_cast(value) # :nodoc:
242
+ case value
243
+ when BigDecimal
244
+ value.to_f
232
245
  else
233
246
  super
234
247
  end
@@ -256,24 +269,11 @@ module ActiveRecord
256
269
  end
257
270
  end
258
271
 
259
- def type_cast(value, column) # :nodoc:
260
- return value.to_f if BigDecimal === value
261
- return super unless String === value
262
- return super unless column && value
263
-
264
- value = super
265
- if column.type == :string && value.encoding == Encoding::ASCII_8BIT
266
- logger.error "Binary data inserted for `string` type on column `#{column.name}`" if logger
267
- value = value.encode Encoding::UTF_8
268
- end
269
- value
270
- end
271
-
272
272
  # DATABASE STATEMENTS ======================================
273
273
 
274
274
  def explain(arel, binds = [])
275
275
  sql = "EXPLAIN QUERY PLAN #{to_sql(arel, binds)}"
276
- ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', binds))
276
+ ExplainPrettyPrinter.new.pp(exec_query(sql, 'EXPLAIN', []))
277
277
  end
278
278
 
279
279
  class ExplainPrettyPrinter
@@ -385,7 +385,7 @@ module ActiveRecord
385
385
  table_name && tables(nil, table_name).any?
386
386
  end
387
387
 
388
- # Returns an array of +SQLite3Column+ objects for the table specified by +table_name+.
388
+ # Returns an array of +Column+ objects for the table specified by +table_name+.
389
389
  def columns(table_name) #:nodoc:
390
390
  table_structure(table_name).map do |field|
391
391
  case field["dflt_value"]
@@ -397,7 +397,9 @@ module ActiveRecord
397
397
  field["dflt_value"] = $1.gsub('""', '"')
398
398
  end
399
399
 
400
- SQLite3Column.new(field['name'], field['dflt_value'], field['type'], field['notnull'].to_i == 0)
400
+ sql_type = field['type']
401
+ cast_type = lookup_cast_type(sql_type)
402
+ new_column(field['name'], field['dflt_value'], cast_type, sql_type, field['notnull'].to_i == 0)
401
403
  end
402
404
  end
403
405
 
@@ -498,14 +500,19 @@ module ActiveRecord
498
500
  end
499
501
 
500
502
  def rename_column(table_name, column_name, new_column_name) #:nodoc:
501
- unless columns(table_name).detect{|c| c.name == column_name.to_s }
502
- raise ActiveRecord::ActiveRecordError, "Missing column #{table_name}.#{column_name}"
503
- end
504
- alter_table(table_name, :rename => {column_name.to_s => new_column_name.to_s})
505
- rename_column_indexes(table_name, column_name, new_column_name)
503
+ column = column_for(table_name, column_name)
504
+ alter_table(table_name, rename: {column.name => new_column_name.to_s})
505
+ rename_column_indexes(table_name, column.name, new_column_name)
506
506
  end
507
507
 
508
508
  protected
509
+
510
+ def initialize_type_map(m)
511
+ super
512
+ m.register_type(/binary/i, SQLite3Binary.new)
513
+ register_class_with_limit m, %r(char)i, SQLite3String
514
+ end
515
+
509
516
  def select(sql, name = nil, binds = []) #:nodoc:
510
517
  exec_query(sql, name, binds)
511
518
  end
@@ -1,6 +1,6 @@
1
1
  module ActiveRecord
2
2
  module ConnectionHandling
3
- RAILS_ENV = -> { (Rails.env if defined?(Rails)) || ENV["RAILS_ENV"] || ENV["RACK_ENV"] }
3
+ RAILS_ENV = -> { Rails.env if defined?(Rails) }
4
4
  DEFAULT_ENV = -> { RAILS_ENV.call || "default_env" }
5
5
 
6
6
  # Establishes the connection to the database. Accepts a hash as input where
@@ -16,7 +16,6 @@ module ActiveRecord
16
16
  mattr_accessor :logger, instance_writer: false
17
17
 
18
18
  ##
19
- # :singleton-method:
20
19
  # Contains the database configuration - as is typically stored in config/database.yml -
21
20
  # as a Hash.
22
21
  #
@@ -85,6 +84,7 @@ module ActiveRecord
85
84
  mattr_accessor :dump_schema_after_migration, instance_writer: false
86
85
  self.dump_schema_after_migration = true
87
86
 
87
+ # :nodoc:
88
88
  mattr_accessor :maintain_test_schema, instance_accessor: false
89
89
 
90
90
  def self.disable_implicit_join_references=(value)
@@ -93,6 +93,7 @@ module ActiveRecord
93
93
  end
94
94
 
95
95
  class_attribute :default_connection_handler, instance_writer: false
96
+ class_attribute :find_by_statement_cache
96
97
 
97
98
  def self.connection_handler
98
99
  ActiveRecord::RuntimeRegistry.connection_handler || default_connection_handler
@@ -106,7 +107,79 @@ module ActiveRecord
106
107
  end
107
108
 
108
109
  module ClassMethods
110
+ def allocate
111
+ define_attribute_methods
112
+ super
113
+ end
114
+
115
+ def initialize_find_by_cache
116
+ self.find_by_statement_cache = {}.extend(Mutex_m)
117
+ end
118
+
119
+ def inherited(child_class)
120
+ child_class.initialize_find_by_cache
121
+ super
122
+ end
123
+
124
+ def find(*ids)
125
+ # We don't have cache keys for this stuff yet
126
+ return super unless ids.length == 1
127
+ return super if block_given? ||
128
+ primary_key.nil? ||
129
+ default_scopes.any? ||
130
+ columns_hash.include?(inheritance_column) ||
131
+ ids.first.kind_of?(Array)
132
+
133
+ id = ids.first
134
+ if ActiveRecord::Base === id
135
+ id = id.id
136
+ ActiveSupport::Deprecation.warn "You are passing an instance of ActiveRecord::Base to `find`." \
137
+ "Please pass the id of the object by calling `.id`"
138
+ end
139
+ key = primary_key
140
+
141
+ s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
142
+ find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
143
+ where(key => params.bind).limit(1)
144
+ }
145
+ }
146
+ record = s.execute([id], self, connection).first
147
+ unless record
148
+ raise RecordNotFound, "Couldn't find #{name} with '#{primary_key}'=#{id}"
149
+ end
150
+ record
151
+ end
152
+
153
+ def find_by(*args)
154
+ return super if current_scope || args.length > 1 || reflect_on_all_aggregations.any?
155
+
156
+ hash = args.first
157
+
158
+ return super if hash.values.any? { |v|
159
+ v.nil? || Array === v || Hash === v
160
+ }
161
+
162
+ key = hash.keys
163
+
164
+ klass = self
165
+ s = find_by_statement_cache[key] || find_by_statement_cache.synchronize {
166
+ find_by_statement_cache[key] ||= StatementCache.create(connection) { |params|
167
+ wheres = key.each_with_object({}) { |param,o|
168
+ o[param] = params.bind
169
+ }
170
+ klass.where(wheres).limit(1)
171
+ }
172
+ }
173
+ begin
174
+ s.execute(hash.values, self, connection).first
175
+ rescue TypeError => e
176
+ raise ActiveRecord::StatementInvalid.new(e.message, e)
177
+ end
178
+ end
179
+
109
180
  def initialize_generated_modules
181
+ super
182
+
110
183
  generated_association_methods
111
184
  end
112
185
 
@@ -180,16 +253,12 @@ module ActiveRecord
180
253
  # # Instantiates a single new object
181
254
  # User.new(first_name: 'Jamie')
182
255
  def initialize(attributes = nil, options = {})
183
- defaults = self.class.column_defaults.dup
184
- defaults.each { |k, v| defaults[k] = v.dup if v.duplicable? }
185
-
186
- @attributes = self.class.initialize_attributes(defaults)
187
- @column_types_override = nil
188
- @column_types = self.class.column_types
256
+ @attributes = self.class.default_attributes.dup
189
257
 
190
258
  init_internals
191
259
  initialize_internals_callback
192
260
 
261
+ self.class.define_attribute_methods
193
262
  # +options+ argument is only needed to make protected_attributes gem easier to hook.
194
263
  # Remove it when we drop support to this gem.
195
264
  init_attributes(attributes, options) if attributes
@@ -209,13 +278,11 @@ module ActiveRecord
209
278
  # post.init_with('attributes' => { 'title' => 'hello world' })
210
279
  # post.title # => 'hello world'
211
280
  def init_with(coder)
212
- @attributes = self.class.initialize_attributes(coder['attributes'])
213
- @column_types_override = coder['column_types']
214
- @column_types = self.class.column_types
281
+ @attributes = coder['attributes']
215
282
 
216
283
  init_internals
217
284
 
218
- @new_record = false
285
+ @new_record = coder['new_record']
219
286
 
220
287
  self.class.define_attribute_methods
221
288
 
@@ -253,17 +320,13 @@ module ActiveRecord
253
320
 
254
321
  ##
255
322
  def initialize_dup(other) # :nodoc:
256
- cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
257
- self.class.initialize_attributes(cloned_attributes, :serialized => false)
258
-
259
- @attributes = cloned_attributes
260
- @attributes[self.class.primary_key] = nil
323
+ @attributes = @attributes.dup
324
+ @attributes.reset(self.class.primary_key)
261
325
 
262
326
  run_callbacks(:initialize) unless _initialize_callbacks.empty?
263
327
 
264
328
  @aggregation_cache = {}
265
329
  @association_cache = {}
266
- @attributes_cache = {}
267
330
 
268
331
  @new_record = true
269
332
  @destroyed = false
@@ -284,7 +347,10 @@ module ActiveRecord
284
347
  # Post.new.encode_with(coder)
285
348
  # coder # => {"attributes" => {"id" => nil, ... }}
286
349
  def encode_with(coder)
287
- coder['attributes'] = attributes_for_coder
350
+ # FIXME: Remove this when we better serialize attributes
351
+ coder['raw_attributes'] = attributes_before_type_cast
352
+ coder['attributes'] = @attributes
353
+ coder['new_record'] = new_record?
288
354
  end
289
355
 
290
356
  # Returns true if +comparison_object+ is the same exact object, or +comparison_object+
@@ -307,7 +373,11 @@ module ActiveRecord
307
373
  # Delegates to id in order to allow two records of the same type and id to work with something like:
308
374
  # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
309
375
  def hash
310
- id.hash
376
+ if id
377
+ id.hash
378
+ else
379
+ super
380
+ end
311
381
  end
312
382
 
313
383
  # Clone and freeze the attributes hash such that associations are still
@@ -363,6 +433,29 @@ module ActiveRecord
363
433
  "#<#{self.class} #{inspection}>"
364
434
  end
365
435
 
436
+ # Takes a PP and prettily prints this record to it, allowing you to get a nice result from `pp record`
437
+ # when pp is required.
438
+ def pretty_print(pp)
439
+ pp.object_address_group(self) do
440
+ if defined?(@attributes) && @attributes
441
+ column_names = self.class.column_names.select { |name| has_attribute?(name) || new_record? }
442
+ pp.seplist(column_names, proc { pp.text ',' }) do |column_name|
443
+ column_value = read_attribute(column_name)
444
+ pp.breakable ' '
445
+ pp.group(1) do
446
+ pp.text column_name
447
+ pp.text ':'
448
+ pp.breakable
449
+ pp.pp column_value
450
+ end
451
+ end
452
+ else
453
+ pp.breakable ' '
454
+ pp.text 'not initialized'
455
+ end
456
+ end
457
+ end
458
+
366
459
  # Returns a hash of the given methods with their names as keys and returned values as values.
367
460
  def slice(*methods)
368
461
  Hash[methods.map! { |method| [method, public_send(method)] }].with_indifferent_access
@@ -426,12 +519,10 @@ module ActiveRecord
426
519
  end
427
520
 
428
521
  def init_internals
429
- pk = self.class.primary_key
430
- @attributes[pk] = nil unless @attributes.key?(pk)
522
+ @attributes.ensure_initialized(self.class.primary_key)
431
523
 
432
524
  @aggregation_cache = {}
433
525
  @association_cache = {}
434
- @attributes_cache = {}
435
526
  @readonly = false
436
527
  @destroyed = false
437
528
  @marked_for_destruction = false
@@ -451,5 +542,11 @@ module ActiveRecord
451
542
  def init_attributes(attributes, options)
452
543
  assign_attributes(attributes)
453
544
  end
545
+
546
+ def thaw
547
+ if frozen?
548
+ @attributes = @attributes.dup
549
+ end
550
+ end
454
551
  end
455
552
  end