sskirby-activerecord 3.2.1

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.
Files changed (150) hide show
  1. data/CHANGELOG.md +6749 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +222 -0
  4. data/examples/associations.png +0 -0
  5. data/examples/performance.rb +177 -0
  6. data/examples/simple.rb +14 -0
  7. data/lib/active_record.rb +147 -0
  8. data/lib/active_record/aggregations.rb +255 -0
  9. data/lib/active_record/associations.rb +1604 -0
  10. data/lib/active_record/associations/alias_tracker.rb +79 -0
  11. data/lib/active_record/associations/association.rb +239 -0
  12. data/lib/active_record/associations/association_scope.rb +119 -0
  13. data/lib/active_record/associations/belongs_to_association.rb +79 -0
  14. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
  15. data/lib/active_record/associations/builder/association.rb +55 -0
  16. data/lib/active_record/associations/builder/belongs_to.rb +85 -0
  17. data/lib/active_record/associations/builder/collection_association.rb +75 -0
  18. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
  19. data/lib/active_record/associations/builder/has_many.rb +71 -0
  20. data/lib/active_record/associations/builder/has_one.rb +62 -0
  21. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  22. data/lib/active_record/associations/collection_association.rb +574 -0
  23. data/lib/active_record/associations/collection_proxy.rb +132 -0
  24. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
  25. data/lib/active_record/associations/has_many_association.rb +108 -0
  26. data/lib/active_record/associations/has_many_through_association.rb +180 -0
  27. data/lib/active_record/associations/has_one_association.rb +73 -0
  28. data/lib/active_record/associations/has_one_through_association.rb +36 -0
  29. data/lib/active_record/associations/join_dependency.rb +214 -0
  30. data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
  31. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  32. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  33. data/lib/active_record/associations/join_helper.rb +55 -0
  34. data/lib/active_record/associations/preloader.rb +177 -0
  35. data/lib/active_record/associations/preloader/association.rb +127 -0
  36. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  37. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  38. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  39. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  40. data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
  41. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  42. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  43. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  44. data/lib/active_record/associations/preloader/through_association.rb +67 -0
  45. data/lib/active_record/associations/singular_association.rb +64 -0
  46. data/lib/active_record/associations/through_association.rb +83 -0
  47. data/lib/active_record/attribute_assignment.rb +221 -0
  48. data/lib/active_record/attribute_methods.rb +272 -0
  49. data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
  50. data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
  51. data/lib/active_record/attribute_methods/dirty.rb +101 -0
  52. data/lib/active_record/attribute_methods/primary_key.rb +114 -0
  53. data/lib/active_record/attribute_methods/query.rb +39 -0
  54. data/lib/active_record/attribute_methods/read.rb +135 -0
  55. data/lib/active_record/attribute_methods/serialization.rb +93 -0
  56. data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
  57. data/lib/active_record/attribute_methods/write.rb +69 -0
  58. data/lib/active_record/autosave_association.rb +422 -0
  59. data/lib/active_record/base.rb +716 -0
  60. data/lib/active_record/callbacks.rb +275 -0
  61. data/lib/active_record/coders/yaml_column.rb +41 -0
  62. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
  63. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
  64. data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
  65. data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
  66. data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
  67. data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
  68. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
  69. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
  70. data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
  71. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
  72. data/lib/active_record/connection_adapters/column.rb +270 -0
  73. data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
  74. data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
  75. data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
  76. data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
  77. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
  78. data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
  79. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  80. data/lib/active_record/counter_cache.rb +119 -0
  81. data/lib/active_record/dynamic_finder_match.rb +56 -0
  82. data/lib/active_record/dynamic_matchers.rb +79 -0
  83. data/lib/active_record/dynamic_scope_match.rb +23 -0
  84. data/lib/active_record/errors.rb +195 -0
  85. data/lib/active_record/explain.rb +85 -0
  86. data/lib/active_record/explain_subscriber.rb +21 -0
  87. data/lib/active_record/fixtures.rb +906 -0
  88. data/lib/active_record/fixtures/file.rb +65 -0
  89. data/lib/active_record/identity_map.rb +156 -0
  90. data/lib/active_record/inheritance.rb +167 -0
  91. data/lib/active_record/integration.rb +49 -0
  92. data/lib/active_record/locale/en.yml +40 -0
  93. data/lib/active_record/locking/optimistic.rb +183 -0
  94. data/lib/active_record/locking/pessimistic.rb +77 -0
  95. data/lib/active_record/log_subscriber.rb +68 -0
  96. data/lib/active_record/migration.rb +765 -0
  97. data/lib/active_record/migration/command_recorder.rb +105 -0
  98. data/lib/active_record/model_schema.rb +366 -0
  99. data/lib/active_record/nested_attributes.rb +469 -0
  100. data/lib/active_record/observer.rb +121 -0
  101. data/lib/active_record/persistence.rb +372 -0
  102. data/lib/active_record/query_cache.rb +74 -0
  103. data/lib/active_record/querying.rb +58 -0
  104. data/lib/active_record/railtie.rb +119 -0
  105. data/lib/active_record/railties/console_sandbox.rb +6 -0
  106. data/lib/active_record/railties/controller_runtime.rb +49 -0
  107. data/lib/active_record/railties/databases.rake +620 -0
  108. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  109. data/lib/active_record/readonly_attributes.rb +26 -0
  110. data/lib/active_record/reflection.rb +534 -0
  111. data/lib/active_record/relation.rb +534 -0
  112. data/lib/active_record/relation/batches.rb +90 -0
  113. data/lib/active_record/relation/calculations.rb +354 -0
  114. data/lib/active_record/relation/delegation.rb +49 -0
  115. data/lib/active_record/relation/finder_methods.rb +398 -0
  116. data/lib/active_record/relation/predicate_builder.rb +58 -0
  117. data/lib/active_record/relation/query_methods.rb +417 -0
  118. data/lib/active_record/relation/spawn_methods.rb +148 -0
  119. data/lib/active_record/result.rb +34 -0
  120. data/lib/active_record/sanitization.rb +194 -0
  121. data/lib/active_record/schema.rb +58 -0
  122. data/lib/active_record/schema_dumper.rb +204 -0
  123. data/lib/active_record/scoping.rb +152 -0
  124. data/lib/active_record/scoping/default.rb +142 -0
  125. data/lib/active_record/scoping/named.rb +202 -0
  126. data/lib/active_record/serialization.rb +18 -0
  127. data/lib/active_record/serializers/xml_serializer.rb +202 -0
  128. data/lib/active_record/session_store.rb +358 -0
  129. data/lib/active_record/store.rb +50 -0
  130. data/lib/active_record/test_case.rb +73 -0
  131. data/lib/active_record/timestamp.rb +113 -0
  132. data/lib/active_record/transactions.rb +360 -0
  133. data/lib/active_record/translation.rb +22 -0
  134. data/lib/active_record/validations.rb +83 -0
  135. data/lib/active_record/validations/associated.rb +43 -0
  136. data/lib/active_record/validations/uniqueness.rb +180 -0
  137. data/lib/active_record/version.rb +10 -0
  138. data/lib/rails/generators/active_record.rb +25 -0
  139. data/lib/rails/generators/active_record/migration.rb +15 -0
  140. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  141. data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
  142. data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
  143. data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
  144. data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
  145. data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
  146. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  147. data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
  148. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
  149. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
  150. metadata +242 -0
