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
@@ -4,65 +4,77 @@ module ActiveRecord
4
4
  def deprecated_collection_count_method(collection_name)# :nodoc:
5
5
  module_eval <<-"end_eval", __FILE__, __LINE__
6
6
  def #{collection_name}_count(force_reload = false)
7
+ unless has_attribute?(:#{collection_name}_count)
8
+ ActiveSupport::Deprecation.warn :#{collection_name}_count
9
+ end
7
10
  #{collection_name}.reload if force_reload
8
11
  #{collection_name}.size
9
12
  end
10
13
  end_eval
11
14
  end
12
-
15
+
13
16
  def deprecated_add_association_relation(association_name)# :nodoc:
14
17
  module_eval <<-"end_eval", __FILE__, __LINE__
15
18
  def add_#{association_name}(*items)
16
19
  #{association_name}.concat(items)
17
20
  end
21
+ deprecate :add_#{association_name} => "use #{association_name}.concat instead"
18
22
  end_eval
19
23
  end
20
-
24
+
21
25
  def deprecated_remove_association_relation(association_name)# :nodoc:
22
26
  module_eval <<-"end_eval", __FILE__, __LINE__
23
27
  def remove_#{association_name}(*items)
24
28
  #{association_name}.delete(items)
25
29
  end
30
+ deprecate :remove_#{association_name} => "use #{association_name}.delete instead"
26
31
  end_eval
27
32
  end
28
-
33
+
29
34
  def deprecated_has_collection_method(collection_name)# :nodoc:
30
35
  module_eval <<-"end_eval", __FILE__, __LINE__
31
36
  def has_#{collection_name}?(force_reload = false)
32
37
  !#{collection_name}(force_reload).empty?
33
38
  end
39
+ deprecate :has_#{collection_name}? => "use !#{collection_name}.empty? instead"
34
40
  end_eval
35
41
  end
36
-
42
+
37
43
  def deprecated_find_in_collection_method(collection_name)# :nodoc:
38
44
  module_eval <<-"end_eval", __FILE__, __LINE__
39
45
  def find_in_#{collection_name}(association_id)
40
46
  #{collection_name}.find(association_id)
41
47
  end
48
+ deprecate :find_in_#{collection_name} => "use #{collection_name}.find instead"
42
49
  end_eval
43
50
  end
44
-
51
+
45
52
  def deprecated_find_all_in_collection_method(collection_name)# :nodoc:
46
53
  module_eval <<-"end_eval", __FILE__, __LINE__
47
54
  def find_all_in_#{collection_name}(runtime_conditions = nil, orderings = nil, limit = nil, joins = nil)
48
- #{collection_name}.find_all(runtime_conditions, orderings, limit, joins)
55
+ ActiveSupport::Deprecation.silence do
56
+ #{collection_name}.find_all(runtime_conditions, orderings, limit, joins)
57
+ end
49
58
  end
59
+ deprecate :find_all_in_#{collection_name} => "use #{collection_name}.find(:all, ...) instead"
50
60
  end_eval
51
61
  end
52
-
62
+
53
63
  def deprecated_collection_create_method(collection_name)# :nodoc:
54
64
  module_eval <<-"end_eval", __FILE__, __LINE__
55
65
  def create_in_#{collection_name}(attributes = {})
56
66
  #{collection_name}.create(attributes)
57
67
  end
68
+ deprecate :create_in_#{collection_name} => "use #{collection_name}.create instead"
58
69
  end_eval
59
70
  end
60
-
71
+
61
72
  def deprecated_collection_build_method(collection_name)# :nodoc:
62
73
  module_eval <<-"end_eval", __FILE__, __LINE__
63
74
  def build_to_#{collection_name}(attributes = {})
64
75
  #{collection_name}.build(attributes)
65
76
  end
77
+ deprecate :build_to_#{collection_name} => "use #{collection_name}.build instead"
66
78
  end_eval
67
79
  end
68
80
 
@@ -75,16 +87,18 @@ module ActiveRecord
75
87
  raise "Comparison object is a #{association_class_name}, should have been \#{comparison_object.class.name}"
76
88
  end
77
89
  end
90
+ deprecate :#{association_name}? => :==
78
91
  end_eval
79
92
  end
80
-
93
+
81
94
  def deprecated_has_association_method(association_name) # :nodoc:
82
95
  module_eval <<-"end_eval", __FILE__, __LINE__
83
96
  def has_#{association_name}?(force_reload = false)
84
97
  !#{association_name}(force_reload).nil?
85
98
  end
99
+ deprecate :has_#{association_name}? => "use !#{association_name} insead"
86
100
  end_eval
87
- end
101
+ end
88
102
  end
89
103
  end
90
104
  end
@@ -10,6 +10,7 @@ module ActiveRecord
10
10
  def find_on_conditions(ids, conditions) # :nodoc:
11
11
  find(ids, :conditions => conditions)
12
12
  end
13
+ deprecate :find_on_conditions => "use find(ids, :conditions => conditions)"
13
14
 
14
15
  # This method is deprecated in favor of find(:first, options).
15
16
  #
@@ -21,6 +22,7 @@ module ActiveRecord
21
22
  def find_first(conditions = nil, orderings = nil, joins = nil) # :nodoc:
22
23
  find(:first, :conditions => conditions, :order => orderings, :joins => joins)
23
24
  end
25
+ deprecate :find_first => "use find(:first, ...)"
24
26
 
25
27
  # This method is deprecated in favor of find(:all, options).
26
28
  #
@@ -36,6 +38,7 @@ module ActiveRecord
36
38
  limit, offset = limit.is_a?(Array) ? limit : [ limit, nil ]
37
39
  find(:all, :conditions => conditions, :order => orderings, :joins => joins, :limit => limit, :offset => offset)
38
40
  end
41
+ deprecate :find_all => "use find(:all, ...)"
39
42
  end
40
43
  end
41
- end
44
+ end
@@ -252,7 +252,7 @@ class Fixtures < YAML::Omap
252
252
  end
253
253
  all_loaded_fixtures.merge! fixtures_map
254
254
 
255
- connection.transaction do
255
+ connection.transaction(Thread.current['open_transactions'] == 0) do
256
256
  fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
257
257
  fixtures.each { |fixture| fixture.insert_fixtures }
258
258
 
@@ -294,21 +294,24 @@ class Fixtures < YAML::Omap
294
294
  def read_fixture_files
295
295
  if File.file?(yaml_file_path)
296
296
  # YAML fixtures
297
+ yaml_string = ""
298
+ Dir["#{@fixture_path}/**/*.yml"].select {|f| test(?f,f) }.each do |subfixture_path|
299
+ yaml_string << IO.read(subfixture_path)
300
+ end
301
+ yaml_string << IO.read(yaml_file_path)
297
302
  begin
298
- yaml_string = ""
299
- Dir["#{@fixture_path}/**/*.yml"].select {|f| test(?f,f) }.each do |subfixture_path|
300
- yaml_string << IO.read(subfixture_path)
301
- end
302
- yaml_string << IO.read(yaml_file_path)
303
-
304
- if yaml = YAML::load(erb_render(yaml_string))
305
- yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value)
306
- yaml.each do |name, data|
307
- self[name] = Fixture.new(data, @class_name)
303
+ yaml = YAML::load(erb_render(yaml_string))
304
+ rescue Exception=>boom
305
+ raise Fixture::FormatError, "a YAML error occurred parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{boom.class}: #{boom}"
306
+ end
307
+ if yaml
308
+ yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value)
309
+ yaml.each do |name, data|
310
+ unless data
311
+ raise Fixture::FormatError, "Bad data for #{@class_name} fixture named #{name} (nil)"
308
312
  end
