activerecord 4.1.0 → 4.2.0

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 (185) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +776 -1330
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +12 -8
  5. data/lib/active_record/association_relation.rb +4 -0
  6. data/lib/active_record/associations/alias_tracker.rb +14 -13
  7. data/lib/active_record/associations/association.rb +2 -2
  8. data/lib/active_record/associations/association_scope.rb +83 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +15 -5
  10. data/lib/active_record/associations/builder/association.rb +15 -4
  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 +9 -6
  13. data/lib/active_record/associations/builder/has_many.rb +1 -1
  14. data/lib/active_record/associations/builder/has_one.rb +2 -2
  15. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  16. data/lib/active_record/associations/collection_association.rb +66 -29
  17. data/lib/active_record/associations/collection_proxy.rb +22 -26
  18. data/lib/active_record/associations/has_many_association.rb +65 -18
  19. data/lib/active_record/associations/has_many_through_association.rb +55 -27
  20. data/lib/active_record/associations/has_one_association.rb +0 -1
  21. data/lib/active_record/associations/join_dependency/join_association.rb +19 -15
  22. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  23. data/lib/active_record/associations/join_dependency.rb +20 -12
  24. data/lib/active_record/associations/preloader/association.rb +34 -11
  25. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  26. data/lib/active_record/associations/preloader.rb +49 -59
  27. data/lib/active_record/associations/singular_association.rb +25 -4
  28. data/lib/active_record/associations/through_association.rb +23 -14
  29. data/lib/active_record/associations.rb +171 -42
  30. data/lib/active_record/attribute.rb +149 -0
  31. data/lib/active_record/attribute_assignment.rb +18 -10
  32. data/lib/active_record/attribute_decorators.rb +66 -0
  33. data/lib/active_record/attribute_methods/before_type_cast.rb +2 -2
  34. data/lib/active_record/attribute_methods/dirty.rb +98 -44
  35. data/lib/active_record/attribute_methods/primary_key.rb +14 -8
  36. data/lib/active_record/attribute_methods/query.rb +1 -1
  37. data/lib/active_record/attribute_methods/read.rb +22 -59
  38. data/lib/active_record/attribute_methods/serialization.rb +37 -147
  39. data/lib/active_record/attribute_methods/time_zone_conversion.rb +34 -28
  40. data/lib/active_record/attribute_methods/write.rb +14 -21
  41. data/lib/active_record/attribute_methods.rb +67 -94
  42. data/lib/active_record/attribute_set/builder.rb +86 -0
  43. data/lib/active_record/attribute_set.rb +77 -0
  44. data/lib/active_record/attributes.rb +139 -0
  45. data/lib/active_record/autosave_association.rb +45 -38
  46. data/lib/active_record/base.rb +10 -20
  47. data/lib/active_record/callbacks.rb +7 -7
  48. data/lib/active_record/coders/json.rb +13 -0
  49. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +78 -52
  50. data/lib/active_record/connection_adapters/abstract/database_statements.rb +38 -59
  51. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -0
  52. data/lib/active_record/connection_adapters/abstract/quoting.rb +59 -55
  53. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +46 -5
  54. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +126 -54
  55. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  56. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +198 -64
  57. data/lib/active_record/connection_adapters/abstract/transaction.rb +126 -114
  58. data/lib/active_record/connection_adapters/abstract_adapter.rb +154 -55
  59. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +240 -135
  60. data/lib/active_record/connection_adapters/column.rb +28 -239
  61. data/lib/active_record/connection_adapters/connection_specification.rb +16 -25
  62. data/lib/active_record/connection_adapters/mysql2_adapter.rb +20 -22
  63. data/lib/active_record/connection_adapters/mysql_adapter.rb +65 -149
  64. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  65. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  66. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +39 -27
  67. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +99 -0
  68. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  69. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +14 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +27 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +17 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +15 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +97 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -374
  93. data/lib/active_record/connection_adapters/postgresql/quoting.rb +55 -135
  94. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  95. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  96. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +127 -38
  97. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  98. data/lib/active_record/connection_adapters/postgresql_adapter.rb +220 -466
  99. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  100. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +66 -61
  101. data/lib/active_record/connection_handling.rb +3 -3
  102. data/lib/active_record/core.rb +143 -32
  103. data/lib/active_record/counter_cache.rb +60 -7
  104. data/lib/active_record/enum.rb +10 -11
  105. data/lib/active_record/errors.rb +49 -27
  106. data/lib/active_record/explain.rb +1 -1
  107. data/lib/active_record/fixtures.rb +56 -70
  108. data/lib/active_record/gem_version.rb +2 -2
  109. data/lib/active_record/inheritance.rb +35 -10
  110. data/lib/active_record/integration.rb +4 -4
  111. data/lib/active_record/locking/optimistic.rb +35 -17
  112. data/lib/active_record/log_subscriber.rb +1 -1
  113. data/lib/active_record/migration/command_recorder.rb +19 -2
  114. data/lib/active_record/migration/join_table.rb +1 -1
  115. data/lib/active_record/migration.rb +52 -49
  116. data/lib/active_record/model_schema.rb +49 -57
  117. data/lib/active_record/nested_attributes.rb +7 -7
  118. data/lib/active_record/null_relation.rb +19 -5
  119. data/lib/active_record/persistence.rb +50 -31
  120. data/lib/active_record/query_cache.rb +3 -3
  121. data/lib/active_record/querying.rb +10 -7
  122. data/lib/active_record/railtie.rb +14 -11
  123. data/lib/active_record/railties/databases.rake +56 -54
  124. data/lib/active_record/readonly_attributes.rb +0 -1
  125. data/lib/active_record/reflection.rb +286 -102
  126. data/lib/active_record/relation/batches.rb +0 -1
  127. data/lib/active_record/relation/calculations.rb +39 -31
  128. data/lib/active_record/relation/delegation.rb +2 -2
  129. data/lib/active_record/relation/finder_methods.rb +80 -36
  130. data/lib/active_record/relation/merger.rb +25 -30
  131. data/lib/active_record/relation/predicate_builder/array_handler.rb +31 -13
  132. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  133. data/lib/active_record/relation/predicate_builder.rb +11 -10
  134. data/lib/active_record/relation/query_methods.rb +141 -55
  135. data/lib/active_record/relation/spawn_methods.rb +3 -0
  136. data/lib/active_record/relation.rb +69 -30
  137. data/lib/active_record/result.rb +18 -7
  138. data/lib/active_record/sanitization.rb +12 -2
  139. data/lib/active_record/schema.rb +0 -1
  140. data/lib/active_record/schema_dumper.rb +58 -26
  141. data/lib/active_record/schema_migration.rb +11 -0
  142. data/lib/active_record/scoping/default.rb +8 -7
  143. data/lib/active_record/scoping/named.rb +4 -0
  144. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  145. data/lib/active_record/statement_cache.rb +95 -10
  146. data/lib/active_record/store.rb +19 -10
  147. data/lib/active_record/tasks/database_tasks.rb +73 -7
  148. data/lib/active_record/tasks/mysql_database_tasks.rb +3 -2
  149. data/lib/active_record/tasks/postgresql_database_tasks.rb +1 -1
  150. data/lib/active_record/tasks/sqlite_database_tasks.rb +5 -1
  151. data/lib/active_record/timestamp.rb +11 -9
  152. data/lib/active_record/transactions.rb +37 -21
  153. data/lib/active_record/type/big_integer.rb +13 -0
  154. data/lib/active_record/type/binary.rb +50 -0
  155. data/lib/active_record/type/boolean.rb +30 -0
  156. data/lib/active_record/type/date.rb +46 -0
  157. data/lib/active_record/type/date_time.rb +43 -0
  158. data/lib/active_record/type/decimal.rb +40 -0
  159. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  160. data/lib/active_record/type/decorator.rb +14 -0
  161. data/lib/active_record/type/float.rb +19 -0
  162. data/lib/active_record/type/hash_lookup_type_map.rb +17 -0
  163. data/lib/active_record/type/integer.rb +55 -0
  164. data/lib/active_record/type/mutable.rb +16 -0
  165. data/lib/active_record/type/numeric.rb +36 -0
  166. data/lib/active_record/type/serialized.rb +56 -0
  167. data/lib/active_record/type/string.rb +36 -0
  168. data/lib/active_record/type/text.rb +11 -0
  169. data/lib/active_record/type/time.rb +26 -0
  170. data/lib/active_record/type/time_value.rb +38 -0
  171. data/lib/active_record/type/type_map.rb +64 -0
  172. data/lib/active_record/type/unsigned_integer.rb +15 -0
  173. data/lib/active_record/type/value.rb +101 -0
  174. data/lib/active_record/type.rb +23 -0
  175. data/lib/active_record/validations/associated.rb +5 -3
  176. data/lib/active_record/validations/presence.rb +6 -4
  177. data/lib/active_record/validations/uniqueness.rb +11 -17
  178. data/lib/active_record/validations.rb +25 -19
  179. data/lib/active_record.rb +3 -0
  180. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  181. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +4 -1
  182. data/lib/rails/generators/active_record/migration/templates/migration.rb +6 -0
  183. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  184. metadata +65 -10
  185. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -31,7 +31,7 @@ module ActiveRecord
