activerecord 3.0.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


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

Files changed (181) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +2102 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +35 -44
  5. data/examples/performance.rb +110 -100
  6. data/lib/active_record/aggregations.rb +59 -75
  7. data/lib/active_record/associations/alias_tracker.rb +76 -0
  8. data/lib/active_record/associations/association.rb +248 -0
  9. data/lib/active_record/associations/association_scope.rb +135 -0
  10. data/lib/active_record/associations/belongs_to_association.rb +60 -59
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +16 -59
  12. data/lib/active_record/associations/builder/association.rb +108 -0
  13. data/lib/active_record/associations/builder/belongs_to.rb +98 -0
  14. data/lib/active_record/associations/builder/collection_association.rb +89 -0
  15. data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +39 -0
  16. data/lib/active_record/associations/builder/has_many.rb +15 -0
  17. data/lib/active_record/associations/builder/has_one.rb +25 -0
  18. data/lib/active_record/associations/builder/singular_association.rb +32 -0
  19. data/lib/active_record/associations/collection_association.rb +608 -0
  20. data/lib/active_record/associations/collection_proxy.rb +986 -0
  21. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +40 -112
  22. data/lib/active_record/associations/has_many_association.rb +83 -76
  23. data/lib/active_record/associations/has_many_through_association.rb +147 -66
  24. data/lib/active_record/associations/has_one_association.rb +67 -108
  25. data/lib/active_record/associations/has_one_through_association.rb +21 -25
  26. data/lib/active_record/associations/join_dependency/join_association.rb +174 -0
  27. data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
  28. data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
  29. data/lib/active_record/associations/join_dependency.rb +235 -0
  30. data/lib/active_record/associations/join_helper.rb +45 -0
  31. data/lib/active_record/associations/preloader/association.rb +121 -0
  32. data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
  33. data/lib/active_record/associations/preloader/collection_association.rb +24 -0
  34. data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
  35. data/lib/active_record/associations/preloader/has_many.rb +17 -0
  36. data/lib/active_record/associations/preloader/has_many_through.rb +19 -0
  37. data/lib/active_record/associations/preloader/has_one.rb +23 -0
  38. data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
  39. data/lib/active_record/associations/preloader/singular_association.rb +21 -0
  40. data/lib/active_record/associations/preloader/through_association.rb +63 -0
  41. data/lib/active_record/associations/preloader.rb +178 -0
  42. data/lib/active_record/associations/singular_association.rb +64 -0
  43. data/lib/active_record/associations/through_association.rb +87 -0
  44. data/lib/active_record/associations.rb +512 -1224
  45. data/lib/active_record/attribute_assignment.rb +201 -0
  46. data/lib/active_record/attribute_methods/before_type_cast.rb +49 -12
  47. data/lib/active_record/attribute_methods/dirty.rb +51 -28
  48. data/lib/active_record/attribute_methods/primary_key.rb +94 -22
  49. data/lib/active_record/attribute_methods/query.rb +5 -4
  50. data/lib/active_record/attribute_methods/read.rb +63 -72
  51. data/lib/active_record/attribute_methods/serialization.rb +162 -0
  52. data/lib/active_record/attribute_methods/time_zone_conversion.rb +39 -41
  53. data/lib/active_record/attribute_methods/write.rb +39 -13
  54. data/lib/active_record/attribute_methods.rb +362 -29
  55. data/lib/active_record/autosave_association.rb +132 -75
  56. data/lib/active_record/base.rb +83 -1627
  57. data/lib/active_record/callbacks.rb +69 -47
  58. data/lib/active_record/coders/yaml_column.rb +38 -0
  59. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +411 -138
  60. data/lib/active_record/connection_adapters/abstract/database_limits.rb +21 -11
  61. data/lib/active_record/connection_adapters/abstract/database_statements.rb +234 -173
  62. data/lib/active_record/connection_adapters/abstract/query_cache.rb +36 -22
  63. data/lib/active_record/connection_adapters/abstract/quoting.rb +82 -25
  64. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +176 -414
  65. data/lib/active_record/connection_adapters/abstract/schema_dumper.rb +70 -0
  66. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +562 -232
  67. data/lib/active_record/connection_adapters/abstract/transaction.rb +203 -0
  68. data/lib/active_record/connection_adapters/abstract_adapter.rb +281 -53
  69. data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +782 -0
  70. data/lib/active_record/connection_adapters/column.rb +318 -0
  71. data/lib/active_record/connection_adapters/connection_specification.rb +96 -0
  72. data/lib/active_record/connection_adapters/mysql2_adapter.rb +273 -0
  73. data/lib/active_record/connection_adapters/mysql_adapter.rb +365 -450
  74. data/lib/active_record/connection_adapters/postgresql/array_parser.rb +97 -0
  75. data/lib/active_record/connection_adapters/postgresql/cast.rb +152 -0
  76. data/lib/active_record/connection_adapters/postgresql/database_statements.rb +242 -0
  77. data/lib/active_record/connection_adapters/postgresql/oid.rb +366 -0
  78. data/lib/active_record/connection_adapters/postgresql/quoting.rb +171 -0
  79. data/lib/active_record/connection_adapters/postgresql/referential_integrity.rb +30 -0
  80. data/lib/active_record/connection_adapters/postgresql/schema_statements.rb +489 -0
  81. data/lib/active_record/connection_adapters/postgresql_adapter.rb +672 -752
  82. data/lib/active_record/connection_adapters/schema_cache.rb +129 -0
  83. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +588 -17
  84. data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
  85. data/lib/active_record/connection_handling.rb +98 -0
  86. data/lib/active_record/core.rb +463 -0
  87. data/lib/active_record/counter_cache.rb +108 -101
  88. data/lib/active_record/dynamic_matchers.rb +131 -0
  89. data/lib/active_record/errors.rb +54 -13
  90. data/lib/active_record/explain.rb +38 -0
  91. data/lib/active_record/explain_registry.rb +30 -0
  92. data/lib/active_record/explain_subscriber.rb +29 -0
  93. data/lib/active_record/fixture_set/file.rb +55 -0
  94. data/lib/active_record/fixtures.rb +703 -785
  95. data/lib/active_record/inheritance.rb +200 -0
  96. data/lib/active_record/integration.rb +60 -0
  97. data/lib/active_record/locale/en.yml +8 -1
  98. data/lib/active_record/locking/optimistic.rb +69 -60
  99. data/lib/active_record/locking/pessimistic.rb +34 -12
  100. data/lib/active_record/log_subscriber.rb +40 -6
  101. data/lib/active_record/migration/command_recorder.rb +164 -0
  102. data/lib/active_record/migration/join_table.rb +15 -0
  103. data/lib/active_record/migration.rb +614 -216
  104. data/lib/active_record/model_schema.rb +345 -0
  105. data/lib/active_record/nested_attributes.rb +248 -119
  106. data/lib/active_record/null_relation.rb +65 -0
  107. data/lib/active_record/persistence.rb +275 -57
  108. data/lib/active_record/query_cache.rb +29 -9
  109. data/lib/active_record/querying.rb +62 -0
  110. data/lib/active_record/railtie.rb +135 -21
  111. data/lib/active_record/railties/console_sandbox.rb +5 -0
  112. data/lib/active_record/railties/controller_runtime.rb +17 -5
  113. data/lib/active_record/railties/databases.rake +249 -359
  114. data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
  115. data/lib/active_record/readonly_attributes.rb +30 -0
  116. data/lib/active_record/reflection.rb +283 -103
  117. data/lib/active_record/relation/batches.rb +38 -34
  118. data/lib/active_record/relation/calculations.rb +252 -139
  119. data/lib/active_record/relation/delegation.rb +125 -0
  120. data/lib/active_record/relation/finder_methods.rb +182 -188
  121. data/lib/active_record/relation/merger.rb +161 -0
  122. data/lib/active_record/relation/predicate_builder.rb +86 -21
  123. data/lib/active_record/relation/query_methods.rb +917 -134
  124. data/lib/active_record/relation/spawn_methods.rb +53 -92
  125. data/lib/active_record/relation.rb +405 -143
  126. data/lib/active_record/result.rb +67 -0
  127. data/lib/active_record/runtime_registry.rb +17 -0
  128. data/lib/active_record/sanitization.rb +168 -0
  129. data/lib/active_record/schema.rb +20 -14
  130. data/lib/active_record/schema_dumper.rb +55 -46
  131. data/lib/active_record/schema_migration.rb +39 -0
  132. data/lib/active_record/scoping/default.rb +146 -0
  133. data/lib/active_record/scoping/named.rb +175 -0
  134. data/lib/active_record/scoping.rb +82 -0
  135. data/lib/active_record/serialization.rb +8 -46
  136. data/lib/active_record/serializers/xml_serializer.rb +21 -68
  137. data/lib/active_record/statement_cache.rb +26 -0
  138. data/lib/active_record/store.rb +156 -0
  139. data/lib/active_record/tasks/database_tasks.rb +203 -0
  140. data/lib/active_record/tasks/firebird_database_tasks.rb +56 -0
  141. data/lib/active_record/tasks/mysql_database_tasks.rb +143 -0
  142. data/lib/active_record/tasks/oracle_database_tasks.rb +45 -0
  143. data/lib/active_record/tasks/postgresql_database_tasks.rb +90 -0
  144. data/lib/active_record/tasks/sqlite_database_tasks.rb +51 -0
  145. data/lib/active_record/tasks/sqlserver_database_tasks.rb +48 -0
  146. data/lib/active_record/test_case.rb +57 -28
  147. data/lib/active_record/timestamp.rb +49 -18
  148. data/lib/active_record/transactions.rb +106 -63
  149. data/lib/active_record/translation.rb +22 -0
  150. data/lib/active_record/validations/associated.rb +25 -24
  151. data/lib/active_record/validations/presence.rb +65 -0
  152. data/lib/active_record/validations/uniqueness.rb +123 -83
  153. data/lib/active_record/validations.rb +29 -29
  154. data/lib/active_record/version.rb +7 -5
  155. data/lib/active_record.rb +83 -34
  156. data/lib/rails/generators/active_record/migration/migration_generator.rb +46 -9
  157. data/lib/rails/generators/active_record/migration/templates/create_table_migration.rb +19 -0
  158. data/lib/rails/generators/active_record/migration/templates/migration.rb +30 -8
  159. data/lib/rails/generators/active_record/model/model_generator.rb +15 -5
  160. data/lib/rails/generators/active_record/model/templates/model.rb +7 -2
  161. data/lib/rails/generators/active_record/model/templates/module.rb +3 -1
  162. data/lib/rails/generators/active_record.rb +4 -8
  163. metadata +163 -121
  164. data/CHANGELOG +0 -6023
  165. data/examples/associations.png +0 -0
  166. data/lib/active_record/association_preload.rb +0 -403
  167. data/lib/active_record/associations/association_collection.rb +0 -562
  168. data/lib/active_record/associations/association_proxy.rb +0 -295
  169. data/lib/active_record/associations/through_association_scope.rb +0 -154
  170. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +0 -113
  171. data/lib/active_record/connection_adapters/sqlite_adapter.rb +0 -401
  172. data/lib/active_record/dynamic_finder_match.rb +0 -53
  173. data/lib/active_record/dynamic_scope_match.rb +0 -32
  174. data/lib/active_record/named_scope.rb +0 -138
  175. data/lib/active_record/observer.rb +0 -140
  176. data/lib/active_record/session_store.rb +0 -340
  177. data/lib/rails/generators/active_record/model/templates/migration.rb +0 -16
  178. data/lib/rails/generators/active_record/observer/observer_generator.rb +0 -15
  179. data/lib/rails/generators/active_record/observer/templates/observer.rb +0 -2
  180. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +0 -24
  181. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +0 -16
