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,6 +1,6 @@
1
1
  module ActiveRecord
2
2
  module Calculations #:nodoc:
3
- CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset]
3
+ CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct, :limit, :offset, :include]
4
4
  def self.included(base)
5
5
  base.extend(ClassMethods)
6
6
  end
@@ -9,7 +9,7 @@ module ActiveRecord
9
9
  # Count operates using three different approaches.
10
10
  #
11
11
  # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
12
- # * Count by conditions or joins: For backwards compatibility, you can pass in +conditions+ and +joins+ as individual parameters.
12
+ # * Count by conditions or joins: This API has been deprecated and will be removed in Rails 2.0
13
13
  # * Count using options will find the row count matched by the options used.
14
14
  #
15
15
  # The last approach, count using options, accepts an option hash as the only parameter. The options are:
@@ -29,7 +29,7 @@ module ActiveRecord
29
29
  # Examples for counting all:
30
30
  # Person.count # returns the total count of all people
31
31
  #
32
- # Examples for count by +conditions+ and +joins+ (for backwards compatibility):
32
+ # Examples for count by +conditions+ and +joins+ (this has been deprecated):
33
33
  # Person.count("age > 26") # returns the number of people older than 26
34
34
  # Person.find("age > 26 AND job.salary > 60000", "LEFT JOIN jobs on jobs.person_id = person.id") # returns the total number of rows matching the conditions and joins fetched by SELECT COUNT(*).
35
35
  #
@@ -42,29 +42,7 @@ module ActiveRecord
42
42
  #
43
43
  # Note: Person.count(:all) will not work because it will use :all as the condition. Use Person.count instead.
44
44
  def count(*args)
45
- options = {}
46
- column_name = :all
47
- # For backwards compatibility, we need to handle both count(conditions=nil, joins=nil) or count(options={}) or count(column_name=:all, options={}).
48
- if args.size >= 0 && args.size <= 2
49
- if args.first.is_a?(Hash)
50
- options = args.first
51
- elsif args[1].is_a?(Hash)
52
- options = args[1]
53
- column_name = args.first
54
- else
55
- # Handle legacy paramter options: def count(conditions=nil, joins=nil)
56
- options.merge!(:conditions => args[0]) if args.length > 0
57
- options.merge!(:joins => args[1]) if args.length > 1
58
- end
59
- else
60
- raise(ArgumentError, "Unexpected parameters passed to count(*args): expected either count(conditions=nil, joins=nil) or count(options={})")
61
- end
62
-
63
- if options[:include] || scope(:find, :include)
64
- count_with_associations(options)
65
- else
66
- calculate(:count, column_name, options)
67
- end
45
+ calculate(:count, *construct_count_options_from_legacy_args(*args))
68
46
  end
69
47
 
70
48
  # Calculates average value on a given column. The value is returned as a float. See #calculate for examples with options.
@@ -136,44 +114,115 @@ module ActiveRecord
136
114
  column_name = options[:select] if options[:select]
137
115
  column_name = '*' if column_name == :all
138
116
  column = column_for column_name
139
- aggregate = select_aggregate(operation, column_name, options)
140
- aggregate_alias = column_alias_for(operation, column_name)
141
- if options[:group]
142
- execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias, options)
143
- else
144
- execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias, options)
117
+ catch :invalid_query do
118
+ if options[:group]
119
+ return execute_grouped_calculation(operation, column_name, column, options)
120
+ else
121
+ return execute_simple_calculation(operation, column_name, column, options)
122
+ end
145
123
  end
124
+ 0
146
125
  end
147
126
 
148
127
  protected
