activerecord 1.14.4 → 1.15.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 (159) hide show
  1. data/CHANGELOG +400 -1
  2. data/README +2 -2
  3. data/RUNNING_UNIT_TESTS +21 -3
  4. data/Rakefile +55 -10
  5. data/lib/active_record.rb +10 -4
  6. data/lib/active_record/acts/list.rb +15 -4
  7. data/lib/active_record/acts/nested_set.rb +11 -12
  8. data/lib/active_record/acts/tree.rb +13 -14
  9. data/lib/active_record/aggregations.rb +46 -22
  10. data/lib/active_record/associations.rb +213 -162
  11. data/lib/active_record/associations/association_collection.rb +45 -15
  12. data/lib/active_record/associations/association_proxy.rb +32 -13
  13. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
  14. data/lib/active_record/associations/has_many_association.rb +37 -17
  15. data/lib/active_record/associations/has_many_through_association.rb +120 -30
  16. data/lib/active_record/associations/has_one_association.rb +1 -1
  17. data/lib/active_record/attribute_methods.rb +75 -0
  18. data/lib/active_record/base.rb +282 -203
  19. data/lib/active_record/calculations.rb +95 -54
  20. data/lib/active_record/callbacks.rb +13 -24
  21. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
  22. data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
  23. data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
  24. data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
  25. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
  26. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
  27. data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
  28. data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
  29. data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
  30. data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
  31. data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
  32. data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
  33. data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
  34. data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
  35. data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
  36. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
  37. data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
  38. data/lib/active_record/deprecated_associations.rb +24 -10
  39. data/lib/active_record/deprecated_finders.rb +4 -1
  40. data/lib/active_record/fixtures.rb +37 -23
  41. data/lib/active_record/locking/optimistic.rb +106 -0
  42. data/lib/active_record/locking/pessimistic.rb +77 -0
  43. data/lib/active_record/migration.rb +8 -5
  44. data/lib/active_record/observer.rb +73 -34
  45. data/lib/active_record/reflection.rb +21 -7
  46. data/lib/active_record/schema_dumper.rb +33 -5
  47. data/lib/active_record/timestamp.rb +23 -34
  48. data/lib/active_record/transactions.rb +37 -30
  49. data/lib/active_record/validations.rb +46 -30
  50. data/lib/active_record/vendor/mysql.rb +20 -5
  51. data/lib/active_record/version.rb +2 -2
  52. data/lib/active_record/wrappings.rb +1 -2
  53. data/lib/active_record/xml_serialization.rb +308 -0
  54. data/test/aaa_create_tables_test.rb +5 -1
  55. data/test/abstract_unit.rb +18 -8
  56. data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
  57. data/test/adapter_test.rb +9 -7
  58. data/test/adapter_test_sqlserver.rb +81 -0
  59. data/test/aggregations_test.rb +29 -0
  60. data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
  61. data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
  62. data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
  63. data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
  64. data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
  65. data/test/associations_test.rb +339 -45
  66. data/test/attribute_methods_test.rb +49 -0
  67. data/test/base_test.rb +321 -67
  68. data/test/calculations_test.rb +48 -10
  69. data/test/callbacks_test.rb +13 -0
  70. data/test/connection_test_firebird.rb +8 -0
  71. data/test/connections/native_db2/connection.rb +18 -17
  72. data/test/connections/native_firebird/connection.rb +19 -17
  73. data/test/connections/native_frontbase/connection.rb +27 -0
  74. data/test/connections/native_mysql/connection.rb +18 -15
  75. data/test/connections/native_openbase/connection.rb +14 -15
  76. data/test/connections/native_oracle/connection.rb +16 -12
  77. data/test/connections/native_postgresql/connection.rb +16 -17
  78. data/test/connections/native_sqlite/connection.rb +3 -6
  79. data/test/connections/native_sqlite3/connection.rb +3 -6
  80. data/test/connections/native_sqlserver/connection.rb +16 -17
  81. data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
  82. data/test/connections/native_sybase/connection.rb +16 -17
  83. data/test/datatype_test_postgresql.rb +52 -0
  84. data/test/defaults_test.rb +52 -10
  85. data/test/deprecated_associations_test.rb +151 -107
  86. data/test/deprecated_finder_test.rb +83 -66
  87. data/test/empty_date_time_test.rb +25 -0
  88. data/test/finder_test.rb +118 -11
  89. data/test/fixtures/accounts.yml +6 -1
  90. data/test/fixtures/author.rb +27 -4
  91. data/test/fixtures/categorizations.yml +8 -2
  92. data/test/fixtures/category.rb +1 -2
  93. data/test/fixtures/comments.yml +0 -6
  94. data/test/fixtures/companies.yml +6 -1
  95. data/test/fixtures/company.rb +23 -1
  96. data/test/fixtures/company_in_module.rb +8 -10
  97. data/test/fixtures/customer.rb +2 -2
  98. data/test/fixtures/customers.yml +9 -0
  99. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  100. data/test/fixtures/db_definitions/db2.sql +9 -0
  101. data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
  102. data/test/fixtures/db_definitions/firebird.sql +13 -1
  103. data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
  104. data/test/fixtures/db_definitions/frontbase.sql +262 -0
  105. data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/frontbase2.sql +4 -0
  107. data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
  108. data/test/fixtures/db_definitions/mysql.sql +23 -14
  109. data/test/fixtures/db_definitions/openbase.sql +13 -1
  110. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  111. data/test/fixtures/db_definitions/oracle.sql +29 -2
  112. data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
  113. data/test/fixtures/db_definitions/postgresql.sql +13 -3
  114. data/test/fixtures/db_definitions/schema.rb +29 -1
  115. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  116. data/test/fixtures/db_definitions/sqlite.sql +12 -3
  117. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  118. data/test/fixtures/db_definitions/sqlserver.sql +35 -0
  119. data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
  120. data/test/fixtures/db_definitions/sybase.sql +13 -4
  121. data/test/fixtures/developer.rb +12 -0
  122. data/test/fixtures/edge.rb +5 -0
  123. data/test/fixtures/edges.yml +6 -0
  124. data/test/fixtures/funny_jokes.yml +3 -7
  125. data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
  126. data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
  127. data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
  128. data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
  129. data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
  130. data/test/fixtures/mixin.rb +15 -0
  131. data/test/fixtures/mixins.yml +38 -0
  132. data/test/fixtures/post.rb +3 -2
  133. data/test/fixtures/project.rb +3 -1
  134. data/test/fixtures/topic.rb +6 -1
  135. data/test/fixtures/topics.yml +4 -4
  136. data/test/fixtures/vertex.rb +9 -0
  137. data/test/fixtures/vertices.yml +4 -0
  138. data/test/fixtures_test.rb +45 -0
  139. data/test/inheritance_test.rb +67 -6
  140. data/test/lifecycle_test.rb +40 -19
  141. data/test/locking_test.rb +170 -26
  142. data/test/method_scoping_test.rb +2 -2
  143. data/test/migration_test.rb +387 -110
  144. data/test/migration_test_firebird.rb +124 -0
  145. data/test/mixin_nested_set_test.rb +14 -2
  146. data/test/mixin_test.rb +56 -18
  147. data/test/modules_test.rb +8 -2
  148. data/test/multiple_db_test.rb +2 -2
  149. data/test/pk_test.rb +1 -0
  150. data/test/reflection_test.rb +8 -2
  151. data/test/schema_authorization_test_postgresql.rb +75 -0
  152. data/test/schema_dumper_test.rb +40 -4
  153. data/test/table_name_test_sqlserver.rb +23 -0
  154. data/test/threaded_connections_test.rb +19 -16
  155. data/test/transactions_test.rb +86 -72
  156. data/test/validations_test.rb +126 -56
  157. data/test/xml_serialization_test.rb +125 -0
  158. metadata +45 -11
  159. data/lib/active_record/locking.rb +0 -79