31
31
  class Result
32
32
  include Enumerable
33
33
 
34
- IDENTITY_TYPE = Class.new { def type_cast(v); v; end }.new # :nodoc:
34
+ IDENTITY_TYPE = Type::Value.new # :nodoc:
35
35
 
36
36
  attr_reader :columns, :rows, :column_types
37
37
 
@@ -42,12 +42,8 @@ module ActiveRecord
42
42
  @column_types = column_types
43
43
  end
44
44
 
45
- def identity_type # :nodoc:
46
- IDENTITY_TYPE
47
- end
48
-
49
- def column_type(name)
50
- @column_types[name] || identity_type
45
+ def length
46
+ @rows.length
51
47
  end
52
48
 
53
49
  def each
@@ -82,6 +78,15 @@ module ActiveRecord
82
78
  hash_rows.last
83
79
  end
84
80
 
81
+ def cast_values(type_overrides = {}) # :nodoc:
82
+ types = columns.map { |name| column_type(name, type_overrides) }
83
+ result = rows.map do |values|
84
+ types.zip(values).map { |type, value| type.type_cast_from_database(value) }
85
+ end
86
+
87
+ columns.one? ? result.map!(&:first) : result
88
+ end
89
+
85
90
  def initialize_copy(other)
86
91
  @columns = columns.dup
