activerecord 1.15.6 → 2.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 (185) hide show
  1. data/CHANGELOG +2454 -34
  2. data/README +1 -1
  3. data/RUNNING_UNIT_TESTS +3 -34
  4. data/Rakefile +98 -77
  5. data/install.rb +1 -1
  6. data/lib/active_record.rb +13 -22
  7. data/lib/active_record/aggregations.rb +38 -49
  8. data/lib/active_record/associations.rb +452 -333
  9. data/lib/active_record/associations/association_collection.rb +66 -20
  10. data/lib/active_record/associations/association_proxy.rb +9 -8
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -51
  12. data/lib/active_record/associations/has_many_association.rb +21 -57
  13. data/lib/active_record/associations/has_many_through_association.rb +38 -18
  14. data/lib/active_record/associations/has_one_association.rb +30 -14
  15. data/lib/active_record/attribute_methods.rb +253 -0
  16. data/lib/active_record/base.rb +719 -494
  17. data/lib/active_record/calculations.rb +62 -63
  18. data/lib/active_record/callbacks.rb +57 -83
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +38 -9
  20. data/lib/active_record/connection_adapters/abstract/database_statements.rb +56 -15
  21. data/lib/active_record/connection_adapters/abstract/query_cache.rb +87 -0
  22. data/lib/active_record/connection_adapters/abstract/quoting.rb +23 -12
  23. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +191 -62
  24. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +37 -34
  25. data/lib/active_record/connection_adapters/abstract_adapter.rb +28 -17
  26. data/lib/active_record/connection_adapters/mysql_adapter.rb +119 -37
  27. data/lib/active_record/connection_adapters/postgresql_adapter.rb +473 -210
  28. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +91 -107
  30. data/lib/active_record/fixtures.rb +503 -113
  31. data/lib/active_record/locking/optimistic.rb +72 -34
  32. data/lib/active_record/migration.rb +80 -57
  33. data/lib/active_record/observer.rb +13 -10
  34. data/lib/active_record/query_cache.rb +16 -57
  35. data/lib/active_record/reflection.rb +35 -38
  36. data/lib/active_record/schema.rb +5 -5
  37. data/lib/active_record/schema_dumper.rb +35 -13
  38. data/lib/active_record/serialization.rb +98 -0
  39. data/lib/active_record/serializers/json_serializer.rb +71 -0
  40. data/lib/active_record/{xml_serialization.rb → serializers/xml_serializer.rb} +90 -83
  41. data/lib/active_record/timestamp.rb +20 -21
  42. data/lib/active_record/transactions.rb +39 -43
  43. data/lib/active_record/validations.rb +256 -107
  44. data/lib/active_record/version.rb +3 -3
  45. data/lib/activerecord.rb +1 -0
  46. data/test/aaa_create_tables_test.rb +15 -2
  47. data/test/abstract_unit.rb +24 -17
  48. data/test/active_schema_test_mysql.rb +20 -8
  49. data/test/adapter_test.rb +23 -5
  50. data/test/adapter_test_sqlserver.rb +15 -1
  51. data/test/aggregations_test.rb +16 -1
  52. data/test/all.sh +2 -2
  53. data/test/associations/ar_joins_test.rb +0 -0
  54. data/test/associations/callbacks_test.rb +51 -30
  55. data/test/associations/cascaded_eager_loading_test.rb +1 -29
  56. data/test/associations/eager_singularization_test.rb +145 -0
  57. data/test/associations/eager_test.rb +42 -6
  58. data/test/associations/extension_test.rb +6 -1
  59. data/test/associations/inner_join_association_test.rb +88 -0
  60. data/test/associations/join_model_test.rb +47 -16
  61. data/test/associations_test.rb +449 -226
  62. data/test/attribute_methods_test.rb +97 -0
  63. data/test/base_test.rb +251 -105
  64. data/test/binary_test.rb +22 -27
  65. data/test/calculations_test.rb +37 -5
  66. data/test/callbacks_test.rb +23 -0
  67. data/test/connection_test_firebird.rb +2 -2
  68. data/test/connection_test_mysql.rb +30 -0
  69. data/test/connections/native_mysql/connection.rb +3 -0
  70. data/test/connections/native_sqlite/connection.rb +5 -14
  71. data/test/connections/native_sqlite3/connection.rb +5 -14
  72. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -1
  73. data/test/{copy_table_sqlite.rb → copy_table_test_sqlite.rb} +8 -3
  74. data/test/datatype_test_postgresql.rb +178 -27
  75. data/test/{empty_date_time_test.rb → date_time_test.rb} +13 -1
  76. data/test/defaults_test.rb +8 -1
  77. data/test/deprecated_finder_test.rb +7 -128
  78. data/test/finder_test.rb +192 -54
  79. data/test/fixtures/all/developers.yml +0 -0
  80. data/test/fixtures/all/people.csv +0 -0
  81. data/test/fixtures/all/tasks.yml +0 -0
  82. data/test/fixtures/author.rb +12 -5
  83. data/test/fixtures/binaries.yml +130 -435
  84. data/test/fixtures/category.rb +6 -0
  85. data/test/fixtures/company.rb +8 -1
  86. data/test/fixtures/computer.rb +1 -0
  87. data/test/fixtures/contact.rb +16 -0
  88. data/test/fixtures/customer.rb +2 -2
  89. data/test/fixtures/db_definitions/db2.drop.sql +1 -0
  90. data/test/fixtures/db_definitions/db2.sql +4 -0
  91. data/test/fixtures/db_definitions/firebird.drop.sql +3 -1
  92. data/test/fixtures/db_definitions/firebird.sql +6 -0
  93. data/test/fixtures/db_definitions/frontbase.drop.sql +1 -0
  94. data/test/fixtures/db_definitions/frontbase.sql +5 -0
  95. data/test/fixtures/db_definitions/openbase.sql +41 -25
  96. data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
  97. data/test/fixtures/db_definitions/oracle.sql +5 -0
  98. data/test/fixtures/db_definitions/postgresql.drop.sql +7 -0
  99. data/test/fixtures/db_definitions/postgresql.sql +87 -58
  100. data/test/fixtures/db_definitions/postgresql2.sql +1 -2
  101. data/test/fixtures/db_definitions/schema.rb +280 -0
  102. data/test/fixtures/db_definitions/schema2.rb +11 -0
  103. data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
  104. data/test/fixtures/db_definitions/sqlite.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase.drop.sql +1 -0
  106. data/test/fixtures/db_definitions/sybase.sql +4 -0
  107. data/test/fixtures/developer.rb +10 -0
  108. data/test/fixtures/example.log +1 -0
  109. data/test/fixtures/flowers.jpg +0 -0
  110. data/test/fixtures/item.rb +7 -0
  111. data/test/fixtures/items.yml +4 -0
  112. data/test/fixtures/joke.rb +0 -3
  113. data/test/fixtures/matey.rb +4 -0
  114. data/test/fixtures/mateys.yml +4 -0
  115. data/test/fixtures/minimalistic.rb +2 -0
  116. data/test/fixtures/minimalistics.yml +2 -0
  117. data/test/fixtures/mixins.yml +2 -100
  118. data/test/fixtures/parrot.rb +13 -0
  119. data/test/fixtures/parrots.yml +27 -0
  120. data/test/fixtures/parrots_pirates.yml +7 -0
  121. data/test/fixtures/pirate.rb +5 -0
  122. data/test/fixtures/pirates.yml +9 -0
  123. data/test/fixtures/post.rb +1 -0
  124. data/test/fixtures/project.rb +3 -2
  125. data/test/fixtures/reserved_words/distinct.yml +5 -0
  126. data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
  127. data/test/fixtures/reserved_words/group.yml +14 -0
  128. data/test/fixtures/reserved_words/select.yml +8 -0
  129. data/test/fixtures/reserved_words/values.yml +7 -0
  130. data/test/fixtures/ship.rb +3 -0
  131. data/test/fixtures/ships.yml +5 -0
  132. data/test/fixtures/tagging.rb +4 -0
  133. data/test/fixtures/taggings.yml +8 -1
  134. data/test/fixtures/topic.rb +13 -1
  135. data/test/fixtures/treasure.rb +4 -0
  136. data/test/fixtures/treasures.yml +10 -0
  137. data/test/fixtures_test.rb +205 -24
  138. data/test/inheritance_test.rb +7 -1
  139. data/test/json_serialization_test.rb +180 -0
  140. data/test/lifecycle_test.rb +1 -1
  141. data/test/locking_test.rb +85 -2
  142. data/test/migration_test.rb +206 -40
  143. data/test/mixin_test.rb +13 -515
  144. data/test/pk_test.rb +3 -6
  145. data/test/query_cache_test.rb +104 -0
  146. data/test/reflection_test.rb +16 -0
  147. data/test/reserved_word_test_mysql.rb +177 -0
  148. data/test/schema_dumper_test.rb +38 -3
  149. data/test/serialization_test.rb +47 -0
  150. data/test/transactions_test.rb +74 -23
  151. data/test/unconnected_test.rb +1 -1
  152. data/test/validations_test.rb +322 -32
  153. data/test/xml_serialization_test.rb +121 -44
  154. metadata +48 -41
  155. data/examples/associations.rb +0 -87
  156. data/examples/shared_setup.rb +0 -15
  157. data/examples/validation.rb +0 -85
  158. data/lib/active_record/acts/list.rb +0 -256
  159. data/lib/active_record/acts/nested_set.rb +0 -211
  160. data/lib/active_record/acts/tree.rb +0 -96
  161. data/lib/active_record/connection_adapters/db2_adapter.rb +0 -228
  162. data/lib/active_record/connection_adapters/firebird_adapter.rb +0 -728
  163. data/lib/active_record/connection_adapters/frontbase_adapter.rb +0 -861
  164. data/lib/active_record/connection_adapters/openbase_adapter.rb +0 -350
  165. data/lib/active_record/connection_adapters/oracle_adapter.rb +0 -690
  166. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +0 -591
  167. data/lib/active_record/connection_adapters/sybase_adapter.rb +0 -662
  168. data/lib/active_record/deprecated_associations.rb +0 -104
  169. data/lib/active_record/deprecated_finders.rb +0 -44
  170. data/lib/active_record/vendor/simple.rb +0 -693
  171. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  172. data/lib/active_record/wrappings.rb +0 -58
  173. data/test/connections/native_sqlserver/connection.rb +0 -23
  174. data/test/connections/native_sqlserver_odbc/connection.rb +0 -25
  175. data/test/deprecated_associations_test.rb +0 -396
  176. data/test/fixtures/db_definitions/mysql.drop.sql +0 -32
  177. data/test/fixtures/db_definitions/mysql.sql +0 -234
  178. data/test/fixtures/db_definitions/mysql2.drop.sql +0 -2
  179. data/test/fixtures/db_definitions/mysql2.sql +0 -5
  180. data/test/fixtures/db_definitions/sqlserver.drop.sql +0 -34
  181. data/test/fixtures/db_definitions/sqlserver.sql +0 -243
  182. data/test/fixtures/db_definitions/sqlserver2.drop.sql +0 -2
  183. data/test/fixtures/db_definitions/sqlserver2.sql +0 -5
  184. data/test/fixtures/mixin.rb +0 -63
  185. data/test/mixin_nested_set_test.rb +0 -196
