activerecord 4.1.8 → 4.2.11.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of activerecord might be problematic. Click here for more details.

Files changed (186) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +1165 -1591
  3. data/README.rdoc +15 -10
  4. data/lib/active_record/aggregations.rb +15 -8
  5. data/lib/active_record/association_relation.rb +13 -0
  6. data/lib/active_record/associations/alias_tracker.rb +3 -12
  7. data/lib/active_record/associations/association.rb +16 -4
  8. data/lib/active_record/associations/association_scope.rb +84 -43
  9. data/lib/active_record/associations/belongs_to_association.rb +28 -10
  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/collection_association.rb +5 -1
  13. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +9 -14
  14. data/lib/active_record/associations/builder/has_many.rb +1 -1
  15. data/lib/active_record/associations/builder/has_one.rb +2 -2
  16. data/lib/active_record/associations/builder/singular_association.rb +8 -1
  17. data/lib/active_record/associations/collection_association.rb +87 -30
  18. data/lib/active_record/associations/collection_proxy.rb +33 -35
  19. data/lib/active_record/associations/foreign_association.rb +11 -0
  20. data/lib/active_record/associations/has_many_association.rb +83 -22
  21. data/lib/active_record/associations/has_many_through_association.rb +49 -26
  22. data/lib/active_record/associations/has_one_association.rb +1 -1
  23. data/lib/active_record/associations/join_dependency/join_association.rb +25 -15
  24. data/lib/active_record/associations/join_dependency/join_part.rb +0 -1
  25. data/lib/active_record/associations/join_dependency.rb +26 -12
  26. data/lib/active_record/associations/preloader/association.rb +14 -10
  27. data/lib/active_record/associations/preloader/through_association.rb +4 -3
  28. data/lib/active_record/associations/preloader.rb +37 -26
  29. data/lib/active_record/associations/singular_association.rb +17 -2
  30. data/lib/active_record/associations/through_association.rb +16 -12
  31. data/lib/active_record/associations.rb +158 -49
  32. data/lib/active_record/attribute.rb +163 -0
  33. data/lib/active_record/attribute_assignment.rb +20 -12
  34. data/lib/active_record/attribute_decorators.rb +66 -0
  35. data/lib/active_record/attribute_methods/before_type_cast.rb +7 -2
  36. data/lib/active_record/attribute_methods/dirty.rb +107 -43
  37. data/lib/active_record/attribute_methods/primary_key.rb +7 -8
  38. data/lib/active_record/attribute_methods/query.rb +1 -1
  39. data/lib/active_record/attribute_methods/read.rb +22 -59
  40. data/lib/active_record/attribute_methods/serialization.rb +16 -150
  41. data/lib/active_record/attribute_methods/time_zone_conversion.rb +38 -28
  42. data/lib/active_record/attribute_methods/write.rb +9 -24
  43. data/lib/active_record/attribute_methods.rb +57 -95
  44. data/lib/active_record/attribute_set/builder.rb +106 -0
  45. data/lib/active_record/attribute_set.rb +81 -0
  46. data/lib/active_record/attributes.rb +147 -0
  47. data/lib/active_record/autosave_association.rb +30 -12
  48. data/lib/active_record/base.rb +13 -24
  49. data/lib/active_record/callbacks.rb +6 -6
  50. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +85 -53
  51. data/lib/active_record/connection_adapters/abstract/database_statements.rb +52 -50
  52. data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
  53. data/lib/active_record/connection_adapters/abstract/quoting.rb +60 -60
  54. data/lib/active_record/connection_adapters/abstract/savepoints.rb +1 -1
  55. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +39 -4
  56. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +139 -57
  57. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +14 -34
  58. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +271 -74
  59. data/lib/active_record/connection_adapters/abstract/transaction.rb +125 -118
  60. data/lib/active_record/connection_adapters/abstract_adapter.rb +177 -60
  61. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +295 -141
  62. data/lib/active_record/connection_adapters/column.rb +29 -240
  63. data/lib/active_record/connection_adapters/connection_specification.rb +15 -24
  64. data/lib/active_record/connection_adapters/mysql2_adapter.rb +17 -33
  65. data/lib/active_record/connection_adapters/mysql_adapter.rb +68 -145
  66. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +15 -27
  67. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  68. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +40 -25
  69. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  70. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  71. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  72. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  73. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  74. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  75. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  76. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  78. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  79. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  80. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  81. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid.rb +29 -385
  95. data/lib/active_record/connection_adapters/postgresql/quoting.rb +46 -136
  96. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +4 -4
  97. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  98. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +134 -43
  99. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  100. data/lib/active_record/connection_adapters/postgresql_adapter.rb +224 -477
  101. data/lib/active_record/connection_adapters/schema_cache.rb +14 -28
  102. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +61 -75
  103. data/lib/active_record/connection_handling.rb +1 -1
  104. data/lib/active_record/core.rb +163 -40
  105. data/lib/active_record/counter_cache.rb +60 -6
  106. data/lib/active_record/enum.rb +10 -12
  107. data/lib/active_record/errors.rb +53 -30
  108. data/lib/active_record/explain.rb +1 -1
  109. data/lib/active_record/explain_subscriber.rb +1 -1
  110. data/lib/active_record/fixtures.rb +62 -74
  111. data/lib/active_record/gem_version.rb +4 -4
  112. data/lib/active_record/inheritance.rb +35 -10
  113. data/lib/active_record/integration.rb +4 -4
  114. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  115. data/lib/active_record/locking/optimistic.rb +46 -26
  116. data/lib/active_record/migration/command_recorder.rb +19 -2
  117. data/lib/active_record/migration/join_table.rb +1 -1
  118. data/lib/active_record/migration.rb +79 -47
  119. data/lib/active_record/model_schema.rb +52 -58
  120. data/lib/active_record/nested_attributes.rb +18 -8
  121. data/lib/active_record/no_touching.rb +1 -1
  122. data/lib/active_record/persistence.rb +48 -27
  123. data/lib/active_record/query_cache.rb +3 -3
  124. data/lib/active_record/querying.rb +10 -7
  125. data/lib/active_record/railtie.rb +19 -14
  126. data/lib/active_record/railties/databases.rake +55 -56
  127. data/lib/active_record/readonly_attributes.rb +0 -1
  128. data/lib/active_record/reflection.rb +281 -117
  129. data/lib/active_record/relation/batches.rb +0 -1
  130. data/lib/active_record/relation/calculations.rb +41 -37
  131. data/lib/active_record/relation/delegation.rb +1 -1
  132. data/lib/active_record/relation/finder_methods.rb +71 -48
  133. data/lib/active_record/relation/merger.rb +39 -29
  134. data/lib/active_record/relation/predicate_builder/array_handler.rb +32 -13
  135. data/lib/active_record/relation/predicate_builder/relation_handler.rb +1 -1
  136. data/lib/active_record/relation/predicate_builder.rb +42 -12
  137. data/lib/active_record/relation/query_methods.rb +130 -73
  138. data/lib/active_record/relation/spawn_methods.rb +10 -3
  139. data/lib/active_record/relation.rb +57 -25
  140. data/lib/active_record/result.rb +18 -7
  141. data/lib/active_record/sanitization.rb +12 -2
  142. data/lib/active_record/schema.rb +0 -1
  143. data/lib/active_record/schema_dumper.rb +59 -28
  144. data/lib/active_record/schema_migration.rb +5 -4
  145. data/lib/active_record/scoping/default.rb +6 -4
  146. data/lib/active_record/scoping/named.rb +4 -0
  147. data/lib/active_record/serializers/xml_serializer.rb +3 -7
  148. data/lib/active_record/statement_cache.rb +95 -10
  149. data/lib/active_record/store.rb +5 -5
  150. data/lib/active_record/tasks/database_tasks.rb +61 -8
  151. data/lib/active_record/tasks/mysql_database_tasks.rb +32 -17
  152. data/lib/active_record/tasks/postgresql_database_tasks.rb +20 -9
  153. data/lib/active_record/timestamp.rb +9 -7
  154. data/lib/active_record/transactions.rb +54 -28
  155. data/lib/active_record/type/big_integer.rb +13 -0
  156. data/lib/active_record/type/binary.rb +50 -0
  157. data/lib/active_record/type/boolean.rb +31 -0
  158. data/lib/active_record/type/date.rb +50 -0
  159. data/lib/active_record/type/date_time.rb +54 -0
  160. data/lib/active_record/type/decimal.rb +64 -0
  161. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  162. data/lib/active_record/type/decorator.rb +14 -0
  163. data/lib/active_record/type/float.rb +19 -0
  164. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  165. data/lib/active_record/type/integer.rb +59 -0
  166. data/lib/active_record/type/mutable.rb +16 -0
  167. data/lib/active_record/type/numeric.rb +36 -0
  168. data/lib/active_record/type/serialized.rb +62 -0
  169. data/lib/active_record/type/string.rb +40 -0
  170. data/lib/active_record/type/text.rb +11 -0
  171. data/lib/active_record/type/time.rb +26 -0
  172. data/lib/active_record/type/time_value.rb +38 -0
  173. data/lib/active_record/type/type_map.rb +64 -0
  174. data/lib/active_record/type/unsigned_integer.rb +15 -0
  175. data/lib/active_record/type/value.rb +110 -0
  176. data/lib/active_record/type.rb +23 -0
  177. data/lib/active_record/validations/associated.rb +5 -3
  178. data/lib/active_record/validations/presence.rb +5 -3
  179. data/lib/active_record/validations/uniqueness.rb +24 -20
  180. data/lib/active_record/validations.rb +25 -19
  181. data/lib/active_record.rb +5 -0
  182. data/lib/rails/generators/active_record/migration/migration_generator.rb +8 -4
  183. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +1 -1
  184. data/lib/rails/generators/active_record/model/templates/model.rb +1 -1
  185. metadata +66 -11
  186. data/lib/active_record/connection_adapters/postgresql/cast.rb +0 -168