87
92
  @rows = rows.dup
@@ -91,6 +96,12 @@ module ActiveRecord
91
96
 
92
97
  private
93
98
 
99
+ def column_type(name, type_overrides = {})
100
+ type_overrides.fetch(name) do
101
+ column_types.fetch(name, IDENTITY_TYPE)
102
+ end
103
+ end
104
+
94
105
  def hash_rows
95
106
  @hash_rows ||=
96
107
  begin
@@ -87,12 +87,15 @@ module ActiveRecord
87
87
  # { address: Address.new("123 abc st.", "chicago") }
88
88
  # # => "address_street='123 abc st.' and address_city='chicago'"
89
89
  def sanitize_sql_hash_for_conditions(attrs, default_table_name = self.table_name)
90
+ ActiveSupport::Deprecation.warn(<<-EOWARN)
91
+ sanitize_sql_hash_for_conditions is deprecated, and will be removed in Rails 5.0
92
+ EOWARN
90
93
  attrs = PredicateBuilder.resolve_column_aliases self, attrs
91
94
  attrs = expand_hash_conditions_for_aggregates(attrs)
92
95
 
93
96
  table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
94
97
  PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
95
- connection.visitor.accept b
98
+ connection.visitor.compile b
96
99
  }.join(' AND ')
97
100
  end
98
101
  alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
@@ -107,6 +110,13 @@ module ActiveRecord
107
110
  end.join(', ')
108
111
  end
109
112
 
113
+ # Sanitizes a +string+ so that it is safe to use within an SQL
114
+ # LIKE statement. This method uses +escape_character+ to escape all occurrences of "\", "_" and "%"
115
+ def sanitize_sql_like(string, escape_character = "\\")
116
+ pattern = Regexp.union(escape_character, "%", "_")
117
+ string.gsub(pattern) { |x| [escape_character, x].join }
118
+ end
119
+
110
120
  # Accepts an array of conditions. The array has each value
111
121
  # sanitized and interpolated into the SQL statement.
112
122
  # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
@@ -127,7 +137,7 @@ module ActiveRecord
127
137
  raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
128
138
  bound = values.dup
129
139
  c = connection
130
- statement.gsub('?') do
140
+ statement.gsub(/\?/) do
131
141
  replace_bind_variable(bound.shift, c)
132
142
  end
133
143
  end
@@ -1,4 +1,3 @@
1
-
2
1
  module ActiveRecord
3
2
  # = Active Record Schema
4
3
  #
@@ -91,16 +91,17 @@ HEADER
91
91
  end
92
92
 
93
93
  def tables(stream)
94
- @connection.tables.sort.each do |tbl|
95
- next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
96
- case ignored
97
- when String; remove_prefix_and_suffix(tbl) == ignored
98
- when Regexp; remove_prefix_and_suffix(tbl) =~ ignored
99
- else
100
- raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
101
- end
94
+ sorted_tables = @connection.tables.sort
95
+
96
+ sorted_tables.each do |table_name|
97
+ table(table_name, stream) unless ignored?(table_name)
98
+ end
99
+
100
+ # dump foreign keys at the end to make sure all dependent tables exist.
101
+ if @connection.supports_foreign_keys?
102
+ sorted_tables.each do |tbl|
103
+ foreign_keys(tbl, stream) unless ignored?(tbl)
102
104
  end
