activerecord 5.2.3

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 (244) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +937 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +217 -0
  5. data/examples/performance.rb +185 -0
  6. data/examples/simple.rb +15 -0
  7. data/lib/active_record.rb +188 -0
  8. data/lib/active_record/aggregations.rb +283 -0
  9. data/lib/active_record/association_relation.rb +40 -0
  10. data/lib/active_record/associations.rb +1860 -0
  11. data/lib/active_record/associations/alias_tracker.rb +81 -0
  12. data/lib/active_record/associations/association.rb +299 -0
  13. data/lib/active_record/associations/association_scope.rb +168 -0
  14. data/lib/active_record/associations/belongs_to_association.rb +130 -0
  15. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +40 -0
  16. data/lib/active_record/associations/builder/association.rb +140 -0
  17. data/lib/active_record/associations/builder/belongs_to.rb +163 -0
  18. data/lib/active_record/associations/builder/collection_association.rb +82 -0
  19. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +135 -0
  20. data/lib/active_record/associations/builder/has_many.rb +17 -0
  21. data/lib/active_record/associations/builder/has_one.rb +30 -0
  22. data/lib/active_record/associations/builder/singular_association.rb +42 -0
  23. data/lib/active_record/associations/collection_association.rb +513 -0
  24. data/lib/active_record/associations/collection_proxy.rb +1131 -0
  25. data/lib/active_record/associations/foreign_association.rb +13 -0
  26. data/lib/active_record/associations/has_many_association.rb +144 -0
  27. data/lib/active_record/associations/has_many_through_association.rb +227 -0
  28. data/lib/active_record/associations/has_one_association.rb +120 -0
  29. data/lib/active_record/associations/has_one_through_association.rb +45 -0
  30. data/lib/active_record/associations/join_dependency.rb +262 -0
  31. data/lib/active_record/associations/join_dependency/join_association.rb +60 -0
  32. data/lib/active_record/associations/join_dependency/join_base.rb +23 -0
  33. data/lib/active_record/associations/join_dependency/join_part.rb +71 -0
  34. data/lib/active_record/associations/preloader.rb +193 -0
  35. data/lib/active_record/associations/preloader/association.rb +131 -0
  36. data/lib/active_record/associations/preloader/through_association.rb +107 -0
  37. data/lib/active_record/associations/singular_association.rb +73 -0
  38. data/lib/active_record/associations/through_association.rb +121 -0
  39. data/lib/active_record/attribute_assignment.rb +88 -0
  40. data/lib/active_record/attribute_decorators.rb +90 -0
  41. data/lib/active_record/attribute_methods.rb +492 -0
  42. data/lib/active_record/attribute_methods/before_type_cast.rb +78 -0
  43. data/lib/active_record/attribute_methods/dirty.rb +150 -0
  44. data/lib/active_record/attribute_methods/primary_key.rb +143 -0
  45. data/lib/active_record/attribute_methods/query.rb +42 -0
  46. data/lib/active_record/attribute_methods/read.rb +85 -0
  47. data/lib/active_record/attribute_methods/serialization.rb +90 -0
  48. data/lib/active_record/attribute_methods/time_zone_conversion.rb +91 -0
  49. data/lib/active_record/attribute_methods/write.rb +68 -0
  50. data/lib/active_record/attributes.rb +266 -0
  51. data/lib/active_record/autosave_association.rb +498 -0
  52. data/lib/active_record/base.rb +329 -0
  53. data/lib/active_record/callbacks.rb +353 -0
  54. data/lib/active_record/coders/json.rb +15 -0
  55. data/lib/active_record/coders/yaml_column.rb +50 -0
  56. data/lib/active_record/collection_cache_key.rb +53 -0
  57. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +1068 -0
  58. data/lib/active_record/connection_adapters/abstract/database_limits.rb +72 -0
  59. data/lib/active_record/connection_adapters/abstract/database_statements.rb +540 -0
  60. data/lib/active_record/connection_adapters/abstract/query_cache.rb +145 -0
  61. data/lib/active_record/connection_adapters/abstract/quoting.rb +200 -0
  62. data/lib/active_record/connection_adapters/abstract/savepoints.rb +23 -0
  63. data/lib/active_record/connection_adapters/abstract/schema_creation.rb +146 -0
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +685 -0
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +95 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +1396 -0
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +283 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +628 -0
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +887 -0
  70. data/lib/active_record/connection_adapters/column.rb +91 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +287 -0
  72. data/lib/active_record/connection_adapters/determine_if_preparable_visitor.rb +33 -0
  73. data/lib/active_record/connection_adapters/mysql/column.rb +27 -0
  74. data/lib/active_record/connection_adapters/mysql/database_statements.rb +140 -0
  75. data/lib/active_record/connection_adapters/mysql/explain_pretty_printer.rb +72 -0
  76. data/lib/active_record/connection_adapters/mysql/quoting.rb +44 -0
  77. data/lib/active_record/connection_adapters/mysql/schema_creation.rb +73 -0
  78. data/lib/active_record/connection_adapters/mysql/schema_definitions.rb +87 -0
  79. data/lib/active_record/connection_adapters/mysql/schema_dumper.rb +80 -0
  80. data/lib/active_record/connection_adapters/mysql/schema_statements.rb +148 -0
  81. data/lib/active_record/connection_adapters/mysql/type_metadata.rb +35 -0
  82. data/lib/active_record/connection_adapters/mysql2_adapter.rb +129 -0
  83. data/lib/active_record/connection_adapters/postgresql/column.rb +44 -0
  84. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +163 -0
  85. data/lib/active_record/connection_adapters/postgresql/explain_pretty_printer.rb +44 -0
  86. data/lib/active_record/connection_adapters/postgresql/oid.rb +34 -0
  87. data/lib/active_record/connection_adapters/postgresql/oid/array.rb +92 -0
  88. data/lib/active_record/connection_adapters/postgresql/oid/bit.rb +56 -0
  89. data/lib/active_record/connection_adapters/postgresql/oid/bit_varying.rb +15 -0
  90. data/lib/active_record/connection_adapters/postgresql/oid/bytea.rb +17 -0
  91. data/lib/active_record/connection_adapters/postgresql/oid/cidr.rb +50 -0
  92. data/lib/active_record/connection_adapters/postgresql/oid/date.rb +23 -0
  93. data/lib/active_record/connection_adapters/postgresql/oid/date_time.rb +23 -0
  94. data/lib/active_record/connection_adapters/postgresql/oid/decimal.rb +15 -0
  95. data/lib/active_record/connection_adapters/postgresql/oid/enum.rb +21 -0
  96. data/lib/active_record/connection_adapters/postgresql/oid/hstore.rb +71 -0
  97. data/lib/active_record/connection_adapters/postgresql/oid/inet.rb +15 -0
  98. data/lib/active_record/connection_adapters/postgresql/oid/jsonb.rb +15 -0
  99. data/lib/active_record/connection_adapters/postgresql/oid/legacy_point.rb +45 -0
  100. data/lib/active_record/connection_adapters/postgresql/oid/money.rb +41 -0
  101. data/lib/active_record/connection_adapters/postgresql/oid/oid.rb +15 -0
  102. data/lib/active_record/connection_adapters/postgresql/oid/point.rb +65 -0
  103. data/lib/active_record/connection_adapters/postgresql/oid/range.rb +97 -0
  104. data/lib/active_record/connection_adapters/postgresql/oid/specialized_string.rb +18 -0
  105. data/lib/active_record/connection_adapters/postgresql/oid/type_map_initializer.rb +111 -0
  106. data/lib/active_record/connection_adapters/postgresql/oid/uuid.rb +23 -0
  107. data/lib/active_record/connection_adapters/postgresql/oid/vector.rb +28 -0
  108. data/lib/active_record/connection_adapters/postgresql/oid/xml.rb +30 -0
  109. data/lib/active_record/connection_adapters/postgresql/quoting.rb +168 -0
  110. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +43 -0
  111. data/lib/active_record/connection_adapters/postgresql/schema_creation.rb +65 -0
  112. data/lib/active_record/connection_adapters/postgresql/schema_definitions.rb +206 -0
  113. data/lib/active_record/connection_adapters/postgresql/schema_dumper.rb +50 -0
  114. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +774 -0
  115. data/lib/active_record/connection_adapters/postgresql/type_metadata.rb +39 -0
  116. data/lib/active_record/connection_adapters/postgresql/utils.rb +81 -0
  117. data/lib/active_record/connection_adapters/postgresql_adapter.rb +863 -0
  118. data/lib/active_record/connection_adapters/schema_cache.rb +118 -0
  119. data/lib/active_record/connection_adapters/sql_type_metadata.rb +34 -0
  120. data/lib/active_record/connection_adapters/sqlite3/explain_pretty_printer.rb +21 -0
  121. data/lib/active_record/connection_adapters/sqlite3/quoting.rb +67 -0
  122. data/lib/active_record/connection_adapters/sqlite3/schema_creation.rb +17 -0
  123. data/lib/active_record/connection_adapters/sqlite3/schema_definitions.rb +19 -0
  124. data/lib/active_record/connection_adapters/sqlite3/schema_dumper.rb +18 -0
  125. data/lib/active_record/connection_adapters/sqlite3/schema_statements.rb +106 -0
  126. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +573 -0
  127. data/lib/active_record/connection_adapters/statement_pool.rb +61 -0
  128. data/lib/active_record/connection_handling.rb +145 -0
  129. data/lib/active_record/core.rb +559 -0
  130. data/lib/active_record/counter_cache.rb +218 -0
  131. data/lib/active_record/define_callbacks.rb +22 -0
  132. data/lib/active_record/dynamic_matchers.rb +122 -0
  133. data/lib/active_record/enum.rb +244 -0
  134. data/lib/active_record/errors.rb +380 -0
  135. data/lib/active_record/explain.rb +50 -0
  136. data/lib/active_record/explain_registry.rb +32 -0
  137. data/lib/active_record/explain_subscriber.rb +34 -0
  138. data/lib/active_record/fixture_set/file.rb +82 -0
  139. data/lib/active_record/fixtures.rb +1065 -0
  140. data/lib/active_record/gem_version.rb +17 -0
  141. data/lib/active_record/inheritance.rb +283 -0
  142. data/lib/active_record/integration.rb +155 -0
  143. data/lib/active_record/internal_metadata.rb +45 -0
  144. data/lib/active_record/legacy_yaml_adapter.rb +48 -0
  145. data/lib/active_record/locale/en.yml +48 -0
  146. data/lib/active_record/locking/optimistic.rb +198 -0
  147. data/lib/active_record/locking/pessimistic.rb +89 -0
  148. data/lib/active_record/log_subscriber.rb +137 -0
  149. data/lib/active_record/migration.rb +1378 -0
  150. data/lib/active_record/migration/command_recorder.rb +240 -0
  151. data/lib/active_record/migration/compatibility.rb +217 -0
  152. data/lib/active_record/migration/join_table.rb +17 -0
  153. data/lib/active_record/model_schema.rb +521 -0
  154. data/lib/active_record/nested_attributes.rb +600 -0
  155. data/lib/active_record/no_touching.rb +58 -0
  156. data/lib/active_record/null_relation.rb +68 -0
  157. data/lib/active_record/persistence.rb +763 -0
  158. data/lib/active_record/query_cache.rb +45 -0
  159. data/lib/active_record/querying.rb +70 -0
  160. data/lib/active_record/railtie.rb +226 -0
  161. data/lib/active_record/railties/console_sandbox.rb +7 -0
  162. data/lib/active_record/railties/controller_runtime.rb +56 -0
  163. data/lib/active_record/railties/databases.rake +377 -0
  164. data/lib/active_record/readonly_attributes.rb +24 -0
  165. data/lib/active_record/reflection.rb +1044 -0
  166. data/lib/active_record/relation.rb +629 -0
  167. data/lib/active_record/relation/batches.rb +287 -0
  168. data/lib/active_record/relation/batches/batch_enumerator.rb +69 -0
  169. data/lib/active_record/relation/calculations.rb +417 -0
  170. data/lib/active_record/relation/delegation.rb +147 -0
  171. data/lib/active_record/relation/finder_methods.rb +565 -0
  172. data/lib/active_record/relation/from_clause.rb +26 -0
  173. data/lib/active_record/relation/merger.rb +193 -0
  174. data/lib/active_record/relation/predicate_builder.rb +152 -0
  175. data/lib/active_record/relation/predicate_builder/array_handler.rb +48 -0
  176. data/lib/active_record/relation/predicate_builder/association_query_value.rb +46 -0
  177. data/lib/active_record/relation/predicate_builder/base_handler.rb +19 -0
  178. data/lib/active_record/relation/predicate_builder/basic_object_handler.rb +20 -0
  179. data/lib/active_record/relation/predicate_builder/polymorphic_array_value.rb +56 -0
  180. data/lib/active_record/relation/predicate_builder/range_handler.rb +42 -0
  181. data/lib/active_record/relation/predicate_builder/relation_handler.rb +19 -0
  182. data/lib/active_record/relation/query_attribute.rb +45 -0
  183. data/lib/active_record/relation/query_methods.rb +1231 -0
  184. data/lib/active_record/relation/record_fetch_warning.rb +51 -0
  185. data/lib/active_record/relation/spawn_methods.rb +77 -0
  186. data/lib/active_record/relation/where_clause.rb +186 -0
  187. data/lib/active_record/relation/where_clause_factory.rb +34 -0
  188. data/lib/active_record/result.rb +149 -0
  189. data/lib/active_record/runtime_registry.rb +24 -0
  190. data/lib/active_record/sanitization.rb +222 -0
  191. data/lib/active_record/schema.rb +70 -0
  192. data/lib/active_record/schema_dumper.rb +255 -0
  193. data/lib/active_record/schema_migration.rb +56 -0
  194. data/lib/active_record/scoping.rb +106 -0
  195. data/lib/active_record/scoping/default.rb +152 -0
  196. data/lib/active_record/scoping/named.rb +213 -0
  197. data/lib/active_record/secure_token.rb +40 -0
  198. data/lib/active_record/serialization.rb +22 -0
  199. data/lib/active_record/statement_cache.rb +121 -0
  200. data/lib/active_record/store.rb +211 -0
  201. data/lib/active_record/suppressor.rb +61 -0
  202. data/lib/active_record/table_metadata.rb +82 -0
  203. data/lib/active_record/tasks/database_tasks.rb +337 -0
  204. data/lib/active_record/tasks/mysql_database_tasks.rb +115 -0
  205. data/lib/active_record/tasks/postgresql_database_tasks.rb +143 -0
  206. data/lib/active_record/tasks/sqlite_database_tasks.rb +83 -0
  207. data/lib/active_record/timestamp.rb +153 -0
  208. data/lib/active_record/touch_later.rb +64 -0
  209. data/lib/active_record/transactions.rb +502 -0
  210. data/lib/active_record/translation.rb +24 -0
  211. data/lib/active_record/type.rb +79 -0
  212. data/lib/active_record/type/adapter_specific_registry.rb +136 -0
  213. data/lib/active_record/type/date.rb +9 -0
  214. data/lib/active_record/type/date_time.rb +9 -0
  215. data/lib/active_record/type/decimal_without_scale.rb +15 -0
  216. data/lib/active_record/type/hash_lookup_type_map.rb +25 -0
  217. data/lib/active_record/type/internal/timezone.rb +17 -0
  218. data/lib/active_record/type/json.rb +30 -0
  219. data/lib/active_record/type/serialized.rb +71 -0
  220. data/lib/active_record/type/text.rb +11 -0
  221. data/lib/active_record/type/time.rb +21 -0
  222. data/lib/active_record/type/type_map.rb +62 -0
  223. data/lib/active_record/type/unsigned_integer.rb +17 -0
  224. data/lib/active_record/type_caster.rb +9 -0
  225. data/lib/active_record/type_caster/connection.rb +33 -0
  226. data/lib/active_record/type_caster/map.rb +23 -0
  227. data/lib/active_record/validations.rb +93 -0
  228. data/lib/active_record/validations/absence.rb +25 -0
  229. data/lib/active_record/validations/associated.rb +60 -0
  230. data/lib/active_record/validations/length.rb +26 -0
  231. data/lib/active_record/validations/presence.rb +68 -0
  232. data/lib/active_record/validations/uniqueness.rb +238 -0
  233. data/lib/active_record/version.rb +10 -0
  234. data/lib/rails/generators/active_record.rb +19 -0
  235. data/lib/rails/generators/active_record/application_record/application_record_generator.rb +27 -0
  236. data/lib/rails/generators/active_record/application_record/templates/application_record.rb.tt +5 -0
  237. data/lib/rails/generators/active_record/migration.rb +35 -0
  238. data/lib/rails/generators/active_record/migration/migration_generator.rb +78 -0
  239. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb.tt +24 -0
  240. data/lib/rails/generators/active_record/migration/templates/migration.rb.tt +46 -0
  241. data/lib/rails/generators/active_record/model/model_generator.rb +48 -0
  242. data/lib/rails/generators/active_record/model/templates/model.rb.tt +13 -0
  243. data/lib/rails/generators/active_record/model/templates/module.rb.tt +7 -0
  244. metadata +333 -0