@@ -1,16 +1,16 @@
1
1
  # -*- coding: utf-8 -*-
2
+ require 'arel/collectors/bind'
2
3
 
3
4
  module ActiveRecord
4
5
  # = Active Record Relation
5
6
  class Relation
6
- JoinOperation = Struct.new(:relation, :join_class, :on)
7
-
8
7
  MULTI_VALUE_METHODS = [:includes, :eager_load, :preload, :select, :group,
9
8
  :order, :joins, :where, :having, :bind, :references,
10
9
  :extending, :unscope]
11
10
 
12
11
  SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
13
12
  :reverse_order, :distinct, :create_with, :uniq]
13
+ INVALID_METHODS_FOR_DELETE_ALL = [:limit, :distinct, :offset, :group, :having]
14
14
 
15
15
  VALUE_METHODS = MULTI_VALUE_METHODS + SINGLE_VALUE_METHODS
16
16
 
@@ -79,22 +79,26 @@ module ActiveRecord
79
79
  scope.unscope!(where: @klass.inheritance_column)
80
80
  end
81
81
 
82
- um = scope.where(@klass.arel_table[@klass.primary_key].eq(id_was || id)).arel.compile_update(substitutes, @klass.primary_key)
82
+ relation = scope.where(@klass.primary_key => (id_was || id))
83
+ bvs = binds + relation.bind_values
84
+ um = relation
85
+ .arel
86
+ .compile_update(substitutes, @klass.primary_key)
83
87
 