149
- def construct_calculation_sql(aggregate, aggregate_alias, options) #:nodoc:
150
- scope = scope(:find)
151
- sql = "SELECT #{aggregate} AS #{aggregate_alias}"
128
+ def construct_count_options_from_legacy_args(*args)
129
+ options = {}
130
+ column_name = :all
131
+
132
+ # We need to handle
133
+ # count()
134
+ # count(options={})
135
+ # count(column_name=:all, options={})
136
+ # count(conditions=nil, joins=nil) # deprecated
137
+ if args.size > 2
138
+ raise ArgumentError, "Unexpected parameters passed to count(options={}): #{args.inspect}"
139
+ elsif args.size > 0
140
+ if args[0].is_a?(Hash)
141
+ options = args[0]
142
+ elsif args[1].is_a?(Hash)
143
+ column_name, options = args
144
+ else
145
+ # Deprecated count(conditions, joins=nil)
146
+ ActiveSupport::Deprecation.warn(
147
+ "You called count(#{args[0].inspect}, #{args[1].inspect}), which is a deprecated API call. " +
148
+ "Instead you should use count(column_name, options). Passing the conditions and joins as " +
149
+ "string parameters will be removed in Rails 2.0.", caller(2)
150
+ )
151
+ options.merge!(:conditions => args[0])
152
+ options.merge!(:joins => args[1]) if args[1]
153
+ end
154
+ end
155
+
156
+ [column_name, options]
157
+ end
158
+
159
+ def construct_calculation_sql(operation, column_name, options) #:nodoc:
160
+ operation = operation.to_s.downcase
161
+ options = options.symbolize_keys
162
+
163
+ scope = scope(:find)
164
+ merged_includes = merge_includes(scope ? scope[:include] : [], options[:include])
165
+ aggregate_alias = column_alias_for(operation, column_name)
166
+ use_workaround = !Base.connection.supports_count_distinct? && options[:distinct] && operation.to_s.downcase == 'count'
167
+ join_dependency = nil
168
+
169
+ if merged_includes.any? && operation.to_s.downcase == 'count'
170
+ options[:distinct] = true
171
+ column_name = options[:select] || [table_name, primary_key] * '.'
172
+ end
173
+
174
+ sql = "SELECT #{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name}) AS #{aggregate_alias}"
175
+
176
+ # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
177
+ sql = "SELECT COUNT(*) AS #{aggregate_alias}" if use_workaround
178
+
152
179
  sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
180
+ sql << " FROM (SELECT DISTINCT #{column_name}" if use_workaround
153
181
  sql << " FROM #{table_name} "
182
+ if merged_includes.any?
183
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, options[:joins])
184
+ sql << join_dependency.join_associations.collect{|join| join.association_join }.join
185
+ end
154
186
  add_joins!(sql, options, scope)
155
187
  add_conditions!(sql, options[:conditions], scope)
156
- sql << " GROUP BY #{options[:group_field]}" if options[:group]
157
- sql << " HAVING #{options[:having]}" if options[:group] && options[:having]
158
- sql << " ORDER BY #{options[:order]}" if options[:order]
159
- add_limit!(sql, options)
188
+ add_limited_ids_condition!(sql, options, join_dependency) if join_dependency && !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
189
+
190
+ if options[:group]
191
+ group_key = Base.connection.adapter_name == 'FrontBase' ? :group_alias : :group_field
192
+ sql << " GROUP BY #{options[group_key]} "
193
+ end
194
+
195
+ if options[:group] && options[:having]
196
+ # FrontBase requires identifiers in the HAVING clause and chokes on function calls
197
+ if Base.connection.adapter_name == 'FrontBase'
198
+ options[:having].downcase!
199
+ options[:having].gsub!(/#{operation}\s*\(\s*#{column_name}\s*\)/, aggregate_alias)
200
+ end
201
+
202
+ sql << " HAVING #{options[:having]} "
203
+ end
204
+
205
+ sql << " ORDER BY #{options[:order]} " if options[:order]
206
+ add_limit!(sql, options, scope)
207
+ sql << ')' if use_workaround
160
208
  sql
161
209
  end
162
210
 
163
- def execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias, options) #:nodoc:
164
- value = connection.select_value(construct_calculation_sql(aggregate, aggregate_alias, options))
211
+ def execute_simple_calculation(operation, column_name, column, options) #:nodoc:
212
+ value = connection.select_value(construct_calculation_sql(operation, column_name, options))
165
213
  type_cast_calculated_value(value, column, operation)
