activerecord_csi 2.3.5.p6
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 +5858 -0
- data/README +351 -0
- data/RUNNING_UNIT_TESTS +36 -0
- data/Rakefile +270 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +162 -0
- data/install.rb +30 -0
- data/lib/active_record/aggregations.rb +261 -0
- data/lib/active_record/association_preload.rb +389 -0
- data/lib/active_record/associations/association_collection.rb +475 -0
- data/lib/active_record/associations/association_proxy.rb +278 -0
- data/lib/active_record/associations/belongs_to_association.rb +76 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +53 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
- data/lib/active_record/associations/has_many_association.rb +122 -0
- data/lib/active_record/associations/has_many_through_association.rb +266 -0
- data/lib/active_record/associations/has_one_association.rb +133 -0
- data/lib/active_record/associations/has_one_through_association.rb +37 -0
- data/lib/active_record/associations.rb +2241 -0
- data/lib/active_record/attribute_methods.rb +388 -0
- data/lib/active_record/autosave_association.rb +364 -0
- data/lib/active_record/base.rb +3171 -0
- data/lib/active_record/batches.rb +81 -0
- data/lib/active_record/calculations.rb +311 -0
- data/lib/active_record/callbacks.rb +360 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +371 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +139 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +289 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +94 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +722 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +434 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +241 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +630 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1113 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +453 -0
- data/lib/active_record/dirty.rb +183 -0
- data/lib/active_record/dynamic_finder_match.rb +41 -0
- data/lib/active_record/dynamic_scope_match.rb +25 -0
- data/lib/active_record/fixtures.rb +996 -0
- data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
- data/lib/active_record/locale/en.yml +58 -0
- data/lib/active_record/locking/optimistic.rb +148 -0
- data/lib/active_record/locking/pessimistic.rb +55 -0
- data/lib/active_record/migration.rb +566 -0
- data/lib/active_record/named_scope.rb +192 -0
- data/lib/active_record/nested_attributes.rb +392 -0
- data/lib/active_record/observer.rb +197 -0
- data/lib/active_record/query_cache.rb +33 -0
- data/lib/active_record/reflection.rb +320 -0
- data/lib/active_record/schema.rb +51 -0
- data/lib/active_record/schema_dumper.rb +182 -0
- data/lib/active_record/serialization.rb +101 -0
- data/lib/active_record/serializers/json_serializer.rb +91 -0
- data/lib/active_record/serializers/xml_serializer.rb +357 -0
- data/lib/active_record/session_store.rb +326 -0
- data/lib/active_record/test_case.rb +66 -0
- data/lib/active_record/timestamp.rb +71 -0
- data/lib/active_record/transactions.rb +235 -0
- data/lib/active_record/validations.rb +1135 -0
- data/lib/active_record/version.rb +9 -0
- data/lib/active_record.rb +84 -0
- data/lib/activerecord.rb +2 -0
- data/test/assets/example.log +1 -0
- data/test/assets/flowers.jpg +0 -0
- data/test/cases/aaa_create_tables_test.rb +24 -0
- data/test/cases/active_schema_test_mysql.rb +100 -0
- data/test/cases/active_schema_test_postgresql.rb +24 -0
- data/test/cases/adapter_test.rb +145 -0
- data/test/cases/aggregations_test.rb +167 -0
- data/test/cases/ar_schema_test.rb +32 -0
- data/test/cases/associations/belongs_to_associations_test.rb +425 -0
- data/test/cases/associations/callbacks_test.rb +161 -0
- data/test/cases/associations/cascaded_eager_loading_test.rb +131 -0
- data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +130 -0
- data/test/cases/associations/eager_singularization_test.rb +145 -0
- data/test/cases/associations/eager_test.rb +834 -0
- data/test/cases/associations/extension_test.rb +62 -0
- data/test/cases/associations/habtm_join_table_test.rb +56 -0
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +822 -0
- data/test/cases/associations/has_many_associations_test.rb +1134 -0
- data/test/cases/associations/has_many_through_associations_test.rb +346 -0
- data/test/cases/associations/has_one_associations_test.rb +330 -0
- data/test/cases/associations/has_one_through_associations_test.rb +209 -0
- data/test/cases/associations/inner_join_association_test.rb +93 -0
- data/test/cases/associations/join_model_test.rb +712 -0
- data/test/cases/associations_test.rb +262 -0
- data/test/cases/attribute_methods_test.rb +305 -0
- data/test/cases/autosave_association_test.rb +1142 -0
- data/test/cases/base_test.rb +2154 -0
- data/test/cases/batches_test.rb +61 -0
- data/test/cases/binary_test.rb +30 -0
- data/test/cases/calculations_test.rb +348 -0
- data/test/cases/callbacks_observers_test.rb +38 -0
- data/test/cases/callbacks_test.rb +438 -0
- data/test/cases/class_inheritable_attributes_test.rb +32 -0
- data/test/cases/column_alias_test.rb +17 -0
- data/test/cases/column_definition_test.rb +70 -0
- data/test/cases/connection_pool_test.rb +25 -0
- data/test/cases/connection_test_firebird.rb +8 -0
- data/test/cases/connection_test_mysql.rb +64 -0
- data/test/cases/copy_table_test_sqlite.rb +80 -0
- data/test/cases/database_statements_test.rb +12 -0
- data/test/cases/datatype_test_postgresql.rb +204 -0
- data/test/cases/date_time_test.rb +37 -0
- data/test/cases/default_test_firebird.rb +16 -0
- data/test/cases/defaults_test.rb +111 -0
- data/test/cases/deprecated_finder_test.rb +30 -0
- data/test/cases/dirty_test.rb +316 -0
- data/test/cases/finder_respond_to_test.rb +76 -0
- data/test/cases/finder_test.rb +1066 -0
- data/test/cases/fixtures_test.rb +656 -0
- data/test/cases/helper.rb +68 -0
- data/test/cases/i18n_test.rb +46 -0
- data/test/cases/inheritance_test.rb +262 -0
- data/test/cases/invalid_date_test.rb +24 -0
- data/test/cases/json_serialization_test.rb +205 -0
- data/test/cases/lifecycle_test.rb +193 -0
- data/test/cases/locking_test.rb +304 -0
- data/test/cases/method_scoping_test.rb +704 -0
- data/test/cases/migration_test.rb +1523 -0
- data/test/cases/migration_test_firebird.rb +124 -0
- data/test/cases/mixin_test.rb +96 -0
- data/test/cases/modules_test.rb +81 -0
- data/test/cases/multiple_db_test.rb +85 -0
- data/test/cases/named_scope_test.rb +361 -0
- data/test/cases/nested_attributes_test.rb +581 -0
- data/test/cases/pk_test.rb +119 -0
- data/test/cases/pooled_connections_test.rb +103 -0
- data/test/cases/query_cache_test.rb +123 -0
- data/test/cases/readonly_test.rb +107 -0
- data/test/cases/reflection_test.rb +194 -0
- data/test/cases/reload_models_test.rb +22 -0
- data/test/cases/repair_helper.rb +50 -0
- data/test/cases/reserved_word_test_mysql.rb +176 -0
- data/test/cases/sanitize_test.rb +25 -0
- data/test/cases/schema_authorization_test_postgresql.rb +75 -0
- data/test/cases/schema_dumper_test.rb +211 -0
- data/test/cases/schema_test_postgresql.rb +178 -0
- data/test/cases/serialization_test.rb +47 -0
- data/test/cases/synonym_test_oracle.rb +17 -0
- data/test/cases/timestamp_test.rb +75 -0
- data/test/cases/transactions_test.rb +522 -0
- data/test/cases/unconnected_test.rb +32 -0
- data/test/cases/validations_i18n_test.rb +955 -0
- data/test/cases/validations_test.rb +1640 -0
- data/test/cases/xml_serialization_test.rb +240 -0
- data/test/config.rb +5 -0
- data/test/connections/jdbc_jdbcderby/connection.rb +18 -0
- data/test/connections/jdbc_jdbch2/connection.rb +18 -0
- data/test/connections/jdbc_jdbchsqldb/connection.rb +18 -0
- data/test/connections/jdbc_jdbcmysql/connection.rb +26 -0
- data/test/connections/jdbc_jdbcpostgresql/connection.rb +26 -0
- data/test/connections/jdbc_jdbcsqlite3/connection.rb +25 -0
- data/test/connections/native_db2/connection.rb +25 -0
- data/test/connections/native_firebird/connection.rb +26 -0
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +25 -0
- data/test/connections/native_openbase/connection.rb +21 -0
- data/test/connections/native_oracle/connection.rb +27 -0
- data/test/connections/native_postgresql/connection.rb +25 -0
- data/test/connections/native_sqlite/connection.rb +25 -0
- data/test/connections/native_sqlite3/connection.rb +25 -0
- data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
- data/test/connections/native_sybase/connection.rb +23 -0
- data/test/fixtures/accounts.yml +29 -0
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author_addresses.yml +5 -0
- data/test/fixtures/author_favorites.yml +4 -0
- data/test/fixtures/authors.yml +9 -0
- data/test/fixtures/binaries.yml +132 -0
- data/test/fixtures/books.yml +7 -0
- data/test/fixtures/categories/special_categories.yml +9 -0
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
- data/test/fixtures/categories.yml +14 -0
- data/test/fixtures/categories_ordered.yml +7 -0
- data/test/fixtures/categories_posts.yml +23 -0
- data/test/fixtures/categorizations.yml +17 -0
- data/test/fixtures/clubs.yml +6 -0
- data/test/fixtures/comments.yml +59 -0
- data/test/fixtures/companies.yml +56 -0
- data/test/fixtures/computers.yml +4 -0
- data/test/fixtures/courses.yml +7 -0
- data/test/fixtures/customers.yml +26 -0
- data/test/fixtures/developers.yml +21 -0
- data/test/fixtures/developers_projects.yml +17 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/entrants.yml +14 -0
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/fixtures/fk_test_has_fk.yml +3 -0
- data/test/fixtures/fk_test_has_pk.yml +2 -0
- data/test/fixtures/funny_jokes.yml +10 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/jobs.yml +7 -0
- data/test/fixtures/legacy_things.yml +3 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/member_types.yml +6 -0
- data/test/fixtures/members.yml +6 -0
- data/test/fixtures/memberships.yml +20 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixed_case_monkeys.yml +6 -0
- data/test/fixtures/mixins.yml +29 -0
- data/test/fixtures/movies.yml +7 -0
- data/test/fixtures/naked/csv/accounts.csv +1 -0
- data/test/fixtures/naked/yml/accounts.yml +1 -0
- data/test/fixtures/naked/yml/companies.yml +1 -0
- data/test/fixtures/naked/yml/courses.yml +1 -0
- data/test/fixtures/organizations.yml +5 -0
- data/test/fixtures/owners.yml +7 -0
- data/test/fixtures/parrots.yml +27 -0
- data/test/fixtures/parrots_pirates.yml +7 -0
- data/test/fixtures/people.yml +15 -0
- data/test/fixtures/pets.yml +14 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/posts.yml +52 -0
- data/test/fixtures/price_estimates.yml +7 -0
- data/test/fixtures/projects.yml +7 -0
- data/test/fixtures/readers.yml +9 -0
- data/test/fixtures/references.yml +17 -0
- data/test/fixtures/reserved_words/distinct.yml +5 -0
- data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
- data/test/fixtures/reserved_words/group.yml +14 -0
- data/test/fixtures/reserved_words/select.yml +8 -0
- data/test/fixtures/reserved_words/values.yml +7 -0
- data/test/fixtures/ships.yml +5 -0
- data/test/fixtures/sponsors.yml +9 -0
- data/test/fixtures/subscribers.yml +7 -0
- data/test/fixtures/subscriptions.yml +12 -0
- data/test/fixtures/taggings.yml +28 -0
- data/test/fixtures/tags.yml +7 -0
- data/test/fixtures/tasks.yml +7 -0
- data/test/fixtures/topics.yml +42 -0
- data/test/fixtures/toys.yml +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures/warehouse-things.yml +3 -0
- data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
- data/test/migrations/decimal/1_give_me_big_numbers.rb +15 -0
- data/test/migrations/duplicate/1_people_have_last_names.rb +9 -0
- data/test/migrations/duplicate/2_we_need_reminders.rb +12 -0
- data/test/migrations/duplicate/3_foo.rb +7 -0
- data/test/migrations/duplicate/3_innocent_jointable.rb +12 -0
- data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
- data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
- data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +12 -0
- data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +9 -0
- data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +12 -0
- data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +9 -0
- data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +8 -0
- data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +12 -0
- data/test/migrations/missing/1000_people_have_middle_names.rb +9 -0
- data/test/migrations/missing/1_people_have_last_names.rb +9 -0
- data/test/migrations/missing/3_we_need_reminders.rb +12 -0
- data/test/migrations/missing/4_innocent_jointable.rb +12 -0
- data/test/migrations/valid/1_people_have_last_names.rb +9 -0
- data/test/migrations/valid/2_we_need_reminders.rb +12 -0
- data/test/migrations/valid/3_innocent_jointable.rb +12 -0
- data/test/models/author.rb +146 -0
- data/test/models/auto_id.rb +4 -0
- data/test/models/binary.rb +2 -0
- data/test/models/bird.rb +3 -0
- data/test/models/book.rb +4 -0
- data/test/models/categorization.rb +5 -0
- data/test/models/category.rb +34 -0
- data/test/models/citation.rb +6 -0
- data/test/models/club.rb +13 -0
- data/test/models/column_name.rb +3 -0
- data/test/models/comment.rb +29 -0
- data/test/models/company.rb +171 -0
- data/test/models/company_in_module.rb +61 -0
- data/test/models/computer.rb +3 -0
- data/test/models/contact.rb +16 -0
- data/test/models/contract.rb +5 -0
- data/test/models/course.rb +3 -0
- data/test/models/customer.rb +73 -0
- data/test/models/default.rb +2 -0
- data/test/models/developer.rb +101 -0
- data/test/models/edge.rb +5 -0
- data/test/models/entrant.rb +3 -0
- data/test/models/essay.rb +3 -0
- data/test/models/event.rb +3 -0
- data/test/models/guid.rb +2 -0
- data/test/models/item.rb +7 -0
- data/test/models/job.rb +5 -0
- data/test/models/joke.rb +3 -0
- data/test/models/keyboard.rb +3 -0
- data/test/models/legacy_thing.rb +3 -0
- data/test/models/matey.rb +4 -0
- data/test/models/member.rb +12 -0
- data/test/models/member_detail.rb +5 -0
- data/test/models/member_type.rb +3 -0
- data/test/models/membership.rb +9 -0
- data/test/models/minimalistic.rb +2 -0
- data/test/models/mixed_case_monkey.rb +3 -0
- data/test/models/movie.rb +5 -0
- data/test/models/order.rb +4 -0
- data/test/models/organization.rb +6 -0
- data/test/models/owner.rb +5 -0
- data/test/models/parrot.rb +16 -0
- data/test/models/person.rb +16 -0
- data/test/models/pet.rb +5 -0
- data/test/models/pirate.rb +70 -0
- data/test/models/post.rb +100 -0
- data/test/models/price_estimate.rb +3 -0
- data/test/models/project.rb +30 -0
- data/test/models/reader.rb +4 -0
- data/test/models/reference.rb +4 -0
- data/test/models/reply.rb +46 -0
- data/test/models/ship.rb +10 -0
- data/test/models/ship_part.rb +5 -0
- data/test/models/sponsor.rb +4 -0
- data/test/models/subject.rb +4 -0
- data/test/models/subscriber.rb +8 -0
- data/test/models/subscription.rb +4 -0
- data/test/models/tag.rb +7 -0
- data/test/models/tagging.rb +10 -0
- data/test/models/task.rb +3 -0
- data/test/models/topic.rb +80 -0
- data/test/models/toy.rb +6 -0
- data/test/models/treasure.rb +8 -0
- data/test/models/vertex.rb +9 -0
- data/test/models/warehouse_thing.rb +5 -0
- data/test/schema/mysql_specific_schema.rb +24 -0
- data/test/schema/postgresql_specific_schema.rb +114 -0
- data/test/schema/schema.rb +493 -0
- data/test/schema/schema2.rb +6 -0
- data/test/schema/sqlite_specific_schema.rb +25 -0
- metadata +420 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Deprecates the use of the former message interpolation syntax in activerecord
|
|
2
|
+
# as in "must have %d characters". The new syntax uses explicit variable names
|
|
3
|
+
# as in "{{value}} must have {{count}} characters".
|
|
4
|
+
|
|
5
|
+
require 'i18n/backend/simple'
|
|
6
|
+
module I18n
|
|
7
|
+
module Backend
|
|
8
|
+
class Simple
|
|
9
|
+
DEPRECATED_INTERPOLATORS = { '%d' => '{{count}}', '%s' => '{{value}}' }
|
|
10
|
+
|
|
11
|
+
protected
|
|
12
|
+
def interpolate_with_deprecated_syntax(locale, string, values = {})
|
|
13
|
+
return string unless string.is_a?(String) && !values.empty?
|
|
14
|
+
|
|
15
|
+
string = string.gsub(/%d|%s/) do |s|
|
|
16
|
+
instead = DEPRECATED_INTERPOLATORS[s]
|
|
17
|
+
ActiveSupport::Deprecation.warn "using #{s} in messages is deprecated; use #{instead} instead."
|
|
18
|
+
instead
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
interpolate_without_deprecated_syntax(locale, string, values)
|
|
22
|
+
end
|
|
23
|
+
alias_method_chain :interpolate, :deprecated_syntax
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
en:
|
|
2
|
+
activerecord:
|
|
3
|
+
errors:
|
|
4
|
+
# The values :model, :attribute and :value are always available for interpolation
|
|
5
|
+
# The value :count is available when applicable. Can be used for pluralization.
|
|
6
|
+
messages:
|
|
7
|
+
inclusion: "is not included in the list"
|
|
8
|
+
exclusion: "is reserved"
|
|
9
|
+
invalid: "is invalid"
|
|
10
|
+
confirmation: "doesn't match confirmation"
|
|
11
|
+
accepted: "must be accepted"
|
|
12
|
+
empty: "can't be empty"
|
|
13
|
+
blank: "can't be blank"
|
|
14
|
+
too_long: "is too long (maximum is {{count}} characters)"
|
|
15
|
+
too_short: "is too short (minimum is {{count}} characters)"
|
|
16
|
+
wrong_length: "is the wrong length (should be {{count}} characters)"
|
|
17
|
+
taken: "has already been taken"
|
|
18
|
+
not_a_number: "is not a number"
|
|
19
|
+
greater_than: "must be greater than {{count}}"
|
|
20
|
+
greater_than_or_equal_to: "must be greater than or equal to {{count}}"
|
|
21
|
+
equal_to: "must be equal to {{count}}"
|
|
22
|
+
less_than: "must be less than {{count}}"
|
|
23
|
+
less_than_or_equal_to: "must be less than or equal to {{count}}"
|
|
24
|
+
odd: "must be odd"
|
|
25
|
+
even: "must be even"
|
|
26
|
+
record_invalid: "Validation failed: {{errors}}"
|
|
27
|
+
# Append your own errors here or at the model/attributes scope.
|
|
28
|
+
|
|
29
|
+
full_messages:
|
|
30
|
+
format: "{{attribute}} {{message}}"
|
|
31
|
+
|
|
32
|
+
# You can define own errors for models or model attributes.
|
|
33
|
+
# The values :model, :attribute and :value are always available for interpolation.
|
|
34
|
+
#
|
|
35
|
+
# For example,
|
|
36
|
+
# models:
|
|
37
|
+
# user:
|
|
38
|
+
# blank: "This is a custom blank message for {{model}}: {{attribute}}"
|
|
39
|
+
# attributes:
|
|
40
|
+
# login:
|
|
41
|
+
# blank: "This is a custom blank message for User login"
|
|
42
|
+
# Will define custom blank validation message for User model and
|
|
43
|
+
# custom blank validation message for login attribute of User model.
|
|
44
|
+
#models:
|
|
45
|
+
|
|
46
|
+
# Translate model names. Used in Model.human_name().
|
|
47
|
+
#models:
|
|
48
|
+
# For example,
|
|
49
|
+
# user: "Dude"
|
|
50
|
+
# will translate User model name to "Dude"
|
|
51
|
+
|
|
52
|
+
# Translate model attribute names. Used in Model.human_attribute_name(attribute).
|
|
53
|
+
#attributes:
|
|
54
|
+
# For example,
|
|
55
|
+
# user:
|
|
56
|
+
# login: "Handle"
|
|
57
|
+
# will translate User attribute "login" as "Handle"
|
|
58
|
+
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module Locking
|
|
3
|
+
# == What is Optimistic Locking
|
|
4
|
+
#
|
|
5
|
+
# Optimistic locking allows multiple users to access the same record for edits, and assumes a minimum of
|
|
6
|
+
# conflicts with the data. It does this by checking whether another process has made changes to a record since
|
|
7
|
+
# it was opened, an ActiveRecord::StaleObjectError is thrown if that has occurred and the update is ignored.
|
|
8
|
+
#
|
|
9
|
+
# Check out ActiveRecord::Locking::Pessimistic for an alternative.
|
|
10
|
+
#
|
|
11
|
+
# == Usage
|
|
12
|
+
#
|
|
13
|
+
# Active Records support optimistic locking if the field <tt>lock_version</tt> is present. Each update to the
|
|
14
|
+
# record increments the lock_version column and the locking facilities ensure that records instantiated twice
|
|
15
|
+
# will let the last one saved raise a StaleObjectError if the first was also updated. Example:
|
|
16
|
+
#
|
|
17
|
+
# p1 = Person.find(1)
|
|
18
|
+
# p2 = Person.find(1)
|
|
19
|
+
#
|
|
20
|
+
# p1.first_name = "Michael"
|
|
21
|
+
# p1.save
|
|
22
|
+
#
|
|
23
|
+
# p2.first_name = "should fail"
|
|
24
|
+
# p2.save # Raises a ActiveRecord::StaleObjectError
|
|
25
|
+
#
|
|
26
|
+
# You're then responsible for dealing with the conflict by rescuing the exception and either rolling back, merging,
|
|
27
|
+
# or otherwise apply the business logic needed to resolve the conflict.
|
|
28
|
+
#
|
|
29
|
+
# You must ensure that your database schema defaults the lock_version column to 0.
|
|
30
|
+
#
|
|
31
|
+
# This behavior can be turned off by setting <tt>ActiveRecord::Base.lock_optimistically = false</tt>.
|
|
32
|
+
# To override the name of the lock_version column, invoke the <tt>set_locking_column</tt> method.
|
|
33
|
+
# This method uses the same syntax as <tt>set_table_name</tt>
|
|
34
|
+
module Optimistic
|
|
35
|
+
def self.included(base) #:nodoc:
|
|
36
|
+
base.extend ClassMethods
|
|
37
|
+
|
|
38
|
+
base.cattr_accessor :lock_optimistically, :instance_writer => false
|
|
39
|
+
base.lock_optimistically = true
|
|
40
|
+
|
|
41
|
+
base.alias_method_chain :update, :lock
|
|
42
|
+
base.alias_method_chain :attributes_from_column_definition, :lock
|
|
43
|
+
|
|
44
|
+
class << base
|
|
45
|
+
alias_method :locking_column=, :set_locking_column
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def locking_enabled? #:nodoc:
|
|
50
|
+
self.class.locking_enabled?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
def attributes_from_column_definition_with_lock
|
|
55
|
+
result = attributes_from_column_definition_without_lock
|
|
56
|
+
|
|
57
|
+
# If the locking column has no default value set,
|
|
58
|
+
# start the lock version at zero. Note we can't use
|
|
59
|
+
# locking_enabled? at this point as @attributes may
|
|
60
|
+
# not have been initialized yet
|
|
61
|
+
|
|
62
|
+
if lock_optimistically && result.include?(self.class.locking_column)
|
|
63
|
+
result[self.class.locking_column] ||= 0
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
return result
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
|
|
70
|
+
return update_without_lock(attribute_names) unless locking_enabled?
|
|
71
|
+
return 0 if attribute_names.empty?
|
|
72
|
+
|
|
73
|
+
lock_col = self.class.locking_column
|
|
74
|
+
previous_value = send(lock_col).to_i
|
|
75
|
+
send(lock_col + '=', previous_value + 1)
|
|
76
|
+
|
|
77
|
+
attribute_names += [lock_col]
|
|
78
|
+
attribute_names.uniq!
|
|
79
|
+
|
|
80
|
+
begin
|
|
81
|
+
affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
|
|
82
|
+
UPDATE #{self.class.quoted_table_name}
|
|
83
|
+
SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false, false, attribute_names))}
|
|
84
|
+
WHERE #{self.class.primary_key} = #{quote_value(id)}
|
|
85
|
+
AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}
|
|
86
|
+
end_sql
|
|
87
|
+
|
|
88
|
+
unless affected_rows == 1
|
|
89
|
+
raise ActiveRecord::StaleObjectError, "Attempted to update a stale object"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
affected_rows
|
|
93
|
+
|
|
94
|
+
# If something went wrong, revert the version.
|
|
95
|
+
rescue Exception
|
|
96
|
+
send(lock_col + '=', previous_value)
|
|
97
|
+
raise
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
module ClassMethods
|
|
102
|
+
DEFAULT_LOCKING_COLUMN = 'lock_version'
|
|
103
|
+
|
|
104
|
+
def self.extended(base)
|
|
105
|
+
class <<base
|
|
106
|
+
alias_method_chain :update_counters, :lock
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Is optimistic locking enabled for this table? Returns true if the
|
|
111
|
+
# +lock_optimistically+ flag is set to true (which it is, by default)
|
|
112
|
+
# and the table includes the +locking_column+ column (defaults to
|
|
113
|
+
# +lock_version+).
|
|
114
|
+
def locking_enabled?
|
|
115
|
+
lock_optimistically && columns_hash[locking_column]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Set the column to use for optimistic locking. Defaults to +lock_version+.
|
|
119
|
+
def set_locking_column(value = nil, &block)
|
|
120
|
+
define_attr_method :locking_column, value, &block
|
|
121
|
+
value
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The version column used for optimistic locking. Defaults to +lock_version+.
|
|
125
|
+
def locking_column
|
|
126
|
+
reset_locking_column
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Quote the column name used for optimistic locking.
|
|
130
|
+
def quoted_locking_column
|
|
131
|
+
connection.quote_column_name(locking_column)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Reset the column used for optimistic locking back to the +lock_version+ default.
|
|
135
|
+
def reset_locking_column
|
|
136
|
+
set_locking_column DEFAULT_LOCKING_COLUMN
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Make sure the lock version column gets updated when counters are
|
|
140
|
+
# updated.
|
|
141
|
+
def update_counters_with_lock(id, counters)
|
|
142
|
+
counters = counters.merge(locking_column => 1) if locking_enabled?
|
|
143
|
+
update_counters_without_lock(id, counters)
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module Locking
|
|
3
|
+
# Locking::Pessimistic provides support for row-level locking using
|
|
4
|
+
# SELECT ... FOR UPDATE and other lock types.
|
|
5
|
+
#
|
|
6
|
+
# Pass <tt>:lock => true</tt> to ActiveRecord::Base.find to obtain an exclusive
|
|
7
|
+
# lock on the selected rows:
|
|
8
|
+
# # select * from accounts where id=1 for update
|
|
9
|
+
# Account.find(1, :lock => true)
|
|
10
|
+
#
|
|
11
|
+
# Pass <tt>:lock => 'some locking clause'</tt> to give a database-specific locking clause
|
|
12
|
+
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'.
|
|
13
|
+
#
|
|
14
|
+
# Example:
|
|
15
|
+
# Account.transaction do
|
|
16
|
+
# # select * from accounts where name = 'shugo' limit 1 for update
|
|
17
|
+
# shugo = Account.find(:first, :conditions => "name = 'shugo'", :lock => true)
|
|
18
|
+
# yuko = Account.find(:first, :conditions => "name = 'yuko'", :lock => true)
|
|
19
|
+
# shugo.balance -= 100
|
|
20
|
+
# shugo.save!
|
|
21
|
+
# yuko.balance += 100
|
|
22
|
+
# yuko.save!
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# You can also use ActiveRecord::Base#lock! method to lock one record by id.
|
|
26
|
+
# This may be better if you don't need to lock every row. Example:
|
|
27
|
+
# Account.transaction do
|
|
28
|
+
# # select * from accounts where ...
|
|
29
|
+
# accounts = Account.find(:all, :conditions => ...)
|
|
30
|
+
# account1 = accounts.detect { |account| ... }
|
|
31
|
+
# account2 = accounts.detect { |account| ... }
|
|
32
|
+
# # select * from accounts where id=? for update
|
|
33
|
+
# account1.lock!
|
|
34
|
+
# account2.lock!
|
|
35
|
+
# account1.balance -= 100
|
|
36
|
+
# account1.save!
|
|
37
|
+
# account2.balance += 100
|
|
38
|
+
# account2.save!
|
|
39
|
+
# end
|
|
40
|
+
#
|
|
41
|
+
# Database-specific information on row locking:
|
|
42
|
+
# MySQL: http://dev.mysql.com/doc/refman/5.1/en/innodb-locking-reads.html
|
|
43
|
+
# PostgreSQL: http://www.postgresql.org/docs/8.1/interactive/sql-select.html#SQL-FOR-UPDATE-SHARE
|
|
44
|
+
module Pessimistic
|
|
45
|
+
# Obtain a row lock on this record. Reloads the record to obtain the requested
|
|
46
|
+
# lock. Pass an SQL locking clause to append the end of the SELECT statement
|
|
47
|
+
# or pass true for "FOR UPDATE" (the default, an exclusive row lock). Returns
|
|
48
|
+
# the locked record.
|
|
49
|
+
def lock!(lock = true)
|
|
50
|
+
reload(:lock => lock) unless new_record?
|
|
51
|
+
self
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|