84
88
  @klass.connection.update(
85
89
  um,
86
90
  'SQL',
87
- binds)
91
+ bvs,
92
+ )
88
93
  end
89
94
 
90
95
  def substitute_values(values) # :nodoc:
91
- substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
92
- binds = substitutes.map do |arel_attr, value|
96
+ binds = values.map do |arel_attr, value|
93
97
  [@klass.columns_hash[arel_attr.name], value]
94
98
  end
95
99
 
96
- substitutes.each_with_index do |tuple, i|
97
- tuple[1] = @klass.connection.substitute_at(binds[i][0], i)
100
+ substitutes = values.each_with_index.map do |(arel_attr, _), i|
101
+ [arel_attr, @klass.connection.substitute_at(binds[i][0])]
98
102
  end
99
103
 
100
104
  [substitutes, binds]
@@ -230,6 +234,7 @@ module ActiveRecord
230
234
  # Please see further details in the
231
235
  # {Active Record Query Interface guide}[http://guides.rubyonrails.org/active_record_querying.html#running-explain].
232
236
  def explain
237
+ #TODO: Fix for binds.
233
238
  exec_explain(collecting_queries_for_explain { exec_queries })
234
239
  end
235
240
 
@@ -239,6 +244,11 @@ module ActiveRecord
239
244
  @records