@@ -1,8 +1,15 @@
1
+ require 'active_support/test_case'
2
+
3
+ ActiveSupport::Deprecation.warn('ActiveRecord::TestCase is deprecated, please use ActiveSupport::TestCase')
1
4
  module ActiveRecord
2
5
  # = Active Record Test Case
3
6
  #
4
7
  # Defines some test assertions to test against SQL queries.
5
8
  class TestCase < ActiveSupport::TestCase #:nodoc:
9
+ def teardown
10
+ SQLCounter.clear_log
11
+ end
12
+
6
13
  def assert_date_from_db(expected, actual, message = nil)
7
14
  # SybaseAdapter doesn't have a separate column type just for dates,
8
15
  # so the time is in the string and incorrectly formatted
@@ -14,54 +21,76 @@ module ActiveRecord
14
21
  end
15
22
 
16
23
  def assert_sql(*patterns_to_match)
17
- $queries_executed = []
24
+ SQLCounter.clear_log
18
25
  yield
26
+ SQLCounter.log_all
19
27
  ensure
20
28
  failed_patterns = []
21
29
  patterns_to_match.each do |pattern|
22
- failed_patterns << pattern unless $queries_executed.any?{ |sql| pattern === sql }
30
+ failed_patterns << pattern unless SQLCounter.log_all.any?{ |sql| pattern === sql }
23
31
  end