@@ -0,0 +1,71 @@
1
+ module ActiveRecord #:nodoc:
2
+ module Serialization
3
+ # Returns a JSON string representing the model. Some configuration is
4
+ # available through +options+.
5
+ #
6
+ # Without any +options+, the returned JSON string will include all
7
+ # the model's attributes. For example:
8
+ #
9
+ # konata = User.find(1)
10
+ # konata.to_json
11
+ #
12
+ # {"id": 1, "name": "Konata Izumi", "age": 16,
13
+ # "created_at": "2006/08/01", "awesome": true}
14
+ #
15
+ # The :only and :except options can be used to limit the attributes
16
+ # included, and work similar to the #attributes method. For example:
17
+ #
18
+ # konata.to_json(:only => [ :id, :name ])
19
+ #
20
+ # {"id": 1, "name": "Konata Izumi"}
21
+ #
22
+ # konata.to_json(:except => [ :id, :created_at, :age ])
23
+ #
24
+ # {"name": "Konata Izumi", "awesome": true}
25
+ #
26
+ # To include any methods on the model, use :methods.
27
+ #
28
+ # konata.to_json(:methods => :permalink)
29
+ #
30
+ # {"id": 1, "name": "Konata Izumi", "age": 16,
31
+ # "created_at": "2006/08/01", "awesome": true,
32
+ # "permalink": "1-konata-izumi"}
33
+ #
34
+ # To include associations, use :include.
35
+ #
36
+ # konata.to_json(:include => :posts)
37
+ #
38
+ # {"id": 1, "name": "Konata Izumi", "age": 16,
39
+ # "created_at": "2006/08/01", "awesome": true,
40
+ # "posts": [{"id": 1, "author_id": 1, "title": "Welcome to the weblog"},
41
+ # {"id": 2, author_id: 1, "title": "So I was thinking"}]}
42
+ #
43
+ # 2nd level and higher order associations work as well:
44
+ #
45
+ # konata.to_json(:include => { :posts => {
46
+ # :include => { :comments => {
47
+ # :only => :body } },
48
+ # :only => :title } })
49
+ #
50
+ # {"id": 1, "name": "Konata Izumi", "age": 16,
51
+ # "created_at": "2006/08/01", "awesome": true,
52
+ # "posts": [{"comments": [{"body": "1st post!"}, {"body": "Second!"}],
53
+ # "title": "Welcome to the weblog"},
54
+ # {"comments": [{"body": "Don't think too hard"}],
55
+ # "title": "So I was thinking"}]}
56
+ def to_json(options = {})
57
+ JsonSerializer.new(self, options).to_s
58
+ end
59
+
60
+ def from_json(json)
61
+ self.attributes = ActiveSupport::JSON.decode(json)
62
+ self
63
+ end
64
+
65
+ class JsonSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
66
+ def serialize
67
+ serializable_record.to_json
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,12 +1,12 @@
1
1
  module ActiveRecord #:nodoc:
2
- module XmlSerialization
2
+ module Serialization
3
3
  # Builds an XML document to represent the model. Some configuration is
4
- # availble through +options+, however more complicated cases should use
4
+ # available through +options+, however more complicated cases should
5
5
  # override ActiveRecord's to_xml.
6
6
  #
7
- # By default the generated XML document will include the processing
7
+ # By default the generated XML document will include the processing
8
8
  # instruction and all object's attributes. For example:
9
- #
9
+ #
10
10
  # <?xml version="1.0" encoding="UTF-8"?>
11
11
  # <topic>
12
12
  # <title>The First Topic</title>
@@ -27,7 +27,7 @@ module ActiveRecord #:nodoc:
27
27
  # :except options are the same as for the #attributes method.
28
28
  # The default is to dasherize all column names, to disable this,
29
29
  # set :dasherize to false. To not have the column type included
30
- # in the XML output, set :skip_types to false.
30
+ # in the XML output, set :skip_types to true.
31
31
  #
32
32
  # For instance:
33
33
  #
@@ -42,7 +42,7 @@ module ActiveRecord #:nodoc:
42
42
  # <parent-id></parent-id>
43
43
  # <last-read type="date">2004-04-15</last-read>
44
44
  # </topic>
45
- #
45
+ #
46
46
  # To include first level associations use :include