@@ -0,0 +1,143 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "tempfile"
4
+
5
+ module ActiveRecord
6
+ module Tasks # :nodoc:
7
+ class PostgreSQLDatabaseTasks # :nodoc:
8
+ DEFAULT_ENCODING = ENV["CHARSET"] || "utf8"
9
+ ON_ERROR_STOP_1 = "ON_ERROR_STOP=1".freeze
10
+ SQL_COMMENT_BEGIN = "--".freeze
11
+
12
+ delegate :connection, :establish_connection, :clear_active_connections!,
13
+ to: ActiveRecord::Base
14
+
15
+ def initialize(configuration)
16
+ @configuration = configuration
17
+ end
18
+
19
+ def create(master_established = false)
20
+ establish_master_connection unless master_established
21
+ connection.create_database configuration["database"],
22
+ configuration.merge("encoding" => encoding)
23
+ establish_connection configuration
24
+ rescue ActiveRecord::StatementInvalid => error
25
+ if error.cause.is_a?(PG::DuplicateDatabase)
26
+ raise DatabaseAlreadyExists
27
+ else
28
+ raise
29
+ end
30
+ end
31
+
32
+ def drop
33
+ establish_master_connection
34
+ connection.drop_database configuration["database"]
35
+ end
36
+
37
+ def charset
38
+ connection.encoding
39
+ end
40
+
41
+ def collation
42
+ connection.collation
43
+ end
44
+
45
+ def purge
46
+ clear_active_connections!
47
+ drop
48
+ create true
49
+ end
50
+
51
+ def structure_dump(filename, extra_flags)
52
+ set_psql_env
53
+
54
+ search_path = \
55
+ case ActiveRecord::Base.dump_schemas
56
+ when :schema_search_path
57
+ configuration["schema_search_path"]
58
+ when :all
59
+ nil
60
+ when String
61
+ ActiveRecord::Base.dump_schemas
62
+ end
63
+
64
+ args = ["-s", "-x", "-O", "-f", filename]
65
+ args.concat(Array(extra_flags)) if extra_flags
66
+ unless search_path.blank?
67
+ args += search_path.split(",").map do |part|
68
+ "--schema=#{part.strip}"
69
+ end
70
+ end
71
+
72
+ ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
73
+ if ignore_tables.any?
74
+ args += ignore_tables.flat_map { |table| ["-T", table] }
75
+ end
76
+
77
+ args << configuration["database"]
78
+ run_cmd("pg_dump", args, "dumping")
79
+ remove_sql_header_comments(filename)
80
+ File.open(filename, "a") { |f| f << "SET search_path TO #{connection.schema_search_path};\n\n" }
81
+ end
82
+
83
+ def structure_load(filename, extra_flags)
84
+ set_psql_env
85
+ args = ["-v", ON_ERROR_STOP_1, "-q", "-f", filename]
86
+ args.concat(Array(extra_flags)) if extra_flags
87
+ args << configuration["database"]
88
+ run_cmd("psql", args, "loading")
89
+ end
90
+
91
+ private
92
+
93
+ def configuration
94
+ @configuration
95
+ end
96
+
97
+ def encoding
98
+ configuration["encoding"] || DEFAULT_ENCODING
99
+ end
100
+
101
+ def establish_master_connection
102
+ establish_connection configuration.merge(
103
+ "database" => "postgres",
104
+ "schema_search_path" => "public"
105
+ )
106
+ end
107
+
108
+ def set_psql_env
109
+ ENV["PGHOST"] = configuration["host"] if configuration["host"]
110
+ ENV["PGPORT"] = configuration["port"].to_s if configuration["port"]
111
+ ENV["PGPASSWORD"] = configuration["password"].to_s if configuration["password"]
112
+ ENV["PGUSER"] = configuration["username"].to_s if configuration["username"]
113
+ end
114
+
115
+ def run_cmd(cmd, args, action)
116
+ fail run_cmd_error(cmd, args, action) unless Kernel.system(cmd, *args)
117
+ end
118
+
119
+ def run_cmd_error(cmd, args, action)
120
+ msg = "failed to execute:\n".dup
121
+ msg << "#{cmd} #{args.join(' ')}\n\n"
122
+ msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
123
+ msg
124
+ end
125
+
126
+ def remove_sql_header_comments(filename)
127
+ removing_comments = true
128
+ tempfile = Tempfile.open("uncommented_structure.sql")
129
+ begin
130
+ File.foreach(filename) do |line|
131
+ unless removing_comments && (line.start_with?(SQL_COMMENT_BEGIN) || line.blank?)
132
+ tempfile << line
133
+ removing_comments = false
134
+ end
135
+ end
136
+ ensure
137
+ tempfile.close
138
+ end
139
+ FileUtils.cp(tempfile.path, filename)
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ module Tasks # :nodoc:
5
+ class SQLiteDatabaseTasks # :nodoc:
6
+ delegate :connection, :establish_connection, to: ActiveRecord::Base
7
+
8
+ def initialize(configuration, root = ActiveRecord::Tasks::DatabaseTasks.root)
9
+ @configuration, @root = configuration, root
10
+ end
11
+
12
+ def create
13
+ raise DatabaseAlreadyExists if File.exist?(configuration["database"])
14
+
15
+ establish_connection configuration
16
+ connection
17
+ end
18
+
19
+ def drop
20
+ require "pathname"
21
+ path = Pathname.new configuration["database"]
22
+ file = path.absolute? ? path.to_s : File.join(root, path)
23
+
24
+ FileUtils.rm(file)
25
+ rescue Errno::ENOENT => error
26
+ raise NoDatabaseError.new(error.message)
27
+ end
28
+
29
+ def purge
30
+ drop
31
+ rescue NoDatabaseError
32
+ ensure
33
+ create
34
+ end
35
+
36
+ def charset
37
+ connection.encoding
38
+ end
39
+
40
+ def structure_dump(filename, extra_flags)
41
+ args = []
42
+ args.concat(Array(extra_flags)) if extra_flags
43
+ args << configuration["database"]
44
+
45
+ ignore_tables = ActiveRecord::SchemaDumper.ignore_tables
46
+ if ignore_tables.any?
47
+ condition = ignore_tables.map { |table| connection.quote(table) }.join(", ")
48
+ args << "SELECT sql FROM sqlite_master WHERE tbl_name NOT IN (#{condition}) ORDER BY tbl_name, type DESC, name"
49
+ else
50
+ args << ".schema"
51
+ end
52
+ run_cmd("sqlite3", args, filename)
53
+ end
54
+
55
+ def structure_load(filename, extra_flags)
56
+ dbfile = configuration["database"]
57
+ flags = extra_flags.join(" ") if extra_flags
58
+ `sqlite3 #{flags} #{dbfile} < "#{filename}"`
59
+ end
60
+
61
+ private
62
+
63
+ def configuration
64
+ @configuration
65
+ end
66
+
67
+ def root
68
+ @root
69
+ end
70
+
71
+ def run_cmd(cmd, args, out)
72
+ fail run_cmd_error(cmd, args) unless Kernel.system(cmd, *args, out: out)
73
+ end
74
+
75
+ def run_cmd_error(cmd, args)
76
+ msg = "failed to execute:\n".dup
77
+ msg << "#{cmd} #{args.join(' ')}\n\n"
78
+ msg << "Please check the output above for any errors and make sure that `#{cmd}` is installed in your PATH and has proper permissions.\n\n"
79
+ msg
80
+ end
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,153 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ # = Active Record \Timestamp
5
+ #
6
+ # Active Record automatically timestamps create and update operations if the
7
+ # table has fields named <tt>created_at/created_on</tt> or
8
+ # <tt>updated_at/updated_on</tt>.
9
+ #
10
+ # Timestamping can be turned off by setting:
11
+ #
12
+ # config.active_record.record_timestamps = false
13
+ #
14
+ # Timestamps are in UTC by default but you can use the local timezone by setting:
15
+ #
16
+ # config.active_record.default_timezone = :local
17
+ #
18
+ # == Time Zone aware attributes
19
+ #
20
+ # Active Record keeps all the <tt>datetime</tt> and <tt>time</tt> columns
21
+ # timezone aware. By default, these values are stored in the database as UTC
22
+ # and converted back to the current <tt>Time.zone</tt> when pulled from the database.
23
+ #
24
+ # This feature can be turned off completely by setting:
25
+ #
26
+ # config.active_record.time_zone_aware_attributes = false
27
+ #
28
+ # You can also specify that only <tt>datetime</tt> columns should be time-zone
29
+ # aware (while <tt>time</tt> should not) by setting:
30
+ #
31
+ # ActiveRecord::Base.time_zone_aware_types = [:datetime]
32
+ #
33
+ # You can also add database specific timezone aware types. For example, for PostgreSQL:
34
+ #
35
+ # ActiveRecord::Base.time_zone_aware_types += [:tsrange, :tstzrange]
36
+ #
37
+ # Finally, you can indicate specific attributes of a model for which time zone
38
+ # conversion should not applied, for instance by setting:
39
+ #
40
+ # class Topic < ActiveRecord::Base
41
+ # self.skip_time_zone_conversion_for_attributes = [:written_on]
42
+ # end
43
+ module Timestamp
44
+ extend ActiveSupport::Concern
45
+
46
+ included do
47
+ class_attribute :record_timestamps, default: true
48
+ end
49
+
50
+ def initialize_dup(other) # :nodoc:
51
+ super
52
+ clear_timestamp_attributes
53
+ end
54
+
55
+ module ClassMethods # :nodoc:
56
+ def touch_attributes_with_time(*names, time: nil)
57
+ attribute_names = timestamp_attributes_for_update_in_model
58
+ attribute_names |= names.map(&:to_s)
59
+ time ||= current_time_from_proper_timezone
60
+ attribute_names.each_with_object({}) { |attr_name, result| result[attr_name] = time }
61
+ end
62
+
63
+ private
64
+ def timestamp_attributes_for_create_in_model
65
+ timestamp_attributes_for_create.select { |c| column_names.include?(c) }
66
+ end
67
+
68
+ def timestamp_attributes_for_update_in_model
69
+ timestamp_attributes_for_update.select { |c| column_names.include?(c) }
70
+ end
71
+
72
+ def all_timestamp_attributes_in_model
73
+ timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
74
+ end
75
+
76
+ def timestamp_attributes_for_create
77
+ ["created_at", "created_on"]
78
+ end
79
+
80
+ def timestamp_attributes_for_update
81
+ ["updated_at", "updated_on"]
82
+ end
83
+
84
+ def current_time_from_proper_timezone
85
+ default_timezone == :utc ? Time.now.utc : Time.now
86
+ end
87
+ end
88
+
89
+ private
90
+
91
+ def _create_record
92
+ if record_timestamps
93
+ current_time = current_time_from_proper_timezone
94
+
95
+ all_timestamp_attributes_in_model.each do |column|
96
+ if !attribute_present?(column)
97
+ _write_attribute(column, current_time)
98
+ end
99
+ end
100
+ end
101
+
102
+ super
103
+ end
104
+
105
+ def _update_record(*args, touch: true, **options)
106
+ if touch && should_record_timestamps?
107
+ current_time = current_time_from_proper_timezone
108
+
109
+ timestamp_attributes_for_update_in_model.each do |column|
110
+ next if will_save_change_to_attribute?(column)
111
+ _write_attribute(column, current_time)
112
+ end
113
+ end
114
+ super(*args)
115
+ end
116
+
117
+ def should_record_timestamps?
118
+ record_timestamps && (!partial_writes? || has_changes_to_save?)
119
+ end
120
+
121
+ def timestamp_attributes_for_create_in_model
122
+ self.class.send(:timestamp_attributes_for_create_in_model)
123
+ end
124
+
125
+ def timestamp_attributes_for_update_in_model
126
+ self.class.send(:timestamp_attributes_for_update_in_model)
127
+ end
128
+
129
+ def all_timestamp_attributes_in_model
130
+ self.class.send(:all_timestamp_attributes_in_model)
131
+ end
132
+
133
+ def current_time_from_proper_timezone
134
+ self.class.send(:current_time_from_proper_timezone)
135
+ end
136
+
137
+ def max_updated_column_timestamp(timestamp_names = timestamp_attributes_for_update_in_model)
138
+ timestamp_names
139
+ .map { |attr| self[attr] }
140
+ .compact
141
+ .map(&:to_time)
142
+ .max
143
+ end
144
+
145
+ # Clear attributes and changed_attributes
146
+ def clear_timestamp_attributes
147
+ all_timestamp_attributes_in_model.each do |attribute_name|
148
+ self[attribute_name] = nil
149
+ clear_attribute_changes([attribute_name])
150
+ end
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ # = Active Record Touch Later
5
+ module TouchLater
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ before_commit_without_transaction_enrollment :touch_deferred_attributes
10
+ end
11
+
12
+ def touch_later(*names) # :nodoc:
13
+ unless persisted?
14
+ raise ActiveRecordError, <<-MSG.squish
15
+ cannot touch on a new or destroyed record object. Consider using
16
+ persisted?, new_record?, or destroyed? before touching
17
+ MSG
18
+ end
19
+
20
+ @_defer_touch_attrs ||= timestamp_attributes_for_update_in_model
21
+ @_defer_touch_attrs |= names
22
+ @_touch_time = current_time_from_proper_timezone
23
+
24
+ surreptitiously_touch @_defer_touch_attrs
25
+ self.class.connection.add_transaction_record self
26
+
27
+ # touch the parents as we are not calling the after_save callbacks
28
+ self.class.reflect_on_all_associations(:belongs_to).each do |r|
29
+ if touch = r.options[:touch]
30
+ ActiveRecord::Associations::Builder::BelongsTo.touch_record(self, changes_to_save, r.foreign_key, r.name, touch, :touch_later)
31
+ end
32
+ end
33
+ end
34
+
35
+ def touch(*names, time: nil) # :nodoc:
36
+ if has_defer_touch_attrs?
37
+ names |= @_defer_touch_attrs
38
+ end
39
+ super(*names, time: time)
40
+ end
41
+
42
+ private
43
+
44
+ def surreptitiously_touch(attrs)
45
+ attrs.each { |attr| write_attribute attr, @_touch_time }
46
+ clear_attribute_changes attrs
47
+ end
48
+
49
+ def touch_deferred_attributes
50
+ if has_defer_touch_attrs? && persisted?
51
+ touch(*@_defer_touch_attrs, time: @_touch_time)
52
+ @_defer_touch_attrs, @_touch_time = nil, nil
53
+ end
54
+ end
55
+
56
+ def has_defer_touch_attrs?
57
+ defined?(@_defer_touch_attrs) && @_defer_touch_attrs.present?
58
+ end
59
+
60
+ def belongs_to_touch_method
61
+ :touch_later
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,502 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveRecord
4
+ # See ActiveRecord::Transactions::ClassMethods for documentation.
5
+ module Transactions
6
+ extend ActiveSupport::Concern
7
+ #:nodoc:
8
+ ACTIONS = [:create, :destroy, :update]
9
+
10
+ included do
11
+ define_callbacks :commit, :rollback,
12
+ :before_commit,
13
+ :before_commit_without_transaction_enrollment,
14
+ :commit_without_transaction_enrollment,
15
+ :rollback_without_transaction_enrollment,
16
+ scope: [:kind, :name]
17
+ end
18
+
19
+ # = Active Record Transactions
20
+ #
21
+ # \Transactions are protective blocks where SQL statements are only permanent
22
+ # if they can all succeed as one atomic action. The classic example is a
23
+ # transfer between two accounts where you can only have a deposit if the
24
+ # withdrawal succeeded and vice versa. \Transactions enforce the integrity of
25
+ # the database and guard the data against program errors or database
26
+ # break-downs. So basically you should use transaction blocks whenever you
27
+ # have a number of statements that must be executed together or not at all.
28
+ #
29
+ # For example:
30
+ #
31
+ # ActiveRecord::Base.transaction do
32
+ # david.withdrawal(100)
33
+ # mary.deposit(100)
34
+ # end
35
+ #
36
+ # This example will only take money from David and give it to Mary if neither
37
+ # +withdrawal+ nor +deposit+ raise an exception. Exceptions will force a
38
+ # ROLLBACK that returns the database to the state before the transaction
39
+ # began. Be aware, though, that the objects will _not_ have their instance
40
+ # data returned to their pre-transactional state.
41
+ #
42
+ # == Different Active Record classes in a single transaction
43
+ #
44
+ # Though the #transaction class method is called on some Active Record class,
45
+ # the objects within the transaction block need not all be instances of
46
+ # that class. This is because transactions are per-database connection, not
47
+ # per-model.
48
+ #
49
+ # In this example a +balance+ record is transactionally saved even
50
+ # though #transaction is called on the +Account+ class:
51
+ #
52
+ # Account.transaction do
53
+ # balance.save!
54
+ # account.save!
55
+ # end
56
+ #
57
+ # The #transaction method is also available as a model instance method.
58
+ # For example, you can also do this:
59
+ #
60
+ # balance.transaction do
61
+ # balance.save!
62
+ # account.save!
63
+ # end
64
+ #
65
+ # == Transactions are not distributed across database connections
66
+ #
67
+ # A transaction acts on a single database connection. If you have
68
+ # multiple class-specific databases, the transaction will not protect
69
+ # interaction among them. One workaround is to begin a transaction
70
+ # on each class whose models you alter:
71
+ #
72
+ # Student.transaction do
73
+ # Course.transaction do
74
+ # course.enroll(student)
75
+ # student.units += course.units
76
+ # end
77
+ # end
78
+ #
79
+ # This is a poor solution, but fully distributed transactions are beyond
80
+ # the scope of Active Record.
81
+ #
82
+ # == +save+ and +destroy+ are automatically wrapped in a transaction
83
+ #
84
+ # Both {#save}[rdoc-ref:Persistence#save] and
85
+ # {#destroy}[rdoc-ref:Persistence#destroy] come wrapped in a transaction that ensures
86
+ # that whatever you do in validations or callbacks will happen under its
87
+ # protected cover. So you can use validations to check for values that
88
+ # the transaction depends on or you can raise exceptions in the callbacks
89
+ # to rollback, including <tt>after_*</tt> callbacks.
90
+ #
91
+ # As a consequence changes to the database are not seen outside your connection
92
+ # until the operation is complete. For example, if you try to update the index
93
+ # of a search engine in +after_save+ the indexer won't see the updated record.
94
+ # The #after_commit callback is the only one that is triggered once the update
95
+ # is committed. See below.
96
+ #
97
+ # == Exception handling and rolling back
98
+ #
99
+ # Also have in mind that exceptions thrown within a transaction block will
100
+ # be propagated (after triggering the ROLLBACK), so you should be ready to
101
+ # catch those in your application code.
102
+ #
103
+ # One exception is the ActiveRecord::Rollback exception, which will trigger
104
+ # a ROLLBACK when raised, but not be re-raised by the transaction block.
105
+ #
106
+ # *Warning*: one should not catch ActiveRecord::StatementInvalid exceptions
107
+ # inside a transaction block. ActiveRecord::StatementInvalid exceptions indicate that an
108
+ # error occurred at the database level, for example when a unique constraint
109
+ # is violated. On some database systems, such as PostgreSQL, database errors
110
+ # inside a transaction cause the entire transaction to become unusable
111
+ # until it's restarted from the beginning. Here is an example which
112
+ # demonstrates the problem:
113
+ #
114
+ # # Suppose that we have a Number model with a unique column called 'i'.
115
+ # Number.transaction do
116
+ # Number.create(i: 0)
117
+ # begin
118
+ # # This will raise a unique constraint error...
119
+ # Number.create(i: 0)
120
+ # rescue ActiveRecord::StatementInvalid
121
+ # # ...which we ignore.
122
+ # end
123
+ #
124
+ # # On PostgreSQL, the transaction is now unusable. The following
125
+ # # statement will cause a PostgreSQL error, even though the unique
126
+ # # constraint is no longer violated:
127
+ # Number.create(i: 1)
128
+ # # => "PG::Error: ERROR: current transaction is aborted, commands
129
+ # # ignored until end of transaction block"
130
+ # end
131
+ #
132
+ # One should restart the entire transaction if an
133
+ # ActiveRecord::StatementInvalid occurred.
134
+ #
135
+ # == Nested transactions
136
+ #
137
+ # #transaction calls can be nested. By default, this makes all database
138
+ # statements in the nested transaction block become part of the parent
139
+ # transaction. For example, the following behavior may be surprising:
140
+ #
141
+ # User.transaction do
142
+ # User.create(username: 'Kotori')
143
+ # User.transaction do
144
+ # User.create(username: 'Nemu')
145
+ # raise ActiveRecord::Rollback
146
+ # end
147
+ # end
148
+ #
149
+ # creates both "Kotori" and "Nemu". Reason is the ActiveRecord::Rollback
150
+ # exception in the nested block does not issue a ROLLBACK. Since these exceptions
151
+ # are captured in transaction blocks, the parent block does not see it and the
152
+ # real transaction is committed.
153
+ #
154
+ # In order to get a ROLLBACK for the nested transaction you may ask for a real
155
+ # sub-transaction by passing <tt>requires_new: true</tt>. If anything goes wrong,
156
+ # the database rolls back to the beginning of the sub-transaction without rolling
157
+ # back the parent transaction. If we add it to the previous example:
158
+ #
159
+ # User.transaction do
160
+ # User.create(username: 'Kotori')
161
+ # User.transaction(requires_new: true) do
162
+ # User.create(username: 'Nemu')
163
+ # raise ActiveRecord::Rollback
164
+ # end
165
+ # end
166
+ #
167
+ # only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it.
168
+ #
169
+ # Most databases don't support true nested transactions. At the time of
170
+ # writing, the only database that we're aware of that supports true nested
171
+ # transactions, is MS-SQL. Because of this, Active Record emulates nested
172
+ # transactions by using savepoints on MySQL and PostgreSQL. See
173
+ # https://dev.mysql.com/doc/refman/5.7/en/savepoint.html
174
+ # for more information about savepoints.
175
+ #
176
+ # === \Callbacks
177
+ #
178
+ # There are two types of callbacks associated with committing and rolling back transactions:
179
+ # #after_commit and #after_rollback.
180
+ #
181
+ # #after_commit callbacks are called on every record saved or destroyed within a
182
+ # transaction immediately after the transaction is committed. #after_rollback callbacks
183
+ # are called on every record saved or destroyed within a transaction immediately after the
184
+ # transaction or savepoint is rolled back.
185
+ #
186
+ # These callbacks are useful for interacting with other systems since you will be guaranteed
187
+ # that the callback is only executed when the database is in a permanent state. For example,
188
+ # #after_commit is a good spot to put in a hook to clearing a cache since clearing it from
189
+ # within a transaction could trigger the cache to be regenerated before the database is updated.
190
+ #
191
+ # === Caveats
192
+ #
193
+ # If you're on MySQL, then do not use Data Definition Language (DDL) operations in nested
194
+ # transactions blocks that are emulated with savepoints. That is, do not execute statements
195
+ # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
196
+ # releases all savepoints upon executing a DDL operation. When +transaction+
197
+ # is finished and tries to release the savepoint it created earlier, a
198
+ # database error will occur because the savepoint has already been
199
+ # automatically released. The following example demonstrates the problem:
200
+ #
201
+ # Model.connection.transaction do # BEGIN
202
+ # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
203
+ # Model.connection.create_table(...) # active_record_1 now automatically released
204
+ # end # RELEASE SAVEPOINT active_record_1
205
+ # # ^^^^ BOOM! database error!
206
+ # end
207
+ #
208
+ # Note that "TRUNCATE" is also a MySQL DDL statement!
209
+ module ClassMethods
210
+ # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
211
+ def transaction(options = {}, &block)
212
+ connection.transaction(options, &block)
213
+ end
214
+
215
+ def before_commit(*args, &block) # :nodoc:
216
+ set_options_for_callbacks!(args)
217
+ set_callback(:before_commit, :before, *args, &block)
218
+ end
219
+
220
+ # This callback is called after a record has been created, updated, or destroyed.
221
+ #
222
+ # You can specify that the callback should only be fired by a certain action with
223
+ # the +:on+ option:
224
+ #
225
+ # after_commit :do_foo, on: :create
226
+ # after_commit :do_bar, on: :update
227
+ # after_commit :do_baz, on: :destroy
228
+ #
229
+ # after_commit :do_foo_bar, on: [:create, :update]
230
+ # after_commit :do_bar_baz, on: [:update, :destroy]
231
+ #
232
+ def after_commit(*args, &block)
233
+ set_options_for_callbacks!(args)
234
+ set_callback(:commit, :after, *args, &block)
235
+ end
236
+
237
+ # Shortcut for <tt>after_commit :hook, on: :create</tt>.
238
+ def after_create_commit(*args, &block)
239
+ set_options_for_callbacks!(args, on: :create)
240
+ set_callback(:commit, :after, *args, &block)
241
+ end
242
+
243
+ # Shortcut for <tt>after_commit :hook, on: :update</tt>.
244
+ def after_update_commit(*args, &block)
245
+ set_options_for_callbacks!(args, on: :update)
246
+ set_callback(:commit, :after, *args, &block)
247
+ end
248
+
249
+ # Shortcut for <tt>after_commit :hook, on: :destroy</tt>.
250
+ def after_destroy_commit(*args, &block)
251
+ set_options_for_callbacks!(args, on: :destroy)
252
+ set_callback(:commit, :after, *args, &block)
253
+ end
254
+
255
+ # This callback is called after a create, update, or destroy are rolled back.
256
+ #
257
+ # Please check the documentation of #after_commit for options.
258
+ def after_rollback(*args, &block)
259
+ set_options_for_callbacks!(args)
260
+ set_callback(:rollback, :after, *args, &block)
261
+ end
262
+
263
+ def before_commit_without_transaction_enrollment(*args, &block) # :nodoc:
264
+ set_options_for_callbacks!(args)
265
+ set_callback(:before_commit_without_transaction_enrollment, :before, *args, &block)
266
+ end
267
+
268
+ def after_commit_without_transaction_enrollment(*args, &block) # :nodoc:
269
+ set_options_for_callbacks!(args)
270
+ set_callback(:commit_without_transaction_enrollment, :after, *args, &block)
271
+ end
272
+
273
+ def after_rollback_without_transaction_enrollment(*args, &block) # :nodoc:
274
+ set_options_for_callbacks!(args)
275
+ set_callback(:rollback_without_transaction_enrollment, :after, *args, &block)
276
+ end
277
+
278
+ private
279
+
280
+ def set_options_for_callbacks!(args, enforced_options = {})
281
+ options = args.extract_options!.merge!(enforced_options)
282
+ args << options
283
+
284
+ if options[:on]
285
+ fire_on = Array(options[:on])
286
+ assert_valid_transaction_action(fire_on)
287
+ options[:if] = Array(options[:if])
288
+ options[:if].unshift(-> { transaction_include_any_action?(fire_on) })
289
+ end
290
+ end
291
+
292
+ def assert_valid_transaction_action(actions)
293
+ if (actions - ACTIONS).any?
294
+ raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS}"
295
+ end
296
+ end
297
+ end
298
+
299
+ # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
300
+ def transaction(options = {}, &block)
301
+ self.class.transaction(options, &block)
302
+ end
303
+
304
+ def destroy #:nodoc:
305
+ with_transaction_returning_status { super }
306
+ end
307
+
308
+ def save(*) #:nodoc:
309
+ rollback_active_record_state! do
310
+ with_transaction_returning_status { super }
311
+ end
312
+ end
313
+
314
+ def save!(*) #:nodoc:
315
+ with_transaction_returning_status { super }
316
+ end
317
+
318
+ def touch(*) #:nodoc:
319
+ with_transaction_returning_status { super }
320
+ end
321
+
322
+ # Reset id and @new_record if the transaction rolls back.
323
+ def rollback_active_record_state!
324
+ remember_transaction_record_state
325
+ yield
326
+ rescue Exception
327
+ restore_transaction_record_state
328
+ raise
329
+ ensure
330
+ clear_transaction_record_state
331
+ end
332
+
333
+ def before_committed! # :nodoc:
334
+ _run_before_commit_without_transaction_enrollment_callbacks
335
+ _run_before_commit_callbacks
336
+ end
337
+
338
+ # Call the #after_commit callbacks.
339
+ #
340
+ # Ensure that it is not called if the object was never persisted (failed create),
341
+ # but call it after the commit of a destroyed object.
342
+ def committed!(should_run_callbacks: true) #:nodoc:
343
+ if should_run_callbacks && (destroyed? || persisted?)
344
+ @_committed_already_called = true
345
+ _run_commit_without_transaction_enrollment_callbacks
346
+ _run_commit_callbacks
347
+ end
348
+ ensure
349
+ @_committed_already_called = false
350
+ force_clear_transaction_record_state
351
+ end
352
+
353
+ # Call the #after_rollback callbacks. The +force_restore_state+ argument indicates if the record
354
+ # state should be rolled back to the beginning or just to the last savepoint.
355
+ def rolledback!(force_restore_state: false, should_run_callbacks: true) #:nodoc:
356
+ if should_run_callbacks
357
+ _run_rollback_callbacks
358
+ _run_rollback_without_transaction_enrollment_callbacks
359
+ end
360
+ ensure
361
+ restore_transaction_record_state(force_restore_state)
362
+ clear_transaction_record_state
363
+ end
364
+
365
+ # Add the record to the current transaction so that the #after_rollback and #after_commit callbacks
366
+ # can be called.
367
+ def add_to_transaction
368
+ if has_transactional_callbacks?
369
+ self.class.connection.add_transaction_record(self)
370
+ else
371
+ sync_with_transaction_state
372
+ set_transaction_state(self.class.connection.transaction_state)
373
+ end
374
+ remember_transaction_record_state
375
+ end
376
+
377
+ # Executes +method+ within a transaction and captures its return value as a
378
+ # status flag. If the status is true the transaction is committed, otherwise
379
+ # a ROLLBACK is issued. In any case the status flag is returned.
380
+ #
381
+ # This method is available within the context of an ActiveRecord::Base
382
+ # instance.
383
+ def with_transaction_returning_status
384
+ status = nil
385
+ self.class.transaction do
386
+ add_to_transaction
387
+ status = yield
388
+ raise ActiveRecord::Rollback unless status
389
+ end
390
+ status
391
+ ensure
392
+ if @transaction_state && @transaction_state.committed?
393
+ clear_transaction_record_state
394
+ end
395
+ end
396
+
397
+ protected
398
+ attr_reader :_committed_already_called, :_trigger_update_callback, :_trigger_destroy_callback
399
+
400
+ private
401
+
402
+ # Save the new record state and id of a record so it can be restored later if a transaction fails.
403
+ def remember_transaction_record_state
404
+ @_start_transaction_state.reverse_merge!(
405
+ id: id,
406
+ new_record: @new_record,
407
+ destroyed: @destroyed,
408
+ frozen?: frozen?,
409
+ )
410
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
411
+ remember_new_record_before_last_commit
412
+ end
413
+
414
+ def remember_new_record_before_last_commit
415
+ if _committed_already_called
416
+ @_new_record_before_last_commit = false
417
+ else
418
+ @_new_record_before_last_commit = @_start_transaction_state[:new_record]
419
+ end
420
+ end
421
+
422
+ # Clear the new record state and id of a record.
423
+ def clear_transaction_record_state
424
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
425
+ force_clear_transaction_record_state if @_start_transaction_state[:level] < 1
426
+ end
427
+
428
+ # Force to clear the transaction record state.
429
+ def force_clear_transaction_record_state
430
+ @_start_transaction_state.clear
431
+ end
432
+
433
+ # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
434
+ def restore_transaction_record_state(force = false)
435
+ unless @_start_transaction_state.empty?
436
+ transaction_level = (@_start_transaction_state[:level] || 0) - 1
437
+ if transaction_level < 1 || force
438
+ restore_state = @_start_transaction_state
439
+ thaw
440
+ @new_record = restore_state[:new_record]
441
+ @destroyed = restore_state[:destroyed]
442
+ pk = self.class.primary_key
443
+ if pk && _read_attribute(pk) != restore_state[:id]
444
+ _write_attribute(pk, restore_state[:id])
445
+ end
446
+ freeze if restore_state[:frozen?]
447
+ end
448
+ end
449
+ end
450
+
451
+ # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
452
+ def transaction_include_any_action?(actions)
453
+ actions.any? do |action|
454
+ case action
455
+ when :create
456
+ persisted? && @_new_record_before_last_commit
457
+ when :update
458
+ !(@_new_record_before_last_commit || destroyed?) && _trigger_update_callback
459
+ when :destroy
460
+ _trigger_destroy_callback
461
+ end
462
+ end
463
+ end
464
+
465
+ def set_transaction_state(state)
466
+ @transaction_state = state
467
+ end
468
+
469
+ def has_transactional_callbacks?
470
+ !_rollback_callbacks.empty? || !_commit_callbacks.empty? || !_before_commit_callbacks.empty?
471
+ end
472
+
473
+ # Updates the attributes on this particular Active Record object so that
474
+ # if it's associated with a transaction, then the state of the Active Record
475
+ # object will be updated to reflect the current state of the transaction.
476
+ #
477
+ # The <tt>@transaction_state</tt> variable stores the states of the associated
478
+ # transaction. This relies on the fact that a transaction can only be in
479
+ # one rollback or commit (otherwise a list of states would be required).
480
+ # Each Active Record object inside of a transaction carries that transaction's
481
+ # TransactionState.
482
+ #
483
+ # This method checks to see if the ActiveRecord object's state reflects
484
+ # the TransactionState, and rolls back or commits the Active Record object
485
+ # as appropriate.
486
+ #
487
+ # Since Active Record objects can be inside multiple transactions, this
488
+ # method recursively goes through the parent of the TransactionState and
489
+ # checks if the Active Record object reflects the state of the object.
490
+ def sync_with_transaction_state
491
+ update_attributes_from_transaction_state(@transaction_state)
492
+ end
493
+
494
+ def update_attributes_from_transaction_state(transaction_state)
495
+ if transaction_state && transaction_state.finalized?
496
+ restore_transaction_record_state(transaction_state.fully_rolledback?) if transaction_state.rolledback?
497
+ force_clear_transaction_record_state if transaction_state.fully_committed?
498
+ clear_transaction_record_state if transaction_state.fully_completed?
499
+ end
500
+ end
501
+ end
502
+ end