313
+ self[name] = Fixture.new(data, @class_name)
309
314
  end
310
- rescue Exception=>boom
311
- raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{boom.class}: #{boom}"
312
315
  end
313
316
  elsif File.file?(csv_file_path)
314
317
  # CSV fixtures
@@ -368,7 +371,7 @@ class Fixture #:nodoc:
368
371
  when String
369
372
  @fixture = read_fixture_file(fixture)
370
373
  else
371
- raise ArgumentError, "Bad fixture argument #{fixture.inspect}"
374
+ raise ArgumentError, "Bad fixture argument #{fixture.inspect} during creation of #{class_name} fixture"
372
375
  end
373
376
 
374
377
  @class_name = class_name
@@ -392,7 +395,13 @@ class Fixture #:nodoc:
392
395
  end
393
396
 
394
397
  def value_list
395
- @fixture.values.map { |v| ActiveRecord::Base.connection.quote(v).gsub('\\n', "\n").gsub('\\r', "\r") }.join(", ")
398
+ klass = @class_name.constantize rescue nil
399
+
400
+ list = @fixture.inject([]) do |fixtures, (key, value)|
401
+ col = klass.columns_hash[key] if klass.kind_of?(ActiveRecord::Base)
402
+ fixtures << ActiveRecord::Base.connection.quote(value, col).gsub('[^\]\\n', "\n").gsub('[^\]\\r', "\r")
403
+ end
404
+ list * ', '
396
405
  end