24
- assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
32
+ assert failed_patterns.empty?, "Query pattern(s) #{failed_patterns.map{ |p| p.inspect }.join(', ')} not found.#{SQLCounter.log.size == 0 ? '' : "\nQueries:\n#{SQLCounter.log.join("\n")}"}"
25
33
  end
26
34
 
27
- def assert_queries(num = 1)
28
- $queries_executed = []
35
+ def assert_queries(num = 1, options = {})
36
+ ignore_none = options.fetch(:ignore_none) { num == :any }
37
+ SQLCounter.clear_log
29
38
  yield
30
39
  ensure
31
- %w{ BEGIN COMMIT }.each { |x| $queries_executed.delete(x) }
32
- assert_equal num, $queries_executed.size, "#{$queries_executed.size} instead of #{num} queries were executed.#{$queries_executed.size == 0 ? '' : "\nQueries:\n#{$queries_executed.join("\n")}"}"
40
+ the_log = ignore_none ? SQLCounter.log_all : SQLCounter.log
41
+ if num == :any
42
+ assert_operator the_log.size, :>=, 1, "1 or more queries expected, but none were executed."
43
+ else
44
+ mesg = "#{the_log.size} instead of #{num} queries were executed.#{the_log.size == 0 ? '' : "\nQueries:\n#{the_log.join("\n")}"}"
45
+ assert_equal num, the_log.size, mesg
46
+ end
33
47
  end
34
48
 
35
49
  def assert_no_queries(&block)
36
- assert_queries(0, &block)
50
+ assert_queries(0, :ignore_none => true, &block)
37
51
  end