47
47
  #
48
48
  # firm.to_xml :include => [ :account, :clients ]
@@ -52,7 +52,7 @@ module ActiveRecord #:nodoc:
52
52
  # <id type="integer">1</id>
53
53
  # <rating type="integer">1</rating>
54
54
  # <name>37signals</name>
55
- # <clients>
55
+ # <clients type="array">
56
56
  # <client>
57
57
  # <rating type="integer">1</rating>
58
58
  # <name>Summit</name>
@@ -90,7 +90,24 @@ module ActiveRecord #:nodoc:
90
90
  # <abc>def</abc>
91
91
  # </firm>
92
92
  #
93
- # You may override the to_xml method in your ActiveRecord::Base
93
+ # Alternatively, you can also just yield the builder object as part of the to_xml call:
94
+ #
95
+ # firm.to_xml do |xml|
96
+ # xml.creator do
97
+ # xml.first_name "David"
98
+ # xml.last_name "Heinemeier Hansson"
99
+ # end
100
+ # end
101
+ #
102
+ # <firm>
103
+ # # ... normal attributes as shown above ...
104
+ # <creator>
105
+ # <first_name>David</first_name>
106
+ # <last_name>Heinemeier Hansson</last_name>
107
+ # </creator>
108
+ # </firm>
109
+ #
110
+ # You can override the to_xml method in your ActiveRecord::Base
94
111
  # subclasses if you need to. The general form of doing this is