@@ -1,10 +1,10 @@
1
1
  require 'singleton'
2
+ require 'set'
2
3
 
3
4
  module ActiveRecord
4
5
  module Observing # :nodoc:
5
- def self.append_features(base)
6
- super
7
- base.extend(ClassMethods)
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
8
  end
9
9
 
10
10
  module ClassMethods
@@ -13,18 +13,44 @@ module ActiveRecord
13
13
  # # Calls PersonObserver.instance
14
14
  # ActiveRecord::Base.observers = :person_observer
15
15
  #
16
- # # Calls Cacher.instance and GarbageCollector.instance
16
+ # # Calls Cacher.instance and GarbageCollector.instance
17
17
  # ActiveRecord::Base.observers = :cacher, :garbage_collector
18
18
  #
19
19
  # # Same as above, just using explicit class references
20
20
  # ActiveRecord::Base.observers = Cacher, GarbageCollector
21
+ #
22
+ # Note: Setting this does not instantiate the observers yet. #instantiate_observers is
23
+ # called during startup, and before each development request.
21
24
  def observers=(*observers)
22
- observers = [ observers ].flatten.each do |observer|
23
- observer.is_a?(Symbol) ?
24
- observer.to_s.camelize.constantize.instance :
25
+ @observers = observers.flatten
26
+ end
27
+
28
+ # Gets the current observers.
29
+ def observers
30
+ @observers ||= []
31
+ end
32
+
33
+ # Instantiate the global ActiveRecord observers
34
+ def instantiate_observers
35
+ return if @observers.blank?
36
+ @observers.each do |observer|
37
+ if observer.respond_to?(:to_sym) # Symbol or String
38
+ observer.to_s.camelize.constantize.instance
39
+ elsif observer.respond_to?(:instance)
25
40
  observer.instance