38
52
 
39
- def self.use_concurrent_connections
40
- setup :connection_allow_concurrency_setup
41
- teardown :connection_allow_concurrency_teardown
53
+ end
54
+
55
+ class SQLCounter
56
+ class << self
57
+ attr_accessor :ignored_sql, :log, :log_all
58
+ def clear_log; self.log = []; self.log_all = []; end
42
59
  end
43
60
 
44
- def connection_allow_concurrency_setup
45
- @connection = ActiveRecord::Base.remove_connection
46
- ActiveRecord::Base.establish_connection(@connection.merge({:allow_concurrency => true}))
61
+ self.clear_log
62
+
63
+ self.ignored_sql = [/^PRAGMA/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
64
+
65
+ # FIXME: this needs to be refactored so specific database can add their own
66
+ # ignored SQL, or better yet, use a different notification for the queries
67
+ # instead examining the SQL content.
68
+ oracle_ignored = [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
69
+ mysql_ignored = [/^SHOW TABLES/i, /^SHOW FULL FIELDS/]
70
+ postgresql_ignored = [/^\s*select\b.*\bfrom\b.*pg_namespace\b/im, /^\s*select\b.*\battname\b.*\bfrom\b.*\bpg_attribute\b/im, /^SHOW search_path/i]
71
+ sqlite3_ignored = [/^\s*SELECT name\b.*\bFROM sqlite_master/im]
72
+
73
+ [oracle_ignored, mysql_ignored, postgresql_ignored, sqlite3_ignored].each do |db_ignored_sql|
74
+ ignored_sql.concat db_ignored_sql
47
75
  end
48
76
 
49
- def connection_allow_concurrency_teardown
50
- ActiveRecord::Base.clear_all_connections!
51
- ActiveRecord::Base.establish_connection(@connection)
77
+ attr_reader :ignore
78
+
79
+ def initialize(ignore = Regexp.union(self.class.ignored_sql))
80
+ @ignore = ignore
52
81
  end
53
82
 
54
- def with_kcode(kcode)
55
- if RUBY_VERSION < '1.9'
56
- orig_kcode, $KCODE = $KCODE, kcode
57
- begin
58
- yield
59
- ensure
60
- $KCODE = orig_kcode
61
- end
62
- else
63
- yield
64
- end
83
+ def call(name, start, finish, message_id, values)
84
+ sql = values[:sql]
85
+
86
+ # FIXME: this seems bad. we should probably have a better way to indicate
87
+ # the query was cached
88
+ return if 'CACHE' == values[:name]
89
+
90
+ self.class.log_all << sql
91
+ self.class.log << sql unless ignore =~ sql
65
92
  end
66
93
  end
94
+
95
+ ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
67
96
  end
@@ -1,3 +1,4 @@
1
+
1
2
  module ActiveRecord
2
3
  # = Active Record Timestamp
3
4
  #
@@ -7,47 +8,56 @@ module ActiveRecord
7
8
  #
8
9
  # Timestamping can be turned off by setting:
9
10
  #
10
- # <tt>ActiveRecord::Base.record_timestamps = false</tt>
11
+ # config.active_record.record_timestamps = false
11
12
  #
12
13
  # Timestamps are in the local timezone by default but you can use UTC by setting:
13
14
  #
14
- # <tt>ActiveRecord::Base.default_timezone = :utc</tt>
15
+ # config.active_record.default_timezone = :utc
15
16
  #
16
17
  # == Time Zone aware attributes
17
18
  #
18
19
  # By default, ActiveRecord::Base keeps all the datetime columns time zone aware by executing following code.
19
20
  #
20
- # ActiveRecord::Base.time_zone_aware_attributes = true
21
+ # config.active_record.time_zone_aware_attributes = true
21
22
  #
22
23
  # This feature can easily be turned off by assigning value <tt>false</tt> .
23
24
  #
24
- # If your attributes are time zone aware and you desire to skip time zone conversion for certain
25
- # attributes then you can do following:
25
+ # If your attributes are time zone aware and you desire to skip time zone conversion to the current Time.zone
26
+ # when reading certain attributes then you can do following:
26
27
  #
27
- # Topic.skip_time_zone_conversion_for_attributes = [:written_on]
28
+ # class Topic < ActiveRecord::Base
29
+ # self.skip_time_zone_conversion_for_attributes = [:written_on]
30
+ # end
28
31
  module Timestamp
29
32
  extend ActiveSupport::Concern
30
33
 
31
34
  included do
32
- class_inheritable_accessor :record_timestamps, :instance_writer => false
35
+ class_attribute :record_timestamps
33
36
  self.record_timestamps = true
34
37
  end
35
38
 
39
+ def initialize_dup(other) # :nodoc:
40
+ clear_timestamp_attributes
41
+ super
42
+ end
43
+
36
44
  private
37
45
 
38
- def create #:nodoc:
39
- if record_timestamps
46
+ def create_record
47
+ if self.record_timestamps
40
48
  current_time = current_time_from_proper_timezone
41
49
 
42
50
  all_timestamp_attributes.each do |column|
43
- write_attribute(column.to_s, current_time) if respond_to?(column) && self.send(column).nil?
51
+ if respond_to?(column) && respond_to?("#{column}=") && self.send(column).nil?
52
+ write_attribute(column.to_s, current_time)
53
+ end
44
54
  end
45
55
  end
46
56
 
47
57
  super
48
58
  end
49
59
 
50
- def update(*args) #:nodoc:
60
+ def update_record(*args)
51
61
  if should_record_timestamps?
52
62
  current_time = current_time_from_proper_timezone
53
63
 
@@ -61,28 +71,49 @@ module ActiveRecord
61
71
  end
62
72
 
63
73
  def should_record_timestamps?
64
- record_timestamps && (!partial_updates? || changed?)
74
+ self.record_timestamps && (!partial_writes? || 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) }
65
79
  end
66
80
 
67
81
  def timestamp_attributes_for_update_in_model
68
- timestamp_attributes_for_update.select { |c| respond_to?(c) }
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
69
87
  end
70
88
 
71
- def timestamp_attributes_for_update #:nodoc:
89
+ def timestamp_attributes_for_update
72
90
  [:updated_at, :updated_on]
73
91
  end
74
92
 
75
- def timestamp_attributes_for_create #:nodoc:
93
+ def timestamp_attributes_for_create
76
94
  [:created_at, :created_on]
77
95
  end
78
96
 
79
- def all_timestamp_attributes #:nodoc:
97
+ def all_timestamp_attributes
80
98
  timestamp_attributes_for_create + timestamp_attributes_for_update
81
99
  end
82
100
 
83
- def current_time_from_proper_timezone #:nodoc:
101
+ def max_updated_column_timestamp
102
+ if (timestamps = timestamp_attributes_for_update.map { |attr| self[attr] }.compact).present?
103
+ timestamps.map { |ts| ts.to_time }.max
104
+ end
105
+ end
106
+
107
+ def current_time_from_proper_timezone
84
108
  self.class.default_timezone == :utc ? Time.now.utc : Time.now
85
109
  end
110
+
111
+ # Clear attributes and changed_attributes
112
+ def clear_timestamp_attributes
113
+ all_timestamp_attributes_in_model.each do |attribute_name|
114
+ self[attribute_name] = nil
115
+ changed_attributes.delete(attribute_name)
116
+ end
117
+ end
86
118
  end
87
119
  end
88
-
@@ -4,6 +4,7 @@ module ActiveRecord
4
4
  # See ActiveRecord::Transactions::ClassMethods for documentation.
5
5
  module Transactions
6
6
  extend ActiveSupport::Concern
7
+ ACTIONS = [:create, :destroy, :update]
7
8
 
8
9
  class TransactionError < ActiveRecordError # :nodoc:
9
10
  end
@@ -11,6 +12,7 @@ module ActiveRecord
11
12
  included do
12
13
  define_callbacks :commit, :rollback, :terminator => "result == false", :scope => [:kind, :name]
13
14
  end
15
+
14
16
  # = Active Record Transactions
15
17
  #
16
18
  # Transactions are protective blocks where SQL statements are only permanent
@@ -107,10 +109,10 @@ module ActiveRecord
107
109
  #
108
110
  # # Suppose that we have a Number model with a unique column called 'i'.
109
111
  # Number.transaction do
110
- # Number.create(:i => 0)
112
+ # Number.create(i: 0)
111
113
  # begin
112
114
  # # This will raise a unique constraint error...
113
- # Number.create(:i => 0)
115
+ # Number.create(i: 0)
114
116
  # rescue ActiveRecord::StatementInvalid
115
117
  # # ...which we ignore.
116
118
  # end
@@ -118,7 +120,7 @@ module ActiveRecord
118
120
  # # On PostgreSQL, the transaction is now unusable. The following
119
121
  # # statement will cause a PostgreSQL error, even though the unique
120
122
  # # constraint is no longer violated:
121
- # Number.create(:i => 1)
123
+ # Number.create(i: 1)
122
124
  # # => "PGError: ERROR: current transaction is aborted, commands
123
125
  # # ignored until end of transaction block"
124
126
  # end
@@ -130,38 +132,41 @@ module ActiveRecord
130
132
  #
131
133
  # +transaction+ calls can be nested. By default, this makes all database
132
134
  # statements in the nested transaction block become part of the parent
133
- # transaction. For example:
135
+ # transaction. For example, the following behavior may be surprising:
134
136
  #
135
137
  # User.transaction do
136
- # User.create(:username => 'Kotori')
138
+ # User.create(username: 'Kotori')
137
139
  # User.transaction do
138
- # User.create(:username => 'Nemu')
140
+ # User.create(username: 'Nemu')
139
141
  # raise ActiveRecord::Rollback
140
142
  # end
141
143
  # end
142
144
  #
143
- # User.find(:all) # => empty
145
+ # creates both "Kotori" and "Nemu". Reason is the <tt>ActiveRecord::Rollback</tt>
146
+ # exception in the nested block does not issue a ROLLBACK. Since these exceptions
147
+ # are captured in transaction blocks, the parent block does not see it and the
148
+ # real transaction is committed.
144
149
  #
145
- # It is also possible to requires a sub-transaction by passing
146
- # <tt>:requires_new => true</tt>. If anything goes wrong, the
147
- # database rolls back to the beginning of the sub-transaction
148
- # without rolling back the parent transaction. For example:
150
+ # In order to get a ROLLBACK for the nested transaction you may ask for a real
151
+ # sub-transaction by passing <tt>requires_new: true</tt>. If anything goes wrong,
152
+ # the database rolls back to the beginning of the sub-transaction without rolling
153
+ # back the parent transaction. If we add it to the previous example:
149
154
  #
150
155
  # User.transaction do
151
- # User.create(:username => 'Kotori')
152
- # User.transaction(:requires_new => true) do
153
- # User.create(:username => 'Nemu')
156
+ # User.create(username: 'Kotori')
157
+ # User.transaction(requires_new: true) do
158
+ # User.create(username: 'Nemu')
154
159
  # raise ActiveRecord::Rollback
155
160
  # end
156
161
  # end
157
162
  #
158
- # User.find(:all) # => Returns only Kotori
163
+ # only "Kotori" is created. This works on MySQL and PostgreSQL. SQLite3 version >= '3.6.8' also supports it.
159
164
  #
160
165
  # Most databases don't support true nested transactions. At the time of
161
166
  # writing, the only database that we're aware of that supports true nested
162
167
  # transactions, is MS-SQL. Because of this, Active Record emulates nested
163
- # transactions by using savepoints. See
164
- # http://dev.mysql.com/doc/refman/5.0/en/savepoints.html
168
+ # transactions by using savepoints on MySQL and PostgreSQL. See
169
+ # http://dev.mysql.com/doc/refman/5.6/en/savepoint.html
165
170
  # for more information about savepoints.
166
171
  #
167
172
  # === Callbacks
@@ -190,7 +195,7 @@ module ActiveRecord
190
195
  # automatically released. The following example demonstrates the problem:
191
196
  #
192
197
  # Model.connection.transaction do # BEGIN
193
- # Model.connection.transaction(:requires_new => true) do # CREATE SAVEPOINT active_record_1
198
+ # Model.connection.transaction(requires_new: true) do # CREATE SAVEPOINT active_record_1
194
199
  # Model.connection.create_table(...) # active_record_1 now automatically released
195
200
  # end # RELEASE savepoint active_record_1
196
201
  # # ^^^^ BOOM! database error!
@@ -204,28 +209,56 @@ module ActiveRecord
204
209
  connection.transaction(options, &block)
205
210
  end
206
211
 
212
+ # This callback is called after a record has been created, updated, or destroyed.
213
+ #
214
+ # You can specify that the callback should only be fired by a certain action with
215
+ # the +:on+ option:
216
+ #
217
+ # after_commit :do_foo, on: :create
218
+ # after_commit :do_bar, on: :update
219
+ # after_commit :do_baz, on: :destroy
220
+ #
221
+ # after_commit :do_foo_bar, :on [:create, :update]
222
+ # after_commit :do_bar_baz, :on [:update, :destroy]
223
+ #
224
+ # Note that transactional fixtures do not play well with this feature. Please
225
+ # use the +test_after_commit+ gem to have these hooks fired in tests.
207
226
  def after_commit(*args, &block)
208
- options = args.last
209
- if options.is_a?(Hash) && options[:on]
210
- options[:if] = Array.wrap(options[:if])
211
- options[:if] << "transaction_include_action?(:#{options[:on]})"
212
- end
227
+ set_options_for_callbacks!(args)
213
228
  set_callback(:commit, :after, *args, &block)
214
229
  end
215
230
 
231
+ # This callback is called after a create, update, or destroy are rolled back.
232
+ #
233
+ # Please check the documentation of +after_commit+ for options.
216
234
  def after_rollback(*args, &block)
235
+ set_options_for_callbacks!(args)
236
+ set_callback(:rollback, :after, *args, &block)
237
+ end
238
+
239
+ private
240
+
241
+ def set_options_for_callbacks!(args)
217
242
  options = args.last
218
243
  if options.is_a?(Hash) && options[:on]
219
- options[:if] = Array.wrap(options[:if])
220
- options[:if] << "transaction_include_action?(:#{options[:on]})"
244
+ assert_valid_transaction_action(options[:on])
245
+ options[:if] = Array(options[:if])
246
+ fire_on = Array(options[:on]).map(&:to_sym)
247
+ options[:if] << "transaction_include_any_action?(#{fire_on})"
248
+ end
249
+ end
250
+
251
+ def assert_valid_transaction_action(actions)
252
+ actions = Array(actions)
253
+ if (actions - ACTIONS).any?
254
+ raise ArgumentError, ":on conditions for after_commit and after_rollback callbacks have to be one of #{ACTIONS.join(",")}"
221
255
  end
222
- set_callback(:rollback, :after, *args, &block)
223
256
  end
224
257
  end
225
258
 
226
259
  # See ActiveRecord::Transactions::ClassMethods for detailed documentation.
227
- def transaction(&block)
228
- self.class.transaction(&block)
260
+ def transaction(options = {}, &block)
261
+ self.class.transaction(options, &block)
229
262
  end
230
263
 
231
264
  def destroy #:nodoc:
@@ -254,8 +287,11 @@ module ActiveRecord
254
287
  end
255
288
 
256
289
  # Call the after_commit callbacks
290
+ #
291
+ # Ensure that it is not called if the object was never persisted (failed create),
292
+ # but call it after the commit of a destroyed object
257
293
  def committed! #:nodoc:
258
- _run_commit_callbacks
294
+ run_callbacks :commit if destroyed? || persisted?
259
295
  ensure
260
296
  clear_transaction_record_state
261
297
  end
@@ -263,7 +299,7 @@ module ActiveRecord
263
299
  # Call the after rollback callbacks. The restore_state argument indicates if the record
264
300
  # state should be rolled back to the beginning or just to the last savepoint.
265
301
  def rolledback!(force_restore_state = false) #:nodoc:
266
- _run_rollback_callbacks
302
+ run_callbacks :rollback
267
303
  ensure
268
304
  restore_transaction_record_state(force_restore_state)
269
305
  end
@@ -286,7 +322,13 @@ module ActiveRecord
286
322
  status = nil
287
323
  self.class.transaction do
288
324
  add_to_transaction
289
- status = yield
325
+ begin
326
+ status = yield
327
+ rescue ActiveRecord::Rollback
328
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
329
+ status = nil
330
+ end
331
+
290
332
  raise ActiveRecord::Rollback unless status
291
333
  end
292
334
  status
@@ -295,61 +337,62 @@ module ActiveRecord
295
337
  protected
296
338
 
297
339
  # Save the new record state and id of a record so it can be restored later if a transaction fails.
298
- def remember_transaction_record_state #:nodoc
299
- @_start_transaction_state ||= {}
340
+ def remember_transaction_record_state #:nodoc:
341
+ @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
300
342
  unless @_start_transaction_state.include?(:new_record)
301
- @_start_transaction_state[:id] = id if has_attribute?(self.class.primary_key)
302
343
  @_start_transaction_state[:new_record] = @new_record
303
344
  end
304
345
  unless @_start_transaction_state.include?(:destroyed)
305
346
  @_start_transaction_state[:destroyed] = @destroyed
306
347
  end
307
348
  @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) + 1