103
- table(tbl, stream)
104
105
  end
105
106
  end
106
107
 
@@ -110,17 +111,15 @@ HEADER
110
111
  tbl = StringIO.new
111
112
 
112
113
  # first dump primary key column
113
- if @connection.respond_to?(:pk_and_sequence_for)
114
- pk, _ = @connection.pk_and_sequence_for(table)
115
- elsif @connection.respond_to?(:primary_key)
116
- pk = @connection.primary_key(table)
117
- end
114
+ pk = @connection.primary_key(table)
118
115
 
119
116
  tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
120
117
  pkcol = columns.detect { |c| c.name == pk }
121
118
  if pkcol
122
119
  if pk != 'id'
123
120
  tbl.print %Q(, primary_key: "#{pk}")
121
+ elsif pkcol.sql_type == 'bigint'
122
+ tbl.print ", id: :bigserial"
124
123
  elsif pkcol.sql_type == 'uuid'
125
124
  tbl.print ", id: :uuid"
126
125
  tbl.print %Q(, default: "#{pkcol.default_function}") if pkcol.default_function
@@ -128,7 +127,7 @@ HEADER
128
127
  else
129
128
  tbl.print ", id: false"
130
129
  end
131
- tbl.print ", force: true"
130
+ tbl.print ", force: :cascade"
132
131
  tbl.puts " do |t|"
133
132
 
134
133
  # then dump all non-primary key columns
@@ -186,34 +185,67 @@ HEADER
186
185
  if (indexes = @connection.indexes(table)).any?
187
186
  add_index_statements = indexes.map do |index|
188
187
  statement_parts = [
189
- ('add_index ' + remove_prefix_and_suffix(index.table).inspect),
188
+ "add_index #{remove_prefix_and_suffix(index.table).inspect}",
190
189
  index.columns.inspect,
191
- ('name: ' + index.name.inspect),
190
+ "name: #{index.name.inspect}",
192
191
  ]
193
192
  statement_parts << 'unique: true' if index.unique
194
193
 
195
194
  index_lengths = (index.lengths || []).compact
196
- statement_parts << ('length: ' + Hash[index.columns.zip(index.lengths)].inspect) unless index_lengths.empty?
195
+ statement_parts << "length: #{Hash[index.columns.zip(index.lengths)].inspect}" if index_lengths.any?
197
196
 
198
- index_orders = (index.orders || {})
199
- statement_parts << ('order: ' + index.orders.inspect) unless index_orders.empty?
197
+ index_orders = index.orders || {}
198
+ statement_parts << "order: #{index.orders.inspect}" if index_orders.any?
199
+ statement_parts << "where: #{index.where.inspect}" if index.where
200
+ statement_parts << "using: #{index.using.inspect}" if index.using
201
+ statement_parts << "type: #{index.type.inspect}" if index.type
200
202
 
201
- statement_parts << ('where: ' + index.where.inspect) if index.where
203
+ " #{statement_parts.join(', ')}"
204
+ end
202
205
 
203
- statement_parts << ('using: ' + index.using.inspect) if index.using
206
+ stream.puts add_index_statements.sort.join("\n")
207
+ stream.puts
208
+ end
209
+ end
204
210
 
205
- statement_parts << ('type: ' + index.type.inspect) if index.type
211
+ def foreign_keys(table, stream)
212
+ if (foreign_keys = @connection.foreign_keys(table)).any?
213
+ add_foreign_key_statements = foreign_keys.map do |foreign_key|
214
+ parts = [
215
+ "add_foreign_key #{remove_prefix_and_suffix(foreign_key.from_table).inspect}",
216
+ remove_prefix_and_suffix(foreign_key.to_table).inspect,
217
+ ]
206
218
 
207
- ' ' + statement_parts.join(', ')
219
+ if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table)
220
+ parts << "column: #{foreign_key.column.inspect}"
221
+ end
222
+
223
+ if foreign_key.custom_primary_key?
224
+ parts << "primary_key: #{foreign_key.primary_key.inspect}"
225
+ end
226
+
227
+ if foreign_key.name !~ /^fk_rails_[0-9a-f]{10}$/
228
+ parts << "name: #{foreign_key.name.inspect}"
229
+ end
230
+
231
+ parts << "on_update: #{foreign_key.on_update.inspect}" if foreign_key.on_update
232
+ parts << "on_delete: #{foreign_key.on_delete.inspect}" if foreign_key.on_delete
233
+
234
+ " #{parts.join(', ')}"
208
235
  end