95
112
  #
96
113
  # class IHaveMyOwnXML < ActiveRecord::Base
@@ -103,18 +120,18 @@ module ActiveRecord #:nodoc:
103
120
  # end
104
121
  # end
105
122
  # end
106
- def to_xml(options = {})
107
- XmlSerializer.new(self, options).to_s
123
+ def to_xml(options = {}, &block)
124
+ serializer = XmlSerializer.new(self, options)
125
+ block_given? ? serializer.to_s(&block) : serializer.to_s
108
126
  end
109
- end
110
127
 
111
- class XmlSerializer #:nodoc:
112
- attr_reader :options
113
-
114
- def initialize(record, options = {})
115
- @record, @options = record, options.dup
128
+ def from_xml(xml)
129
+ self.attributes = Hash.from_xml(xml).values.first
130
+ self
116
131
  end
117
-
132
+ end
133
+
134
+ class XmlSerializer < ActiveRecord::Serialization::Serializer #:nodoc:
118
135
  def builder
119
136
  @builder ||= begin
120
137
  options[:indent] ||= 2
@@ -124,7 +141,7 @@ module ActiveRecord #:nodoc:
124
141
  builder.instruct!
125
142
  options[:skip_instruct] = true
126
143
  end
127
-
144
+
128
145
  builder
129
146
  end
130
147
  end
@@ -133,7 +150,7 @@ module ActiveRecord #:nodoc:
133
150
  root = (options[:root] || @record.class.to_s.underscore).to_s
134
151
  dasherize? ? root.dasherize : root
135
152
  end
136
-
153
+
137
154
  def dasherize?
138
155
  !options.has_key?(:dasherize) || options[:dasherize]
139
156
  end
@@ -146,64 +163,22 @@ module ActiveRecord #:nodoc:
146
163
  # level model can have both :except and :only set. So if
147
164
  # :only is set, always delete :except.
148
165
  def serializable_attributes
149
- attribute_names = @record.attribute_names
150
-
151
- if options[:only]
152
- options.delete(:except)
153
- attribute_names = attribute_names & Array(options[:only]).collect { |n| n.to_s }
154
- else
155
- options[:except] = Array(options[:except]) | Array(@record.class.inheritance_column)
156
- attribute_names = attribute_names - options[:except].collect { |n| n.to_s }
157
- end
158
-
159
- attribute_names.collect { |name| Attribute.new(name, @record) }
166
+ serializable_attribute_names.collect { |name| Attribute.new(name, @record) }
160
167
  end
161
168
 
162
169
  def serializable_method_attributes
163
- Array(options[:methods]).collect { |name| MethodAttribute.new(name.to_s, @record) }
170
+ Array(options[:methods]).inject([]) do |method_attributes, name|
171
+ method_attributes << MethodAttribute.new(name.to_s, @record) if @record.respond_to?(name.to_s)
172
+ method_attributes
173
+ end
164
174
  end
165
175
 
166
-
167
176
  def add_attributes
168
177
  (serializable_attributes + serializable_method_attributes).each do |attribute|
169
178
  add_tag(attribute)
170
179
  end
171
180
  end
172
181
 
