activerecord 1.13.2 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +452 -10
- data/RUNNING_UNIT_TESTS +1 -1
- data/lib/active_record.rb +5 -2
- data/lib/active_record/acts/list.rb +1 -1
- data/lib/active_record/acts/tree.rb +29 -25
- data/lib/active_record/aggregations.rb +3 -2
- data/lib/active_record/associations.rb +783 -337
- data/lib/active_record/associations/association_collection.rb +7 -12
- data/lib/active_record/associations/association_proxy.rb +62 -24
- data/lib/active_record/associations/belongs_to_association.rb +27 -46
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +38 -38
- data/lib/active_record/associations/has_many_association.rb +61 -56
- data/lib/active_record/associations/has_many_through_association.rb +144 -0
- data/lib/active_record/associations/has_one_association.rb +22 -16
- data/lib/active_record/base.rb +482 -182
- data/lib/active_record/calculations.rb +225 -0
- data/lib/active_record/callbacks.rb +7 -7
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +162 -47
- data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -1
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +21 -1
- data/lib/active_record/connection_adapters/abstract_adapter.rb +34 -2
- data/lib/active_record/connection_adapters/db2_adapter.rb +107 -61
- data/lib/active_record/connection_adapters/mysql_adapter.rb +29 -6
- data/lib/active_record/connection_adapters/openbase_adapter.rb +349 -0
- data/lib/active_record/connection_adapters/{oci_adapter.rb → oracle_adapter.rb} +125 -59
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +24 -21
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +47 -8
- data/lib/active_record/connection_adapters/sqlserver_adapter.rb +36 -16
- data/lib/active_record/connection_adapters/sybase_adapter.rb +684 -0
- data/lib/active_record/fixtures.rb +42 -17
- data/lib/active_record/locking.rb +36 -15
- data/lib/active_record/migration.rb +111 -8
- data/lib/active_record/observer.rb +25 -1
- data/lib/active_record/reflection.rb +103 -41
- data/lib/active_record/schema.rb +2 -2
- data/lib/active_record/schema_dumper.rb +55 -18
- data/lib/active_record/timestamp.rb +6 -6
- data/lib/active_record/validations.rb +65 -40
- data/lib/active_record/vendor/db2.rb +10 -5
- data/lib/active_record/vendor/simple.rb +693 -702
- data/lib/active_record/version.rb +2 -2
- data/rakefile +4 -4
- data/test/aaa_create_tables_test.rb +25 -6
- data/test/abstract_unit.rb +39 -1
- data/test/adapter_test.rb +31 -4
- data/test/associations_cascaded_eager_loading_test.rb +106 -0
- data/test/associations_go_eager_test.rb +85 -16
- data/test/associations_join_model_test.rb +338 -0
- data/test/associations_test.rb +129 -50
- data/test/base_test.rb +204 -49
- data/test/binary_test.rb +1 -1
- data/test/calculations_test.rb +169 -0
- data/test/callbacks_test.rb +5 -23
- data/test/class_inheritable_attributes_test.rb +1 -1
- data/test/column_alias_test.rb +1 -1
- data/test/connections/native_mysql/connection.rb +1 -0
- data/test/connections/native_openbase/connection.rb +22 -0
- data/test/connections/{native_oci → native_oracle}/connection.rb +7 -9
- data/test/connections/native_sqlite/connection.rb +1 -1
- data/test/connections/native_sqlite3/connection.rb +1 -0
- data/test/connections/native_sqlite3/in_memory_connection.rb +1 -0
- data/test/connections/native_sybase/connection.rb +24 -0
- data/test/defaults_test.rb +18 -0
- data/test/deprecated_associations_test.rb +2 -2
- data/test/deprecated_finder_test.rb +0 -6
- data/test/finder_test.rb +26 -23
- data/test/fixtures/accounts.yml +10 -0
- data/test/fixtures/author.rb +31 -6
- data/test/fixtures/author_favorites.yml +4 -0
- data/test/fixtures/categories/special_categories.yml +9 -0
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
- data/test/fixtures/categories_posts.yml +4 -0
- data/test/fixtures/categorization.rb +5 -0
- data/test/fixtures/categorizations.yml +11 -0
- data/test/fixtures/category.rb +6 -0
- data/test/fixtures/company.rb +17 -5
- data/test/fixtures/company_in_module.rb +19 -5
- data/test/fixtures/db_definitions/db2.drop.sql +3 -0
- data/test/fixtures/db_definitions/db2.sql +121 -100
- data/test/fixtures/db_definitions/db22.sql +2 -2
- data/test/fixtures/db_definitions/firebird.drop.sql +4 -0
- data/test/fixtures/db_definitions/firebird.sql +26 -0
- data/test/fixtures/db_definitions/mysql.drop.sql +3 -0
- data/test/fixtures/db_definitions/mysql.sql +21 -1
- data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase.sql +282 -0
- data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
- data/test/fixtures/db_definitions/openbase2.sql +7 -0
- data/test/fixtures/db_definitions/{oci.drop.sql → oracle.drop.sql} +6 -0
- data/test/fixtures/db_definitions/{oci.sql → oracle.sql} +25 -4
- data/test/fixtures/db_definitions/{oci2.drop.sql → oracle2.drop.sql} +0 -0
- data/test/fixtures/db_definitions/{oci2.sql → oracle2.sql} +0 -0
- data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
- data/test/fixtures/db_definitions/postgresql.sql +22 -1
- data/test/fixtures/db_definitions/schema.rb +32 -0
- data/test/fixtures/db_definitions/sqlite.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlite.sql +18 -0
- data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
- data/test/fixtures/db_definitions/sqlserver.sql +23 -3
- data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
- data/test/fixtures/db_definitions/sybase.sql +204 -0
- data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
- data/test/fixtures/db_definitions/sybase2.sql +5 -0
- data/test/fixtures/developers.yml +6 -1
- data/test/fixtures/developers_projects.yml +4 -0
- data/test/fixtures/funny_jokes.yml +14 -0
- data/test/fixtures/joke.rb +6 -0
- data/test/fixtures/legacy_thing.rb +3 -0
- data/test/fixtures/legacy_things.yml +3 -0
- data/test/fixtures/mixin.rb +1 -1
- data/test/fixtures/person.rb +4 -1
- data/test/fixtures/post.rb +26 -1
- data/test/fixtures/project.rb +1 -0
- data/test/fixtures/reader.rb +4 -0
- data/test/fixtures/readers.yml +4 -0
- data/test/fixtures/reply.rb +2 -1
- data/test/fixtures/tag.rb +5 -0
- data/test/fixtures/tagging.rb +6 -0
- data/test/fixtures/taggings.yml +18 -0
- data/test/fixtures/tags.yml +7 -0
- data/test/fixtures/tasks.yml +2 -2
- data/test/fixtures/topic.rb +2 -2
- data/test/fixtures/topics.yml +1 -0
- data/test/fixtures_test.rb +47 -13
- data/test/inheritance_test.rb +2 -2
- data/test/locking_test.rb +15 -1
- data/test/method_scoping_test.rb +248 -13
- data/test/migration_test.rb +68 -11
- data/test/mixin_nested_set_test.rb +1 -1
- data/test/modules_test.rb +6 -1
- data/test/readonly_test.rb +1 -1
- data/test/reflection_test.rb +63 -9
- data/test/schema_dumper_test.rb +41 -0
- data/test/{synonym_test_oci.rb → synonym_test_oracle.rb} +1 -1
- data/test/threaded_connections_test.rb +10 -0
- data/test/unconnected_test.rb +12 -5
- data/test/validations_test.rb +197 -10
- metadata +295 -260
- data/test/fixtures/db_definitions/create_oracle_db.bat +0 -0
- data/test/fixtures/db_definitions/create_oracle_db.sh +0 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
data/lib/active_record/schema.rb
CHANGED
|
@@ -47,11 +47,11 @@ module ActiveRecord
|
|
|
47
47
|
cols = columns('schema_info')
|
|
48
48
|
|
|
49
49
|
info = info.map do |k,v|
|
|
50
|
-
v = quote(v, cols.detect { |c| c.name == k.to_s })
|
|
50
|
+
v = Base.connection.quote(v, cols.detect { |c| c.name == k.to_s })
|
|
51
51
|
"#{k} = #{v}"
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
update "UPDATE
|
|
54
|
+
Base.connection.update "UPDATE #{Migrator.schema_info_table_name} SET #{info.join(", ")}"
|
|
55
55
|
end
|
|
56
56
|
end
|
|
57
57
|
end
|
|
@@ -3,6 +3,12 @@ module ActiveRecord
|
|
|
3
3
|
# output format (i.e., ActiveRecord::Schema).
|
|
4
4
|
class SchemaDumper #:nodoc:
|
|
5
5
|
private_class_method :new
|
|
6
|
+
|
|
7
|
+
# A list of tables which should not be dumped to the schema.
|
|
8
|
+
# Acceptable values are strings as well as regexp.
|
|
9
|
+
# This setting is only used if ActiveRecord::Base.schema_format == :ruby
|
|
10
|
+
cattr_accessor :ignore_tables
|
|
11
|
+
@@ignore_tables = []
|
|
6
12
|
|
|
7
13
|
def self.dump(connection=ActiveRecord::Base.connection, stream=STDOUT)
|
|
8
14
|
new(connection).dump(stream)
|
|
@@ -43,32 +49,63 @@ HEADER
|
|
|
43
49
|
|
|
44
50
|
def tables(stream)
|
|
45
51
|
@connection.tables.sort.each do |tbl|
|
|
46
|
-
next if
|
|
52
|
+
next if ["schema_info", ignore_tables].flatten.any? do |ignored|
|
|
53
|
+
case ignored
|
|
54
|
+
when String: tbl == ignored
|
|
55
|
+
when Regexp: tbl =~ ignored
|
|
56
|
+
else
|
|
57
|
+
raise StandardError, 'ActiveRecord::SchemaDumper.ignore_tables accepts an array of String and / or Regexp values.'
|
|
58
|
+
end
|
|
59
|
+
end
|
|
47
60
|
table(tbl, stream)
|
|
48
61
|
end
|
|
49
62
|
end
|
|
50
63
|
|
|
51
64
|
def table(table, stream)
|
|
52
65
|
columns = @connection.columns(table)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
begin
|
|
67
|
+
tbl = StringIO.new
|
|
68
|
+
|
|
69
|
+
if @connection.respond_to?(:pk_and_sequence_for)
|
|
70
|
+
pk, pk_seq = @connection.pk_and_sequence_for(table)
|
|
71
|
+
end
|
|
72
|
+
pk ||= 'id'
|
|
73
|
+
|
|
74
|
+
tbl.print " create_table #{table.inspect}"
|
|
75
|
+
if columns.detect { |c| c.name == pk }
|
|
76
|
+
if pk != 'id'
|
|
77
|
+
tbl.print %Q(, :primary_key => "#{pk}")
|
|
78
|
+
end
|
|
79
|
+
else
|
|
80
|
+
tbl.print ", :id => false"
|
|
81
|
+
end
|
|
82
|
+
tbl.print ", :force => true"
|
|
83
|
+
tbl.puts " do |t|"
|
|
84
|
+
|
|
85
|
+
columns.each do |column|
|
|
86
|
+
raise StandardError, "Unknown type '#{column.sql_type}' for column '#{column.name}'" if @types[column.type].nil?
|
|
87
|
+
next if column.name == pk
|
|
88
|
+
tbl.print " t.column #{column.name.inspect}, #{column.type.inspect}"
|
|
89
|
+
tbl.print ", :limit => #{column.limit.inspect}" if column.limit != @types[column.type][:limit]
|
|
90
|
+
tbl.print ", :default => #{column.default.inspect}" if !column.default.nil?
|
|
91
|
+
tbl.print ", :null => false" if !column.null
|
|
92
|
+
tbl.puts
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
tbl.puts " end"
|
|
96
|
+
tbl.puts
|
|
97
|
+
|
|
98
|
+
indexes(table, tbl)
|
|
99
|
+
|
|
100
|
+
tbl.rewind
|
|
101
|
+
stream.print tbl.read
|
|
102
|
+
rescue => e
|
|
103
|
+
stream.puts "# Could not dump table #{table.inspect} because of following #{e.class}"
|
|
104
|
+
stream.puts "# #{e.message}"
|
|
65
105
|
stream.puts
|
|
66
106
|
end
|
|
67
|
-
|
|
68
|
-
stream
|
|
69
|
-
stream.puts
|
|
70
|
-
|
|
71
|
-
indexes(table, stream)
|
|
107
|
+
|
|
108
|
+
stream
|
|
72
109
|
end
|
|
73
110
|
|
|
74
111
|
def indexes(table, stream)
|
|
@@ -4,8 +4,8 @@ module ActiveRecord
|
|
|
4
4
|
# automatically included, so you don't need to do that manually.
|
|
5
5
|
#
|
|
6
6
|
# This behavior can be turned off by setting <tt>ActiveRecord::Base.record_timestamps = false</tt>.
|
|
7
|
-
# This behavior can use
|
|
8
|
-
module Timestamp
|
|
7
|
+
# This behavior by default uses local time, but can use UTC by setting <tt>ActiveRecord::Base.default_timezone = :utc</tt>
|
|
8
|
+
module Timestamp
|
|
9
9
|
def self.append_features(base) # :nodoc:
|
|
10
10
|
super
|
|
11
11
|
|
|
@@ -16,14 +16,14 @@ module ActiveRecord
|
|
|
16
16
|
alias_method :update_without_timestamps, :update
|
|
17
17
|
alias_method :update, :update_with_timestamps
|
|
18
18
|
end
|
|
19
|
-
end
|
|
20
|
-
|
|
19
|
+
end
|
|
20
|
+
|
|
21
21
|
def create_with_timestamps #:nodoc:
|
|
22
22
|
if record_timestamps
|
|
23
23
|
t = ( self.class.default_timezone == :utc ? Time.now.utc : Time.now )
|
|
24
24
|
write_attribute('created_at', t) if respond_to?(:created_at) && created_at.nil?
|
|
25
25
|
write_attribute('created_on', t) if respond_to?(:created_on) && created_on.nil?
|
|
26
|
-
|
|
26
|
+
|
|
27
27
|
write_attribute('updated_at', t) if respond_to?(:updated_at)
|
|
28
28
|
write_attribute('updated_on', t) if respond_to?(:updated_on)
|
|
29
29
|
end
|
|
@@ -38,7 +38,7 @@ module ActiveRecord
|
|
|
38
38
|
end
|
|
39
39
|
update_without_timestamps
|
|
40
40
|
end
|
|
41
|
-
end
|
|
41
|
+
end
|
|
42
42
|
|
|
43
43
|
class Base
|
|
44
44
|
# Records the creation date and possibly time in created_on (date only) or created_at (date and time) and the update date and possibly
|
|
@@ -6,11 +6,11 @@ module ActiveRecord
|
|
|
6
6
|
# rescue ActiveRecord::RecordInvalid => invalid
|
|
7
7
|
# puts invalid.record.errors
|
|
8
8
|
# end
|
|
9
|
-
class RecordInvalid < ActiveRecordError
|
|
9
|
+
class RecordInvalid < ActiveRecordError #:nodoc:
|
|
10
10
|
attr_reader :record
|
|
11
|
-
def initialize(record
|
|
11
|
+
def initialize(record)
|
|
12
12
|
@record = record
|
|
13
|
-
super(
|
|
13
|
+
super("Validation failed: #{@record.errors.full_messages.join(", ")}")
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -31,8 +31,8 @@ module ActiveRecord
|
|
|
31
31
|
:accepted => "must be accepted",
|
|
32
32
|
:empty => "can't be empty",
|
|
33
33
|
:blank => "can't be blank",
|
|
34
|
-
:too_long => "is too long (
|
|
35
|
-
:too_short => "is too short (
|
|
34
|
+
:too_long => "is too long (maximum is %d characters)",
|
|
35
|
+
:too_short => "is too short (minimum is %d characters)",
|
|
36
36
|
:wrong_length => "is the wrong length (should be %d characters)",
|
|
37
37
|
:taken => "has already been taken",
|
|
38
38
|
:not_a_number => "is not a number"
|
|
@@ -147,7 +147,7 @@ module ActiveRecord
|
|
|
147
147
|
def empty?
|
|
148
148
|
return @errors.empty?
|
|
149
149
|
end
|
|
150
|
-
|
|
150
|
+
|
|
151
151
|
# Removes all the errors that have been added.
|
|
152
152
|
def clear
|
|
153
153
|
@errors = {}
|
|
@@ -155,11 +155,14 @@ module ActiveRecord
|
|
|
155
155
|
|
|
156
156
|
# Returns the total number of errors added. Two errors added to the same attribute will be counted as such
|
|
157
157
|
# with this as well.
|
|
158
|
-
def
|
|
158
|
+
def size
|
|
159
159
|
error_count = 0
|
|
160
160
|
@errors.each_value { |attribute| error_count += attribute.length }
|
|
161
161
|
error_count
|
|
162
162
|
end
|
|
163
|
+
|
|
164
|
+
alias_method :count, :size
|
|
165
|
+
alias_method :length, :size
|
|
163
166
|
end
|
|
164
167
|
|
|
165
168
|
|
|
@@ -213,6 +216,9 @@ module ActiveRecord
|
|
|
213
216
|
alias_method :save_without_validation, :save
|
|
214
217
|
alias_method :save, :save_with_validation
|
|
215
218
|
|
|
219
|
+
alias_method :save_without_validation!, :save!
|
|
220
|
+
alias_method :save!, :save_with_validation!
|
|
221
|
+
|
|
216
222
|
alias_method :update_attribute_without_validation_skipping, :update_attribute
|
|
217
223
|
alias_method :update_attribute, :update_attribute_with_validation_skipping
|
|
218
224
|
end
|
|
@@ -361,8 +367,14 @@ module ActiveRecord
|
|
|
361
367
|
end
|
|
362
368
|
end
|
|
363
369
|
|
|
364
|
-
# Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save.
|
|
370
|
+
# Validates that the specified attributes are not blank (as defined by Object#blank?). Happens by default on save. Example:
|
|
365
371
|
#
|
|
372
|
+
# class Person < ActiveRecord::Base
|
|
373
|
+
# validates_presence_of :first_name
|
|
374
|
+
# end
|
|
375
|
+
#
|
|
376
|
+
# The first_name attribute must be in the object and it cannot be blank.
|
|
377
|
+
#
|
|
366
378
|
# Configuration options:
|
|
367
379
|
# * <tt>message</tt> - A custom error message (default is: "can't be blank")
|
|
368
380
|
# * <tt>on</tt> - Specifies when this validation is active (default is :save, other options :create, :update)
|
|
@@ -403,7 +415,7 @@ module ActiveRecord
|
|
|
403
415
|
# * <tt>in</tt> - A synonym(or alias) for :within
|
|
404
416
|
# * <tt>allow_nil</tt> - Attribute may be nil; skip validation.
|
|
405
417
|
#
|
|
406
|
-
# * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (
|
|
418
|
+
# * <tt>too_long</tt> - The error message if the attribute goes over the maximum (default is: "is too long (maximum is %d characters)")
|
|
407
419
|
# * <tt>too_short</tt> - The error message if the attribute goes under the minimum (default is: "is too short (min is %d characters)")
|
|
408
420
|
# * <tt>wrong_length</tt> - The error message if using the :is method and the attribute is the wrong size (default is: "is the wrong length (should be %d characters)")
|
|
409
421
|
# * <tt>message</tt> - The error message to use for a :minimum, :maximum, or :is violation. An alias of the appropriate too_long/too_short/wrong_length message
|
|
@@ -436,31 +448,35 @@ module ActiveRecord
|
|
|
436
448
|
option_value = options[range_options.first]
|
|
437
449
|
|
|
438
450
|
case option
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
451
|
+
when :within, :in
|
|
452
|
+
raise ArgumentError, ":#{option} must be a Range" unless option_value.is_a?(Range)
|
|
453
|
+
|
|
454
|
+
too_short = options[:too_short] % option_value.begin
|
|
455
|
+
too_long = options[:too_long] % option_value.end
|
|
456
|
+
|
|
457
|
+
validates_each(attrs, options) do |record, attr, value|
|
|
458
|
+
if value.nil? or value.split(//).size < option_value.begin
|
|
459
|
+
record.errors.add(attr, too_short)
|
|
460
|
+
elsif value.split(//).size > option_value.end
|
|
461
|
+
record.errors.add(attr, too_long)
|
|
462
|
+
end
|
|
450
463
|
end
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
|
|
464
|
+
when :is, :minimum, :maximum
|
|
465
|
+
raise ArgumentError, ":#{option} must be a nonnegative Integer" unless option_value.is_a?(Integer) and option_value >= 0
|
|
454
466
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
467
|
+
# Declare different validations per option.
|
|
468
|
+
validity_checks = { :is => "==", :minimum => ">=", :maximum => "<=" }
|
|
469
|
+
message_options = { :is => :wrong_length, :minimum => :too_short, :maximum => :too_long }
|
|
458
470
|
|
|
459
|
-
|
|
471
|
+
message = (options[:message] || options[message_options[option]]) % option_value
|
|
460
472
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
473
|
+
validates_each(attrs, options) do |record, attr, value|
|
|
474
|
+
if value.kind_of?(String)
|
|
475
|
+
record.errors.add(attr, message) unless !value.nil? and value.split(//).size.method(validity_checks[option])[option_value]
|
|
476
|
+
else
|
|
477
|
+
record.errors.add(attr, message) unless !value.nil? and value.size.method(validity_checks[option])[option_value]
|
|
478
|
+
end
|
|
479
|
+
end
|
|
464
480
|
end
|
|
465
481
|
end
|
|
466
482
|
|
|
@@ -471,7 +487,14 @@ module ActiveRecord
|
|
|
471
487
|
# can be named "davidhh".
|
|
472
488
|
#
|
|
473
489
|
# class Person < ActiveRecord::Base
|
|
474
|
-
# validates_uniqueness_of :user_name, :scope =>
|
|
490
|
+
# validates_uniqueness_of :user_name, :scope => :account_id
|
|
491
|
+
# end
|
|
492
|
+
#
|
|
493
|
+
# It can also validate whether the value of the specified attributes are unique based on multiple scope parameters. For example,
|
|
494
|
+
# making sure that a teacher can only be on the schedule once per semester for a particular class.
|
|
495
|
+
#
|
|
496
|
+
# class TeacherSchedule < ActiveRecord::Base
|
|
497
|
+
# validates_uniqueness_of :teacher_id, :scope => [:semester_id, :class_id]
|
|
475
498
|
# end
|
|
476
499
|
#
|
|
477
500
|
# When the record is created, a check is performed to make sure that no record exists in the database with the given value for the specified
|
|
@@ -479,24 +502,27 @@ module ActiveRecord
|
|
|
479
502
|
#
|
|
480
503
|
# Configuration options:
|
|
481
504
|
# * <tt>message</tt> - Specifies a custom error message (default is: "has already been taken")
|
|
482
|
-
# * <tt>scope</tt> -
|
|
505
|
+
# * <tt>scope</tt> - One or more columns by which to limit the scope of the uniquness constraint.
|
|
483
506
|
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
|
484
507
|
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
|
485
508
|
# method, proc or string should return or evaluate to a true or false value.
|
|
509
|
+
|
|
486
510
|
def validates_uniqueness_of(*attr_names)
|
|
487
511
|
configuration = { :message => ActiveRecord::Errors.default_error_messages[:taken] }
|
|
488
512
|
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
|
489
513
|
|
|
490
514
|
validates_each(attr_names,configuration) do |record, attr_name, value|
|
|
491
|
-
condition_sql = "#{attr_name} #{attribute_condition(value)}"
|
|
515
|
+
condition_sql = "#{record.class.table_name}.#{attr_name} #{attribute_condition(value)}"
|
|
492
516
|
condition_params = [value]
|
|
493
517
|
if scope = configuration[:scope]
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
518
|
+
Array(scope).map do |scope_item|
|
|
519
|
+
scope_value = record.send(scope_item)
|
|
520
|
+
condition_sql << " AND #{record.class.table_name}.#{scope_item} #{attribute_condition(scope_value)}"
|
|
521
|
+
condition_params << scope_value
|
|
522
|
+
end
|
|
497
523
|
end
|
|
498
524
|
unless record.new_record?
|
|
499
|
-
condition_sql << " AND #{record.class.primary_key} <> ?"
|
|
525
|
+
condition_sql << " AND #{record.class.table_name}.#{record.class.primary_key} <> ?"
|
|
500
526
|
condition_params << record.send(:id)
|
|
501
527
|
end
|
|
502
528
|
if record.class.find(:first, :conditions => [condition_sql, *condition_params])
|
|
@@ -696,7 +722,6 @@ module ActiveRecord
|
|
|
696
722
|
def save_with_validation(perform_validation = true)
|
|
697
723
|
if perform_validation && valid? || !perform_validation
|
|
698
724
|
save_without_validation
|
|
699
|
-
true
|
|
700
725
|
else
|
|
701
726
|
false
|
|
702
727
|
end
|
|
@@ -704,9 +729,9 @@ module ActiveRecord
|
|
|
704
729
|
|
|
705
730
|
# Attempts to save the record just like Base#save but will raise a RecordInvalid exception instead of returning false
|
|
706
731
|
# if the record is not valid.
|
|
707
|
-
def
|
|
732
|
+
def save_with_validation!
|
|
708
733
|
if valid?
|
|
709
|
-
|
|
734
|
+
save_without_validation!
|
|
710
735
|
else
|
|
711
736
|
raise RecordInvalid.new(self)
|
|
712
737
|
end
|
|
@@ -66,7 +66,7 @@ module DB2
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def connect(server_name, user_name = '', auth = '')
|
|
69
|
-
check_rc(SQLConnect(@handle, server_name, user_name, auth))
|
|
69
|
+
check_rc(SQLConnect(@handle, server_name, user_name.to_s, auth.to_s))
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
def set_connect_attr(attr, value)
|
|
@@ -110,13 +110,18 @@ module DB2
|
|
|
110
110
|
check_rc(rc)
|
|
111
111
|
end
|
|
112
112
|
|
|
113
|
-
def columns(table_name)
|
|
114
|
-
check_rc(SQLColumns(@handle,
|
|
113
|
+
def columns(table_name, schema_name = '%')
|
|
114
|
+
check_rc(SQLColumns(@handle, '', schema_name.upcase, table_name.upcase, '%'))
|
|
115
115
|
fetch_all
|
|
116
116
|
end
|
|
117
117
|
|
|
118
|
-
def tables
|
|
119
|
-
check_rc(SQLTables(@handle,
|
|
118
|
+
def tables(schema_name = '%')
|
|
119
|
+
check_rc(SQLTables(@handle, '', schema_name.upcase, '%', 'TABLE'))
|
|
120
|
+
fetch_all
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def indexes(table_name, schema_name = '')
|
|
124
|
+
check_rc(SQLStatistics(@handle, '', schema_name.upcase, table_name.upcase, SQL_INDEX_ALL, SQL_ENSURE))
|
|
120
125
|
fetch_all
|
|
121
126
|
end
|
|
122
127
|
|
|
@@ -1,702 +1,693 @@
|
|
|
1
|
-
# :title: Transaction::Simple
|
|
2
|
-
#
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# IN THE SOFTWARE
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
#
|
|
26
|
-
#
|
|
27
|
-
#
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
-
#
|
|
84
|
-
#
|
|
85
|
-
#
|
|
86
|
-
#
|
|
87
|
-
#
|
|
88
|
-
#
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
#
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
108
|
-
#
|
|
109
|
-
#
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
# v.
|
|
115
|
-
#
|
|
116
|
-
#
|
|
117
|
-
# v
|
|
118
|
-
# v.
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
# v.
|
|
122
|
-
# v.transaction_open?
|
|
123
|
-
#
|
|
124
|
-
# v.gsub!(/you/, "
|
|
125
|
-
#
|
|
126
|
-
# v.
|
|
127
|
-
#
|
|
128
|
-
# v.
|
|
129
|
-
# v.
|
|
130
|
-
#
|
|
131
|
-
# v.transaction_open?
|
|
132
|
-
#
|
|
133
|
-
#
|
|
134
|
-
# v.
|
|
135
|
-
# v.
|
|
136
|
-
# v.
|
|
137
|
-
# v.
|
|
138
|
-
#
|
|
139
|
-
#
|
|
140
|
-
# v.
|
|
141
|
-
# v.
|
|
142
|
-
#
|
|
143
|
-
# v.
|
|
144
|
-
#
|
|
145
|
-
# v.
|
|
146
|
-
# v.transaction_open?
|
|
147
|
-
#
|
|
148
|
-
#
|
|
149
|
-
#
|
|
150
|
-
#
|
|
151
|
-
#
|
|
152
|
-
#
|
|
153
|
-
#
|
|
154
|
-
#
|
|
155
|
-
#
|
|
156
|
-
#
|
|
157
|
-
#
|
|
158
|
-
#
|
|
159
|
-
#
|
|
160
|
-
#
|
|
161
|
-
#
|
|
162
|
-
#
|
|
163
|
-
#
|
|
164
|
-
#
|
|
165
|
-
#
|
|
166
|
-
#
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
170
|
-
#
|
|
171
|
-
#
|
|
172
|
-
#
|
|
173
|
-
#
|
|
174
|
-
#
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
#
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
#
|
|
307
|
-
#
|
|
308
|
-
#
|
|
309
|
-
#
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
@
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
@
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
assert_nothing_raised { @value.start_transaction }
|
|
695
|
-
assert_equal(true, @value.transaction_open?)
|
|
696
|
-
assert_nothing_raised { @value[1].gsub!(/second/, "fourth") }
|
|
697
|
-
assert_not_equal(@orig, @value)
|
|
698
|
-
assert_nothing_raised { @value.abort_transaction }
|
|
699
|
-
assert_equal(@orig, @value)
|
|
700
|
-
end
|
|
701
|
-
end
|
|
702
|
-
end
|
|
1
|
+
# :title: Transaction::Simple -- Active Object Transaction Support for Ruby
|
|
2
|
+
# :main: Transaction::Simple
|
|
3
|
+
#
|
|
4
|
+
# == Licence
|
|
5
|
+
#
|
|
6
|
+
# Permission is hereby granted, free of charge, to any person obtaining a
|
|
7
|
+
# copy of this software and associated documentation files (the "Software"),
|
|
8
|
+
# to deal in the Software without restriction, including without limitation
|
|
9
|
+
# the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
10
|
+
# and/or sell copies of the Software, and to permit persons to whom the
|
|
11
|
+
# Software is furnished to do so, subject to the following conditions:
|
|
12
|
+
#
|
|
13
|
+
# The above copyright notice and this permission notice shall be included in
|
|
14
|
+
# all copies or substantial portions of the Software.
|
|
15
|
+
#
|
|
16
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
19
|
+
# THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
21
|
+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
|
22
|
+
# DEALINGS IN THE SOFTWARE.
|
|
23
|
+
#--
|
|
24
|
+
# Transaction::Simple
|
|
25
|
+
# Simple object transaction support for Ruby
|
|
26
|
+
# Version 1.3.0
|
|
27
|
+
#
|
|
28
|
+
# Copyright (c) 2003 - 2005 Austin Ziegler
|
|
29
|
+
#
|
|
30
|
+
# $Id: simple.rb,v 1.5 2005/05/05 16:16:49 austin Exp $
|
|
31
|
+
#++
|
|
32
|
+
# The "Transaction" namespace can be used for additional transaction
|
|
33
|
+
# support objects and modules.
|
|
34
|
+
module Transaction
|
|
35
|
+
# A standard exception for transaction errors.
|
|
36
|
+
class TransactionError < StandardError; end
|
|
37
|
+
# The TransactionAborted exception is used to indicate when a
|
|
38
|
+
# transaction has been aborted in the block form.
|
|
39
|
+
class TransactionAborted < Exception; end
|
|
40
|
+
# The TransactionCommitted exception is used to indicate when a
|
|
41
|
+
# transaction has been committed in the block form.
|
|
42
|
+
class TransactionCommitted < Exception; end
|
|
43
|
+
|
|
44
|
+
te = "Transaction Error: %s"
|
|
45
|
+
|
|
46
|
+
Messages = {
|
|
47
|
+
:bad_debug_object =>
|
|
48
|
+
te % "the transaction debug object must respond to #<<.",
|
|
49
|
+
:unique_names =>
|
|
50
|
+
te % "named transactions must be unique.",
|
|
51
|
+
:no_transaction_open =>
|
|
52
|
+
te % "no transaction open.",
|
|
53
|
+
:cannot_rewind_no_transaction =>
|
|
54
|
+
te % "cannot rewind; there is no current transaction.",
|
|
55
|
+
:cannot_rewind_named_transaction =>
|
|
56
|
+
te % "cannot rewind to transaction %s because it does not exist.",
|
|
57
|
+
:cannot_rewind_transaction_before_block =>
|
|
58
|
+
te % "cannot rewind a transaction started before the execution block.",
|
|
59
|
+
:cannot_abort_no_transaction =>
|
|
60
|
+
te % "cannot abort; there is no current transaction.",
|
|
61
|
+
:cannot_abort_transaction_before_block =>
|
|
62
|
+
te % "cannot abort a transaction started before the execution block.",
|
|
63
|
+
:cannot_abort_named_transaction =>
|
|
64
|
+
te % "cannot abort nonexistant transaction %s.",
|
|
65
|
+
:cannot_commit_no_transaction =>
|
|
66
|
+
te % "cannot commit; there is no current transaction.",
|
|
67
|
+
:cannot_commit_transaction_before_block =>
|
|
68
|
+
te % "cannot commit a transaction started before the execution block.",
|
|
69
|
+
:cannot_commit_named_transaction =>
|
|
70
|
+
te % "cannot commit nonexistant transaction %s.",
|
|
71
|
+
:cannot_start_empty_block_transaction =>
|
|
72
|
+
te % "cannot start a block transaction with no objects.",
|
|
73
|
+
:cannot_obtain_transaction_lock =>
|
|
74
|
+
te % "cannot obtain transaction lock for #%s.",
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
# = Transaction::Simple for Ruby
|
|
78
|
+
# Simple object transaction support for Ruby
|
|
79
|
+
#
|
|
80
|
+
# == Introduction
|
|
81
|
+
# Transaction::Simple provides a generic way to add active transaction
|
|
82
|
+
# support to objects. The transaction methods added by this module will
|
|
83
|
+
# work with most objects, excluding those that cannot be
|
|
84
|
+
# <i>Marshal</i>ed (bindings, procedure objects, IO instances, or
|
|
85
|
+
# singleton objects).
|
|
86
|
+
#
|
|
87
|
+
# The transactions supported by Transaction::Simple are not backed
|
|
88
|
+
# transactions; they are not associated with any sort of data store.
|
|
89
|
+
# They are "live" transactions occurring in memory and in the object
|
|
90
|
+
# itself. This is to allow "test" changes to be made to an object
|
|
91
|
+
# before making the changes permanent.
|
|
92
|
+
#
|
|
93
|
+
# Transaction::Simple can handle an "infinite" number of transaction
|
|
94
|
+
# levels (limited only by memory). If I open two transactions, commit
|
|
95
|
+
# the second, but abort the first, the object will revert to the
|
|
96
|
+
# original version.
|
|
97
|
+
#
|
|
98
|
+
# Transaction::Simple supports "named" transactions, so that multiple
|
|
99
|
+
# levels of transactions can be committed, aborted, or rewound by
|
|
100
|
+
# referring to the appropriate name of the transaction. Names may be any
|
|
101
|
+
# object *except* +nil+. As with Hash keys, String names will be
|
|
102
|
+
# duplicated and frozen before using.
|
|
103
|
+
#
|
|
104
|
+
# Copyright:: Copyright � 2003 - 2005 by Austin Ziegler
|
|
105
|
+
# Version:: 1.3.0
|
|
106
|
+
# Licence:: MIT-Style
|
|
107
|
+
#
|
|
108
|
+
# Thanks to David Black for help with the initial concept that led to
|
|
109
|
+
# this library.
|
|
110
|
+
#
|
|
111
|
+
# == Usage
|
|
112
|
+
# include 'transaction/simple'
|
|
113
|
+
#
|
|
114
|
+
# v = "Hello, you." # -> "Hello, you."
|
|
115
|
+
# v.extend(Transaction::Simple) # -> "Hello, you."
|
|
116
|
+
#
|
|
117
|
+
# v.start_transaction # -> ... (a Marshal string)
|
|
118
|
+
# v.transaction_open? # -> true
|
|
119
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
|
120
|
+
#
|
|
121
|
+
# v.rewind_transaction # -> "Hello, you."
|
|
122
|
+
# v.transaction_open? # -> true
|
|
123
|
+
#
|
|
124
|
+
# v.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
|
125
|
+
# v.abort_transaction # -> "Hello, you."
|
|
126
|
+
# v.transaction_open? # -> false
|
|
127
|
+
#
|
|
128
|
+
# v.start_transaction # -> ... (a Marshal string)
|
|
129
|
+
# v.start_transaction # -> ... (a Marshal string)
|
|
130
|
+
#
|
|
131
|
+
# v.transaction_open? # -> true
|
|
132
|
+
# v.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
|
133
|
+
#
|
|
134
|
+
# v.commit_transaction # -> "Hello, HAL."
|
|
135
|
+
# v.transaction_open? # -> true
|
|
136
|
+
# v.abort_transaction # -> "Hello, you."
|
|
137
|
+
# v.transaction_open? # -> false
|
|
138
|
+
#
|
|
139
|
+
# == Named Transaction Usage
|
|
140
|
+
# v = "Hello, you." # -> "Hello, you."
|
|
141
|
+
# v.extend(Transaction::Simple) # -> "Hello, you."
|
|
142
|
+
#
|
|
143
|
+
# v.start_transaction(:first) # -> ... (a Marshal string)
|
|
144
|
+
# v.transaction_open? # -> true
|
|
145
|
+
# v.transaction_open?(:first) # -> true
|
|
146
|
+
# v.transaction_open?(:second) # -> false
|
|
147
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
|
148
|
+
#
|
|
149
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
|
150
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
|
151
|
+
# v.rewind_transaction(:first) # -> "Hello, you."
|
|
152
|
+
# v.transaction_open? # -> true
|
|
153
|
+
# v.transaction_open?(:first) # -> true
|
|
154
|
+
# v.transaction_open?(:second) # -> false
|
|
155
|
+
#
|
|
156
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
|
157
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
|
158
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
|
159
|
+
# v.transaction_name # -> :second
|
|
160
|
+
# v.abort_transaction(:first) # -> "Hello, you."
|
|
161
|
+
# v.transaction_open? # -> false
|
|
162
|
+
#
|
|
163
|
+
# v.start_transaction(:first) # -> ... (a Marshal string)
|
|
164
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
|
165
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
|
166
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
|
167
|
+
#
|
|
168
|
+
# v.commit_transaction(:first) # -> "Hello, HAL."
|
|
169
|
+
# v.transaction_open? # -> false
|
|
170
|
+
#
|
|
171
|
+
# == Block Usage
|
|
172
|
+
# v = "Hello, you." # -> "Hello, you."
|
|
173
|
+
# Transaction::Simple.start(v) do |tv|
|
|
174
|
+
# # v has been extended with Transaction::Simple and an unnamed
|
|
175
|
+
# # transaction has been started.
|
|
176
|
+
# tv.transaction_open? # -> true
|
|
177
|
+
# tv.gsub!(/you/, "world") # -> "Hello, world."
|
|
178
|
+
#
|
|
179
|
+
# tv.rewind_transaction # -> "Hello, you."
|
|
180
|
+
# tv.transaction_open? # -> true
|
|
181
|
+
#
|
|
182
|
+
# tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
|
183
|
+
# # The following breaks out of the transaction block after
|
|
184
|
+
# # aborting the transaction.
|
|
185
|
+
# tv.abort_transaction # -> "Hello, you."
|
|
186
|
+
# end
|
|
187
|
+
# # v still has Transaction::Simple applied from here on out.
|
|
188
|
+
# v.transaction_open? # -> false
|
|
189
|
+
#
|
|
190
|
+
# Transaction::Simple.start(v) do |tv|
|
|
191
|
+
# tv.start_transaction # -> ... (a Marshal string)
|
|
192
|
+
#
|
|
193
|
+
# tv.transaction_open? # -> true
|
|
194
|
+
# tv.gsub!(/you/, "HAL") # -> "Hello, HAL."
|
|
195
|
+
#
|
|
196
|
+
# # If #commit_transaction were called without having started a
|
|
197
|
+
# # second transaction, then it would break out of the transaction
|
|
198
|
+
# # block after committing the transaction.
|
|
199
|
+
# tv.commit_transaction # -> "Hello, HAL."
|
|
200
|
+
# tv.transaction_open? # -> true
|
|
201
|
+
# tv.abort_transaction # -> "Hello, you."
|
|
202
|
+
# end
|
|
203
|
+
# v.transaction_open? # -> false
|
|
204
|
+
#
|
|
205
|
+
# == Named Transaction Usage
|
|
206
|
+
# v = "Hello, you." # -> "Hello, you."
|
|
207
|
+
# v.extend(Transaction::Simple) # -> "Hello, you."
|
|
208
|
+
#
|
|
209
|
+
# v.start_transaction(:first) # -> ... (a Marshal string)
|
|
210
|
+
# v.transaction_open? # -> true
|
|
211
|
+
# v.transaction_open?(:first) # -> true
|
|
212
|
+
# v.transaction_open?(:second) # -> false
|
|
213
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
|
214
|
+
#
|
|
215
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
|
216
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
|
217
|
+
# v.rewind_transaction(:first) # -> "Hello, you."
|
|
218
|
+
# v.transaction_open? # -> true
|
|
219
|
+
# v.transaction_open?(:first) # -> true
|
|
220
|
+
# v.transaction_open?(:second) # -> false
|
|
221
|
+
#
|
|
222
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
|
223
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
|
224
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
|
225
|
+
# v.transaction_name # -> :second
|
|
226
|
+
# v.abort_transaction(:first) # -> "Hello, you."
|
|
227
|
+
# v.transaction_open? # -> false
|
|
228
|
+
#
|
|
229
|
+
# v.start_transaction(:first) # -> ... (a Marshal string)
|
|
230
|
+
# v.gsub!(/you/, "world") # -> "Hello, world."
|
|
231
|
+
# v.start_transaction(:second) # -> ... (a Marshal string)
|
|
232
|
+
# v.gsub!(/world/, "HAL") # -> "Hello, HAL."
|
|
233
|
+
#
|
|
234
|
+
# v.commit_transaction(:first) # -> "Hello, HAL."
|
|
235
|
+
# v.transaction_open? # -> false
|
|
236
|
+
#
|
|
237
|
+
# == Thread Safety
|
|
238
|
+
# Threadsafe version of Transaction::Simple and
|
|
239
|
+
# Transaction::Simple::Group exist; these are loaded from
|
|
240
|
+
# 'transaction/simple/threadsafe' and
|
|
241
|
+
# 'transaction/simple/threadsafe/group', respectively, and are
|
|
242
|
+
# represented in Ruby code as Transaction::Simple::ThreadSafe and
|
|
243
|
+
# Transaction::Simple::ThreadSafe::Group, respectively.
|
|
244
|
+
#
|
|
245
|
+
# == Contraindications
|
|
246
|
+
# While Transaction::Simple is very useful, it has some severe
|
|
247
|
+
# limitations that must be understood. Transaction::Simple:
|
|
248
|
+
#
|
|
249
|
+
# * uses Marshal. Thus, any object which cannot be <i>Marshal</i>ed
|
|
250
|
+
# cannot use Transaction::Simple. In my experience, this affects
|
|
251
|
+
# singleton objects more often than any other object. It may be that
|
|
252
|
+
# Ruby 2.0 will solve this problem.
|
|
253
|
+
# * does not manage resources. Resources external to the object and its
|
|
254
|
+
# instance variables are not managed at all. However, all instance
|
|
255
|
+
# variables and objects "belonging" to those instance variables are
|
|
256
|
+
# managed. If there are object reference counts to be handled,
|
|
257
|
+
# Transaction::Simple will probably cause problems.
|
|
258
|
+
# * is not inherently thread-safe. In the ACID ("atomic, consistent,
|
|
259
|
+
# isolated, durable") test, Transaction::Simple provides CD, but it is
|
|
260
|
+
# up to the user of Transaction::Simple to provide isolation and
|
|
261
|
+
# atomicity. Transactions should be considered "critical sections" in
|
|
262
|
+
# multi-threaded applications. If thread safety and atomicity is
|
|
263
|
+
# absolutely required, use Transaction::Simple::ThreadSafe, which uses
|
|
264
|
+
# a Mutex object to synchronize the accesses on the object during the
|
|
265
|
+
# transaction operations.
|
|
266
|
+
# * does not necessarily maintain Object#__id__ values on rewind or
|
|
267
|
+
# abort. This may change for future versions that will be Ruby 1.8 or
|
|
268
|
+
# better *only*. Certain objects that support #replace will maintain
|
|
269
|
+
# Object#__id__.
|
|
270
|
+
# * Can be a memory hog if you use many levels of transactions on many
|
|
271
|
+
# objects.
|
|
272
|
+
#
|
|
273
|
+
module Simple
|
|
274
|
+
TRANSACTION_SIMPLE_VERSION = '1.3.0'
|
|
275
|
+
|
|
276
|
+
# Sets the Transaction::Simple debug object. It must respond to #<<.
|
|
277
|
+
# Sets the transaction debug object. Debugging will be performed
|
|
278
|
+
# automatically if there's a debug object. The generic transaction
|
|
279
|
+
# error class.
|
|
280
|
+
def self.debug_io=(io)
|
|
281
|
+
if io.nil?
|
|
282
|
+
@tdi = nil
|
|
283
|
+
@debugging = false
|
|
284
|
+
else
|
|
285
|
+
unless io.respond_to?(:<<)
|
|
286
|
+
raise TransactionError, Messages[:bad_debug_object]
|
|
287
|
+
end
|
|
288
|
+
@tdi = io
|
|
289
|
+
@debugging = true
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
# Returns +true+ if we are debugging.
|
|
294
|
+
def self.debugging?
|
|
295
|
+
@debugging
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Returns the Transaction::Simple debug object. It must respond to
|
|
299
|
+
# #<<.
|
|
300
|
+
def self.debug_io
|
|
301
|
+
@tdi ||= ""
|
|
302
|
+
@tdi
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# If +name+ is +nil+ (default), then returns +true+ if there is
|
|
306
|
+
# currently a transaction open.
|
|
307
|
+
#
|
|
308
|
+
# If +name+ is specified, then returns +true+ if there is currently a
|
|
309
|
+
# transaction that responds to +name+ open.
|
|
310
|
+
def transaction_open?(name = nil)
|
|
311
|
+
if name.nil?
|
|
312
|
+
if Transaction::Simple.debugging?
|
|
313
|
+
Transaction::Simple.debug_io << "Transaction " <<
|
|
314
|
+
"[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
|
|
315
|
+
end
|
|
316
|
+
return (not @__transaction_checkpoint__.nil?)
|
|
317
|
+
else
|
|
318
|
+
if Transaction::Simple.debugging?
|
|
319
|
+
Transaction::Simple.debug_io << "Transaction(#{name.inspect}) " <<
|
|
320
|
+
"[#{(@__transaction_checkpoint__.nil?) ? 'closed' : 'open'}]\n"
|
|
321
|
+
end
|
|
322
|
+
return ((not @__transaction_checkpoint__.nil?) and @__transaction_names__.include?(name))
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
# Returns the current name of the transaction. Transactions not
|
|
327
|
+
# explicitly named are named +nil+.
|
|
328
|
+
def transaction_name
|
|
329
|
+
if @__transaction_checkpoint__.nil?
|
|
330
|
+
raise TransactionError, Messages[:no_transaction_open]
|
|
331
|
+
end
|
|
332
|
+
if Transaction::Simple.debugging?
|
|
333
|
+
Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
|
|
334
|
+
"Transaction Name: #{@__transaction_names__[-1].inspect}\n"
|
|
335
|
+
end
|
|
336
|
+
if @__transaction_names__[-1].kind_of?(String)
|
|
337
|
+
@__transaction_names__[-1].dup
|
|
338
|
+
else
|
|
339
|
+
@__transaction_names__[-1]
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
# Starts a transaction. Stores the current object state. If a
|
|
344
|
+
# transaction name is specified, the transaction will be named.
|
|
345
|
+
# Transaction names must be unique. Transaction names of +nil+ will be
|
|
346
|
+
# treated as unnamed transactions.
|
|
347
|
+
def start_transaction(name = nil)
|
|
348
|
+
@__transaction_level__ ||= 0
|
|
349
|
+
@__transaction_names__ ||= []
|
|
350
|
+
|
|
351
|
+
if name.nil?
|
|
352
|
+
@__transaction_names__ << nil
|
|
353
|
+
ss = "" if Transaction::Simple.debugging?
|
|
354
|
+
else
|
|
355
|
+
if @__transaction_names__.include?(name)
|
|
356
|
+
raise TransactionError, Messages[:unique_names]
|
|
357
|
+
end
|
|
358
|
+
name = name.dup.freeze if name.kind_of?(String)
|
|
359
|
+
@__transaction_names__ << name
|
|
360
|
+
ss = "(#{name.inspect})" if Transaction::Simple.debugging?
|
|
361
|
+
end
|
|
362
|
+
|
|
363
|
+
@__transaction_level__ += 1
|
|
364
|
+
|
|
365
|
+
if Transaction::Simple.debugging?
|
|
366
|
+
Transaction::Simple.debug_io << "#{'>' * @__transaction_level__} " <<
|
|
367
|
+
"Start Transaction#{ss}\n"
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
@__transaction_checkpoint__ = Marshal.dump(self)
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
# Rewinds the transaction. If +name+ is specified, then the
|
|
374
|
+
# intervening transactions will be aborted and the named transaction
|
|
375
|
+
# will be rewound. Otherwise, only the current transaction is rewound.
|
|
376
|
+
def rewind_transaction(name = nil)
|
|
377
|
+
if @__transaction_checkpoint__.nil?
|
|
378
|
+
raise TransactionError, Messages[:cannot_rewind_no_transaction]
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
# Check to see if we are trying to rewind a transaction that is
|
|
382
|
+
# outside of the current transaction block.
|
|
383
|
+
if @__transaction_block__ and name
|
|
384
|
+
nix = @__transaction_names__.index(name) + 1
|
|
385
|
+
if nix < @__transaction_block__
|
|
386
|
+
raise TransactionError, Messages[:cannot_rewind_transaction_before_block]
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
if name.nil?
|
|
391
|
+
__rewind_this_transaction
|
|
392
|
+
ss = "" if Transaction::Simple.debugging?
|
|
393
|
+
else
|
|
394
|
+
unless @__transaction_names__.include?(name)
|
|
395
|
+
raise TransactionError, Messages[:cannot_rewind_named_transaction] % name.inspect
|
|
396
|
+
end
|
|
397
|
+
ss = "(#{name})" if Transaction::Simple.debugging?
|
|
398
|
+
|
|
399
|
+
while @__transaction_names__[-1] != name
|
|
400
|
+
@__transaction_checkpoint__ = __rewind_this_transaction
|
|
401
|
+
if Transaction::Simple.debugging?
|
|
402
|
+
Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
|
|
403
|
+
"Rewind Transaction#{ss}\n"
|
|
404
|
+
end
|
|
405
|
+
@__transaction_level__ -= 1
|
|
406
|
+
@__transaction_names__.pop
|
|
407
|
+
end
|
|
408
|
+
__rewind_this_transaction
|
|
409
|
+
end
|
|
410
|
+
if Transaction::Simple.debugging?
|
|
411
|
+
Transaction::Simple.debug_io << "#{'|' * @__transaction_level__} " <<
|
|
412
|
+
"Rewind Transaction#{ss}\n"
|
|
413
|
+
end
|
|
414
|
+
self
|
|
415
|
+
end
|
|
416
|
+
|
|
417
|
+
# Aborts the transaction. Resets the object state to what it was
|
|
418
|
+
# before the transaction was started and closes the transaction. If
|
|
419
|
+
# +name+ is specified, then the intervening transactions and the named
|
|
420
|
+
# transaction will be aborted. Otherwise, only the current transaction
|
|
421
|
+
# is aborted.
|
|
422
|
+
#
|
|
423
|
+
# If the current or named transaction has been started by a block
|
|
424
|
+
# (Transaction::Simple.start), then the execution of the block will be
|
|
425
|
+
# halted with +break+ +self+.
|
|
426
|
+
def abort_transaction(name = nil)
|
|
427
|
+
if @__transaction_checkpoint__.nil?
|
|
428
|
+
raise TransactionError, Messages[:cannot_abort_no_transaction]
|
|
429
|
+
end
|
|
430
|
+
|
|
431
|
+
# Check to see if we are trying to abort a transaction that is
|
|
432
|
+
# outside of the current transaction block. Otherwise, raise
|
|
433
|
+
# TransactionAborted if they are the same.
|
|
434
|
+
if @__transaction_block__ and name
|
|
435
|
+
nix = @__transaction_names__.index(name) + 1
|
|
436
|
+
if nix < @__transaction_block__
|
|
437
|
+
raise TransactionError, Messages[:cannot_abort_transaction_before_block]
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
raise TransactionAborted if @__transaction_block__ == nix
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
raise TransactionAborted if @__transaction_block__ == @__transaction_level__
|
|
444
|
+
|
|
445
|
+
if name.nil?
|
|
446
|
+
__abort_transaction(name)
|
|
447
|
+
else
|
|
448
|
+
unless @__transaction_names__.include?(name)
|
|
449
|
+
raise TransactionError, Messages[:cannot_abort_named_transaction] % name.inspect
|
|
450
|
+
end
|
|
451
|
+
__abort_transaction(name) while @__transaction_names__.include?(name)
|
|
452
|
+
end
|
|
453
|
+
self
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
# If +name+ is +nil+ (default), the current transaction level is
|
|
457
|
+
# closed out and the changes are committed.
|
|
458
|
+
#
|
|
459
|
+
# If +name+ is specified and +name+ is in the list of named
|
|
460
|
+
# transactions, then all transactions are closed and committed until
|
|
461
|
+
# the named transaction is reached.
|
|
462
|
+
def commit_transaction(name = nil)
|
|
463
|
+
if @__transaction_checkpoint__.nil?
|
|
464
|
+
raise TransactionError, Messages[:cannot_commit_no_transaction]
|
|
465
|
+
end
|
|
466
|
+
@__transaction_block__ ||= nil
|
|
467
|
+
|
|
468
|
+
# Check to see if we are trying to commit a transaction that is
|
|
469
|
+
# outside of the current transaction block. Otherwise, raise
|
|
470
|
+
# TransactionCommitted if they are the same.
|
|
471
|
+
if @__transaction_block__ and name
|
|
472
|
+
nix = @__transaction_names__.index(name) + 1
|
|
473
|
+
if nix < @__transaction_block__
|
|
474
|
+
raise TransactionError, Messages[:cannot_commit_transaction_before_block]
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
raise TransactionCommitted if @__transaction_block__ == nix
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
raise TransactionCommitted if @__transaction_block__ == @__transaction_level__
|
|
481
|
+
|
|
482
|
+
if name.nil?
|
|
483
|
+
ss = "" if Transaction::Simple.debugging?
|
|
484
|
+
__commit_transaction
|
|
485
|
+
if Transaction::Simple.debugging?
|
|
486
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
|
|
487
|
+
"Commit Transaction#{ss}\n"
|
|
488
|
+
end
|
|
489
|
+
else
|
|
490
|
+
unless @__transaction_names__.include?(name)
|
|
491
|
+
raise TransactionError, Messages[:cannot_commit_named_transaction] % name.inspect
|
|
492
|
+
end
|
|
493
|
+
ss = "(#{name})" if Transaction::Simple.debugging?
|
|
494
|
+
|
|
495
|
+
while @__transaction_names__[-1] != name
|
|
496
|
+
if Transaction::Simple.debugging?
|
|
497
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
|
|
498
|
+
"Commit Transaction#{ss}\n"
|
|
499
|
+
end
|
|
500
|
+
__commit_transaction
|
|
501
|
+
end
|
|
502
|
+
if Transaction::Simple.debugging?
|
|
503
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
|
|
504
|
+
"Commit Transaction#{ss}\n"
|
|
505
|
+
end
|
|
506
|
+
__commit_transaction
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
self
|
|
510
|
+
end
|
|
511
|
+
|
|
512
|
+
# Alternative method for calling the transaction methods. An optional
|
|
513
|
+
# name can be specified for named transaction support.
|
|
514
|
+
#
|
|
515
|
+
# #transaction(:start):: #start_transaction
|
|
516
|
+
# #transaction(:rewind):: #rewind_transaction
|
|
517
|
+
# #transaction(:abort):: #abort_transaction
|
|
518
|
+
# #transaction(:commit):: #commit_transaction
|
|
519
|
+
# #transaction(:name):: #transaction_name
|
|
520
|
+
# #transaction:: #transaction_open?
|
|
521
|
+
def transaction(action = nil, name = nil)
|
|
522
|
+
case action
|
|
523
|
+
when :start
|
|
524
|
+
start_transaction(name)
|
|
525
|
+
when :rewind
|
|
526
|
+
rewind_transaction(name)
|
|
527
|
+
when :abort
|
|
528
|
+
abort_transaction(name)
|
|
529
|
+
when :commit
|
|
530
|
+
commit_transaction(name)
|
|
531
|
+
when :name
|
|
532
|
+
transaction_name
|
|
533
|
+
when nil
|
|
534
|
+
transaction_open?(name)
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# Allows specific variables to be excluded from transaction support.
|
|
539
|
+
# Must be done after extending the object but before starting the
|
|
540
|
+
# first transaction on the object.
|
|
541
|
+
#
|
|
542
|
+
# vv.transaction_exclusions << "@io"
|
|
543
|
+
def transaction_exclusions
|
|
544
|
+
@transaction_exclusions ||= []
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
class << self
|
|
548
|
+
def __common_start(name, vars, &block)
|
|
549
|
+
if vars.empty?
|
|
550
|
+
raise TransactionError, Messages[:cannot_start_empty_block_transaction]
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
if block
|
|
554
|
+
begin
|
|
555
|
+
vlevel = {}
|
|
556
|
+
|
|
557
|
+
vars.each do |vv|
|
|
558
|
+
vv.extend(Transaction::Simple)
|
|
559
|
+
vv.start_transaction(name)
|
|
560
|
+
vlevel[vv.__id__] = vv.instance_variable_get(:@__transaction_level__)
|
|
561
|
+
vv.instance_variable_set(:@__transaction_block__, vlevel[vv.__id__])
|
|
562
|
+
end
|
|
563
|
+
|
|
564
|
+
yield(*vars)
|
|
565
|
+
rescue TransactionAborted
|
|
566
|
+
vars.each do |vv|
|
|
567
|
+
if name.nil? and vv.transaction_open?
|
|
568
|
+
loop do
|
|
569
|
+
tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
|
|
570
|
+
vv.instance_variable_set(:@__transaction_block__, -1)
|
|
571
|
+
break if tlevel < vlevel[vv.__id__]
|
|
572
|
+
vv.abort_transaction if vv.transaction_open?
|
|
573
|
+
end
|
|
574
|
+
elsif vv.transaction_open?(name)
|
|
575
|
+
vv.instance_variable_set(:@__transaction_block__, -1)
|
|
576
|
+
vv.abort_transaction(name)
|
|
577
|
+
end
|
|
578
|
+
end
|
|
579
|
+
rescue TransactionCommitted
|
|
580
|
+
nil
|
|
581
|
+
ensure
|
|
582
|
+
vars.each do |vv|
|
|
583
|
+
if name.nil? and vv.transaction_open?
|
|
584
|
+
loop do
|
|
585
|
+
tlevel = vv.instance_variable_get(:@__transaction_level__) || -1
|
|
586
|
+
break if tlevel < vlevel[vv.__id__]
|
|
587
|
+
vv.instance_variable_set(:@__transaction_block__, -1)
|
|
588
|
+
vv.commit_transaction if vv.transaction_open?
|
|
589
|
+
end
|
|
590
|
+
elsif vv.transaction_open?(name)
|
|
591
|
+
vv.instance_variable_set(:@__transaction_block__, -1)
|
|
592
|
+
vv.commit_transaction(name)
|
|
593
|
+
end
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
else
|
|
597
|
+
vars.each do |vv|
|
|
598
|
+
vv.extend(Transaction::Simple)
|
|
599
|
+
vv.start_transaction(name)
|
|
600
|
+
end
|
|
601
|
+
end
|
|
602
|
+
end
|
|
603
|
+
private :__common_start
|
|
604
|
+
|
|
605
|
+
def start_named(name, *vars, &block)
|
|
606
|
+
__common_start(name, vars, &block)
|
|
607
|
+
end
|
|
608
|
+
|
|
609
|
+
def start(*vars, &block)
|
|
610
|
+
__common_start(nil, vars, &block)
|
|
611
|
+
end
|
|
612
|
+
end
|
|
613
|
+
|
|
614
|
+
def __abort_transaction(name = nil) #:nodoc:
|
|
615
|
+
@__transaction_checkpoint__ = __rewind_this_transaction
|
|
616
|
+
|
|
617
|
+
if name.nil?
|
|
618
|
+
ss = "" if Transaction::Simple.debugging?
|
|
619
|
+
else
|
|
620
|
+
ss = "(#{name.inspect})" if Transaction::Simple.debugging?
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
if Transaction::Simple.debugging?
|
|
624
|
+
Transaction::Simple.debug_io << "#{'<' * @__transaction_level__} " <<
|
|
625
|
+
"Abort Transaction#{ss}\n"
|
|
626
|
+
end
|
|
627
|
+
@__transaction_level__ -= 1
|
|
628
|
+
@__transaction_names__.pop
|
|
629
|
+
if @__transaction_level__ < 1
|
|
630
|
+
@__transaction_level__ = 0
|
|
631
|
+
@__transaction_names__ = []
|
|
632
|
+
end
|
|
633
|
+
end
|
|
634
|
+
|
|
635
|
+
TRANSACTION_CHECKPOINT = "@__transaction_checkpoint__" #:nodoc:
|
|
636
|
+
SKIP_TRANSACTION_VARS = [TRANSACTION_CHECKPOINT, "@__transaction_level__"] #:nodoc:
|
|
637
|
+
|
|
638
|
+
def __rewind_this_transaction #:nodoc:
|
|
639
|
+
rr = Marshal.restore(@__transaction_checkpoint__)
|
|
640
|
+
|
|
641
|
+
begin
|
|
642
|
+
self.replace(rr) if respond_to?(:replace)
|
|
643
|
+
rescue
|
|
644
|
+
nil
|
|
645
|
+
end
|
|
646
|
+
|
|
647
|
+
rr.instance_variables.each do |vv|
|
|
648
|
+
next if SKIP_TRANSACTION_VARS.include?(vv)
|
|
649
|
+
next if self.transaction_exclusions.include?(vv)
|
|
650
|
+
if respond_to?(:instance_variable_get)
|
|
651
|
+
instance_variable_set(vv, rr.instance_variable_get(vv))
|
|
652
|
+
else
|
|
653
|
+
instance_eval(%q|#{vv} = rr.instance_eval("#{vv}")|)
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
new_ivar = instance_variables - rr.instance_variables - SKIP_TRANSACTION_VARS
|
|
658
|
+
new_ivar.each do |vv|
|
|
659
|
+
if respond_to?(:instance_variable_set)
|
|
660
|
+
instance_variable_set(vv, nil)
|
|
661
|
+
else
|
|
662
|
+
instance_eval(%q|#{vv} = nil|)
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
if respond_to?(:instance_variable_get)
|
|
667
|
+
rr.instance_variable_get(TRANSACTION_CHECKPOINT)
|
|
668
|
+
else
|
|
669
|
+
rr.instance_eval(TRANSACTION_CHECKPOINT)
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
def __commit_transaction #:nodoc:
|
|
674
|
+
if respond_to?(:instance_variable_get)
|
|
675
|
+
@__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_variable_get(TRANSACTION_CHECKPOINT)
|
|
676
|
+
else
|
|
677
|
+
@__transaction_checkpoint__ = Marshal.restore(@__transaction_checkpoint__).instance_eval(TRANSACTION_CHECKPOINT)
|
|
678
|
+
end
|
|
679
|
+
|
|
680
|
+
@__transaction_level__ -= 1
|
|
681
|
+
@__transaction_names__.pop
|
|
682
|
+
|
|
683
|
+
if @__transaction_level__ < 1
|
|
684
|
+
@__transaction_level__ = 0
|
|
685
|
+
@__transaction_names__ = []
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
private :__abort_transaction
|
|
690
|
+
private :__rewind_this_transaction
|
|
691
|
+
private :__commit_transaction
|
|
692
|
+
end
|
|
693
|
+
end
|