209
236
 
210
- stream.puts add_index_statements.sort.join("\n")
211
- stream.puts
237
+ stream.puts add_foreign_key_statements.sort.join("\n")
212
238
  end
213
239
  end
214
240
 
215
241
  def remove_prefix_and_suffix(table)
216
242
  table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
217
243
  end
244
+
245
+ def ignored?(table_name)
246
+ ['schema_migrations', ignore_tables].flatten.any? do |ignored|
247
+ ignored === remove_prefix_and_suffix(table_name)
248
+ end
249
+ end
218
250
  end
219
251
  end
@@ -5,6 +5,9 @@ require 'active_record/base'
5
5
  module ActiveRecord
6
6
  class SchemaMigration < ActiveRecord::Base
7
7
  class << self
8
+ def primary_key
9
+ nil
10
+ end
8
11
 
9
12
  def table_name
10
13
  "#{table_name_prefix}#{ActiveRecord::Base.schema_migrations_table_name}#{table_name_suffix}"
@@ -36,6 +39,14 @@ module ActiveRecord
36
39
  connection.drop_table(table_name)
37
40
  end
38
41
  end
42
+
43
+ def normalize_migration_number(number)
44
+ "%.3d" % number.to_i
45
+ end
46
+
47
+ def normalized_versions
48
+ pluck(:version).map { |v| normalize_migration_number v }
49
+ end
39
50
  end
40
51
 
41
52
  def version
@@ -11,7 +11,7 @@ module ActiveRecord
11
11
  end
12
12
 
13
13
  module ClassMethods
14
- # Returns a scope for the model without the +default_scope+.
14
+ # Returns a scope for the model without the previously set scopes.
15
15
  #
16
16
  # class Post < ActiveRecord::Base
17
17
  # def self.default_scope
@@ -19,11 +19,12 @@ module ActiveRecord
19
19
  # end
20
20
  # end
21
21
  #
22
- # Post.all # Fires "SELECT * FROM posts WHERE published = true"
23
- # Post.unscoped.all # Fires "SELECT * FROM posts"
22
+ # Post.all # Fires "SELECT * FROM posts WHERE published = true"
23
+ # Post.unscoped.all # Fires "SELECT * FROM posts"
24
+ # Post.where(published: false).unscoped.all # Fires "SELECT * FROM posts"
24
25
  #
25
26
  # This method also accepts a block. All queries inside the block will
26
- # not use the +default_scope+:
27
+ # not use the previously set scopes.
27
28
  #
28
29
  # Post.unscoped {
29
30
  # Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
@@ -93,14 +94,14 @@ module ActiveRecord
93
94
  self.default_scopes += [scope]
94
95
  end
95
96
 
96
- def build_default_scope # :nodoc:
97
+ def build_default_scope(base_rel = relation) # :nodoc:
97
98
  if !Base.is_a?(method(:default_scope).owner)
98
99
  # The user has defined their own default scope method, so call that
99
100
  evaluate_default_scope { default_scope }
100
101
  elsif default_scopes.any?
101
102
  evaluate_default_scope do
102
- default_scopes.inject(relation) do |default_scope, scope|
103
- default_scope.merge(unscoped { scope.call })
103
+ default_scopes.inject(base_rel) do |default_scope, scope|
104
+ default_scope.merge(base_rel.scoping { scope.call })
104
105
  end
105
106
  end
106
107
  end
@@ -139,6 +139,10 @@ module ActiveRecord
139
139
  # Article.published.featured.latest_article
140
140
  # Article.featured.titles
141
141
  def scope(name, body, &block)
142
+ unless body.respond_to?(:call)
143
+ raise ArgumentError, 'The scope body needs to be callable.'
144
+ end
145
+
142
146
  if dangerous_class_method?(name)
143
147
  raise ArgumentError, "You tried to define a scope named \"#{name}\" " \
144
148
  "on the model \"#{self.name}\", but Active Record already defined " \
@@ -180,13 +180,9 @@ module ActiveRecord #:nodoc:
180
180
  class Attribute < ActiveModel::Serializers::Xml::Serializer::Attribute #:nodoc:
181
181
  def compute_type
182
182
  klass = @serializable.class
183
- type = if klass.serialized_attributes.key?(name)
184
- super
185
- elsif klass.columns_hash.key?(name)
186
- klass.columns_hash[name].type
187
- else
188
- NilClass
189
- end
183
+ column = klass.columns_hash[name] || Type::Value.new
184
+
185
+ type = ActiveSupport::XmlMini::TYPE_NAMES[value.class.name] || column.type
190
186
 