41
+ else
42
+ raise ArgumentError, "#{observer} must be a lowercase, underscored class name (or an instance of the class itself) responding to the instance method. Example: Person.observers = :big_brother # calls BigBrother.instance"
43
+ end
26
44
  end
27
45
  end
46
+
47
+ protected
48
+ # Notify observers when the observed class is subclassed.
49
+ def inherited(subclass)
50
+ super
51
+ changed
52
+ notify_observers :observed_class_inherited, subclass
53
+ end
28
54
  end
29
55
  end
30
56
 
@@ -85,12 +111,12 @@ module ActiveRecord
85
111
  # The observer can implement callback methods for each of the methods described in the Callbacks module.
86
112
  #
87
113
  # == Storing Observers in Rails
88
- #
114
+ #
89
115
  # If you're using Active Record within Rails, observer classes are usually stored in app/models with the
90
116
  # naming convention of app/models/audit_observer.rb.
91
117
  #
92
118
  # == Configuration
93
- #
119
+ #
94
120
  # In order to activate an observer, list it in the <tt>config.active_record.observers</tt> configuration setting in your
95
121
  # <tt>config/environment.rb</tt> file.
96
122
  #
@@ -103,37 +129,50 @@ module ActiveRecord
103
129
 
104
130
  # Observer subclasses should be reloaded by the dispatcher in Rails
105
131
  # when Dependencies.mechanism = :load.
106
- include Reloadable::Subclasses
107
-
108
- # Attaches the observer to the supplied model classes.
109
- def self.observe(*models)
110
- define_method(:observed_class) { models }
132
+ include Reloadable::Deprecated
133
+
134
+ class << self
135
+ # Attaches the observer to the supplied model classes.
136
+ def observe(*models)
137
+ define_method(:observed_classes) { Set.new(models) }
138
+ end
139
+
140
+ # The class observed by default is inferred from the observer's class name:
141
+ # assert_equal [Person], PersonObserver.observed_class
142
+ def observed_class
143
+ name.scan(/(.*)Observer/)[0][0].constantize
144
+ end
111
145
  end
112
146
 
147
+ # Start observing the declared classes and their subclasses.
113
148
  def initialize