@@ -0,0 +1,50 @@
1
+ module ActiveRecord
2
+ # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column.
3
+ # It's like a simple key/value store backed into your record when you don't care about being able to
4
+ # query that store outside the context of a single record.
5
+ #
6
+ # You can then declare accessors to this store that are then accessible just like any other attribute
7
+ # of the model. This is very helpful for easily exposing store keys to a form or elsewhere that's
8
+ # already built around just accessing attributes on the model.
9
+ #
10
+ # Make sure that you declare the database column used for the serialized store as a text, so there's
11
+ # plenty of room.
12
+ #
13
+ # Examples:
14
+ #
15
+ # class User < ActiveRecord::Base
16
+ # store :settings, accessors: [ :color, :homepage ]
17
+ # end
18
+ #
19
+ # u = User.new(color: 'black', homepage: '37signals.com')
20
+ # u.color # Accessor stored attribute
21
+ # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor
22
+ #
23
+ # # Add additional accessors to an existing store through store_accessor
24
+ # class SuperUser < User
25
+ # store_accessor :settings, :privileges, :servants
26
+ # end
27
+ module Store
28
+ extend ActiveSupport::Concern
29
+
30
+ module ClassMethods
31
+ def store(store_attribute, options = {})
32
+ serialize store_attribute, Hash
33
+ store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors
34
+ end
35
+
36
+ def store_accessor(store_attribute, *keys)
37
+ Array(keys).flatten.each do |key|
38
+ define_method("#{key}=") do |value|
39
+ send(store_attribute)[key] = value
40
+ send("#{store_attribute}_will_change!")
41
+ end
42
+
43
+ define_method(key) do
44
+ send(store_attribute)[key]
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,73 @@
1
+ module ActiveRecord
2
+ # = Active Record Test Case
3
+ #
4
+ # Defines some test assertions to test against SQL queries.
5
+ class TestCase < ActiveSupport::TestCase #:nodoc:
6
+ setup :cleanup_identity_map
7
+
8
+ def setup
9
+ cleanup_identity_map
10
+ end
11
+
12
+ def cleanup_identity_map
13
+ ActiveRecord::IdentityMap.clear
14
+ end
15
+
16
+ # Backport skip to Ruby 1.8. test/unit doesn't support it, so just
17
+ # make it a noop.
18
+ unless instance_methods.map(&:to_s).include?("skip")
19
+ def skip(message)
20
+ end
21
+ end
22
+
23
+ def assert_date_from_db(expected, actual, message = nil)
24
+ # SybaseAdapter doesn't have a separate column type just for dates,
25
+ # so the time is in the string and incorrectly formatted
26
+ if current_adapter?(:SybaseAdapter)
27
+ assert_equal expected.to_s, actual.to_date.to_s, message
28
+ else
29
+ assert_equal expected.to_s, actual.to_s, message
30
+ end
31
+ end
32
+
33
+ def assert_sql(*patterns_to_match)
34
+ ActiveRecord::SQLCounter.log = []
35
+ yield
36
+ ActiveRecord::SQLCounter.log
37
+ ensure
38
+ failed_patterns = []
39
+ patterns_to_match.each do |pattern|
40
+ failed_patterns << pattern unless ActiveRecord::SQLCounter.log.any?{ |sql| pattern === sql }
41
+ end
42
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
43
+ end
44
+
45
+ def assert_queries(num = 1)
46
+ ActiveRecord::SQLCounter.log = []
47
+ yield
48
+ ensure
49
+ assert_equal num, ActiveRecord::SQLCounter.log.size, "#{ActiveRecord::SQLCounter.log.size} instead of #{num} queries were executed.#{ActiveRecord::SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{ActiveRecord::SQLCounter.log.join("\n")}"}"
50
+ end
51
+
52
+ def assert_no_queries(&block)
53
+ prev_ignored_sql = ActiveRecord::SQLCounter.ignored_sql
54
+ ActiveRecord::SQLCounter.ignored_sql = []
55
+ assert_queries(0, &block)
56
+ ensure
57
+ ActiveRecord::SQLCounter.ignored_sql = prev_ignored_sql
58
+ end
59
+
60
+ def with_kcode(kcode)
61
+ if RUBY_VERSION < '1.9'
62
+ orig_kcode, $KCODE = $KCODE, kcode
63
+ begin
64
+ yield
65
+ ensure
66
+ $KCODE = orig_kcode
67
+ end
68
+ else
69
+ yield
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,113 @@
1
+ require 'active_support/core_ext/class/attribute'
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 the local timezone by default but you can use UTC by setting:
15
+ #
16
+ # config.active_record.default_timezone = :utc
17
+ #
18
+ # == Time Zone aware attributes
19
+ #
20
+ # By default, ActiveRecord::Base keeps all the datetime columns time zone aware by executing following code.
21
+ #
22
+ # config.active_record.time_zone_aware_attributes = true
23
+ #
24
+ # This feature can easily be turned off by assigning value <tt>false</tt> .
25
+ #
26
+ # If your attributes are time zone aware and you desire to skip time zone conversion to the current Time.zone
27
+ # when reading certain attributes then you can do following:
28
+ #
29
+ # class Topic < ActiveRecord::Base
30
+ # self.skip_time_zone_conversion_for_attributes = [:written_on]
31
+ # end
32
+ module Timestamp
33
+ extend ActiveSupport::Concern
34
+
35
+ included do
36
+ class_attribute :record_timestamps
37
+ self.record_timestamps = true
38
+ end
39
+
40
+ def initialize_dup(other)
41
+ clear_timestamp_attributes
42
+ end
43
+
44
+ private
45
+
46
+ def create #:nodoc:
47
+ if self.record_timestamps
48
+ current_time = current_time_from_proper_timezone
49
+
50
+ all_timestamp_attributes.each do |column|
51
+ if respond_to?(column) && respond_to?("#{column}=") && self.send(column).nil?
52
+ write_attribute(column.to_s, current_time)
53
+ end
54
+ end
55
+ end
56
+
57
+ super
58
+ end
59
+
60
+ def update(*args) #:nodoc:
61
+ if should_record_timestamps?
62
+ current_time = current_time_from_proper_timezone
63
+
64
+ timestamp_attributes_for_update_in_model.each do |column|
65
+ column = column.to_s
66
+ next if attribute_changed?(column)
67
+ write_attribute(column, current_time)
68
+ end
69
+ end
70
+ super
71
+ end
72
+
73
+ def should_record_timestamps?
74
+ self.record_timestamps && (!partial_updates? || changed? || (attributes.keys & self.class.serialized_attributes.keys).present?)
75
+ end
76
+
77
+ def timestamp_attributes_for_create_in_model
78
+ timestamp_attributes_for_create.select { |c| self.class.column_names.include?(c.to_s) }
79
+ end
80
+
81
+ def timestamp_attributes_for_update_in_model
82
+ timestamp_attributes_for_update.select { |c| self.class.column_names.include?(c.to_s) }
83
+ end
84
+
85
+ def all_timestamp_attributes_in_model
86
+ timestamp_attributes_for_create_in_model + timestamp_attributes_for_update_in_model
87
+ end
88
+
89
+ def timestamp_attributes_for_update #:nodoc:
90
+ [:updated_at, :updated_on]
91
+ end
92
+
93
+ def timestamp_attributes_for_create #:nodoc:
94
+ [:created_at, :created_on]
95
+ end
96
+
97
+ def all_timestamp_attributes #:nodoc:
98
+ timestamp_attributes_for_create + timestamp_attributes_for_update
99
+ end
100
+
101
+ def current_time_from_proper_timezone #:nodoc:
102
+ self.class.default_timezone == :utc ? Time.now.utc : Time.now
103
+ end
104
+
105
+ # Clear attributes and changed_attributes
106
+ def clear_timestamp_attributes
107
+ all_timestamp_attributes_in_model.each do |attribute_name|
108
+ self[attribute_name] = nil
109
+ changed_attributes.delete(attribute_name)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,360 @@
1
+ require 'thread'
2
+
3
+ module ActiveRecord
4
+ # See ActiveRecord::Transactions::ClassMethods for documentation.
5
+ module Transactions
6
+ extend ActiveSupport::Concern
7
+
8
+ class TransactionError < ActiveRecordError # :nodoc:
9
+ end
10
+
11
+ included do
12
+ define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name]
13
+ end
14
+
15
+ # = Active Record Transactions
16
+ #
17
+ # Transactions are protective blocks where SQL statements are only permanent
18
+ # if they can all succeed as one atomic action. The classic example is a
19
+ # transfer between two accounts where you can only have a deposit if the
20
+ # withdrawal succeeded and vice versa. Transactions enforce the integrity of
21
+ # the database and guard the data against program errors or database
22
+ # break-downs. So basically you should use transaction blocks whenever you
23
+ # have a number of statements that must be executed together or not at all.
24
+ #
25
+ # For example:
26
+ #
27
+ # ActiveRecord::Base.transaction do
28
+ # david.withdrawal(100)
29
+ # mary.deposit(100)
30
+ # end
31
+ #
32
+ # This example will only take money from David and give it to Mary if neither
33
+ # +withdrawal+ nor +deposit+ raise an exception. Exceptions will force a
34
+ # ROLLBACK that returns the database to the state before the transaction
35
+ # began. Be aware, though, that the objects will _not_ have their instance
36
+ # data returned to their pre-transactional state.
37
+ #
38
+ # == Different Active Record classes in a single transaction
39
+ #
40
+ # Though the transaction class method is called on some Active Record class,
41
+ # the objects within the transaction block need not all be instances of
42
+ # that class. This is because transactions are per-database connection, not
43
+ # per-model.
44
+ #
45
+ # In this example a +balance+ record is transactionally saved even
46
+ # though +transaction+ is called on the +Account+ class:
47
+ #
48
+ # Account.transaction do
49
+ # balance.save!
50
+ # account.save!
51
+ # end
52
+ #
53
+ # The +transaction+ method is also available as a model instance method.
54
+ # For example, you can also do this:
55
+ #
56
+ # balance.transaction do
57
+ # balance.save!
58
+ # account.save!
59
+ # end
60
+ #
61
+ # == Transactions are not distributed across database connections
62
+ #
63
+ # A transaction acts on a single database connection. If you have
64
+ # multiple class-specific databases, the transaction will not protect
65
+ # interaction among them. One workaround is to begin a transaction
66
+ # on each class whose models you alter:
67
+ #
68
+ # Student.transaction do
69
+ # Course.transaction do
70
+ # course.enroll(student)
71
+ # student.units += course.units
72
+ # end
73
+ # end
74
+ #
75
+ # This is a poor solution, but fully distributed transactions are beyond
76
+ # the scope of Active Record.
77
+ #
78
+ # == +save+ and +destroy+ are automatically wrapped in a transaction
79
+ #
80
+ # Both +save+ and +destroy+ come wrapped in a transaction that ensures
81
+ # that whatever you do in validations or callbacks will happen under its
82
+ # protected cover. So you can use validations to check for values that
83
+ # the transaction depends on or you can raise exceptions in the callbacks
84
+ # to rollback, including <tt>after_*</tt> callbacks.
85
+ #
86
+ # As a consequence changes to the database are not seen outside your connection
87
+ # until the operation is complete. For example, if you try to update the index
88
+ # of a search engine in +after_save+ the indexer won't see the updated record.
89
+ # The +after_commit+ callback is the only one that is triggered once the update
90
+ # is committed. See below.
91
+ #
92
+ # == Exception handling and rolling back
93
+ #
94
+ # Also have in mind that exceptions thrown within a transaction block will
95
+ # be propagated (after triggering the ROLLBACK), so you should be ready to
96
+ # catch those in your application code.
97
+ #
98
+ # One exception is the <tt>ActiveRecord::Rollback</tt> exception, which will trigger
99
+ # a ROLLBACK when raised, but not be re-raised by the transaction block.
100
+ #
101
+ # *Warning*: one should not catch <tt>ActiveRecord::StatementInvalid</tt> exceptions
102
+ # inside a transaction block. <tt>ActiveRecord::StatementInvalid</tt> exceptions indicate that an
103
+ # error occurred at the database level, for example when a unique constraint
104
+ # is violated. On some database systems, such as PostgreSQL, database errors
105
+ # inside a transaction cause the entire transaction to become unusable
106
+ # until it's restarted from the beginning. Here is an example which
107
+ # demonstrates the problem:
108
+ #
109
+ # # Suppose that we have a Number model with a unique column called 'i'.
110
+ # Number.transaction do
111
+ # Number.create(:i => 0)
112
+ # begin
113
+ # # This will raise a unique constraint error...
114
+ # Number.create(:i => 0)
115
+ # rescue ActiveRecord::StatementInvalid
116
+ # # ...which we ignore.
117
+ # end
118
+ #
119
+ # # On PostgreSQL, the transaction is now unusable. The following
120
+ # # statement will cause a PostgreSQL error, even though the unique
121
+ # # constraint is no longer violated:
122
+ # Number.create(:i => 1)
123
+ # # => "PGError: ERROR: current transaction is aborted, commands
124
+ # # ignored until end of transaction block"
125
+ # end
126
+ #
127
+ # One should restart the entire transaction if an
128
+ # <tt>ActiveRecord::StatementInvalid</tt> occurred.
129
+ #
130
+ # == Nested transactions
131
+ #
132
+ # +transaction+ calls can be nested. By default, this makes all database
133
+ # statements in the nested transaction block become part of the parent
134
+ # transaction. For example, the following behavior may be surprising:
135
+ #
136
+ # User.transaction do
137
+ # User.create(:username => 'Kotori')
138
+ # User.transaction do
139
+ # User.create(:username => 'Nemu')
140
+ # raise ActiveRecord::Rollback
141
+ # end
142
+ # end
143
+ #
144
+ # creates both "Kotori" and "Nemu". Reason is the <tt>ActiveRecord::Rollback</tt>
145
+ # exception in the nested block does not issue a ROLLBACK. Since these exceptions
146
+ # are captured in transaction blocks, the parent block does not see it and the
147
+ # real transaction is committed.
148
+ #
149
+ # In order to get a ROLLBACK for the nested transaction you may ask for a real
150
+ # sub-transaction by passing <tt>:requires_new => true</tt>. If anything goes wrong,
151
+ # the database rolls back to the beginning of the sub-transaction without rolling
152
+ # back the parent transaction. If we add it to the previous example:
153
+ #
154
+ # User.transaction do
155
+ # User.create(:username => 'Kotori')
156
+ # User.transaction(:requires_new => true) do
157
+ # User.create(:username => 'Nemu')
158
+ # raise ActiveRecord::Rollback
159
+ # end
160
+ # end
161
+ #
162
+ # only "Kotori" is created. (This works on MySQL and PostgreSQL, but not on SQLite3.)
163
+ #
164
+ # Most databases don't support true nested transactions. At the time of
165
+ # writing, the only database that we're aware of that supports true nested
166
+ # transactions, is MS-SQL. Because of this, Active Record emulates nested
167
+ # transactions by using savepoints on MySQL and PostgreSQL. See
168
+ # http://dev.mysql.com/doc/refman/5.0/en/savepoint.html
169
+ # for more information about savepoints.
170
+ #
171
+ # === Callbacks
172
+ #
173
+ # There are two types of callbacks associated with committing and rolling back transactions:
174
+ # +after_commit+ and +after_rollback+.
175
+ #
176
+ # +after_commit+ callbacks are called on every record saved or destroyed within a
177
+ # transaction immediately after the transaction is committed. +after_rollback+ callbacks
178
+ # are called on every record saved or destroyed within a transaction immediately after the
179
+ # transaction or savepoint is rolled back.
180
+ #
181
+ # These callbacks are useful for interacting with other systems since you will be guaranteed
182
+ # that the callback is only executed when the database is in a permanent state. For example,
183
+ # +after_commit+ is a good spot to put in a hook to clearing a cache since clearing it from
184
+ # within a transaction could trigger the cache to be regenerated before the database is updated.
185
+ #
186
+ # === Caveats
187
+ #
188
+ # If you're on MySQL, then do not use DDL operations in nested transactions
189
+ # blocks that are emulated with savepoints. That is, do not execute statements
190
+ # like 'CREATE TABLE' inside such blocks. This is because MySQL automatically
191
+ # releases all savepoints upon executing a DDL operation. When +transaction+
192
+ # is finished and tries to release the savepoint it created earlier, a
193
+ # database error will occur because the savepoint has already been
194
+ # automatically released. The following example demonstrates the problem:
195
+ #
196
+ # Model.connection.transaction do # BEGIN
197
+ # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
198
+ # Model.connection.create_table(...) # active_record_1 now automatically released
199
+ # end # RELEASE savepoint active_record_1
200
+ # # ^^^^ BOOM! database error!
201
+ # end
202
+ #
203
+ # Note that "TRUNCATE" is also a MySQL DDL statement!
204
+ module ClassMethods
205
+ # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
206
+ def transaction(options = {}, &block)
207
+ # See the ConnectionAdapters::DatabaseStatements#transaction API docs.
208
+ connection.transaction(options, &block)
209
+ end
210
+
211
+ def after_commit(*args, &block)
212
+ options = args.last
213
+ if options.is_a?(Hash) && options[:on]
214
+ options[:if] = Array.wrap(options[:if])
215
+ options[:if] << "transaction_include_action?(:#{options[:on]})"
216
+ end
217
+ set_callback(:commit, :after, *args, &block)
218
+ end
219
+
220
+ def after_rollback(*args, &block)
221
+ options = args.last
222
+ if options.is_a?(Hash) && options[:on]
223
+ options[:if] = Array.wrap(options[:if])
224
+ options[:if] << "transaction_include_action?(:#{options[:on]})"
225
+ end
226
+ set_callback(:rollback, :after, *args, &block)
227
+ end
228
+ end
229
+
230
+ # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
231
+ def transaction(options = {}, &block)
232
+ self.class.transaction(options, &block)
233
+ end
234
+
235
+ def destroy #:nodoc:
236
+ with_transaction_returning_status { super }
237
+ end
238
+
239
+ def save(*) #:nodoc:
240
+ rollback_active_record_state! do
241
+ with_transaction_returning_status { super }
242
+ end
243
+ end
244
+
245
+ def save!(*) #:nodoc:
246
+ with_transaction_returning_status { super }
247
+ end
248
+
249
+ # Reset id and @new_record if the transaction rolls back.
250
+ def rollback_active_record_state!
251
+ remember_transaction_record_state
252
+ yield
253
+ rescue Exception
254
+ IdentityMap.remove(self) if IdentityMap.enabled?
255
+ restore_transaction_record_state
256
+ raise
257
+ ensure
258
+ clear_transaction_record_state
259
+ end
260
+
261
+ # Call the after_commit callbacks
262
+ def committed! #:nodoc:
263
+ run_callbacks :commit
264
+ ensure
265
+ clear_transaction_record_state
266
+ end
267
+
268
+ # Call the after rollback callbacks. The restore_state argument indicates if the record
269
+ # state should be rolled back to the beginning or just to the last savepoint.
270
+ def rolledback!(force_restore_state = false) #:nodoc:
271
+ run_callbacks :rollback
272
+ ensure
273
+ IdentityMap.remove(self) if IdentityMap.enabled?
274
+ restore_transaction_record_state(force_restore_state)
275
+ end
276
+
277
+ # Add the record to the current transaction so that the :after_rollback and :after_commit callbacks
278
+ # can be called.
279
+ def add_to_transaction
280
+ if self.class.connection.add_transaction_record(self)
281
+ remember_transaction_record_state
282
+ end
283
+ end
284
+
285
+ # Executes +method+ within a transaction and captures its return value as a
286
+ # status flag. If the status is true the transaction is committed, otherwise
287
+ # a ROLLBACK is issued. In any case the status flag is returned.
288
+ #
289
+ # This method is available within the context of an ActiveRecord::Base
290
+ # instance.
291
+ def with_transaction_returning_status
292
+ status = nil
293
+ self.class.transaction do
294
+ add_to_transaction
295
+ status = yield
296
+ raise ActiveRecord::Rollback unless status
297
+ end
298
+ status
299
+ end
300
+
301
+ protected
302
+
303
+ # Save the new record state and id of a record so it can be restored later if a transaction fails.
304
+ def remember_transaction_record_state #:nodoc:
305
+ @_start_transaction_state ||= {}
306
+ @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
307
+ unless @_start_transaction_state.include?(:new_record)
308
+ @_start_transaction_state[:new_record] = @new_record
309
+ end
310
+ unless @_start_transaction_state.include?(:destroyed)
311
+ @_start_transaction_state[:destroyed] = @destroyed
312
+ end
313
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
314
+ end
315
+
316
+ # Clear the new record state and id of a record.
317
+ def clear_transaction_record_state #:nodoc:
318
+ if defined?(@_start_transaction_state)
319
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
320
+ remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1
321
+ end
322
+ end
323
+
324
+ # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
325
+ def restore_transaction_record_state(force = false) #:nodoc:
326
+ if defined?(@_start_transaction_state)
327
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
328
+ if @_start_transaction_state[:level] < 1
329
+ restore_state = remove_instance_variable(:@_start_transaction_state)
330
+ @attributes = @attributes.dup if @attributes.frozen?
331
+ @new_record = restore_state[:new_record]
332
+ @destroyed = restore_state[:destroyed]
333
+ if restore_state.has_key?(:id)
334
+ self.id = restore_state[:id]
335
+ else
336
+ @attributes.delete(self.class.primary_key)
337
+ @attributes_cache.delete(self.class.primary_key)
338
+ end
339
+ end
340
+ end
341
+ end
342
+
343
+ # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
344
+ def transaction_record_state(state) #:nodoc:
345
+ @_start_transaction_state[state] if defined?(@_start_transaction_state)
346
+ end
347
+
348
+ # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
349
+ def transaction_include_action?(action) #:nodoc:
350
+ case action
351
+ when :create
352
+ transaction_record_state(:new_record)
353
+ when :destroy
354
+ destroyed?
355
+ when :update
356
+ !(transaction_record_state(:new_record) || destroyed?)
357
+ end
358
+ end
359
+ end
360
+ end