191
187
  { :text => :string,
192
188
  :time => :datetime }[type] || type
@@ -1,26 +1,111 @@
1
1
  module ActiveRecord
2
2
 
3
3
  # Statement cache is used to cache a single statement in order to avoid creating the AST again.
4
- # Initializing the cache is done by passing the statement in the initialization block:
4
+ # Initializing the cache is done by passing the statement in the create block:
5
5
  #
6
- # cache = ActiveRecord::StatementCache.new do
7
- # Book.where(name: "my book").limit(100)
6
+ # cache = StatementCache.create(Book.connection) do |params|
7
+ # Book.where(name: "my book").where("author_id > 3")
8
8
  # end
9
9
  #
10
10
  # The cached statement is executed by using the +execute+ method:
11
11
  #
12
- # cache.execute
12
+ # cache.execute([], Book, Book.connection)
13
13
  #
14
14
  # The relation returned by the block is cached, and for each +execute+ call the cached relation gets duped.
15
15
  # Database is queried when +to_a+ is called on the relation.
16
- class StatementCache
17
- def initialize
18
- @relation = yield
19
- raise ArgumentError.new("Statement cannot be nil") if @relation.nil?
16
+ #
17
+ # If you want to cache the statement without the values you can use the +bind+ method of the
18
+ # block parameter.
19
+ #
20
+ # cache = StatementCache.create(Book.connection) do |params|
21
+ # Book.where(name: params.bind)
22
+ # end
23
+ #
24
+ # And pass the bind values as the first argument of +execute+ call.
25
+ #
26
+ # cache.execute(["my book"], Book, Book.connection)
27
+ class StatementCache # :nodoc:
28
+ class Substitute; end # :nodoc:
29
+
30
+ class Query # :nodoc:
31
+ def initialize(sql)
32
+ @sql = sql
33
+ end
34
+
35
+ def sql_for(binds, connection)
36
+ @sql
37
+ end
38
+ end
39
+
40
+ class PartialQuery < Query # :nodoc:
41
+ def initialize values
42
+ @values = values
43
+ @indexes = values.each_with_index.find_all { |thing,i|
44
+ Arel::Nodes::BindParam === thing
45
+ }.map(&:last)
46
+ end
47
+
48
+ def sql_for(binds, connection)
49
+ val = @values.dup
50
+ binds = binds.dup
51
+ @indexes.each { |i| val[i] = connection.quote(*binds.shift.reverse) }
52
+ val.join
53
+ end
54
+ end
55
+
56
+ def self.query(visitor, ast)
57
+ Query.new visitor.accept(ast, Arel::Collectors::SQLString.new).value
58
+ end
59
+
60
+ def self.partial_query(visitor, ast, collector)
61
+ collected = visitor.accept(ast, collector).value
62
+ PartialQuery.new collected
20
63
  end
21
64
 
22
- def execute
23
- @relation.dup.to_a
65
+ class Params # :nodoc:
66
+ def bind; Substitute.new; end
67
+ end
68
+
69
+ class BindMap # :nodoc:
70
+ def initialize(bind_values)
71
+ @indexes = []
72
+ @bind_values = bind_values
73
+
74
+ bind_values.each_with_index do |(_, value), i|
75
+ if Substitute === value
76
+ @indexes << i
77
+ end
78
+ end
79
+ end
80
+
81
+ def bind(values)
82
+ bvs = @bind_values.map { |pair| pair.dup }
83
+ @indexes.each_with_index { |offset,i| bvs[offset][1] = values[i] }
84
+ bvs
85
+ end
86
+ end
87
+
88
+ attr_reader :bind_map, :query_builder
89
+
90
+ def self.create(connection, block = Proc.new)
91
+ relation = block.call Params.new
92
+ bind_map = BindMap.new relation.bind_values
93
+ query_builder = connection.cacheable_query relation.arel
94
+ new query_builder, bind_map
95
+ end
96
+
97
+ def initialize(query_builder, bind_map)
98
+ @query_builder = query_builder
99
+ @bind_map = bind_map
100
+ end
101
+
102
+ def execute(params, klass, connection)
103
+ bind_values = bind_map.bind params
104
+
105
+ sql = query_builder.sql_for bind_values, connection
106
+
107
+ klass.find_by_sql sql, bind_values
24
108
  end
109
+ alias :call :execute
25
110
  end
26
111
  end
