activerecord 3.1.10 → 4.2.11

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 (237) hide show
  1. checksums.yaml +6 -6
  2. data/CHANGELOG.md +1837 -338
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +39 -43
  5. data/examples/performance.rb +51 -20
  6. data/examples/simple.rb +4 -4
  7. data/lib/active_record/aggregations.rb +57 -43
  8. data/lib/active_record/association_relation.rb +35 -0
  9. data/lib/active_record/associations/alias_tracker.rb +47 -39
  10. data/lib/active_record/associations/association.rb +71 -85
  11. data/lib/active_record/associations/association_scope.rb +138 -89
  12. data/lib/active_record/associations/belongs_to_association.rb +65 -25
  13. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +9 -3
  14. data/lib/active_record/associations/builder/association.rb +125 -29
  15. data/lib/active_record/associations/builder/belongs_to.rb +91 -60
  16. data/lib/active_record/associations/builder/collection_association.rb +69 -49
  17. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +113 -42
  18. data/lib/active_record/associations/builder/has_many.rb +8 -64
  19. data/lib/active_record/associations/builder/has_one.rb +12 -52
  20. data/lib/active_record/associations/builder/singular_association.rb +22 -29
  21. data/lib/active_record/associations/collection_association.rb +294 -187
  22. data/lib/active_record/associations/collection_proxy.rb +961 -94
  23. data/lib/active_record/associations/foreign_association.rb +11 -0
  24. data/lib/active_record/associations/has_many_association.rb +118 -23
  25. data/lib/active_record/associations/has_many_through_association.rb +115 -45
  26. data/lib/active_record/associations/has_one_association.rb +57 -24
  27. data/lib/active_record/associations/has_one_through_association.rb +1 -1
  28. data/lib/active_record/associations/join_dependency/join_association.rb +76 -102
  29. data/lib/active_record/associations/join_dependency/join_base.rb +7 -9
  30. data/lib/active_record/associations/join_dependency/join_part.rb +30 -37
  31. data/lib/active_record/associations/join_dependency.rb +230 -156
  32. data/lib/active_record/associations/preloader/association.rb +96 -55
  33. data/lib/active_record/associations/preloader/collection_association.rb +3 -3
  34. data/lib/active_record/associations/preloader/has_many_through.rb +7 -3
  35. data/lib/active_record/associations/preloader/has_one.rb +1 -1
  36. data/lib/active_record/associations/preloader/singular_association.rb +3 -3
  37. data/lib/active_record/associations/preloader/through_association.rb +61 -32
  38. data/lib/active_record/associations/preloader.rb +113 -87
  39. data/lib/active_record/associations/singular_association.rb +29 -13
  40. data/lib/active_record/associations/through_association.rb +37 -19
  41. data/lib/active_record/associations.rb +505 -371
  42. data/lib/active_record/attribute.rb +163 -0
  43. data/lib/active_record/attribute_assignment.rb +212 -0
  44. data/lib/active_record/attribute_decorators.rb +66 -0
  45. data/lib/active_record/attribute_methods/before_type_cast.rb +52 -7
  46. data/lib/active_record/attribute_methods/dirty.rb +141 -51
  47. data/lib/active_record/attribute_methods/primary_key.rb +87 -36
  48. data/lib/active_record/attribute_methods/query.rb +5 -4
  49. data/lib/active_record/attribute_methods/read.rb +74 -117
  50. data/lib/active_record/attribute_methods/serialization.rb +70 -0
  51. data/lib/active_record/attribute_methods/time_zone_conversion.rb +49 -47
  52. data/lib/active_record/attribute_methods/write.rb +60 -21
  53. data/lib/active_record/attribute_methods.rb +409 -48
  54. data/lib/active_record/attribute_set/builder.rb +106 -0
  55. data/lib/active_record/attribute_set.rb +81 -0
  56. data/lib/active_record/attributes.rb +147 -0
  57. data/lib/active_record/autosave_association.rb +279 -232
  58. data/lib/active_record/base.rb +84 -1969
  59. data/lib/active_record/callbacks.rb +66 -28
  60. data/lib/active_record/coders/json.rb +13 -0
  61. data/lib/active_record/coders/yaml_column.rb +18 -21
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +422 -243
  63. data/lib/active_record/connection_adapters/abstract/database_limits.rb +9 -0
  64. data/lib/active_record/connection_adapters/abstract/database_statements.rb +170 -194
  65. data/lib/active_record/connection_adapters/abstract/query_cache.rb +32 -19
  66. data/lib/active_record/connection_adapters/abstract/quoting.rb +79 -57
  67. data/lib/active_record/connection_adapters/abstract/savepoints.rb +21 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +125 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +273 -170
  70. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +50 -0
  71. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +731 -254
  72. data/lib/active_record/connection_adapters/abstract/transaction.rb +215 -0
  73. data/lib/active_record/connection_adapters/abstract_adapter.rb +339 -95
  74. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +946 -0
  75. data/lib/active_record/connection_adapters/column.rb +33 -221
  76. data/lib/active_record/connection_adapters/connection_specification.rb +275 -0
  77. data/lib/active_record/connection_adapters/mysql2_adapter.rb +140 -602
  78. data/lib/active_record/connection_adapters/mysql_adapter.rb +254 -756
  79. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +93 -0
  80. data/lib/active_record/connection_adapters/postgresql/column.rb +20 -0
  81. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +232 -0
  82. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +100 -0
  83. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +52 -0
  84. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +13 -0
  85. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +15 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +46 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +11 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +36 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +13 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +19 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/float.rb +21 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +59 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +13 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/infinity.rb +13 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/integer.rb +11 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/json.rb +35 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +23 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +43 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +43 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +79 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +19 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/time.rb +11 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +109 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +21 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +26 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +28 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid.rb +36 -0
  108. data/lib/active_record/connection_adapters/postgresql/quoting.rb +108 -0
  109. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  110. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +152 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +596 -0
  112. data/lib/active_record/connection_adapters/postgresql/utils.rb +77 -0
  113. data/lib/active_record/connection_adapters/postgresql_adapter.rb +445 -902
  114. data/lib/active_record/connection_adapters/schema_cache.rb +94 -0
  115. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +578 -25
  116. data/lib/active_record/connection_handling.rb +132 -0
  117. data/lib/active_record/core.rb +579 -0
  118. data/lib/active_record/counter_cache.rb +159 -102
  119. data/lib/active_record/dynamic_matchers.rb +140 -0
  120. data/lib/active_record/enum.rb +197 -0
  121. data/lib/active_record/errors.rb +102 -34
  122. data/lib/active_record/explain.rb +38 -0
  123. data/lib/active_record/explain_registry.rb +30 -0
  124. data/lib/active_record/explain_subscriber.rb +29 -0
  125. data/lib/active_record/fixture_set/file.rb +56 -0
  126. data/lib/active_record/fixtures.rb +318 -260
  127. data/lib/active_record/gem_version.rb +15 -0
  128. data/lib/active_record/inheritance.rb +247 -0
  129. data/lib/active_record/integration.rb +113 -0
  130. data/lib/active_record/legacy_yaml_adapter.rb +30 -0
  131. data/lib/active_record/locale/en.yml +8 -1
  132. data/lib/active_record/locking/optimistic.rb +80 -52
  133. data/lib/active_record/locking/pessimistic.rb +27 -5
  134. data/lib/active_record/log_subscriber.rb +25 -18
  135. data/lib/active_record/migration/command_recorder.rb +130 -38
  136. data/lib/active_record/migration/join_table.rb +15 -0
  137. data/lib/active_record/migration.rb +532 -201
  138. data/lib/active_record/model_schema.rb +342 -0
  139. data/lib/active_record/nested_attributes.rb +229 -139
  140. data/lib/active_record/no_touching.rb +52 -0
  141. data/lib/active_record/null_relation.rb +81 -0
  142. data/lib/active_record/persistence.rb +304 -99
  143. data/lib/active_record/query_cache.rb +25 -43
  144. data/lib/active_record/querying.rb +68 -0
  145. data/lib/active_record/railtie.rb +86 -45
  146. data/lib/active_record/railties/console_sandbox.rb +3 -4
  147. data/lib/active_record/railties/controller_runtime.rb +7 -4
  148. data/lib/active_record/railties/databases.rake +198 -377
  149. data/lib/active_record/railties/jdbcmysql_error.rb +2 -2
  150. data/lib/active_record/readonly_attributes.rb +23 -0
  151. data/lib/active_record/reflection.rb +516 -165
  152. data/lib/active_record/relation/batches.rb +96 -45
  153. data/lib/active_record/relation/calculations.rb +221 -144
  154. data/lib/active_record/relation/delegation.rb +140 -0
  155. data/lib/active_record/relation/finder_methods.rb +362 -243
  156. data/lib/active_record/relation/merger.rb +193 -0
  157. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  158. data/lib/active_record/relation/predicate_builder/relation_handler.rb +13 -0
  159. data/lib/active_record/relation/predicate_builder.rb +135 -41
  160. data/lib/active_record/relation/query_methods.rb +982 -155
  161. data/lib/active_record/relation/spawn_methods.rb +50 -110
  162. data/lib/active_record/relation.rb +371 -180
  163. data/lib/active_record/result.rb +109 -12
  164. data/lib/active_record/runtime_registry.rb +22 -0
  165. data/lib/active_record/sanitization.rb +191 -0
  166. data/lib/active_record/schema.rb +19 -13
  167. data/lib/active_record/schema_dumper.rb +111 -61
  168. data/lib/active_record/schema_migration.rb +53 -0
  169. data/lib/active_record/scoping/default.rb +135 -0
  170. data/lib/active_record/scoping/named.rb +164 -0
  171. data/lib/active_record/scoping.rb +87 -0
  172. data/lib/active_record/serialization.rb +7 -45
  173. data/lib/active_record/serializers/xml_serializer.rb +14 -65
  174. data/lib/active_record/statement_cache.rb +111 -0
  175. data/lib/active_record/store.rb +205 -0
  176. data/lib/active_record/tasks/database_tasks.rb +299 -0
  177. data/lib/active_record/tasks/mysql_database_tasks.rb +159 -0
  178. data/lib/active_record/tasks/postgresql_database_tasks.rb +101 -0
  179. data/lib/active_record/tasks/sqlite_database_tasks.rb +55 -0
  180. data/lib/active_record/timestamp.rb +35 -14
  181. data/lib/active_record/transactions.rb +141 -74
  182. data/lib/active_record/translation.rb +22 -0
  183. data/lib/active_record/type/big_integer.rb +13 -0
  184. data/lib/active_record/type/binary.rb +50 -0
  185. data/lib/active_record/type/boolean.rb +31 -0
  186. data/lib/active_record/type/date.rb +50 -0
  187. data/lib/active_record/type/date_time.rb +54 -0
  188. data/lib/active_record/type/decimal.rb +64 -0
  189. data/lib/active_record/type/decimal_without_scale.rb +11 -0
  190. data/lib/active_record/type/decorator.rb +14 -0
  191. data/lib/active_record/type/float.rb +19 -0
  192. data/lib/active_record/type/hash_lookup_type_map.rb +23 -0
  193. data/lib/active_record/type/integer.rb +59 -0
  194. data/lib/active_record/type/mutable.rb +16 -0
  195. data/lib/active_record/type/numeric.rb +36 -0
  196. data/lib/active_record/type/serialized.rb +62 -0
  197. data/lib/active_record/type/string.rb +40 -0
  198. data/lib/active_record/type/text.rb +11 -0
  199. data/lib/active_record/type/time.rb +26 -0
  200. data/lib/active_record/type/time_value.rb +38 -0
  201. data/lib/active_record/type/type_map.rb +64 -0
  202. data/lib/active_record/type/unsigned_integer.rb +15 -0
  203. data/lib/active_record/type/value.rb +110 -0
  204. data/lib/active_record/type.rb +23 -0
  205. data/lib/active_record/validations/associated.rb +27 -18
  206. data/lib/active_record/validations/presence.rb +67 -0
  207. data/lib/active_record/validations/uniqueness.rb +125 -66
  208. data/lib/active_record/validations.rb +37 -30
  209. data/lib/active_record/version.rb +5 -7
  210. data/lib/active_record.rb +80 -25
  211. data/lib/rails/generators/active_record/migration/migration_generator.rb +54 -9
  212. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  213. data/lib/rails/generators/active_record/migration/templates/migration.rb +25 -11
  214. data/lib/rails/generators/active_record/migration.rb +11 -8
  215. data/lib/rails/generators/active_record/model/model_generator.rb +17 -4
  216. data/lib/rails/generators/active_record/model/templates/model.rb +5 -2
  217. data/lib/rails/generators/active_record/model/templates/module.rb +1 -1
  218. data/lib/rails/generators/active_record.rb +3 -11
  219. metadata +132 -53
  220. data/examples/associations.png +0 -0
  221. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +0 -62
  222. data/lib/active_record/associations/join_helper.rb +0 -55
  223. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +0 -60
  224. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -135
  225. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -556
  226. data/lib/active_record/dynamic_finder_match.rb +0 -56
  227. data/lib/active_record/dynamic_scope_match.rb +0 -23
  228. data/lib/active_record/identity_map.rb +0 -163
  229. data/lib/active_record/named_scope.rb +0 -200
  230. data/lib/active_record/observer.rb +0 -121
  231. data/lib/active_record/session_store.rb +0 -358
  232. data/lib/active_record/test_case.rb +0 -69
  233. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -17
  234. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  235. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -4
  236. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -25
  237. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,34 +1,131 @@