114
- observed_classes = [ observed_class ].flatten
115
- observed_subclasses_class = observed_classes.collect {|c| c.send(:subclasses) }.flatten!
116
- (observed_classes + observed_subclasses_class).each do |klass|
117
- klass.add_observer(self)
118
- klass.send(:define_method, :after_find) unless klass.respond_to?(:after_find)
119
- end
149
+ Set.new(observed_classes + observed_subclasses).each { |klass| add_observer! klass }
120
150
  end
121
-
122
- def update(callback_method, object) #:nodoc:
123
- send(callback_method, object) if respond_to?(callback_method)
151
+
152
+ # Send observed_method(object) if the method exists.
153
+ def update(observed_method, object) #:nodoc:
154
+ send(observed_method, object) if respond_to?(observed_method)
124
155
  end
125
-
126
- private
127
- def observed_class
128
- if self.class.respond_to? "observed_class"
129
- self.class.observed_class
130
- else
131
- Object.const_get(infer_observed_class_name)
132
- end
156
+
157
+ # Special method sent by the observed class when it is inherited.
158
+ # Passes the new subclass.
159
+ def observed_class_inherited(subclass) #:nodoc:
160
+ self.class.observe(observed_classes + [subclass])
161
+ add_observer!(subclass)
162
+ end
163
+
164
+ protected
165
+ def observed_classes
166
+ Set.new([self.class.observed_class].flatten)
167
+ end
168
+
169
+ def observed_subclasses
170
+ observed_classes.sum(&:subclasses)
133
171
  end
134
-
135
- def infer_observed_class_name
136
- self.class.name.scan(/(.*)Observer/)[0][0]
172
+
173
+ def add_observer!(klass)
174
+ klass.add_observer(self)
175
+ klass.class_eval 'def after_find() end' unless klass.respond_to?(:after_find)
137
176
  end
138
177
  end
139
178
  end
@@ -21,33 +21,46 @@ module ActiveRecord
21
21
  reflection
22
22
  end
23
23
 
24
+ # Returns a hash containing all AssociationReflection objects for the current class
25
+ # Example:
26
+ #
27
+ # Invoice.reflections
28
+ # Account.reflections
29
+ #
24
30
  def reflections
25
- read_inheritable_attribute(:reflections) or write_inheritable_attribute(:reflections, {})
31
+ read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
26
32
  end
27
-
33
+
28
34
  # Returns an array of AggregateReflection objects for all the aggregations in the class.
29
35
  def reflect_on_all_aggregations
30
36
  reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
31
37
  end
32
38
 
33
39
  # Returns the AggregateReflection object for the named +aggregation+ (use the symbol). Example:
40
+ #
34
41
  # Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
42
+ #
35
43
  def reflect_on_aggregation(aggregation)
36
44
  reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
37
45
  end
38
46
 
39
47
  # Returns an array of AssociationReflection objects for all the aggregations in the class. If you only want to reflect on a
40
- # certain association type, pass in the symbol (:has_many, :has_one, :belongs_to) for that as the first parameter. Example:
41
- # Account.reflect_on_all_associations # returns an array of all associations
42
- # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
48
+ # certain association type, pass in the symbol (:has_many, :has_one, :belongs_to) for that as the first parameter.
49
+ # Example:
50
+ #
51
+ # Account.reflect_on_all_associations # returns an array of all associations
52
+ # Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
53
+ #
43
54
  def reflect_on_all_associations(macro = nil)
44
55
  association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
45
56
  macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
46
57
  end
47
58
 
48
59
  # Returns the AssociationReflection object for the named +aggregation+ (use the symbol). Example:
60
+ #
49
61
  # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
50
62
  # Invoice.reflect_on_association(:line_items).macro # returns :has_many
63
+ #
51
64
  def reflect_on_association(association)
52
65
  reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
53
66
  end
@@ -147,6 +160,7 @@ module ActiveRecord
147
160
  # Gets an array of possible :through source reflection names
148
161
  #
149
162
  # [singularized, pluralized]
163
+ #
150
164
  def source_reflection_names
151
165
  @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
152
166
  end
@@ -166,7 +180,7 @@ module ActiveRecord
166
180
  def check_validity!