349
+ @_start_transaction_state[:frozen?] = @attributes.frozen?
308
350
  end
309
351
 
310
352
  # Clear the new record state and id of a record.
311
- def clear_transaction_record_state #:nodoc
312
- if defined?(@_start_transaction_state)
313
- @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
314
- remove_instance_variable(:@_start_transaction_state) if @_start_transaction_state[:level] < 1
315
- end
353
+ def clear_transaction_record_state #:nodoc:
354
+ @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
355
+ @_start_transaction_state.clear if @_start_transaction_state[:level] < 1
316
356
  end
317
357
 
318
358
  # Restore the new record state and id of a record that was previously saved by a call to save_record_state.
319
- def restore_transaction_record_state(force = false) #:nodoc
320
- if defined?(@_start_transaction_state)
359
+ def restore_transaction_record_state(force = false) #:nodoc:
360
+ unless @_start_transaction_state.empty?
321
361
  @_start_transaction_state[:level] = (@_start_transaction_state[:level] || 0) - 1
322
- if @_start_transaction_state[:level] < 1
323
- restore_state = remove_instance_variable(:@_start_transaction_state)
324
- if restore_state
325
- @attributes = @attributes.dup if @attributes.frozen?
326
- @new_record = restore_state[:new_record]
327
- @destroyed = restore_state[:destroyed]
328
- if restore_state[:id]
329
- self.id = restore_state[:id]
330
- else
331
- @attributes.delete(self.class.primary_key)
332
- @attributes_cache.delete(self.class.primary_key)
333
- end
362
+ if @_start_transaction_state[:level] < 1 || force
363
+ restore_state = @_start_transaction_state
364
+ was_frozen = restore_state[:frozen?]
365
+ @attributes = @attributes.dup if @attributes.frozen?
366
+ @new_record = restore_state[:new_record]
367
+ @destroyed = restore_state[:destroyed]
368
+ if restore_state.has_key?(:id)
369
+ self.id = restore_state[:id]
370
+ else
371
+ @attributes.delete(self.class.primary_key)
372
+ @attributes_cache.delete(self.class.primary_key)
334
373
  end