240
245
  end
241
246
 
247
+ # Serializes the relation objects Array.
248
+ def encode_with(coder)
249
+ coder.represent_seq(nil, to_a)
250
+ end
251
+
242
252
  def as_json(options = nil) #:nodoc:
243
253
  to_a.as_json(options)
244
254
  end
@@ -294,10 +304,11 @@ module ActiveRecord
294
304
  klass.current_scope = previous
295
305
  end
296
306
 
297
- # Updates all records with details given if they match a set of conditions supplied, limits and order can
298
- # also be supplied. This method constructs a single SQL UPDATE statement and sends it straight to the
299
- # database. It does not instantiate the involved models and it does not trigger Active Record callbacks
300
- # or validations.
307
+ # Updates all records in the current relation with details given. This method constructs a single SQL UPDATE
308
+ # statement and sends it straight to the database. It does not instantiate the involved models and it does not
309
+ # trigger Active Record callbacks or validations. Values passed to `update_all` will not go through
310
+ # ActiveRecord's type-casting behavior. It should receive only values that can be passed as-is to the SQL
311
+ # database.
301
312
  #
302
313
  # ==== Parameters
303
314
  #
@@ -330,7 +341,8 @@ module ActiveRecord
330
341
  stmt.wheres = arel.constraints
331
342
  end
332
343
 
333
- @klass.connection.update stmt, 'SQL', bind_values
344
+ bvs = arel.bind_values + bind_values
345
+ @klass.connection.update stmt, 'SQL', bvs
334
346
  end
335
347
 
336
348
  # Updates an object (or multiple objects) and saves it to the database, if validations pass.
@@ -434,12 +446,21 @@ module ActiveRecord
434
446
  # If you need to destroy dependent associations or call your <tt>before_*</tt> or
435
447
  # +after_destroy+ callbacks, use the +destroy_all+ method instead.
436
448
  #
437
- # If a limit scope is supplied, +delete_all+ raises an ActiveRecord error:
449
+ # If an invalid method is supplied, +delete_all+ raises an ActiveRecord error:
438
450
  #
439
451
  # Post.limit(100).delete_all
440
- # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit scope
452
+ # # => ActiveRecord::ActiveRecordError: delete_all doesn't support limit
441
453
  def delete_all(conditions = nil)
442
- raise ActiveRecordError.new("delete_all doesn't support limit scope") if self.limit_value
454
+ invalid_methods = INVALID_METHODS_FOR_DELETE_ALL.select { |method|
455
+ if MULTI_VALUE_METHODS.include?(method)
456
+ send("#{method}_values").any?
457
+ else
458
+ send("#{method}_value")
459
+ end
460
+ }
461
+ if invalid_methods.any?
462
+ raise ActiveRecordError.new("delete_all doesn't support #{invalid_methods.join(', ')}")
463
+ end
443
464
 
444
465
  if conditions
445
466
  where(conditions).delete_all
@@ -453,7 +474,8 @@ module ActiveRecord
453
474
  stmt.wheres = arel.constraints
454
475
  end
455
476
 
456
- affected = @klass.connection.delete(stmt, 'SQL', bind_values)
477
+ bvs = arel.bind_values + bind_values
478
+ affected = @klass.connection.delete(stmt, 'SQL', bvs)
457
479
 
458
480
  reset
459
481
  affected
@@ -523,11 +545,11 @@ module ActiveRecord
523
545
  find_with_associations { |rel| relation = rel }
524
546
  end
525
547
 