166
214
  end
167
215
 
168
- def execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias, options) #:nodoc:
216
+ def execute_grouped_calculation(operation, column_name, column, options) #:nodoc:
169
217
  group_attr = options[:group].to_s
170
218
  association = reflect_on_association(group_attr.to_sym)
171
219
  associated = association && association.macro == :belongs_to # only count belongs_to associations
172
220
  group_field = (associated ? "#{options[:group]}_id" : options[:group]).to_s
173
221
  group_alias = column_alias_for(group_field)
174
222
  group_column = column_for group_field
175
- sql = construct_calculation_sql(aggregate, aggregate_alias, options.merge(:group_field => group_field, :group_alias => group_alias))
223
+ sql = construct_calculation_sql(operation, column_name, options.merge(:group_field => group_field, :group_alias => group_alias))
176
224
  calculated_data = connection.select_all(sql)
225
+ aggregate_alias = column_alias_for(operation, column_name)
177
226
 
178
227
  if association
179
228
  key_ids = calculated_data.collect { |row| row[group_alias] }
@@ -181,7 +230,7 @@ module ActiveRecord
181
230
  key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
182
231
  end
183
232
 
184
- calculated_data.inject(OrderedHash.new) do |all, row|
233
+ calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
185
234
  key = associated ? key_records[row[group_alias].to_i] : type_cast_calculated_value(row[group_alias], group_column)
186
235
  value = row[aggregate_alias]
187
236
  all << [key, type_cast_calculated_value(value, column, operation)]
@@ -190,15 +239,7 @@ module ActiveRecord
190
239
 
191
240
  private
192
241
  def validate_calculation_options(operation, options = {})
193
- if operation.to_s == 'count'
194
- options.assert_valid_keys(CALCULATIONS_OPTIONS + [:include])
195
- else
196
- options.assert_valid_keys(CALCULATIONS_OPTIONS)
197
- end
198
- end
199
-
200
- def select_aggregate(operation, column_name, options)
201
- "#{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name})"
242
+ options.assert_valid_keys(CALCULATIONS_OPTIONS)
202
243
  end
203
244
 
204
245
  # converts a given key to the value that the database adapter returns as
@@ -208,7 +249,7 @@ module ActiveRecord
208
249
  # count(distinct users.id) #=> count_distinct_users_id
209
250
  # count(*) #=> count_all
210
251
  def column_alias_for(*keys)
211
- keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_')
252
+ connection.table_alias_for(keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_'))
212
253
  end
213
254
 
214
255
  def column_for(field)
@@ -158,6 +158,12 @@ module ActiveRecord
158
158
  # after_initialize will only be run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
159
159
  # callback types will be called.
160
160
  #
161
+ # == before_validation* returning statements
162
+ #
163
+ # If the returning value of a before_validation callback can be evaluated to false, the process will be aborted and Base#save will return false.
164
+ # If Base#save! is called it will raise a RecordNotSave error.
165
+ # Nothing will be appended to the errors object.
166
+ #
161
167
  # == Cancelling callbacks
162
168
  #
163
169
  # If a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns
@@ -170,34 +176,17 @@ module ActiveRecord
170
176
  after_validation_on_update before_destroy after_destroy
171
177
  )
172
178
 
173
- def self.append_features(base) #:nodoc:
174
- super
175
-
179
+ def self.included(base) #:nodoc:
176
180
  base.extend(ClassMethods)
177
181
  base.class_eval do
178
182
  class << self
179
183
  include Observable
180
- alias_method :instantiate_without_callbacks, :instantiate
181
- alias_method :instantiate, :instantiate_with_callbacks
184
+ alias_method_chain :instantiate, :callbacks
182
185
  end
183
186
 