1
1
  module ActiveRecord
2
2
  ###
3
3
  # This class encapsulates a Result returned from calling +exec_query+ on any
4
- # database connection adapter. For example:
4
+ # database connection adapter. For example:
5
5
  #
6
- # x = ActiveRecord::Base.connection.exec_query('SELECT * FROM foo')
7
- # x # => #<ActiveRecord::Result:0xdeadbeef>
6
+ # result = ActiveRecord::Base.connection.exec_query('SELECT id, title, body FROM posts')
7
+ # result # => #<ActiveRecord::Result:0xdeadbeef>
8
+ #
9
+ # # Get the column names of the result:
10
+ # result.columns
11
+ # # => ["id", "title", "body"]
12
+ #
13
+ # # Get the record values of the result:
14
+ # result.rows
15
+ # # => [[1, "title_1", "body_1"],
16
+ # [2, "title_2", "body_2"],
17
+ # ...
18
+ # ]
19
+ #
20
+ # # Get an array of hashes representing the result (column => value):
21
+ # result.to_hash
22
+ # # => [{"id" => 1, "title" => "title_1", "body" => "body_1"},
23
+ # {"id" => 2, "title" => "title_2", "body" => "body_2"},
24
+ # ...
25
+ # ]
26
+ #
27
+ # # ActiveRecord::Result also includes Enumerable.
28
+ # result.each do |row|
29
+ # puts row['title'] + " " + row['body']
30
+ # end
8
31
  class Result