173
- def add_includes
174
- if include_associations = options.delete(:include)
175
- root_only_or_except = { :except => options[:except],
176
- :only => options[:only] }
177
-
178
- include_has_options = include_associations.is_a?(Hash)
179
-
180
- for association in include_has_options ? include_associations.keys : Array(include_associations)
181
- association_options = include_has_options ? include_associations[association] : root_only_or_except
182
-
183
- opts = options.merge(association_options)
184
-
185
- case @record.class.reflect_on_association(association).macro
186
- when :has_many, :has_and_belongs_to_many
187
- records = @record.send(association).to_a
188
- unless records.empty?
189
- tag = records.first.class.to_s.underscore.pluralize
190
- tag = tag.dasherize if dasherize?
191
-
192
- builder.tag!(tag) do
193
- records.each { |r| r.to_xml(opts.merge(:root => association.to_s.singularize)) }
194
- end
195
- end
196
- when :has_one, :belongs_to
197
- if record = @record.send(association)
198
- record.to_xml(opts.merge(:root => association))
199
- end
200
- end
201
- end
202
-
203
- options[:include] = include_associations
204
- end
205
- end
206
-
207
182
  def add_procs
208
183
  if procs = options.delete(:procs)
209
184
  [ *procs ].each do |proc|
@@ -212,36 +187,64 @@ module ActiveRecord #:nodoc:
212
187
  end
213
188
  end
214
189
 
215
-
216
190
  def add_tag(attribute)
217
191
  builder.tag!(
218
- dasherize? ? attribute.name.dasherize : attribute.name,
219
- attribute.value.to_s,
192
+ dasherize? ? attribute.name.dasherize : attribute.name,
193
+ attribute.value.to_s,
220
194
  attribute.decorations(!options[:skip_types])
221
195
  )
222
196
  end
223
197
 
198
+ def add_associations(association, records, opts)
199
+ if records.is_a?(Enumerable)
200
+ tag = association.to_s
201
+ tag = tag.dasherize if dasherize?
202
+ if records.empty?
203
+ builder.tag!(tag, :type => :array)
204
+ else
205
+ builder.tag!(tag, :type => :array) do
206
+ association_name = association.to_s.singularize
207
+ records.each do |record|
208
+ record.to_xml opts.merge(
209
+ :root => association_name,
210
+ :type => (record.class.to_s.underscore == association_name ? nil : record.class.name)
211
+ )
212
+ end
213
+ end
214
+ end
215
+ else
216
+ if record = @record.send(association)
217
+ record.to_xml(opts.merge(:root => association))
218
+ end
219
+ end
220
+ end
221
+
224
222
  def serialize
225
223
  args = [root]
226
224
  if options[:namespace]
227
225
  args << {:xmlns=>options[:namespace]}
228
226
  end
229
-
227
+
228
+ if options[:type]
229
+ args << {:type=>options[:type]}
230
+ end
231
+
230
232
  builder.tag!(*args) do
231
233
  add_attributes
232
- add_includes
234
+ procs = options.delete(:procs)
235
+ add_includes { |association, records, opts| add_associations(association, records, opts) }
236
+ options[:procs] = procs
233
237
  add_procs
238
+ yield builder if block_given?
234
239
  end
235
- end
236
-
237
- alias_method :to_s, :serialize
240
+ end
238
241
 
239
242
  class Attribute #:nodoc:
240
243
  attr_reader :name, :value, :type
241
-
244
+
242
245
  def initialize(name, record)
243
246
  @name, @record = name, record
244
-
247
+
245
248
  @type = compute_type
246
249
  @value = compute_value
247
250
  end
@@ -258,24 +261,28 @@ module ActiveRecord #:nodoc:
258
261
  def needs_encoding?
259
262
  ![ :binary, :date, :datetime, :boolean, :float, :integer ].include?(type)
260
263
  end
261
-
264
+
262
265
  def decorations(include_types = true)
263
266
  decorations = {}
264
267
 
265
268
  if type == :binary
266
269
  decorations[:encoding] = 'base64'
267
270
  end
268
-
271
+
269
272
  if include_types && type != :string
270
273
  decorations[:type] = type