526
- ast = relation.arel.ast
527
- binds = relation.bind_values.dup
528
- visitor.accept(ast) do
529
- connection.quote(*binds.shift.reverse)
530
- end
548
+ arel = relation.arel
549
+ binds = (arel.bind_values + relation.bind_values).dup
550
+ binds.map! { |bv| connection.quote(*bv.reverse) }
551
+ collect = visitor.accept(arel.ast, Arel::Collectors::Bind.new)
552
+ collect.substitute_binds(binds).join
531
553
  end
532
554
  end
533
555
 
@@ -544,7 +566,13 @@ module ActiveRecord
544
566
 
545
567
  Hash[equalities.map { |where|
546
568
  name = where.left.name
547
- [name, binds.fetch(name.to_s) { where.right }]
569
+ [name, binds.fetch(name.to_s) {
570
+ case where.right
571
+ when Array then where.right.map(&:val)
572
+ when Arel::Nodes::Casted
573
+ where.right.val
574
+ end
575
+ }]
548
576
  }]
549
577
  end
550
578
 
@@ -608,11 +636,11 @@ module ActiveRecord
608
636
  private
609
637
 
610
638
  def exec_queries
611
- @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, bind_values)
639
+ @records = eager_loading? ? find_with_associations : @klass.find_by_sql(arel, arel.bind_values + bind_values)
612
640
 
613
641
  preload = preload_values
614
642
  preload += includes_values unless eager_loading?
615
- preloader = ActiveRecord::Associations::Preloader.new
643
+ preloader = build_preloader
616
644
  preload.each do |associations|
617
645
  preloader.preload @records, associations
618
646
  end
@@ -623,6 +651,10 @@ module ActiveRecord
623
651
  @records
624
652
  end
625
653
 
654
+ def build_preloader
655
+ ActiveRecord::Associations::Preloader.new
656
+ end
657
+
626
658
  def references_eager_loaded_tables?
627
659
  joined_tables = arel.join_sources.map do |join|
628
660
  if join.is_a?(Arel::Nodes::StringJoin)
@@ -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,26 +111,23 @@ 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
- end
116
- if !pk && @connection.respond_to?(:primary_key)
117
- pk = @connection.primary_key(table)
118
- end
114
+ pk = @connection.primary_key(table)
119
115
 
120
116
  tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
121
117
  pkcol = columns.detect { |c| c.name == pk }
122
118
  if pkcol
123
119
  if pk != 'id'
124
120
  tbl.print %Q(, primary_key: "#{pk}")
121
+ elsif pkcol.sql_type == 'bigint'
122
+ tbl.print ", id: :bigserial"
125
123
  elsif pkcol.sql_type == 'uuid'
126
124
  tbl.print ", id: :uuid"
127
- tbl.print %Q(, default: "#{pkcol.default_function}") if pkcol.default_function
125
+ tbl.print %Q(, default: #{pkcol.default_function.inspect})
128
126
  end
129
127
  else
130
128
  tbl.print ", id: false"
131
129
  end
132
- tbl.print ", force: true"
130
+ tbl.print ", force: :cascade"
133
131
  tbl.puts " do |t|"
134
132
 
135
133
  # then dump all non-primary key columns
@@ -187,34 +185,67 @@ HEADER
187
185
  if (indexes = @connection.indexes(table)).any?
188
186
  add_index_statements = indexes.map do |index|
189
187
  statement_parts = [
190
- ('add_index ' + remove_prefix_and_suffix(index.table).inspect),
188
+ "add_index #{remove_prefix_and_suffix(index.table).inspect}",
191
189
  index.columns.inspect,
192
- ('name: ' + index.name.inspect),
190
+ "name: #{index.name.inspect}",
193
191
  ]
194
192
  statement_parts << 'unique: true' if index.unique
195
193
 
196
194
  index_lengths = (index.lengths || []).compact
197
- 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?
196
+
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
202
+
203
+ " #{statement_parts.join(', ')}"
204
+ end
205
+
206
+ stream.puts add_index_statements.sort.join("\n")
207
+ stream.puts
208
+ end
209
+ end
210
+
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
+ ]
198
218
 