9
32
  include Enumerable
10
33
 
11
- attr_reader :columns, :rows
34
+ IDENTITY_TYPE = Type::Value.new # :nodoc:
35
+
36
+ attr_reader :columns, :rows, :column_types
37
+
38
+ def initialize(columns, rows, column_types = {})
39
+ @columns = columns
40
+ @rows = rows
41
+ @hash_rows = nil
42
+ @column_types = column_types
43
+ end
12
44
 
13
- def initialize(columns, rows)
14
- @columns = columns
15
- @rows = rows
16
- @hash_rows = nil
45
+ def length
46
+ @rows.length
17
47
  end
18
48
 
19
49
  def each
20
- hash_rows.each { |row| yield row }
50
+ if block_given?
51
+ hash_rows.each { |row| yield row }
52
+ else
53
+ hash_rows.to_enum { @rows.size }
54
+ end
21
55
  end
22
56
 
23
57
  def to_hash
24
58
  hash_rows
25
59
  end
26
60
 
61
+ alias :map! :map
62
+ alias :collect! :map
63
+
64
+ # Returns true if there are no records.
65
+ def empty?
66
+ rows.empty?
67
+ end
68
+
69
+ def to_ary
70
+ hash_rows
71
+ end
72
+
73
+ def [](idx)
74
+ hash_rows[idx]
75
+ end
76
+
77
+ def last
78
+ hash_rows.last
79
+ end
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
+
90
+ def initialize_copy(other)
91
+ @columns = columns.dup
92
+ @rows = rows.dup
93
+ @column_types = column_types.dup
94
+ @hash_rows = nil
95
+ end
96
+
27
97
  private
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
+
28
105
  def hash_rows