167
181
  if options[:through]
168
182
  if through_reflection.nil?
169
- raise HasManyThroughAssociationNotFoundError.new(self)
183
+ raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
170
184
  end
171
185
 
172
186
  if source_reflection.nil?
@@ -174,7 +188,7 @@ module ActiveRecord
174
188
  end
175
189
 
176
190
  if source_reflection.options[:polymorphic]
177
- raise HasManyThroughAssociationPolymorphicError.new(class_name, self, source_reflection)
191
+ raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
178
192
  end
179
193
 
180
194
  unless [:belongs_to, :has_many].include?(source_reflection.macro) && source_reflection.options[:through].nil?
@@ -1,3 +1,6 @@
1
+ require 'stringio'
2
+ require 'bigdecimal'
3
+
1
4
  module ActiveRecord
2
5
  # This class is used to dump the database schema for some connection to some
3
6
  # output format (i.e., ActiveRecord::Schema).
@@ -82,13 +85,27 @@ HEADER
82
85
  tbl.print ", :force => true"
83
86
  tbl.puts " do |t|"
84
87
 
85
- columns.each do |column|
88
+ column_specs = columns.map do |column|
86
89
  raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
87
90
  next if column.name == pk
88
- tbl.print " t.column #{column.name.inspect}, #{column.type.inspect}"
89
- tbl.print ", :limit => #{column.limit.inspect}" if column.limit != @types[column.type][:limit]
90
- tbl.print ", :default => #{column.default.inspect}" if !column.default.nil?
91
- tbl.print ", :null => false" if !column.null
91
+ spec = {}
92
+ spec[:name] = column.name.inspect
93
+ spec[:type] = column.type.inspect
94
+ spec[:limit] = column.limit.inspect if column.limit != @types[column.type][:limit] && column.type != :decimal
95
+ spec[:precision] = column.precision.inspect if !column.precision.nil?
96
+ spec[:scale] = column.scale.inspect if !column.scale.nil?
97
+ spec[:null] = 'false' if !column.null
98
+ spec[:default] = default_string(column.default) if !column.default.nil?
99
+ (spec.keys - [:name, :type]).each{ |k| spec[k].insert(0, "#{k.inspect} => ")}
100
+ spec
101
+ end.compact
102
+ keys = [:name, :type, :limit, :precision, :scale, :default, :null] & column_specs.map{ |spec| spec.keys }.inject([]){ |a,b| a | b }
103
+ lengths = keys.map{ |key| column_specs.map{ |spec| spec[key] ? spec[key].length + 2 : 0 }.max }
104
+ format_string = lengths.map{ |len| "%-#{len}s" }.join("")
105
+ column_specs.each do |colspec|
106
+ values = keys.zip(lengths).map{ |key, len| colspec.key?(key) ? colspec[key] + ", " : " " * len }
107
+ tbl.print " t.column "
108
+ tbl.print((format_string % values).gsub(/,\s*$/, ''))
92
109
  tbl.puts
93
110
  end
94
111
 
@@ -108,6 +125,17 @@ HEADER
108
125
  stream
109
126
  end
110
127
 
128
+ def default_string(value)
129
+ case value
130
+ when BigDecimal
131
+ value.to_s
132
+ when Date, DateTime, Time
133
+ "'" + value.to_s(:db) + "'"
134
+ else
135
+ value.inspect
136
+ end
137
+ end
138
+
111
139
  def indexes(table, stream)
112
140
  indexes = @connection.indexes(table)
113
141
  indexes.each do |index|
@@ -1,26 +1,35 @@
1
1
  module ActiveRecord
2
- # Active Records will automatically record creation and/or update timestamps of database objects
3
- # if fields of the names created_at/created_on or updated_at/updated_on are present. This module is
4
- # automatically included, so you don't need to do that manually.
2
+ # Active Record automatically timestamps create and update if the table has fields
3
+ # created_at/created_on or updated_at/updated_on.
5
4
  #