199
- index_orders = (index.orders || {})
200
- statement_parts << ('order: ' + index.orders.inspect) unless index_orders.empty?
219
+ if foreign_key.column != @connection.foreign_key_column_for(foreign_key.to_table)
220
+ parts << "column: #{foreign_key.column.inspect}"
221
+ end
201
222
 
202
- statement_parts << ('where: ' + index.where.inspect) if index.where
223
+ if foreign_key.custom_primary_key?
224
+ parts << "primary_key: #{foreign_key.primary_key.inspect}"
225
+ end
203
226
 
204
- statement_parts << ('using: ' + index.using.inspect) if index.using
227
+ if foreign_key.name !~ /^fk_rails_[0-9a-f]{10}$/
228
+ parts << "name: #{foreign_key.name.inspect}"
229
+ end
205
230
 
206
- statement_parts << ('type: ' + index.type.inspect) if index.type
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
207
233
 
208
- ' ' + statement_parts.join(', ')
234
+ " #{parts.join(', ')}"
209
235
  end
210
236
 
211
- stream.puts add_index_statements.sort.join("\n")
212
- stream.puts
237
+ stream.puts add_foreign_key_statements.sort.join("\n")
213
238
  end
214
239
  end
215
240
 
216
241
  def remove_prefix_and_suffix(table)
217
242
  table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
218
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
219
250
  end
220
251
  end
@@ -34,15 +34,16 @@ module ActiveRecord
34
34
  end
35
35
 
36
36
  def drop_table
37
- if table_exists?
38
- connection.remove_index table_name, name: index_name
39
- connection.drop_table(table_name)
40
- end
37
+ connection.drop_table table_name if table_exists?
41
38
  end
42
39
 
43
40
  def normalize_migration_number(number)
44
41
  "%.3d" % number.to_i
45
42
  end
43
+
44
+ def normalized_versions
45
+ pluck(:version).map { |v| normalize_migration_number v }
46
+ end
46
47
  end
47
48
 
48
49
  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"
@@ -94,6 +95,7 @@ module ActiveRecord
94
95
  end
95
96
 
96
97
  def build_default_scope(base_rel = relation) # :nodoc:
98
+ return if abstract_class?
97
99
  if !Base.is_a?(method(:default_scope).owner)
98
100
  # The user has defined their own default scope method, so call that
99
101
  evaluate_default_scope { default_scope }
@@ -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
@@ -99,7 +99,7 @@ module ActiveRecord
99
99
  self.local_stored_attributes[store_attribute] |= keys
100
100
  end
101
101
 
102
- def _store_accessors_module
102
+ def _store_accessors_module # :nodoc:
103
103
  @_store_accessors_module ||= begin
104
104
  mod = Module.new
105
105
  include mod
@@ -129,10 +129,10 @@ module ActiveRecord
129
129
 
130
130
  private
131
131
  def store_accessor_for(store_attribute)
132
- @column_types[store_attribute.to_s].accessor
132
+ type_for_attribute(store_attribute.to_s).accessor
133
133
  end
134
134
 
135
- class HashAccessor
135
+ class HashAccessor # :nodoc:
136
136
  def self.read(object, attribute, key)
137
137
  prepare(object, attribute)
138
138
  object.public_send(attribute)[key]
@@ -151,7 +151,7 @@ module ActiveRecord
151
151
  end
152
152
  end
153
153
 
154
- class StringKeyedHashAccessor < HashAccessor
154
+ class StringKeyedHashAccessor < HashAccessor # :nodoc:
155
155
  def self.read(object, attribute, key)
156
156
  super object, attribute, key.to_s
157
157
  end
@@ -161,7 +161,7 @@ module ActiveRecord
161
161
  end
162
162
  end
163
163
 
164
- class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor
164
+ class IndifferentHashAccessor < ActiveRecord::Store::HashAccessor # :nodoc:
165
165
  def self.prepare(object, store_attribute)
166
166
  attribute = object.send(store_attribute)
167
167
  unless attribute.is_a?(ActiveSupport::HashWithIndifferentAccess)