397
406
 
398
407
  def find
@@ -460,7 +469,7 @@ module Test #:nodoc:
460
469
  file_name = table_name.to_s
461
470
  file_name = file_name.singularize if ActiveRecord::Base.pluralize_table_names
462
471
  begin
463
- require file_name
472
+ require_dependency file_name
464
473
  rescue LoadError
465
474
  # Let's hope the developer has included it himself
466
475
  end
@@ -484,12 +493,13 @@ module Test #:nodoc:
484
493
  end
485
494
 
486
495
  def self.uses_transaction(*methods)
487
- @uses_transaction ||= []
488
- @uses_transaction.concat methods.map { |m| m.to_s }
496
+ @uses_transaction = [] unless defined?(@uses_transaction)
497
+ @uses_transaction.concat methods.map(&:to_s)
489
498
  end
490
499
 
491
500
  def self.uses_transaction?(method)
492
- @uses_transaction && @uses_transaction.include?(method.to_s)
501
+ @uses_transaction = [] unless defined?(@uses_transaction)
502
+ @uses_transaction.include?(method.to_s)
493
503
  end
494
504
 
495
505
  def use_transactional_fixtures?
@@ -498,6 +508,8 @@ module Test #:nodoc:
498
508
  end
499
509
 
500
510
  def setup_with_fixtures
511
+ return unless defined?(ActiveRecord::Base) && !ActiveRecord::Base.configurations.blank?
512
+
501
513
  if pre_loaded_fixtures && !use_transactional_fixtures
502
514
  raise RuntimeError, 'pre_loaded_fixtures requires use_transactional_fixtures'
503
515
  end
@@ -512,7 +524,7 @@ module Test #:nodoc:
512
524
  load_fixtures
513
525
  @@already_loaded_fixtures[self.class] = @loaded_fixtures
514
526
  end
515
- ActiveRecord::Base.lock_mutex
527
+ ActiveRecord::Base.send :increment_open_transactions
516
528
  ActiveRecord::Base.connection.begin_db_transaction
517
529
 
518
530
  # Load fixtures for every test.
@@ -528,10 +540,12 @@ module Test #:nodoc:
528
540
  alias_method :setup, :setup_with_fixtures
529
541
 
530
542
  def teardown_with_fixtures
531
- # Rollback changes.
532
- if use_transactional_fixtures?
543
+ return unless defined?(ActiveRecord::Base) && !ActiveRecord::Base.configurations.blank?
544
+
545
+ # Rollback changes if a transaction is active.
546
+ if use_transactional_fixtures? && Thread.current['open_transactions'] != 0
533
547
  ActiveRecord::Base.connection.rollback_db_transaction
534
- ActiveRecord::Base.unlock_mutex
548
+ Thread.current['open_transactions'] = 0
535
549
  end
536
550
  ActiveRecord::Base.verify_active_connections!
537
551
  end