@@ -66,8 +66,9 @@ module ActiveRecord
66
66
  extend ActiveSupport::Concern
67
67
 
68
68
  included do
69
- class_attribute :stored_attributes, instance_accessor: false
70
- self.stored_attributes = {}
69
+ class << self
70
+ attr_accessor :local_stored_attributes
71
+ end
71
72
  end
72
73
 
73
74
  module ClassMethods
@@ -93,18 +94,26 @@ module ActiveRecord
93
94
 
94
95
  # assign new store attribute and create new hash to ensure that each class in the hierarchy
95
96
  # has its own hash of stored attributes.
96
- self.stored_attributes = {} if self.stored_attributes.blank?
97
- self.stored_attributes[store_attribute] ||= []
98
- self.stored_attributes[store_attribute] |= keys
97
+ self.local_stored_attributes ||= {}
98
+ self.local_stored_attributes[store_attribute] ||= []
99
+ self.local_stored_attributes[store_attribute] |= keys
99
100
  end
100
101
 
101
- def _store_accessors_module
102
+ def _store_accessors_module # :nodoc:
102
103
  @_store_accessors_module ||= begin
103
104
  mod = Module.new
104
105
  include mod
105
106
  mod
106
107
  end
107
108
  end
109
+
110
+ def stored_attributes
111
+ parent = superclass.respond_to?(:stored_attributes) ? superclass.stored_attributes : {}
112
+ if self.local_stored_attributes
113
+ parent.merge!(self.local_stored_attributes) { |k, a, b| a | b }
114
+ end
115
+ parent
116
+ end
108
117
  end
109
118
 
110
119
  protected
@@ -120,10 +129,10 @@ module ActiveRecord
120
129
 
121
130
  private
122
131
  def store_accessor_for(store_attribute)
123
- @column_types[store_attribute.to_s].accessor
132
+ type_for_attribute(store_attribute.to_s).accessor
124
133
  end
125
134
 
126
- class HashAccessor
135
+ class HashAccessor # :nodoc:
127
136
  def self.read(object, attribute, key)
128
137
  prepare(object, attribute)
129
138
  object.public_send(attribute)[key]
@@ -142,7 +151,7 @@ module ActiveRecord
142
151
  end
143
152
  end
144
153
 
145
- class StringKeyedHashAccessor < HashAccessor
154
+ class StringKeyedHashAccessor < HashAccessor # :nodoc:
146
155
  def self.read(object, attribute, key)
147
156
  super object, attribute, key.to_s
148
157
  end
@@ -152,7 +161,7 @@ module ActiveRecord
152
161
  end
153
162
  end
154
163
 
155
- class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor
164
+ class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
156
165
  def self.prepare(object, store_attribute)
157
166
  attribute = object.send(store_attribute)
158
167
  unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)
@@ -1,3 +1,5 @@
1
+ require 'active_support/core_ext/string/filters'
2
+
1
3
  module ActiveRecord
2
4
  module Tasks # :nodoc:
3
5
  class DatabaseAlreadyExists < StandardError; end # :nodoc:
@@ -6,7 +8,7 @@ module ActiveRecord
6
8
  # <tt>ActiveRecord::Tasks::DatabaseTasks</tt> is a utility class, which encapsulates
7
9
  # logic behind common tasks used to manage database and migrations.
8
10
  #
9
- # The tasks defined here are used in rake tasks provided by Active Record.
11
+ # The tasks defined here are used with Rake tasks provided by Active Record.
10
12
  #
11
13
  # In order to use DatabaseTasks, a few config values need to be set. All the needed
12
14
  # config values are set by Rails already, so it's necessary to do it only if you
@@ -14,7 +16,6 @@ module ActiveRecord
14
16
  # (in such case after configuring the database tasks, you can also use the rake tasks
15
17
  # defined in Active Record).
16
18
  #
17
- #
18
19
  # The possible config values are:
19
20
  #
20
21
  # * +env+: current environment (like Rails.env).
@@ -28,7 +29,7 @@ module ActiveRecord
28
29
  # Example usage of +DatabaseTasks+ outside Rails could look as such:
29
30
  #
30
31
  # include ActiveRecord::Tasks
31
- # DatabaseTasks.database_configuration = YAML.load(File.read('my_database_config.yml'))
32
+ # DatabaseTasks.database_configuration = YAML.load_file('my_database_config.yml')
32
33
  # DatabaseTasks.db_dir = 'db'
33
34
  # # other settings...
34
35
  #
@@ -59,7 +60,11 @@ module ActiveRecord
59
60
  end
60
61
 
61
62
  def fixtures_path