184
- alias_method :initialize_without_callbacks, :initialize
185
- alias_method :initialize, :initialize_with_callbacks
186
-
187
- alias_method :create_or_update_without_callbacks, :create_or_update
188
- alias_method :create_or_update, :create_or_update_with_callbacks
189
-
190
- alias_method :valid_without_callbacks, :valid?
191
- alias_method :valid?, :valid_with_callbacks
192
-
193
- alias_method :create_without_callbacks, :create
194
- alias_method :create, :create_with_callbacks
195
-
196
- alias_method :update_without_callbacks, :update
197
- alias_method :update, :update_with_callbacks
198
-
199
- alias_method :destroy_without_callbacks, :destroy
200
- alias_method :destroy, :destroy_with_callbacks
187
+ [:initialize, :create_or_update, :valid?, :create, :update, :destroy].each do |method|
188
+ alias_method_chain method, :callbacks
189
+ end
201
190
  end
202
191
 
203
192
  CALLBACKS.each do |method|
@@ -302,12 +291,12 @@ module ActiveRecord
302
291
  # existing objects that have a record.
303
292
  def after_validation_on_update() end
304
293
 
305
- def valid_with_callbacks #:nodoc:
294
+ def valid_with_callbacks? #:nodoc:
306
295
  return false if callback(:before_validation) == false
307
296
  if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
308
297
  return false if result == false
309
298
 
310
- result = valid_without_callbacks
299
+ result = valid_without_callbacks?
311
300
 
312
301
  callback(:after_validation)
313
302
  if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end
@@ -86,6 +86,16 @@ module ActiveRecord
86
86
  conn.disconnect!
87
87
  end
88
88
  end
89
+
90
+ # Clears the cache which maps classes
91
+ def clear_reloadable_connections!
92
+ @@active_connections.each do |name, conn|
93
+ if conn.requires_reloading?
94
+ conn.disconnect!
95
+ @@active_connections.delete(name)
96
+ end
97
+ end
98
+ end
89
99
 
90
100
  # Verify active connections.
91
101
  def verify_active_connections! #:nodoc:
@@ -248,7 +258,8 @@ module ActiveRecord
248
258
  if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
249
259
  active_connections[name] = spec
250
260
  elsif spec.kind_of?(ConnectionSpecification)
251
- self.connection = self.send(spec.adapter_method, spec.config)
261
+ config = spec.config.reverse_merge(:allow_concurrency => @@allow_concurrency)
262
+ self.connection = self.send(spec.adapter_method, config)
252
263
  elsif spec.nil?
253
264
  raise ConnectionNotEstablished
254
265
  else
@@ -0,0 +1,21 @@
1
+ ***************
2
+ *** 90,97 ****
3
+ # Clears the cache which maps classes
4
+ def clear_reloadable_connections!
5
+ @@active_connections.each do |name, conn|
6
+ - conn.disconnect! if conn.supports_reloading?
7
+ - @@active_connections.delete(name)
8
+ end
9
+ end
10
+
11
+ --- 90,99 ----
12
+ # Clears the cache which maps classes
13
+ def clear_reloadable_connections!
14
+ @@active_connections.each do |name, conn|
15
+ + if conn.requires_reloading?
16
+ + conn.disconnect!
17
+ + @@active_connections.delete(name)
18
+ + end
19
+ end
20
+ end
21
+
@@ -4,11 +4,14 @@ module ActiveRecord
4
4
  # Returns an array of record hashes with the column names as keys and
5
5
  # column values as values.
6
6
  def select_all(sql, name = nil)
7
+ select(sql, name)
7
8
  end
8
9
 
9
10
  # Returns a record hash with the column names as keys and column values
10
11
  # as values.
11
12
  def select_one(sql, name = nil)
13
+ result = select(sql, name)
14
+ result.first if result
12
15
  end
13
16
 
14
17
  # Returns a single value from a record
@@ -25,19 +28,24 @@ module ActiveRecord
25
28
  end
26
29
 
27
30
  # Executes the SQL statement in the context of this connection.
28
- # This abstract method raises a NotImplementedError.
29
31
  def execute(sql, name = nil)
30
32
  raise NotImplementedError, "execute is an abstract method"
31
33
  end
32
34
 
33
35
  # Returns the last auto-generated ID from the affected table.