374
+ @attributes.freeze if was_frozen
375
+ @_start_transaction_state.clear
335
376
  end
336
377
  end
337
378
  end
338
379
 
339
380
  # Determine if a record was created or destroyed in a transaction. State should be one of :new_record or :destroyed.
340
- def transaction_record_state(state) #:nodoc
341
- @_start_transaction_state[state] if defined?(@_start_transaction_state)
381
+ def transaction_record_state(state) #:nodoc:
382
+ @_start_transaction_state[state]
342
383
  end
343
384
 
344
385
  # Determine if a transaction included an action for :create, :update, or :destroy. Used in filtering callbacks.
345
- def transaction_include_action?(action) #:nodoc
346
- case action
347
- when :create
348
- transaction_record_state(:new_record)
349
- when :destroy
350
- destroyed?
351
- when :update
352
- !(transaction_record_state(:new_record) || destroyed?)
386
+ def transaction_include_any_action?(actions) #:nodoc:
387
+ actions.any? do |action|
388
+ case action
389
+ when :create
390
+ transaction_record_state(:new_record)
391
+ when :destroy
392
+ destroyed?
393
+ when :update
394
+ !(transaction_record_state(:new_record) || destroyed?)
395
+ end
353
396
  end
354
397
  end
355
398
  end
@@ -0,0 +1,22 @@
1
+ module ActiveRecord
2
+ module Translation
3
+ include ActiveModel::Translation
4
+
5
+ # Set the lookup ancestors for ActiveModel.
6
+ def lookup_ancestors #:nodoc:
7
+ klass = self
8
+ classes = [klass]
9
+ return classes if klass == ActiveRecord::Base
10
+
11
+ while klass != klass.base_class
12
+ classes << klass = klass.superclass
13
+ end
14
+ classes
15
+ end
16
+
17
+ # Set the i18n scope to overwrite ActiveModel.
18
+ def i18n_scope #:nodoc:
19
+ :activerecord
20
+ end
21
+ end
22
+ end
@@ -1,14 +1,16 @@
1
1
  module ActiveRecord