6
- # This behavior can be turned off by setting <tt>ActiveRecord::Base.record_timestamps = false</tt>.
7
- # This behavior by default uses local time, but can use UTC by setting <tt>ActiveRecord::Base.default_timezone = :utc</tt>
5
+ # Timestamping can be turned off by setting
6
+ # <tt>ActiveRecord::Base.record_timestamps = false</tt>
7
+ #
8
+ # Keep in mind that, via inheritance, you can turn off timestamps on a per
9
+ # model basis by setting <tt>record_timestamps</tt> to false in the desired
10
+ # models.
11
+ #
12
+ # class Feed < ActiveRecord::Base
13
+ # self.record_timestamps = false
14
+ # # ...
15
+ # end
16
+ #
17
+ # Timestamps are in the local timezone by default but can use UTC by setting
18
+ # <tt>ActiveRecord::Base.default_timezone = :utc</tt>
8
19
  module Timestamp
9
- def self.append_features(base) # :nodoc:
20
+ def self.included(base) #:nodoc:
10
21
  super
11
22
 
12
- base.class_eval do
13
- alias_method :create_without_timestamps, :create
14
- alias_method :create, :create_with_timestamps
23
+ base.alias_method_chain :create, :timestamps
24
+ base.alias_method_chain :update, :timestamps
15
25
 
16
- alias_method :update_without_timestamps, :update
17
- alias_method :update, :update_with_timestamps
18
- end
26
+ base.cattr_accessor :record_timestamps
27
+ base.record_timestamps = true
19
28
  end
20
29
 
21
30
  def create_with_timestamps #:nodoc:
22
31
  if record_timestamps
23
- t = ( self.class.default_timezone == :utc ? Time.now.utc : Time.now )
32
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
24
33
  write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
25
34
  write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
26
35
 
@@ -32,31 +41,11 @@ module ActiveRecord
32
41
 
33
42
  def update_with_timestamps #:nodoc:
34
43
  if record_timestamps
35
- t = ( self.class.default_timezone == :utc ? Time.now.utc : Time.now )
44
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
36
45
  write_attribute('updated_at', t) if respond_to?(:updated_at)
37
46
  write_attribute('updated_on', t) if respond_to?(:updated_on)
38
47
  end
39
48
  update_without_timestamps
40
49
  end
41
50
  end
42
-
43
- class Base
44
- # Records the creation date and possibly time in created_on (date only) or created_at (date and time) and the update date and possibly
45
- # time in updated_on and updated_at. This only happens if the object responds to either of these messages, which they will do automatically
46
- # if the table has columns of either of these names. This feature is turned on by default.
47
- @@record_timestamps = true
48
- cattr_accessor :record_timestamps
49
-
50
- # deprecated: use ActiveRecord::Base.default_timezone instead.
51
- @@timestamps_gmt = false
52
- def self.timestamps_gmt=( gmt ) #:nodoc:
53
- warn "timestamps_gmt= is deprecated. use default_timezone= instead"
54
- self.default_timezone = ( gmt ? :utc : :local )
55
- end
56
-
57
- def self.timestamps_gmt #:nodoc:
58
- warn "timestamps_gmt is deprecated. use default_timezone instead"
59
- self.default_timezone == :utc
60
- end
61
- end
62
51
  end
@@ -4,21 +4,16 @@ require 'thread'
4
4
 
5
5
  module ActiveRecord
6
6
  module Transactions # :nodoc:
7
- TRANSACTION_MUTEX = Mutex.new
8
-
9
7
  class TransactionError < ActiveRecordError # :nodoc:
10
8
  end
11
9
 
12
- def self.append_features(base)
13
- super
10
+ def self.included(base)
14
11
  base.extend(ClassMethods)
15
12
 
16
13
  base.class_eval do
17
- alias_method :destroy_without_transactions, :destroy
18
- alias_method :destroy, :destroy_with_transactions
19
-
20
- alias_method :save_without_transactions, :save
21
- alias_method :save, :save_with_transactions
14
+ [:destroy, :save, :save!].each do |method|
15
+ alias_method_chain method, :transactions
16
+ end
22
17
  end