29
- @hash_rows ||= @rows.map { |row|
30
- Hash[@columns.zip(row)]
31
- }
106
+ @hash_rows ||=
107
+ begin
108
+ # We freeze the strings to prevent them getting duped when
109
+ # used as keys in ActiveRecord::Base's @attributes hash
110
+ columns = @columns.map { |c| c.dup.freeze }
111
+ @rows.map { |row|
112
+ # In the past we used Hash[columns.zip(row)]
113
+ # though elegant, the verbose way is much more efficient
114
+ # both time and memory wise cause it avoids a big array allocation
115
+ # this method is called a lot and needs to be micro optimised
116
+ hash = {}
117
+
118
+ index = 0
119
+ length = columns.length
120
+
121
+ while index < length
122
+ hash[columns[index]] = row[index]
123
+ index += 1
124
+ end
125
+
126
+ hash
127
+ }
128
+ end
32
129
  end
33
130
  end
34
131
  end
@@ -0,0 +1,22 @@
1
+ require 'active_support/per_thread_registry'
2
+
3
+ module ActiveRecord
4
+ # This is a thread locals registry for Active Record. For example:
5
+ #
6
+ # ActiveRecord::RuntimeRegistry.connection_handler
7
+ #
8
+ # returns the connection handler local to the current thread.
9
+ #
10
+ # See the documentation of <tt>ActiveSupport::PerThreadRegistry</tt>
11
+ # for further details.
12
+ class RuntimeRegistry # :nodoc:
13
+ extend ActiveSupport::PerThreadRegistry
14
+
15
+ attr_accessor :connection_handler, :sql_runtime, :connection_id
16
+
17
+ [:connection_handler, :sql_runtime, :connection_id].each do |val|
18
+ class_eval %{ def self.#{val}; instance.#{val}; end }, __FILE__, __LINE__
19
+ class_eval %{ def self.#{val}=(x); instance.#{val}=x; end }, __FILE__, __LINE__
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,191 @@
1
+ module ActiveRecord
2
+ module Sanitization
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ def quote_value(value, column) #:nodoc:
7
+ connection.quote(value, column)
8
+ end
9
+
10
+ # Used to sanitize objects before they're used in an SQL SELECT statement. Delegates to <tt>connection.quote</tt>.
11
+ def sanitize(object) #:nodoc:
12
+ connection.quote(object)
13
+ end
14
+
15
+ protected
16
+
17
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
18
+ # them into a valid SQL fragment for a WHERE clause.
19
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
20
+ # { name: "foo'bar", group_id: 4 } returns "name='foo''bar' and group_id='4'"
21
+ # "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
22
+ def sanitize_sql_for_conditions(condition, table_name = self.table_name)
23
+ return nil if condition.blank?
24
+
25
+ case condition
26
+ when Array; sanitize_sql_array(condition)
27
+ when Hash; sanitize_sql_hash_for_conditions(condition, table_name)
28
+ else condition
29
+ end
30
+ end
31
+ alias_method :sanitize_sql, :sanitize_sql_for_conditions
32
+ alias_method :sanitize_conditions, :sanitize_sql
33
+
34
+ # Accepts an array, hash, or string of SQL conditions and sanitizes
35
+ # them into a valid SQL fragment for a SET clause.
36
+ # { name: nil, group_id: 4 } returns "name = NULL , group_id='4'"
37
+ def sanitize_sql_for_assignment(assignments, default_table_name = self.table_name)
38
+ case assignments
39
+ when Array; sanitize_sql_array(assignments)
40
+ when Hash; sanitize_sql_hash_for_assignment(assignments, default_table_name)
41
+ else assignments
42
+ end
43
+ end
44
+
45
+ # Accepts a hash of SQL conditions and replaces those attributes
46
+ # that correspond to a +composed_of+ relationship with their expanded
47
+ # aggregate attribute values.
48
+ # Given:
49
+ # class Person < ActiveRecord::Base
50
+ # composed_of :address, class_name: "Address",
51
+ # mapping: [%w(address_street street), %w(address_city city)]
52
+ # end
53
+ # Then:
54
+ # { address: Address.new("813 abc st.", "chicago") }
55
+ # # => { address_street: "813 abc st.", address_city: "chicago" }
56
+ def expand_hash_conditions_for_aggregates(attrs)
57
+ expanded_attrs = {}
58
+ attrs.each do |attr, value|
59
+ if aggregation = reflect_on_aggregation(attr.to_sym)
60
+ mapping = aggregation.mapping
61
+ mapping.each do |field_attr, aggregate_attr|
62
+ if mapping.size == 1 && !value.respond_to?(aggregate_attr)
63
+ expanded_attrs[field_attr] = value
64
+ else
65
+ expanded_attrs[field_attr] = value.send(aggregate_attr)
66
+ end
67
+ end
68
+ else
69
+ expanded_attrs[attr] = value
70
+ end
71
+ end
72
+ expanded_attrs
73
+ end
74
+
75
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
76
+ # { name: "foo'bar", group_id: 4 }
77
+ # # => "name='foo''bar' and group_id= 4"
78
+ # { status: nil, group_id: [1,2,3] }
79
+ # # => "status IS NULL and group_id IN (1,2,3)"
80
+ # { age: 13..18 }
81
+ # # => "age BETWEEN 13 AND 18"
82
+ # { 'other_records.id' => 7 }
83
+ # # => "`other_records`.`id` = 7"
84
+ # { other_records: { id: 7 } }
85
+ # # => "`other_records`.`id` = 7"
86
+ # And for value objects on a composed_of relationship:
87
+ # { address: Address.new("123 abc st.", "chicago") }
88
+ # # => "address_street='123 abc st.' and address_city='chicago'"
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
93
+ attrs = PredicateBuilder.resolve_column_aliases self, attrs
94
+ attrs = expand_hash_conditions_for_aggregates(attrs)
95
+
96
+ table = Arel::Table.new(table_name, arel_engine).alias(default_table_name)
97
+ PredicateBuilder.build_from_hash(self, attrs, table).map { |b|
98
+ connection.visitor.compile b
99
+ }.join(' AND ')
100
+ end
101
+ alias_method :sanitize_sql_hash, :sanitize_sql_hash_for_conditions
102
+
103
+ # Sanitizes a hash of attribute/value pairs into SQL conditions for a SET clause.
104
+ # { status: nil, group_id: 1 }
105
+ # # => "status = NULL , group_id = 1"
106
+ def sanitize_sql_hash_for_assignment(attrs, table)
107
+ c = connection
108
+ attrs.map do |attr, value|
109
+ "#{c.quote_table_name_for_assignment(table, attr)} = #{quote_bound_value(value, c, columns_hash[attr.to_s])}"
110
+ end.join(', ')
111
+ end
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
+
120
+ # Accepts an array of conditions. The array has each value
121
+ # sanitized and interpolated into the SQL statement.
122
+ # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
123
+ def sanitize_sql_array(ary)
124
+ statement, *values = ary
125
+ if values.first.is_a?(Hash) && statement =~ /:\w+/
126
+ replace_named_bind_variables(statement, values.first)
127
+ elsif statement.include?('?')
128
+ replace_bind_variables(statement, values)
129
+ elsif statement.blank?
130
+ statement
131
+ else
132
+ statement % values.collect { |value| connection.quote_string(value.to_s) }
133
+ end
134
+ end
135
+
136
+ def replace_bind_variables(statement, values) #:nodoc:
137
+ raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
138
+ bound = values.dup
139
+ c = connection
140
+ statement.gsub(/\?/) do
141
+ replace_bind_variable(bound.shift, c)
142
+ end
143
+ end
144
+
145
+ def replace_bind_variable(value, c = connection) #:nodoc:
146
+ if ActiveRecord::Relation === value
147
+ value.to_sql
148
+ else
149
+ quote_bound_value(value, c)
150
+ end
151
+ end
152
+
153
+ def replace_named_bind_variables(statement, bind_vars) #:nodoc:
154
+ statement.gsub(/(:?):([a-zA-Z]\w*)/) do
155
+ if $1 == ':' # skip postgresql casts
156
+ $& # return the whole match
157
+ elsif bind_vars.include?(match = $2.to_sym)
158
+ replace_bind_variable(bind_vars[match])
159
+ else
160
+ raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
161
+ end
162
+ end
163
+ end
164
+
165
+ def quote_bound_value(value, c = connection, column = nil) #:nodoc:
166
+ if column
167
+ c.quote(value, column)
168
+ elsif value.respond_to?(:map) && !value.acts_like?(:string)
169
+ if value.respond_to?(:empty?) && value.empty?
170
+ c.quote(nil)
171
+ else
172
+ value.map { |v| c.quote(v) }.join(',')
173
+ end
174
+ else
175
+ c.quote(value)
176
+ end
177
+ end
178
+
179
+ def raise_if_bind_arity_mismatch(statement, expected, provided) #:nodoc:
180
+ unless expected == provided
181
+ raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
182
+ end
183
+ end
184
+ end
185
+
186
+ # TODO: Deprecate this
187
+ def quoted_id
188
+ self.class.quote_value(id, column_for_attribute(self.class.primary_key))
189
+ end
190
+ end
191
+ end
@@ -1,5 +1,3 @@
1
- require 'active_support/core_ext/object/blank'
2
-
3
1
  module ActiveRecord
4
2
  # = Active Record Schema
5
3
  #
@@ -12,16 +10,16 @@ module ActiveRecord
12
10
  #
13
11
  # ActiveRecord::Schema.define do
14
12
  # create_table :authors do |t|
15
- # t.string :name, :null => false
13
+ # t.string :name, null: false
16
14
  # end
17
15
  #
18
16
  # add_index :authors, :name, :unique
19
17
  #
20
18
  # create_table :posts do |t|
21
- # t.integer :author_id, :null => false
19
+ # t.integer :author_id, null: false
22
20
  # t.string :subject
23
21
  # t.text :body
24
- # t.boolean :private, :default => false
22
+ # t.boolean :private, default: false
25
23
  # end
26
24
  #
27
25
  # add_index :posts, :author_id
@@ -30,10 +28,24 @@ module ActiveRecord
30
28
  # ActiveRecord::Schema is only supported by database adapters that also
31
29
  # support migrations, the two features being very similar.
32
30
  class Schema < Migration
31
+
32
+ # Returns the migrations paths.
33
+ #
34
+ # ActiveRecord::Schema.new.migrations_paths
35
+ # # => ["db/migrate"] # Rails migration path by default.
33
36
  def migrations_paths
34
37
  ActiveRecord::Migrator.migrations_paths
35
38
  end
36
39
 
40
+ def define(info, &block) # :nodoc:
41
+ instance_eval(&block)
42
+
43
+ unless info[:version].blank?
44
+ initialize_schema_migrations_table
45
+ connection.assume_migrated_upto_version(info[:version], migrations_paths)
46
+ end
47
+ end
48
+
37
49
  # Eval the given block. All methods available to the current connection
38
50
  # adapter are available within the block, so you can easily use the
39
51
  # database definition DSL to build up your schema (+create_table+,
@@ -42,17 +54,11 @@ module ActiveRecord
42
54
  # The +info+ hash is optional, and if given is used to define metadata
43
55
  # about the current schema (currently, only the schema's version):
44
56
  #
45
- # ActiveRecord::Schema.define(:version => 20380119000001) do
57
+ # ActiveRecord::Schema.define(version: 20380119000001) do
46
58
  # ...
47
59
  # end
48
60
  def self.define(info={}, &block)
49
- schema = new
50
- schema.instance_eval(&block)
51
-
52
- unless info[:version].blank?
53
- initialize_schema_migrations_table
54
- assume_migrated_upto_version(info[:version], schema.migrations_paths)
55
- end
61
+ new.define(info, &block)
56
62
  end
57
63
  end
58
64
  end
@@ -17,13 +17,24 @@ module ActiveRecord
17
17
  cattr_accessor :ignore_tables
18
18
  @@ignore_tables = []
19
19
 
20
- def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
21
- new(connection).dump(stream)
22
- stream
20
+ class << self
21
+ def dump(connection=ActiveRecord::Base.connection, stream=STDOUT, config = ActiveRecord::Base)
22
+ new(connection, generate_options(config)).dump(stream)
23
+ stream
24
+ end
25
+
26
+ private
27
+ def generate_options(config)
28
+ {
29
+ table_name_prefix: config.table_name_prefix,
30
+ table_name_suffix: config.table_name_suffix
31
+ }
32
+ end
23
33
  end
24
34
 
25
35
  def dump(stream)
26
36
  header(stream)
37
+ extensions(stream)
27
38
  tables(stream)
28
39
  trailer(stream)
29
40
  stream
@@ -31,14 +42,15 @@ module ActiveRecord
31
42
 
32
43
  private
33
44
 
34
- def initialize(connection)
45
+ def initialize(connection, options = {})
35
46
  @connection = connection
36
47
  @types = @connection.native_database_types
37
48
  @version = Migrator::current_version rescue nil
49
+ @options = options
38
50
  end
39
51
 
40
52
  def header(stream)
41
- define_params = @version ? ":version => #{@version}" : ""
53
+ define_params = @version ? "version: #{@version}" : ""
42
54
 
43
55
  if stream.respond_to?(:external_encoding) && stream.external_encoding
44
56
  stream.puts "# encoding: #{stream.external_encoding.name}"
@@ -55,7 +67,7 @@ module ActiveRecord
55
67
  # from scratch. The latter is a flawed and unsustainable approach (the more migrations
56
68
  # you'll amass, the slower it'll run and the greater likelihood for issues).
57
69
  #
58
- # It's strongly recommended to check this file into your version control system.
70
+ # It's strongly recommended that you check this file into your version control system.
59
71
 
60
72
  ActiveRecord::Schema.define(#{define_params}) do
61
73
 
@@ -66,17 +78,30 @@ HEADER
66
78
  stream.puts "end"
67
79
  end
68
80
 
81
+ def extensions(stream)
82
+ return unless @connection.supports_extensions?
83
+ extensions = @connection.extensions
84
+ if extensions.any?
85
+ stream.puts " # These are extensions that must be enabled in order to support this database"
86
+ extensions.each do |extension|
87
+ stream.puts " enable_extension #{extension.inspect}"
88
+ end
89
+ stream.puts
90
+ end
91
+ end
92
+
69
93
  def tables(stream)
70
- @connection.tables.sort.each do |tbl|
71
- next if ['schema_migrations', ignore_tables].flatten.any? do |ignored|
72
- case ignored
73
- when String; tbl == ignored
74
- when Regexp; tbl =~ ignored
75
- else
76
- raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
77
- 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)
78
104
  end
79
- table(tbl, stream)
80
105
  end
81
106
  end
82
107
 
@@ -86,51 +111,41 @@ HEADER
86
111
  tbl = StringIO.new
87
112
 
88
113
  # first dump primary key column
89
- if @connection.respond_to?(:pk_and_sequence_for)
90
- pk, _ = @connection.pk_and_sequence_for(table)
91
- elsif @connection.respond_to?(:primary_key)
92
- pk = @connection.primary_key(table)
93
- end
114
+ pk = @connection.primary_key(table)
94
115
 
95
- tbl.print " create_table #{table.inspect}"
96
- if columns.detect { |c| c.name == pk }
116
+ tbl.print " create_table #{remove_prefix_and_suffix(table).inspect}"
117
+ pkcol = columns.detect { |c| c.name == pk }
118
+ if pkcol
97
119
  if pk != 'id'
98
- tbl.print %Q(, :primary_key => "#{pk}")
120
+ tbl.print %Q(, primary_key: "#{pk}")
121
+ elsif pkcol.sql_type == 'bigint'
122
+ tbl.print ", id: :bigserial"
123
+ elsif pkcol.sql_type == 'uuid'
124
+ tbl.print ", id: :uuid"
125
+ tbl.print %Q(, default: #{pkcol.default_function.inspect})
99
126
  end
100
127
  else
101
- tbl.print ", :id => false"
128
+ tbl.print ", id: false"
102
129
  end
103
- tbl.print ", :force => true"
130
+ tbl.print ", force: :cascade"
104
131
  tbl.puts " do |t|"
105
132
 
106
133
  # then dump all non-primary key columns
107
134
  column_specs = columns.map do |column|
108
- raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
135
+ raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" unless @connection.valid_type?(column.type)
109
136
  next if column.name == pk
110
- spec = {}
111
- spec[:name] = column.name.inspect
112
-
113
- # AR has an optimisation which handles zero-scale decimals as integers. This
114
- # code ensures that the dumper still dumps the column as a decimal.
115
- spec[:type] = if column.type == :integer && [/^numeric/, /^decimal/].any? { |e| e.match(column.sql_type) }
116
- 'decimal'
117
- else
118
- column.type.to_s
119
- end
120
- spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && spec[:type] != 'decimal'
121
- spec[:precision] = column.precision.inspect if column.precision
122
- spec[:scale] = column.scale.inspect if column.scale
123
- spec[:null] = 'false' unless column.null
124
- spec[:default] = default_string(column.default) if column.has_default?
125
- (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
126
- spec
137
+ @connection.column_spec(column, @types)
127
138
  end.compact
128
139
 
129
140
  # find all migration keys used in this table
130
- keys = [:name, :limit, :precision, :scale, :default, :null] & column_specs.map{ |k| k.keys }.flatten
141
+ keys = @connection.migration_keys
131
142
 
132
143
  # figure out the lengths for each column based on above keys
133
- lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
144
+ lengths = keys.map { |key|
145
+ column_specs.map { |spec|
146
+ spec[key] ? spec[key].length + 2 : 0
147
+ }.max
148
+ }
134
149
 
135
150
  # the string we're going to sprintf our values against, with standardized column widths
136
151
  format_string = lengths.map{ |len| "%-#{len}s" }
@@ -166,36 +181,71 @@ HEADER
166
181
  stream
167
182
  end
168
183
 
169
- def default_string(value)
170
- case value
171
- when BigDecimal
172
- value.to_s
173
- when Date, DateTime, Time
174
- "'" + value.to_s(:db) + "'"
175
- else
176
- value.inspect
177
- end
178
- end
179
-
180
184
  def indexes(table, stream)
181
185
  if (indexes = @connection.indexes(table)).any?
182
186
  add_index_statements = indexes.map do |index|
183
187
  statement_parts = [
184
- ('add_index ' + index.table.inspect),
188
+ "add_index #{remove_prefix_and_suffix(index.table).inspect}",
185
189
  index.columns.inspect,
186
- (':name => ' + index.name.inspect),
190
+ "name: #{index.name.inspect}",
187
191
  ]
188
- statement_parts << ':unique => true' if index.unique
192
+ statement_parts << 'unique: true' if index.unique
189
193
 
190
194
  index_lengths = (index.lengths || []).compact
191
- 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
192
202
 
193
- ' ' + statement_parts.join(', ')
203
+ " #{statement_parts.join(', ')}"
194
204
  end
195
205
 
196
206
  stream.puts add_index_statements.sort.join("\n")
197
207
  stream.puts
198
208
  end
199
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
+ ]
218
+
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(', ')}"
235
+ end
236
+
237
+ stream.puts add_foreign_key_statements.sort.join("\n")
238
+ end
239
+ end
240
+
241
+ def remove_prefix_and_suffix(table)
242
+ table.gsub(/^(#{@options[:table_name_prefix]})(.+)(#{@options[:table_name_suffix]})$/, "\\2")
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
200
250
  end
201
251
  end