2
2
  module Validations
3
- class AssociatedValidator < ActiveModel::EachValidator
3
+ class AssociatedValidator < ActiveModel::EachValidator #:nodoc:
4
4
  def validate_each(record, attribute, value)
5
- return if (value.is_a?(Array) ? value : [value]).collect{ |r| r.nil? || r.valid? }.all?
6
- record.errors.add(attribute, :invalid, options.merge(:value => value))
5
+ if Array.wrap(value).reject {|r| r.marked_for_destruction? || r.valid?}.any?
6
+ record.errors.add(attribute, :invalid, options.merge(:value => value))
7
+ end
7
8
  end
8
9
  end
9
10
 
10
11
  module ClassMethods
11
- # Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
12
+ # Validates whether the associated object or objects are all valid.
13
+ # Works with any kind of association.
12
14
  #
13
15
  # class Book < ActiveRecord::Base
14
16
  # has_many :pages
@@ -17,29 +19,28 @@ module ActiveRecord
17
19
  # validates_associated :pages, :library
18
20
  # end
19
21
  #
20
- # Warning: If, after the above definition, you then wrote:
22
+ # WARNING: This validation must not be used on both ends of an association.
23
+ # Doing so will lead to a circular dependency and cause infinite recursion.
21
24
  #
22
- # class Page < ActiveRecord::Base
23
- # belongs_to :book
24
- #
25
- # validates_associated :book
26
- # end
27
- #
28
- # this would specify a circular dependency and cause infinite recursion.
29
- #
30
- # NOTE: This validation will not fail if the association hasn't been assigned. If you want to
31
- # ensure that the association is both present and guaranteed to be valid, you also need to
32
- # use +validates_presence_of+.
25
+ # NOTE: This validation will not fail if the association hasn't been
26
+ # assigned. If you want to ensure that the association is both present and
27
+ # guaranteed to be valid, you also need to use +validates_presence_of+.
33
28
  #
34
29
  # Configuration options:
35
- # * <tt>:message</tt> - A custom error message (default is: "is invalid")
36
- # * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
37
- # * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
38
- # occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
39
- # method, proc or string should return or evaluate to a true or false value.
40
- # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
41
- # not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
42
- # method, proc or string should return or evaluate to a true or false value.
30
+ #
31
+ # * <tt>:message</tt> - A custom error message (default is: "is invalid").
32
+ # * <tt>:on</tt> - Specifies when this validation is active. Runs in all
33
+ # validation contexts by default (+nil+), other options are <tt>:create</tt>
34
+ # and <tt>:update</tt>.
35
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
36
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
37
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
38
+ # proc or string should return or evaluate to a +true+ or +false+ value.
39
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
40
+ # determine if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
41
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
42
+ # method, proc or string should return or evaluate to a +true+ or +false+
43
+ # value.
43
44
  def validates_associated(*attr_names)
44
45
  validates_with AssociatedValidator, _merge_attributes(attr_names)
45
46
  end