23
18
  end
24
19
 
@@ -60,7 +55,7 @@ module ActiveRecord
60
55
  # will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
61
56
  # depend on or you can raise exceptions in the callbacks to rollback.
62
57
  #
63
- # == Object-level transactions
58
+ # == Object-level transactions (deprecated)
64
59
  #
65
60
  # You can enable object-level transactions for Active Record objects, though. You do this by naming each of the Active Records
66
61
  # that you want to enable object-level transactions for, like this:
@@ -70,8 +65,14 @@ module ActiveRecord
70
65
  # mary.deposit(100)
71
66
  # end
72
67
  #
73
- # If the transaction fails, David and Mary will be returned to their pre-transactional state. No money will have changed hands in
74
- # neither object nor database.
68
+ # If the transaction fails, David and Mary will be returned to their
69
+ # pre-transactional state. No money will have changed hands in neither
70
+ # object nor database.
71
+ #
72
+ # However, useful state such as validation errors are also rolled back,
73
+ # limiting the usefulness of this feature. As such it is deprecated in
74
+ # Rails 1.2 and will be removed in the next release. Install the
75
+ # object_transactions plugin if you wish to continue using it.
75
76
  #
76
77
  # == Exception handling
77
78
  #
@@ -82,11 +83,14 @@ module ActiveRecord
82
83
  module ClassMethods
83
84
  def transaction(*objects, &block)
84
85
  previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" }
85
- lock_mutex
86
-
86
+ increment_open_transactions
87
+
87
88
  begin
88
- objects.each { |o| o.extend(Transaction::Simple) }
89
- objects.each { |o| o.start_transaction }
89
+ unless objects.empty?
90
+ ActiveSupport::Deprecation.warn "Object transactions are deprecated and will be removed from Rails 2.0. See http://www.rubyonrails.org/deprecation for details.", caller
91
+ objects.each { |o| o.extend(Transaction::Simple) }
92
+ objects.each { |o| o.start_transaction }
93
+ end
90
94
 
91
95
  result = connection.transaction(Thread.current['start_db_transaction'], &block)
92
96
 
@@ -96,22 +100,21 @@ module ActiveRecord
96
100
  objects.each { |o| o.abort_transaction }
97
101
  raise
98
102
  ensure
99
- unlock_mutex
103
+ decrement_open_transactions
100
104
  trap('TERM', previous_handler)
101
105
  end
102
106
  end
103
-
104
- def lock_mutex#:nodoc:
105
- Thread.current['open_transactions'] ||= 0
106
- TRANSACTION_MUTEX.lock if Thread.current['open_transactions'] == 0
107
- Thread.current['start_db_transaction'] = (Thread.current['open_transactions'] == 0)
108
- Thread.current['open_transactions'] += 1
109
- end
110
-
111
- def unlock_mutex#:nodoc:
112
- Thread.current['open_transactions'] -= 1
113
- TRANSACTION_MUTEX.unlock if Thread.current['open_transactions'] == 0
114
- end
107
+
108
+ private
109
+ def increment_open_transactions #:nodoc:
110
+ open = Thread.current['open_transactions'] ||= 0
111
+ Thread.current['start_db_transaction'] = open.zero?
112
+ Thread.current['open_transactions'] = open + 1
113
+ end
114
+
115
+ def decrement_open_transactions #:nodoc:
116
+ Thread.current['open_transactions'] -= 1
117
+ end
115
118
  end
116
119
 
117
120
  def transaction(*objects, &block)
@@ -121,9 +124,13 @@ module ActiveRecord
121
124
  def destroy_with_transactions #:nodoc:
122
125
  transaction { destroy_without_transactions }
123
126
  end
124
-
127
+
125
128
  def save_with_transactions(perform_validation = true) #:nodoc:
126
129
  transaction { save_without_transactions(perform_validation) }
127
130
  end
131
+
132
+ def save_with_transactions! #:nodoc:
133
+ transaction { save_without_transactions! }
134
+ end
128
135
  end
129
136
  end