34
- def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil) end
36
+ def insert(sql, name = nil, pk = nil, id_value = nil, sequence_name = nil)
37
+ raise NotImplementedError, "insert is an abstract method"
38
+ end
35
39
 
36
40
  # Executes the update statement and returns the number of rows affected.
37
- def update(sql, name = nil) end
41
+ def update(sql, name = nil)
42
+ execute(sql, name)
43
+ end
38
44
 
39
45
  # Executes the delete statement and returns the number of rows affected.
40
- def delete(sql, name = nil) end
46
+ def delete(sql, name = nil)
47
+ update(sql, name)
48
+ end
41
49
 
42
50
  # Wrap a block in a transaction. Returns result of block.
43
51
  def transaction(start_db_transaction = true)
@@ -91,6 +99,17 @@ module ActiveRecord
91
99
  end
92
100
  end
93
101
 
102
+ # Appends a locking clause to a SQL statement. *Modifies the +sql+ parameter*.
103
+ # # SELECT * FROM suppliers FOR UPDATE
104
+ # add_lock! 'SELECT * FROM suppliers', :lock => true
105
+ # add_lock! 'SELECT * FROM suppliers', :lock => ' FOR UPDATE'
106
+ def add_lock!(sql, options)
107
+ case lock = options[:lock]
108
+ when true: sql << ' FOR UPDATE'
109
+ when String: sql << " #{lock}"
110
+ end
111
+ end
112
+
94
113
  def default_sequence_name(table, column)
95
114
  nil
96
115
  end
@@ -99,6 +118,13 @@ module ActiveRecord
99
118
  def reset_sequence!(table, column, sequence = nil)
100
119
  # Do nothing by default. Implement for PostgreSQL, Oracle, ...
101
120
  end
121
+
122
+ protected
123
+ # Returns an array of record hashes with the column names as keys and
124
+ # column values as values.
125
+ def select(sql, name = nil)
126
+ raise NotImplementedError, "select is an abstract method"
127
+ end
102
128
  end
103
129
  end
104
130
  end
@@ -4,22 +4,29 @@ module ActiveRecord
4
4
  # Quotes the column value to help prevent
5
5
  # {SQL injection attacks}[http://en.wikipedia.org/wiki/SQL_injection].
6
6
  def quote(value, column = nil)
7
+ # records are quoted as their primary key
8
+ return value.quoted_id if value.respond_to?(:quoted_id)
9
+
7
10
  case value
8
- when String
11
+ when String, ActiveSupport::Multibyte::Chars
12
+ value = value.to_s
9
13
  if column && column.type == :binary && column.class.respond_to?(:string_to_binary)
10
14
  "'#{quote_string(column.class.string_to_binary(value))}'" # ' (for ruby-mode)
11
- elsif column && [:integer, :float].include?(column.type)
15
+ elsif column && [:integer, :float].include?(column.type)
16
+ value = column.type == :integer ? value.to_i : value.to_f
12
17
  value.to_s
13
18
  else
14
19
  "'#{quote_string(value)}'" # ' (for ruby-mode)
15
20
  end
16
- when NilClass then "NULL"
17
- when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
18
- when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
19
- when Float, Fixnum, Bignum then value.to_s
20
- when Date then "'#{value.to_s}'"
21
- when Time, DateTime then "'#{quoted_date(value)}'"
22
- else "'#{quote_string(value.to_yaml)}'"
21
+ when NilClass then "NULL"
22
+ when TrueClass then (column && column.type == :integer ? '1' : quoted_true)
23
+ when FalseClass then (column && column.type == :integer ? '0' : quoted_false)
24
+ when Float, Fixnum, Bignum then value.to_s
25
+ # BigDecimals need to be output in a non-normalized form and quoted.
26
+ when BigDecimal then value.to_s('F')
27
+ when Date then "'#{value.to_s}'"
28
+ when Time, DateTime then "'#{quoted_date(value)}'"
29
+ else "'#{quote_string(value.to_yaml)}'"
23
30
  end
24
31
  end
25
32