62
- @fixtures_path ||= File.join(root, 'test', 'fixtures')
63
+ @fixtures_path ||= if ENV['FIXTURES_PATH']
64
+ File.join(root, ENV['FIXTURES_PATH'])
65
+ else
66
+ File.join(root, 'test', 'fixtures')
67
+ end
63
68
  end
64
69
 
65
70
  def root
@@ -107,6 +112,8 @@ module ActiveRecord
107
112
  def drop(*arguments)
108
113
  configuration = arguments.first
109
114
  class_for_adapter(configuration['adapter']).new(*arguments).drop
115
+ rescue ActiveRecord::NoDatabaseError
116
+ $stderr.puts "Database '#{configuration['database']}' does not exist"
110
117
  rescue Exception => error
111
118
  $stderr.puts error, *(error.backtrace)
112
119
  $stderr.puts "Couldn't drop #{configuration['database']}"
@@ -122,6 +129,18 @@ module ActiveRecord
122
129
  }
123
130
  end
124
131
 
132
+ def migrate
133
+ verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
134
+ version = ENV["VERSION"] ? ENV["VERSION"].to_i : nil
135
+ scope = ENV['SCOPE']
136
+ verbose_was, Migration.verbose = Migration.verbose, verbose
137
+ Migrator.migrate(Migrator.migrations_paths, version) do |migration|
138
+ scope.blank? || scope == migration.scope
139
+ end
140
+ ensure
141
+ Migration.verbose = verbose_was
142
+ end
143
+
125
144
  def charset_current(environment = env)
126
145
  charset ActiveRecord::Base.configurations[environment]
127
146
  end
@@ -144,6 +163,19 @@ module ActiveRecord
144
163
  class_for_adapter(configuration['adapter']).new(configuration).purge
145
164
  end
146
165
 
166
+ def purge_all
167
+ each_local_configuration { |configuration|
168
+ purge configuration
169
+ }
170
+ end
171
+
172
+ def purge_current(environment = env)
173
+ each_current_configuration(environment) { |configuration|
174
+ purge configuration
175
+ }
176
+ ActiveRecord::Base.establish_connection(environment.to_sym)
177
+ end
178
+
147
179
  def structure_dump(*arguments)
148
180
  configuration = arguments.first
149
181
  filename = arguments.delete_at 1
@@ -157,20 +189,54 @@ module ActiveRecord
157
189
  end
158
190
 
159
191
  def load_schema(format = ActiveRecord::Base.schema_format, file = nil)
192
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
193
+ This method will act on a specific connection in the future.
194
+ To act on the current connection, use `load_schema_current` instead.
195
+ MSG
196
+
197
+ load_schema_current(format, file)
198
+ end
199
+
200
+ def schema_file(format = ActiveSupport::Base.schema_format)
201
+ case format
202
+ when :ruby
203
+ File.join(db_dir, "schema.rb")
204
+ when :sql
205
+ File.join(db_dir, "structure.sql")
206
+ end
207
+ end
208
+
209
+ # This method is the successor of +load_schema+. We should rename it
210
+ # after +load_schema+ went through a deprecation cycle. (Rails > 4.2)
211
+ def load_schema_for(configuration, format = ActiveRecord::Base.schema_format, file = nil) # :nodoc:
212
+ file ||= schema_file(format)
213
+
160
214
  case format
161
215
  when :ruby
162
- file ||= File.join(db_dir, "schema.rb")
163
216
  check_schema_file(file)
217
+ ActiveRecord::Base.establish_connection(configuration)
164
218
  load(file)
165
219
  when :sql
166
- file ||= File.join(db_dir, "structure.sql")
167
220
  check_schema_file(file)
168
- structure_load(current_config, file)
221
+ structure_load(configuration, file)
169
222
  else
170
223
  raise ArgumentError, "unknown format #{format.inspect}"
171
224
  end
172
225
  end
173
226
 
227
+ def load_schema_current_if_exists(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
228
+ if File.exist?(file || schema_file(format))
229
+ load_schema_current(format, file, environment)
230
+ end
231
+ end
232
+
233
+ def load_schema_current(format = ActiveRecord::Base.schema_format, file = nil, environment = env)
234
+ each_current_configuration(environment) { |configuration|
235
+ load_schema_for configuration, format, file
236
+ }
237
+ ActiveRecord::Base.establish_connection(environment.to_sym)
238
+ end
239
+
174
240
  def check_schema_file(filename)
175
241
  unless File.exist?(filename)
176
242
  message = %{#{filename} doesn't exist yet. Run `rake db:migrate` to create it, then try again.}