@@ -0,0 +1,106 @@
1
+ module ActiveRecord
2
+ module Locking
3
+ # Active Records support optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
4
+ # record increments the lock_version column and the locking facilities ensure that records instantiated twice
5
+ # will let the last one saved raise a StaleObjectError if the first was also updated. Example:
6
+ #
7
+ # p1 = Person.find(1)
8
+ # p2 = Person.find(1)
9
+ #
10
+ # p1.first_name = "Michael"
11
+ # p1.save
12
+ #
13
+ # p2.first_name = "should fail"
14
+ # p2.save # Raises a ActiveRecord::StaleObjectError
15
+ #
16
+ # You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
17
+ # or otherwise apply the business logic needed to resolve the conflict.
18
+ #
19
+ # You must ensure that your database schema defaults the lock_version column to 0.
20
+ #
21
+ # This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
22
+ # To override the name of the lock_version column, invoke the <tt>set_locking_column</tt> method.
23
+ # This method uses the same syntax as <tt>set_table_name</tt>
24
+ module Optimistic
25
+ def self.included(base) #:nodoc:
26
+ super
27
+ base.extend ClassMethods
28
+
29
+ base.cattr_accessor :lock_optimistically
30
+ base.lock_optimistically = true
31
+
32
+ base.alias_method_chain :update, :lock
33
+ base.alias_method_chain :attributes_from_column_definition, :lock
34
+
35
+ class << base
36
+ alias_method :locking_column=, :set_locking_column
37
+ end
38
+ end
39
+
40
+ def locking_enabled? #:nodoc:
41
+ lock_optimistically && respond_to?(self.class.locking_column)
42
+ end
43
+
44
+ def attributes_from_column_definition_with_lock
45
+ result = attributes_from_column_definition_without_lock
46
+
47
+ # If the locking column has no default value set,
48
+ # start the lock version at zero. Note we can't use
49
+ # locking_enabled? at this point as @attributes may
50
+ # not have been initialized yet
51
+
52
+ if lock_optimistically && result.include?(self.class.locking_column)
53
+ result[self.class.locking_column] ||= 0
54
+ end
55
+
56
+ return result
57
+ end
58
+
59
+ def update_with_lock #:nodoc:
60
+ return update_without_lock unless locking_enabled?
61
+
62
+ lock_col = self.class.locking_column
63
+ previous_value = send(lock_col)
64
+ send(lock_col + '=', previous_value + 1)
65
+
66
+ affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
67
+ UPDATE #{self.class.table_name}
68
+ SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))}
69
+ WHERE #{self.class.primary_key} = #{quote_value(id)}
70
+ AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}
71
+ end_sql
72
+
73
+ unless affected_rows == 1
74
+ raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
75
+ end
76
+
77
+ return true
78
+ end
79
+
80
+ module ClassMethods
81
+ DEFAULT_LOCKING_COLUMN = 'lock_version'
82
+
83
+ # Set the column to use for optimistic locking. Defaults to lock_version.
84
+ def set_locking_column(value = nil, &block)
85
+ define_attr_method :locking_column, value, &block
86
+ value
87
+ end
88
+
89
+ # The version column used for optimistic locking. Defaults to lock_version.
90
+ def locking_column
91
+ reset_locking_column
92
+ end
93
+
94
+ # Quote the column name used for optimistic locking.
95
+ def quoted_locking_column
96
+ connection.quote_column_name(locking_column)
97
+ end
98
+
99
+ # Reset the column used for optimistic locking back to the lock_version default.
100
+ def reset_locking_column
101
+ set_locking_column DEFAULT_LOCKING_COLUMN
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,77 @@
1
+ # Copyright (c) 2006 Shugo Maeda <shugo@ruby-lang.org>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject
9
+ # to the following conditions:
10
+ #
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+ #
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
18
+ # ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
19
+ # CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+
23
+ module ActiveRecord
24
+ module Locking
25
+ # Locking::Pessimistic provides support for row-level locking using
26
+ # SELECT ... FOR UPDATE and other lock types.
27
+ #
28
+ # Pass :lock => true to ActiveRecord::Base.find to obtain an exclusive
29
+ # lock on the selected rows:
30
+ # # select * from accounts where id=1 for update
31
+ # Account.find(1, :lock => true)
32
+ #
33
+ # Pass :lock => 'some locking clause' to give a database-specific locking clause
34
+ # of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'.
35
+ #
36
+ # Example:
37
+ # Account.transaction do
38
+ # # select * from accounts where name = 'shugo' limit 1 for update
39
+ # shugo = Account.find(:first, :conditions => "name = 'shugo'", :lock => true)
40
+ # yuko = Account.find(:first, :conditions => "name = 'yuko'", :lock => true)
41
+ # shugo.balance -= 100
42
+ # shugo.save!
43
+ # yuko.balance += 100
44
+ # yuko.save!
45
+ # end
46
+ #
47
+ # You can also use ActiveRecord::Base#lock! method to lock one record by id.
48
+ # This may be better if you don't need to lock every row. Example:
49
+ # Account.transaction do
50
+ # # select * from accounts where ...
51
+ # accounts = Account.find(:all, :conditions => ...)
52
+ # account1 = accounts.detect { |account| ... }
53
+ # account2 = accounts.detect { |account| ... }
54
+ # # select * from accounts where id=? for update
55
+ # account1.lock!
56
+ # account2.lock!
57
+ # account1.balance -= 100
58
+ # account1.save!
59
+ # account2.balance += 100
60
+ # account2.save!
61
+ # end
62
+ #
63
+ # Database-specific information on row locking:
64
+ # MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
65
+ # PostgreSQL: http://www.postgresql.org/docs/8.1/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
66
+ module Pessimistic
67
+ # Obtain a row lock on this record. Reloads the record to obtain the requested
68
+ # lock. Pass an SQL locking clause to append the end of the SELECT statement
69
+ # or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
70
+ # the locked record.
71
+ def lock!(lock = true)
72
+ reload(:lock => lock) unless new_record?
73
+ self
74
+ end
75
+ end
76
+ end
77
+ end
@@ -64,8 +64,10 @@ module ActiveRecord
64
64
  # * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+ to +new_name+.
