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.
- data/CHANGELOG +400 -1
- data/README +2 -2
- data/RUNNING_UNIT_TESTS +21 -3
- data/Rakefile +55 -10
- data/lib/active_record.rb +10 -4
- data/lib/active_record/acts/list.rb +15 -4
- data/lib/active_record/acts/nested_set.rb +11 -12
- data/lib/active_record/acts/tree.rb +13 -14
- data/lib/active_record/aggregations.rb +46 -22
- data/lib/active_record/associations.rb +213 -162
- data/lib/active_record/associations/association_collection.rb +45 -15
- data/lib/active_record/associations/association_proxy.rb +32 -13
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +18 -18
- data/lib/active_record/associations/has_many_association.rb +37 -17
- data/lib/active_record/associations/has_many_through_association.rb +120 -30
- data/lib/active_record/associations/has_one_association.rb +1 -1
- data/lib/active_record/attribute_methods.rb +75 -0
- data/lib/active_record/base.rb +282 -203
- data/lib/active_record/calculations.rb +95 -54
- data/lib/active_record/callbacks.rb +13 -24
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +12 -1
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb.rej +21 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +30 -4
- data/lib/active_record/connection_adapters/abstract/quoting.rb +16 -9
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +121 -37
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +55 -23
- data/lib/active_record/connection_adapters/abstract_adapter.rb +8 -0
- data/lib/active_record/connection_adapters/db2_adapter.rb +1 -11
- data/lib/active_record/connection_adapters/firebird_adapter.rb +364 -50
- data/lib/active_record/connection_adapters/frontbase_adapter.rb +861 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -33
- data/lib/active_record/connection_adapters/openbase_adapter.rb +4 -3
- data/lib/active_record/connection_adapters/oracle_adapter.rb +151 -127
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +125 -48
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +38 -10
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +183 -155
- data/lib/active_record/connection_adapters/sybase_adapter.rb +190 -212
- data/lib/active_record/deprecated_associations.rb +24 -10
- data/lib/active_record/deprecated_finders.rb +4 -1
- data/lib/active_record/fixtures.rb +37 -23
- data/lib/active_record/locking/optimistic.rb +106 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/migration.rb +8 -5
- data/lib/active_record/observer.rb +73 -34
- data/lib/active_record/reflection.rb +21 -7
- data/lib/active_record/schema_dumper.rb +33 -5
- data/lib/active_record/timestamp.rb +23 -34
- data/lib/active_record/transactions.rb +37 -30
- data/lib/active_record/validations.rb +46 -30
- data/lib/active_record/vendor/mysql.rb +20 -5
- data/lib/active_record/version.rb +2 -2
- data/lib/active_record/wrappings.rb +1 -2
- data/lib/active_record/xml_serialization.rb +308 -0
- data/test/aaa_create_tables_test.rb +5 -1
- data/test/abstract_unit.rb +18 -8
- data/test/{active_schema_mysql.rb → active_schema_test_mysql.rb} +2 -2
- data/test/adapter_test.rb +9 -7
- data/test/adapter_test_sqlserver.rb +81 -0
- data/test/aggregations_test.rb +29 -0
- data/test/{association_callbacks_test.rb → associations/callbacks_test.rb} +10 -8
- data/test/{associations_cascaded_eager_loading_test.rb → associations/cascaded_eager_loading_test.rb} +35 -3
- data/test/{associations_go_eager_test.rb → associations/eager_test.rb} +36 -2
- data/test/{associations_extensions_test.rb → associations/extension_test.rb} +5 -0
- data/test/{associations_join_model_test.rb → associations/join_model_test.rb} +118 -8
- data/test/associations_test.rb +339 -45
- data/test/attribute_methods_test.rb +49 -0
- data/test/base_test.rb +321 -67
- data/test/calculations_test.rb +48 -10
- data/test/callbacks_test.rb +13 -0
- data/test/connection_test_firebird.rb +8 -0
- data/test/connections/native_db2/connection.rb +18 -17
- data/test/connections/native_firebird/connection.rb +19 -17
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +18 -15
- data/test/connections/native_openbase/connection.rb +14 -15
- data/test/connections/native_oracle/connection.rb +16 -12
- data/test/connections/native_postgresql/connection.rb +16 -17
- data/test/connections/native_sqlite/connection.rb +3 -6
- data/test/connections/native_sqlite3/connection.rb +3 -6
- data/test/connections/native_sqlserver/connection.rb +16 -17
- data/test/connections/native_sqlserver_odbc/connection.rb +18 -19
- data/test/connections/native_sybase/connection.rb +16 -17
- data/test/datatype_test_postgresql.rb +52 -0
- data/test/defaults_test.rb +52 -10
- data/test/deprecated_associations_test.rb +151 -107
- data/test/deprecated_finder_test.rb +83 -66
- data/test/empty_date_time_test.rb +25 -0
- data/test/finder_test.rb +118 -11
- data/test/fixtures/accounts.yml +6 -1
- data/test/fixtures/author.rb +27 -4
- data/test/fixtures/categorizations.yml +8 -2
- data/test/fixtures/category.rb +1 -2
- data/test/fixtures/comments.yml +0 -6
- data/test/fixtures/companies.yml +6 -1
- data/test/fixtures/company.rb +23 -1
- data/test/fixtures/company_in_module.rb +8 -10
- data/test/fixtures/customer.rb +2 -2
- data/test/fixtures/customers.yml +9 -0
- data/test/fixtures/db_definitions/db2.drop.sql +1 -0
- data/test/fixtures/db_definitions/db2.sql +9 -0
- data/test/fixtures/db_definitions/firebird.drop.sql +3 -0
- data/test/fixtures/db_definitions/firebird.sql +13 -1
- data/test/fixtures/db_definitions/frontbase.drop.sql +31 -0
- data/test/fixtures/db_definitions/frontbase.sql +262 -0
- data/test/fixtures/db_definitions/frontbase2.drop.sql +1 -0
- data/test/fixtures/db_definitions/frontbase2.sql +4 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +1 -0
- data/test/fixtures/db_definitions/mysql.sql +23 -14
- data/test/fixtures/db_definitions/openbase.sql +13 -1
- data/test/fixtures/db_definitions/oracle.drop.sql +2 -0
- data/test/fixtures/db_definitions/oracle.sql +29 -2
- data/test/fixtures/db_definitions/postgresql.drop.sql +3 -1
- data/test/fixtures/db_definitions/postgresql.sql +13 -3
- data/test/fixtures/db_definitions/schema.rb +29 -1
- data/test/fixtures/db_definitions/sqlite.drop.sql +1 -0
- data/test/fixtures/db_definitions/sqlite.sql +12 -3
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +35 -0
- data/test/fixtures/db_definitions/sybase.drop.sql +2 -0
- data/test/fixtures/db_definitions/sybase.sql +13 -4
- data/test/fixtures/developer.rb +12 -0
- data/test/fixtures/edge.rb +5 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/funny_jokes.yml +3 -7
- data/test/fixtures/migrations_with_decimal/1_give_me_big_numbers.rb +15 -0
- data/test/fixtures/migrations_with_missing_versions/1000_people_have_middle_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/1_people_have_last_names.rb +9 -0
- data/test/fixtures/migrations_with_missing_versions/3_we_need_reminders.rb +12 -0
- data/test/fixtures/migrations_with_missing_versions/4_innocent_jointable.rb +12 -0
- data/test/fixtures/mixin.rb +15 -0
- data/test/fixtures/mixins.yml +38 -0
- data/test/fixtures/post.rb +3 -2
- data/test/fixtures/project.rb +3 -1
- data/test/fixtures/topic.rb +6 -1
- data/test/fixtures/topics.yml +4 -4
- data/test/fixtures/vertex.rb +9 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures_test.rb +45 -0
- data/test/inheritance_test.rb +67 -6
- data/test/lifecycle_test.rb +40 -19
- data/test/locking_test.rb +170 -26
- data/test/method_scoping_test.rb +2 -2
- data/test/migration_test.rb +387 -110
- data/test/migration_test_firebird.rb +124 -0
- data/test/mixin_nested_set_test.rb +14 -2
- data/test/mixin_test.rb +56 -18
- data/test/modules_test.rb +8 -2
- data/test/multiple_db_test.rb +2 -2
- data/test/pk_test.rb +1 -0
- data/test/reflection_test.rb +8 -2
- data/test/schema_authorization_test_postgresql.rb +75 -0
- data/test/schema_dumper_test.rb +40 -4
- data/test/table_name_test_sqlserver.rb +23 -0
- data/test/threaded_connections_test.rb +19 -16
- data/test/transactions_test.rb +86 -72
- data/test/validations_test.rb +126 -56
- data/test/xml_serialization_test.rb +125 -0
- metadata +45 -11
- 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
|
-
|
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
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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.
|
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
|
-
|
532
|
-
|
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
|
-
|
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,
|
68
|
-
#
|
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
|
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
|
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
|
-
|
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)
|