271
274
  end
272
-
275
+
276
+ if value.nil?
277
+ decorations[:nil] = true
278
+ end
279
+
273
280
  decorations
274
281
  end
275
-
282
+
276
283
  protected
277
284
  def compute_type
278
- type = @record.class.columns_hash[name].type
285
+ type = @record.class.serialized_attributes.has_key?(name) ? :yaml : @record.class.columns_hash[name].type
279
286
 
280
287
  case type
281
288
  when :text
@@ -286,10 +293,10 @@ module ActiveRecord #:nodoc:
286
293
  type
287
294
  end
288
295
  end
289
-
296
+
290
297
  def compute_value
291
298
  value = @record.send(name)
292
-
299
+
293
300
  if formatter = Hash::XML_FORMATTING[type.to_s]
294
301
  value ? formatter.call(value) : nil
295
302
  else
@@ -1,6 +1,6 @@
1
1
  module ActiveRecord
2
- # Active Record automatically timestamps create and update if the table has fields
3
- # created_at/created_on or updated_at/updated_on.
2
+ # Active Record automatically timestamps create and update operations if the table has fields
3
+ # named created_at/created_on or updated_at/updated_on.
4
4
  #
5
5
  # Timestamping can be turned off by setting
6
6
  # <tt>ActiveRecord::Base.record_timestamps = false</tt>
@@ -9,34 +9,33 @@ module ActiveRecord
9
9
  # <tt>ActiveRecord::Base.default_timezone = :utc</tt>
10
10
  module Timestamp
11
11
  def self.included(base) #:nodoc:
12
- super
13
-
14
12
  base.alias_method_chain :create, :timestamps
15
13
  base.alias_method_chain :update, :timestamps
16
14
 
17
- base.cattr_accessor :record_timestamps, :instance_writer => false
15
+ base.class_inheritable_accessor :record_timestamps, :instance_writer => false
18
16
  base.record_timestamps = true
19
17
  end
20
18
 
21
- def create_with_timestamps #:nodoc:
22
- if record_timestamps
23
- t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
24
- write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
25
- write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
19
+ private
20
+ def create_with_timestamps #:nodoc:
21
+ if record_timestamps
22
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
23
+ write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
24
+ write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
26
25
 
27
- write_attribute('updated_at', t) if respond_to?(:updated_at)
28
- write_attribute('updated_on', t) if respond_to?(:updated_on)
26
+ write_attribute('updated_at', t) if respond_to?(:updated_at)
27
+ write_attribute('updated_on', t) if respond_to?(:updated_on)
28
+ end
29
+ create_without_timestamps
29
30
  end
30
- create_without_timestamps
31
- end
32
31
 
33
- def update_with_timestamps #:nodoc:
34
- if record_timestamps
35
- t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
36
- write_attribute('updated_at', t) if respond_to?(:updated_at)
37
- write_attribute('updated_on', t) if respond_to?(:updated_on)
32
+ def update_with_timestamps #:nodoc:
33
+ if record_timestamps
34
+ t = self.class.default_timezone == :utc ? Time.now.utc : Time.now
35
+ write_attribute('updated_at', t) if respond_to?(:updated_at)
36
+ write_attribute('updated_on', t) if respond_to?(:updated_on)
37
+ end
38
+ update_without_timestamps
38
39
  end
39
- update_without_timestamps
40
- end
41
40
  end
42
41
  end
@@ -1,5 +1,3 @@
1
- require 'active_record/vendor/simple.rb'
2
- Transaction::Simple.send(:remove_method, :transaction)
3
1
  require 'thread'
4
2
 
5
3
  module ActiveRecord
@@ -32,6 +30,19 @@ module ActiveRecord
32
30
  # Exceptions will force a ROLLBACK that returns the database to the state before the transaction was begun. Be aware, though,
33
31
  # that the objects by default will _not_ have their instance data returned to their pre-transactional state.
34
32
  #