65
65
  # * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column to the table called +table_name+
66
66
  # named +column_name+ specified to be one of the following types:
67
- # :string, :text, :integer, :float, :datetime, :timestamp, :time, :date, :binary, :boolean. A default value can be specified
68
- # by passing an +options+ hash like { :default => 11 }.
67
+ # :string, :text, :integer, :float, :decimal, :datetime, :timestamp, :time,
68
+ # :date, :binary, :boolean. A default value can be specified by passing an
69
+ # +options+ hash like { :default => 11 }. Other options include :limit and :null (e.g. { :limit => 50, :null => false })
70
+ # -- see ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
69
71
  # * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames a column but keeps the type and content.
70
72
  # * <tt>change_column(table_name, column_name, type, options)</tt>: Changes the column to a different type using the same
71
73
  # parameters as add_column.
@@ -156,7 +158,7 @@ module ActiveRecord
156
158
  # add_column :people, :salary, :integer
157
159
  # Person.reset_column_information
158
160
  # Person.find(:all).each do |p|
159
- # p.salary = SalaryCalculator.compute(p)
161
+ # p.update_attribute :salary, SalaryCalculator.compute(p)
160
162
  # end
161
163
  # end
162
164
  # end
@@ -176,7 +178,7 @@ module ActiveRecord
176
178
  # ...
177
179
  # say_with_time "Updating salaries..." do
178
180
  # Person.find(:all).each do |p|
179
- # p.salary = SalaryCalculator.compute(p)
181
+ # p.update_attribute :salary, SalaryCalculator.compute(p)
180
182
  # end
181
183
  # end
182
184
  # ...
@@ -381,7 +383,8 @@ module ActiveRecord
381
383
  end
382
384
 
383
385
  def reached_target_version?(version)
384
- (up? && version.to_i - 1 == @target_version) || (down? && version.to_i == @target_version)
386
+ return false if @target_version == nil
387
+ (up? && version.to_i - 1 >= @target_version) || (down? && version.to_i <= @target_version)
385
388
  end
386
389
 
387
390
  def irrelevant_migration?(version)