33
+ # == Different ActiveRecord classes in a single transaction
34
+ #
35
+ # Though the transaction class method is called on some ActiveRecord class,
36
+ # the objects within the transaction block need not all be instances of
37
+ # that class.
38
+ # In this example a <tt>Balance</tt> record is transactionally saved even
39
+ # though <tt>transaction</tt> is called on the <tt>Account</tt> class:
40
+ #
41
+ # Account.transaction do
42
+ # balance.save!
43
+ # account.save!
44
+ # end
45
+ #
35
46
  # == Transactions are not distributed across database connections
36
47
  #
37
48
  # A transaction acts on a single database connection. If you have
@@ -53,52 +64,20 @@ module ActiveRecord
53
64
  #
54
65
  # Both Base#save and Base#destroy come wrapped in a transaction that ensures that whatever you do in validations or callbacks
55
66
  # will happen under the protected cover of a transaction. So you can use validations to check for values that the transaction
56
- # depend on or you can raise exceptions in the callbacks to rollback.
57
- #
58
- # == Object-level transactions (deprecated)
59
- #
60
- # You can enable object-level transactions for Active Record objects, though. You do this by naming each of the Active Records
61
- # that you want to enable object-level transactions for, like this:
62
- #
63
- # Account.transaction(david, mary) do
64
- # david.withdrawal(100)
65
- # mary.deposit(100)
66
- # end
67
- #
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.
67
+ # depends on or you can raise exceptions in the callbacks to rollback.
76
68
  #
77
69
  # == Exception handling
78
70
  #
79
71
  # Also have in mind that exceptions thrown within a transaction block will be propagated (after triggering the ROLLBACK), so you
80
- # should be ready to catch those in your application code.
81
- #
82
- # Tribute: Object-level transactions are implemented by Transaction::Simple by Austin Ziegler.
72
+ # should be ready to catch those in your application code. One exception is the ActiveRecord::Rollback exception, which will
73
+ # trigger a ROLLBACK when raised, but not be re-raised by the transaction block.
83
74
  module ClassMethods
84
- def transaction(*objects, &block)
75
+ def transaction(&block)
85
76
  previous_handler = trap('TERM') { raise TransactionError, "Transaction aborted" }
86
77
  increment_open_transactions
87
78
 
88
79
  begin
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
94
-
95
- result = connection.transaction(Thread.current['start_db_transaction'], &block)
96
-
97
- objects.each { |o| o.commit_transaction }
98
- return result
99
- rescue Exception => object_transaction_rollback
100
- objects.each { |o| o.abort_transaction }
101
- raise
80
+ connection.transaction(Thread.current['start_db_transaction'], &block)
102
81
  ensure
103
82
  decrement_open_transactions
104
83
  trap('TERM', previous_handler)
@@ -117,8 +96,8 @@ module ActiveRecord
117
96
  end
118
97
  end
119
98
 
120
- def transaction(*objects, &block)
121
- self.class.transaction(*objects, &block)
99
+ def transaction(&block)
100
+ self.class.transaction(&block)
122
101
  end
123
102
 
124
103
  def destroy_with_transactions #:nodoc:
@@ -126,11 +105,28 @@ module ActiveRecord
126
105
  end
127
106
 
128
107
  def save_with_transactions(perform_validation = true) #:nodoc:
129
- transaction { save_without_transactions(perform_validation) }
108
+ rollback_active_record_state! { transaction { save_without_transactions(perform_validation) } }
130
109
  end
131
110
 
132
111
  def save_with_transactions! #:nodoc:
133
- transaction { save_without_transactions! }
112
+ rollback_active_record_state! { transaction { save_without_transactions! } }
113
+ end
114
+
115
+ # Reset id and @new_record if the transaction rolls back.
116
+ def rollback_active_record_state!
117
+ id_present = has_attribute?(self.class.primary_key)
118
+ previous_id = id
119
+ previous_new_record = @new_record
120
+ yield
121
+ rescue Exception
122
+ @new_record = previous_new_record
123
+ if id_present
124
+ self.id = previous_id
125
+ else
126
+ @attributes.delete(self.class.primary_key)
127
+ @attributes_cache.delete(self.class.primary_key)
128
+ end
129
+ raise
134
130
  end
135
131
  end
136
132
  end