activerecord 2.0.5 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +168 -6
- data/README +27 -22
- data/RUNNING_UNIT_TESTS +7 -4
- data/Rakefile +22 -25
- data/lib/active_record.rb +8 -2
- data/lib/active_record/aggregations.rb +21 -12
- data/lib/active_record/association_preload.rb +277 -0
- data/lib/active_record/associations.rb +481 -295
- data/lib/active_record/associations/association_collection.rb +162 -37
- data/lib/active_record/associations/association_proxy.rb +71 -7
- data/lib/active_record/associations/belongs_to_association.rb +5 -3
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +5 -6
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +12 -64
- data/lib/active_record/associations/has_many_association.rb +8 -73
- data/lib/active_record/associations/has_many_through_association.rb +68 -117
- data/lib/active_record/associations/has_one_association.rb +7 -5
- data/lib/active_record/associations/has_one_through_association.rb +28 -0
- data/lib/active_record/attribute_methods.rb +69 -19
- data/lib/active_record/base.rb +496 -275
- data/lib/active_record/calculations.rb +28 -21
- data/lib/active_record/callbacks.rb +9 -38
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +3 -2
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +6 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +232 -45
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +141 -27
- data/lib/active_record/connection_adapters/abstract_adapter.rb +9 -13
- data/lib/active_record/connection_adapters/mysql_adapter.rb +57 -24
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +143 -42
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +1 -1
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +18 -10
- data/lib/active_record/dirty.rb +158 -0
- data/lib/active_record/fixtures.rb +121 -156
- data/lib/active_record/locking/optimistic.rb +14 -11
- data/lib/active_record/locking/pessimistic.rb +2 -2
- data/lib/active_record/migration.rb +157 -77
- data/lib/active_record/named_scope.rb +163 -0
- data/lib/active_record/observer.rb +19 -5
- data/lib/active_record/reflection.rb +34 -14
- data/lib/active_record/schema.rb +7 -14
- data/lib/active_record/schema_dumper.rb +4 -4
- data/lib/active_record/serialization.rb +5 -5
- data/lib/active_record/serializers/json_serializer.rb +37 -28
- data/lib/active_record/serializers/xml_serializer.rb +52 -29
- data/lib/active_record/test_case.rb +36 -0
- data/lib/active_record/timestamp.rb +4 -4
- data/lib/active_record/transactions.rb +3 -3
- data/lib/active_record/validations.rb +182 -248
- data/lib/active_record/version.rb +2 -2
- data/test/{fixtures → assets}/example.log +0 -0
- data/test/{fixtures → assets}/flowers.jpg +0 -0
- data/test/cases/aaa_create_tables_test.rb +24 -0
- data/test/cases/active_schema_test_mysql.rb +95 -0
- data/test/cases/active_schema_test_postgresql.rb +24 -0
- data/test/{adapter_test.rb → cases/adapter_test.rb} +15 -14
- data/test/{adapter_test_sqlserver.rb → cases/adapter_test_sqlserver.rb} +95 -95
- data/test/{aggregations_test.rb → cases/aggregations_test.rb} +20 -20
- data/test/{ar_schema_test.rb → cases/ar_schema_test.rb} +6 -6
- data/test/cases/associations/belongs_to_associations_test.rb +412 -0
- data/test/{associations → cases/associations}/callbacks_test.rb +24 -10
- data/test/{associations → cases/associations}/cascaded_eager_loading_test.rb +18 -17
- data/test/cases/associations/eager_load_nested_include_test.rb +83 -0
- data/test/{associations → cases/associations}/eager_singularization_test.rb +5 -5
- data/test/{associations → cases/associations}/eager_test.rb +216 -51
- data/test/{associations → cases/associations}/extension_test.rb +8 -8
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +684 -0
- data/test/cases/associations/has_many_associations_test.rb +932 -0
- data/test/cases/associations/has_many_through_associations_test.rb +190 -0
- data/test/cases/associations/has_one_associations_test.rb +323 -0
- data/test/cases/associations/has_one_through_associations_test.rb +74 -0
- data/test/{associations → cases/associations}/inner_join_association_test.rb +20 -20
- data/test/{associations → cases/associations}/join_model_test.rb +175 -35
- data/test/cases/associations_test.rb +262 -0
- data/test/{attribute_methods_test.rb → cases/attribute_methods_test.rb} +103 -11
- data/test/{base_test.rb → cases/base_test.rb} +338 -191
- data/test/{binary_test.rb → cases/binary_test.rb} +6 -4
- data/test/{calculations_test.rb → cases/calculations_test.rb} +35 -23
- data/test/{callbacks_test.rb → cases/callbacks_test.rb} +7 -7
- data/test/{class_inheritable_attributes_test.rb → cases/class_inheritable_attributes_test.rb} +3 -3
- data/test/{column_alias_test.rb → cases/column_alias_test.rb} +3 -3
- data/test/{connection_test_firebird.rb → cases/connection_test_firebird.rb} +2 -2
- data/test/{connection_test_mysql.rb → cases/connection_test_mysql.rb} +2 -2
- data/test/{copy_table_test_sqlite.rb → cases/copy_table_test_sqlite.rb} +13 -13
- data/test/{datatype_test_postgresql.rb → cases/datatype_test_postgresql.rb} +8 -8
- data/test/{date_time_test.rb → cases/date_time_test.rb} +5 -5
- data/test/{default_test_firebird.rb → cases/default_test_firebird.rb} +3 -3
- data/test/{defaults_test.rb → cases/defaults_test.rb} +8 -6
- data/test/{deprecated_finder_test.rb → cases/deprecated_finder_test.rb} +3 -3
- data/test/cases/dirty_test.rb +163 -0
- data/test/cases/finder_respond_to_test.rb +76 -0
- data/test/{finder_test.rb → cases/finder_test.rb} +266 -33
- data/test/{fixtures_test.rb → cases/fixtures_test.rb} +88 -72
- data/test/cases/helper.rb +47 -0
- data/test/{inheritance_test.rb → cases/inheritance_test.rb} +61 -17
- data/test/cases/invalid_date_test.rb +24 -0
- data/test/{json_serialization_test.rb → cases/json_serialization_test.rb} +36 -11
- data/test/{lifecycle_test.rb → cases/lifecycle_test.rb} +16 -13
- data/test/{locking_test.rb → cases/locking_test.rb} +17 -10
- data/test/{method_scoping_test.rb → cases/method_scoping_test.rb} +75 -39
- data/test/{migration_test.rb → cases/migration_test.rb} +420 -80
- data/test/{migration_test_firebird.rb → cases/migration_test_firebird.rb} +3 -3
- data/test/{mixin_test.rb → cases/mixin_test.rb} +7 -6
- data/test/{modules_test.rb → cases/modules_test.rb} +11 -6
- data/test/{multiple_db_test.rb → cases/multiple_db_test.rb} +5 -5
- data/test/cases/named_scope_test.rb +157 -0
- data/test/{pk_test.rb → cases/pk_test.rb} +10 -10
- data/test/{query_cache_test.rb → cases/query_cache_test.rb} +12 -10
- data/test/{readonly_test.rb → cases/readonly_test.rb} +11 -11
- data/test/{reflection_test.rb → cases/reflection_test.rb} +15 -14
- data/test/{reserved_word_test_mysql.rb → cases/reserved_word_test_mysql.rb} +4 -5
- data/test/{schema_authorization_test_postgresql.rb → cases/schema_authorization_test_postgresql.rb} +5 -5
- data/test/cases/schema_dumper_test.rb +138 -0
- data/test/cases/schema_test_postgresql.rb +102 -0
- data/test/{serialization_test.rb → cases/serialization_test.rb} +7 -7
- data/test/{synonym_test_oracle.rb → cases/synonym_test_oracle.rb} +5 -5
- data/test/{table_name_test_sqlserver.rb → cases/table_name_test_sqlserver.rb} +3 -3
- data/test/{threaded_connections_test.rb → cases/threaded_connections_test.rb} +7 -7
- data/test/{transactions_test.rb → cases/transactions_test.rb} +31 -5
- data/test/{unconnected_test.rb → cases/unconnected_test.rb} +2 -2
- data/test/{validations_test.rb → cases/validations_test.rb} +141 -39
- data/test/{xml_serialization_test.rb → cases/xml_serialization_test.rb} +12 -12
- data/test/config.rb +5 -0
- data/test/connections/native_db2/connection.rb +1 -1
- data/test/connections/native_firebird/connection.rb +1 -1
- data/test/connections/native_frontbase/connection.rb +1 -1
- data/test/connections/native_mysql/connection.rb +1 -1
- data/test/connections/native_openbase/connection.rb +1 -1
- data/test/connections/native_oracle/connection.rb +1 -1
- data/test/connections/native_postgresql/connection.rb +1 -3
- data/test/connections/native_sqlite/connection.rb +2 -2
- data/test/connections/native_sqlite3/connection.rb +2 -2
- data/test/connections/native_sqlite3/in_memory_connection.rb +3 -3
- data/test/connections/native_sybase/connection.rb +1 -1
- data/test/fixtures/author_addresses.yml +5 -0
- data/test/fixtures/authors.yml +2 -0
- data/test/fixtures/clubs.yml +6 -0
- data/test/fixtures/jobs.yml +7 -0
- data/test/fixtures/members.yml +4 -0
- data/test/fixtures/memberships.yml +20 -0
- data/test/fixtures/owners.yml +7 -0
- data/test/fixtures/people.yml +4 -1
- data/test/fixtures/pets.yml +14 -0
- data/test/fixtures/posts.yml +1 -0
- data/test/fixtures/price_estimates.yml +7 -0
- data/test/fixtures/readers.yml +5 -0
- data/test/fixtures/references.yml +17 -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 +4 -1
- data/test/fixtures/topics.yml +22 -2
- data/test/fixtures/warehouse-things.yml +3 -0
- data/test/{fixtures/migrations_with_decimal → migrations/decimal}/1_give_me_big_numbers.rb +0 -0
- data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/1_people_have_last_names.rb +1 -1
- data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/2_we_need_reminders.rb +1 -1
- data/test/{fixtures/migrations_with_duplicate → migrations/duplicate}/3_foo.rb +0 -0
- data/test/{fixtures/migrations → migrations/duplicate}/3_innocent_jointable.rb +0 -0
- data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
- data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
- data/test/{fixtures/migrations_with_duplicate → migrations/interleaved/pass_1}/3_innocent_jointable.rb +0 -0
- data/test/{fixtures/migrations → migrations/interleaved/pass_2}/1_people_have_last_names.rb +1 -1
- data/test/{fixtures/migrations_with_missing_versions/4_innocent_jointable.rb → migrations/interleaved/pass_2/3_innocent_jointable.rb} +0 -0
- data/test/{fixtures/migrations_with_missing_versions → migrations/interleaved/pass_3}/1_people_have_last_names.rb +1 -1
- 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/{fixtures/migrations_with_missing_versions → migrations/missing}/1000_people_have_middle_names.rb +1 -1
- data/test/migrations/missing/1_people_have_last_names.rb +9 -0
- data/test/{fixtures/migrations_with_missing_versions → migrations/missing}/3_we_need_reminders.rb +1 -1
- data/test/migrations/missing/4_innocent_jointable.rb +12 -0
- data/test/migrations/valid/1_people_have_last_names.rb +9 -0
- data/test/{fixtures/migrations → migrations/valid}/2_we_need_reminders.rb +1 -1
- data/test/migrations/valid/3_innocent_jointable.rb +12 -0
- data/test/{fixtures → models}/author.rb +28 -4
- data/test/{fixtures → models}/auto_id.rb +0 -0
- data/test/{fixtures → models}/binary.rb +0 -0
- data/test/{fixtures → models}/book.rb +0 -0
- data/test/{fixtures → models}/categorization.rb +0 -0
- data/test/{fixtures → models}/category.rb +8 -5
- data/test/{fixtures → models}/citation.rb +0 -0
- data/test/models/club.rb +7 -0
- data/test/{fixtures → models}/column_name.rb +0 -0
- data/test/{fixtures → models}/comment.rb +5 -3
- data/test/{fixtures → models}/company.rb +15 -6
- data/test/{fixtures → models}/company_in_module.rb +5 -3
- data/test/{fixtures → models}/computer.rb +0 -1
- data/test/{fixtures → models}/contact.rb +1 -1
- data/test/{fixtures → models}/course.rb +0 -0
- data/test/{fixtures → models}/customer.rb +8 -8
- data/test/{fixtures → models}/default.rb +0 -0
- data/test/{fixtures → models}/developer.rb +14 -10
- data/test/{fixtures → models}/edge.rb +0 -0
- data/test/{fixtures → models}/entrant.rb +0 -0
- data/test/models/guid.rb +2 -0
- data/test/{fixtures → models}/item.rb +0 -0
- data/test/models/job.rb +5 -0
- data/test/{fixtures → models}/joke.rb +0 -0
- data/test/{fixtures → models}/keyboard.rb +0 -0
- data/test/{fixtures → models}/legacy_thing.rb +0 -0
- data/test/{fixtures → models}/matey.rb +0 -0
- data/test/models/member.rb +9 -0
- data/test/models/membership.rb +9 -0
- data/test/{fixtures → models}/minimalistic.rb +0 -0
- data/test/{fixtures → models}/mixed_case_monkey.rb +0 -0
- data/test/{fixtures → models}/movie.rb +0 -0
- data/test/{fixtures → models}/order.rb +2 -2
- data/test/models/owner.rb +4 -0
- data/test/{fixtures → models}/parrot.rb +0 -0
- data/test/models/person.rb +10 -0
- data/test/models/pet.rb +4 -0
- data/test/models/pirate.rb +9 -0
- data/test/{fixtures → models}/post.rb +23 -2
- data/test/models/price_estimate.rb +3 -0
- data/test/{fixtures → models}/project.rb +1 -0
- data/test/{fixtures → models}/reader.rb +0 -0
- data/test/models/reference.rb +4 -0
- data/test/{fixtures → models}/reply.rb +7 -5
- data/test/{fixtures → models}/ship.rb +0 -0
- data/test/models/sponsor.rb +4 -0
- data/test/{fixtures → models}/subject.rb +0 -0
- data/test/{fixtures → models}/subscriber.rb +2 -0
- data/test/models/subscription.rb +4 -0
- data/test/{fixtures → models}/tag.rb +0 -0
- data/test/{fixtures → models}/tagging.rb +0 -0
- data/test/{fixtures → models}/task.rb +0 -0
- data/test/{fixtures → models}/topic.rb +32 -4
- data/test/{fixtures → models}/treasure.rb +2 -0
- data/test/{fixtures → models}/vertex.rb +0 -0
- data/test/models/warehouse_thing.rb +5 -0
- data/test/schema/mysql_specific_schema.rb +12 -0
- data/test/schema/postgresql_specific_schema.rb +103 -0
- data/test/schema/schema.rb +421 -0
- data/test/schema/schema2.rb +6 -0
- data/test/schema/sqlite_specific_schema.rb +25 -0
- data/test/schema/sqlserver_specific_schema.rb +5 -0
- metadata +192 -176
- data/test/aaa_create_tables_test.rb +0 -72
- data/test/abstract_unit.rb +0 -84
- data/test/active_schema_test_mysql.rb +0 -46
- data/test/all.sh +0 -8
- data/test/association_inheritance_reload.rb +0 -14
- data/test/associations_test.rb +0 -2177
- data/test/fixtures/bad_fixtures/attr_with_numeric_first_char +0 -1
- data/test/fixtures/bad_fixtures/attr_with_spaces +0 -1
- data/test/fixtures/bad_fixtures/blank_line +0 -3
- data/test/fixtures/bad_fixtures/duplicate_attributes +0 -3
- data/test/fixtures/bad_fixtures/missing_value +0 -1
- data/test/fixtures/db_definitions/db2.drop.sql +0 -33
- data/test/fixtures/db_definitions/db2.sql +0 -235
- data/test/fixtures/db_definitions/db22.drop.sql +0 -2
- data/test/fixtures/db_definitions/db22.sql +0 -5
- data/test/fixtures/db_definitions/firebird.drop.sql +0 -65
- data/test/fixtures/db_definitions/firebird.sql +0 -310
- data/test/fixtures/db_definitions/firebird2.drop.sql +0 -2
- data/test/fixtures/db_definitions/firebird2.sql +0 -6
- data/test/fixtures/db_definitions/frontbase.drop.sql +0 -33
- data/test/fixtures/db_definitions/frontbase.sql +0 -273
- data/test/fixtures/db_definitions/frontbase2.drop.sql +0 -1
- data/test/fixtures/db_definitions/frontbase2.sql +0 -4
- data/test/fixtures/db_definitions/openbase.drop.sql +0 -2
- data/test/fixtures/db_definitions/openbase.sql +0 -318
- data/test/fixtures/db_definitions/openbase2.drop.sql +0 -2
- data/test/fixtures/db_definitions/openbase2.sql +0 -7
- data/test/fixtures/db_definitions/oracle.drop.sql +0 -67
- data/test/fixtures/db_definitions/oracle.sql +0 -330
- data/test/fixtures/db_definitions/oracle2.drop.sql +0 -2
- data/test/fixtures/db_definitions/oracle2.sql +0 -6
- data/test/fixtures/db_definitions/postgresql.drop.sql +0 -44
- data/test/fixtures/db_definitions/postgresql.sql +0 -292
- data/test/fixtures/db_definitions/postgresql2.drop.sql +0 -2
- data/test/fixtures/db_definitions/postgresql2.sql +0 -4
- data/test/fixtures/db_definitions/schema.rb +0 -354
- data/test/fixtures/db_definitions/schema2.rb +0 -11
- data/test/fixtures/db_definitions/sqlite.drop.sql +0 -33
- data/test/fixtures/db_definitions/sqlite.sql +0 -219
- data/test/fixtures/db_definitions/sqlite2.drop.sql +0 -2
- data/test/fixtures/db_definitions/sqlite2.sql +0 -5
- data/test/fixtures/db_definitions/sybase.drop.sql +0 -35
- data/test/fixtures/db_definitions/sybase.sql +0 -222
- data/test/fixtures/db_definitions/sybase2.drop.sql +0 -4
- data/test/fixtures/db_definitions/sybase2.sql +0 -5
- data/test/fixtures/developers_projects/david_action_controller +0 -3
- data/test/fixtures/developers_projects/david_active_record +0 -3
- data/test/fixtures/developers_projects/jamis_active_record +0 -2
- data/test/fixtures/person.rb +0 -4
- data/test/fixtures/pirate.rb +0 -5
- data/test/fixtures/subscribers/first +0 -2
- data/test/fixtures/subscribers/second +0 -2
- data/test/schema_dumper_test.rb +0 -131
- data/test/schema_test_postgresql.rb +0 -64
@@ -66,17 +66,20 @@ module ActiveRecord
|
|
66
66
|
return result
|
67
67
|
end
|
68
68
|
|
69
|
-
def update_with_lock #:nodoc:
|
70
|
-
return update_without_lock unless locking_enabled?
|
69
|
+
def update_with_lock(attribute_names = @attributes.keys) #:nodoc:
|
70
|
+
return update_without_lock(attribute_names) unless locking_enabled?
|
71
71
|
|
72
72
|
lock_col = self.class.locking_column
|
73
73
|
previous_value = send(lock_col).to_i
|
74
74
|
send(lock_col + '=', previous_value + 1)
|
75
75
|
|
76
|
+
attribute_names += [lock_col]
|
77
|
+
attribute_names.uniq!
|
78
|
+
|
76
79
|
begin
|
77
80
|
affected_rows = connection.update(<<-end_sql, "#{self.class.name} Update with optimistic locking")
|
78
|
-
UPDATE #{self.class.
|
79
|
-
SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false, false))}
|
81
|
+
UPDATE #{self.class.quoted_table_name}
|
82
|
+
SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false, false, attribute_names))}
|
80
83
|
WHERE #{self.class.primary_key} = #{quote_value(id)}
|
81
84
|
AND #{self.class.quoted_locking_column} = #{quote_value(previous_value)}
|
82
85
|
end_sql
|
@@ -104,20 +107,20 @@ module ActiveRecord
|
|
104
107
|
end
|
105
108
|
|
106
109
|
# Is optimistic locking enabled for this table? Returns true if the
|
107
|
-
#
|
108
|
-
# and the table includes the
|
109
|
-
# lock_version).
|
110
|
+
# +lock_optimistically+ flag is set to true (which it is, by default)
|
111
|
+
# and the table includes the +locking_column+ column (defaults to
|
112
|
+
# +lock_version+).
|
110
113
|
def locking_enabled?
|
111
114
|
lock_optimistically && columns_hash[locking_column]
|
112
115
|
end
|
113
116
|
|
114
|
-
# Set the column to use for optimistic locking. Defaults to lock_version
|
117
|
+
# Set the column to use for optimistic locking. Defaults to +lock_version+.
|
115
118
|
def set_locking_column(value = nil, &block)
|
116
119
|
define_attr_method :locking_column, value, &block
|
117
120
|
value
|
118
121
|
end
|
119
122
|
|
120
|
-
# The version column used for optimistic locking. Defaults to lock_version
|
123
|
+
# The version column used for optimistic locking. Defaults to +lock_version+.
|
121
124
|
def locking_column
|
122
125
|
reset_locking_column
|
123
126
|
end
|
@@ -127,12 +130,12 @@ module ActiveRecord
|
|
127
130
|
connection.quote_column_name(locking_column)
|
128
131
|
end
|
129
132
|
|
130
|
-
# Reset the column used for optimistic locking back to the lock_version default.
|
133
|
+
# Reset the column used for optimistic locking back to the +lock_version+ default.
|
131
134
|
def reset_locking_column
|
132
135
|
set_locking_column DEFAULT_LOCKING_COLUMN
|
133
136
|
end
|
134
137
|
|
135
|
-
#
|
138
|
+
# Make sure the lock version column gets updated when counters are
|
136
139
|
# updated.
|
137
140
|
def update_counters_with_lock(id, counters)
|
138
141
|
counters = counters.merge(locking_column => 1) if locking_enabled?
|
@@ -25,12 +25,12 @@ module ActiveRecord
|
|
25
25
|
# Locking::Pessimistic provides support for row-level locking using
|
26
26
|
# SELECT ... FOR UPDATE and other lock types.
|
27
27
|
#
|
28
|
-
# Pass
|
28
|
+
# Pass <tt>:lock => true</tt> to ActiveRecord::Base.find to obtain an exclusive
|
29
29
|
# lock on the selected rows:
|
30
30
|
# # select * from accounts where id=1 for update
|
31
31
|
# Account.find(1, :lock => true)
|
32
32
|
#
|
33
|
-
# Pass
|
33
|
+
# Pass <tt>:lock => 'some locking clause'</tt> to give a database-specific locking clause
|
34
34
|
# of your own such as 'LOCK IN SHARE MODE' or 'FOR UPDATE NOWAIT'.
|
35
35
|
#
|
36
36
|
# Example:
|
@@ -8,6 +8,18 @@ module ActiveRecord
|
|
8
8
|
end
|
9
9
|
end
|
10
10
|
|
11
|
+
class DuplicateMigrationNameError < ActiveRecordError#:nodoc:
|
12
|
+
def initialize(name)
|
13
|
+
super("Multiple migrations have the name #{name}")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class UnknownMigrationVersionError < ActiveRecordError #:nodoc:
|
18
|
+
def initialize(version)
|
19
|
+
super("No migration with version number #{version}")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
11
23
|
class IllegalMigrationNameError < ActiveRecordError#:nodoc:
|
12
24
|
def initialize(name)
|
13
25
|
super("Illegal name for migration file: #{name}\n\t(only lower case letters, numbers, and '_' allowed)")
|
@@ -70,16 +82,16 @@ module ActiveRecord
|
|
70
82
|
# * <tt>rename_table(old_name, new_name)</tt>: Renames the table called +old_name+ to +new_name+.
|
71
83
|
# * <tt>add_column(table_name, column_name, type, options)</tt>: Adds a new column to the table called +table_name+
|
72
84
|
# named +column_name+ specified to be one of the following types:
|
73
|
-
#
|
74
|
-
#
|
75
|
-
# +options+ hash like { :default => 11 }
|
85
|
+
# <tt>:string</tt>, <tt>:text</tt>, <tt>:integer</tt>, <tt>:float</tt>, <tt>:decimal</tt>, <tt>:datetime</tt>, <tt>:timestamp</tt>, <tt>:time</tt>,
|
86
|
+
# <tt>:date</tt>, <tt>:binary</tt>, <tt>:boolean</tt>. A default value can be specified by passing an
|
87
|
+
# +options+ hash like <tt>{ :default => 11 }</tt>. Other options include <tt>:limit</tt> and <tt>:null</tt> (e.g. <tt>{ :limit => 50, :null => false }</tt>)
|
76
88
|
# -- see ActiveRecord::ConnectionAdapters::TableDefinition#column for details.
|
77
89
|
# * <tt>rename_column(table_name, column_name, new_column_name)</tt>: Renames a column but keeps the type and content.
|
78
90
|
# * <tt>change_column(table_name, column_name, type, options)</tt>: Changes the column to a different type using the same
|
79
91
|
# parameters as add_column.
|
80
92
|
# * <tt>remove_column(table_name, column_name)</tt>: Removes the column named +column_name+ from the table called +table_name+.
|
81
93
|
# * <tt>add_index(table_name, column_names, options)</tt>: Adds a new index with the name of the column. Other options include
|
82
|
-
#
|
94
|
+
# <tt>:name</tt> and <tt>:unique</tt> (e.g. <tt>{ :name => "users_name_index", :unique => true }</tt>).
|
83
95
|
# * <tt>remove_index(table_name, index_name)</tt>: Removes the index specified by +index_name+.
|
84
96
|
#
|
85
97
|
# == Irreversible transformations
|
@@ -91,16 +103,34 @@ module ActiveRecord
|
|
91
103
|
#
|
92
104
|
# The Rails package has several tools to help create and apply migrations.
|
93
105
|
#
|
94
|
-
# To generate a new migration,
|
106
|
+
# To generate a new migration, you can use
|
107
|
+
# script/generate migration MyNewMigration
|
108
|
+
#
|
95
109
|
# where MyNewMigration is the name of your migration. The generator will
|
96
|
-
# create
|
110
|
+
# create an empty migration file <tt>nnn_my_new_migration.rb</tt> in the <tt>db/migrate/</tt>
|
97
111
|
# directory where <tt>nnn</tt> is the next largest migration number.
|
112
|
+
#
|
98
113
|
# You may then edit the <tt>self.up</tt> and <tt>self.down</tt> methods of
|
99
114
|
# MyNewMigration.
|
100
115
|
#
|
116
|
+
# There is a special syntactic shortcut to generate migrations that add fields to a table.
|
117
|
+
# script/generate migration add_fieldname_to_tablename fieldname:string
|
118
|
+
#
|
119
|
+
# This will generate the file <tt>nnn_add_fieldname_to_tablename</tt>, which will look like this:
|
120
|
+
# class AddFieldnameToTablename < ActiveRecord::Migration
|
121
|
+
# def self.up
|
122
|
+
# add_column :tablenames, :fieldname, :string
|
123
|
+
# end
|
124
|
+
#
|
125
|
+
# def self.down
|
126
|
+
# remove_column :tablenames, :fieldname
|
127
|
+
# end
|
128
|
+
# end
|
129
|
+
#
|
101
130
|
# To run migrations against the currently configured database, use
|
102
131
|
# <tt>rake db:migrate</tt>. This will update the database by running all of the
|
103
|
-
# pending migrations, creating the <tt>
|
132
|
+
# pending migrations, creating the <tt>schema_migrations</tt> table
|
133
|
+
# (see "About the schema_migrations table" section below) if missing.
|
104
134
|
#
|
105
135
|
# To roll the database back to a previous migration version, use
|
106
136
|
# <tt>rake db:migrate VERSION=X</tt> where <tt>X</tt> is the version to which
|
@@ -178,7 +208,7 @@ module ActiveRecord
|
|
178
208
|
#
|
179
209
|
# You can quiet them down by setting ActiveRecord::Migration.verbose = false.
|
180
210
|
#
|
181
|
-
# You can also insert your own messages and benchmarks by using the
|
211
|
+
# You can also insert your own messages and benchmarks by using the +say_with_time+
|
182
212
|
# method:
|
183
213
|
#
|
184
214
|
# def self.up
|
@@ -193,6 +223,21 @@ module ActiveRecord
|
|
193
223
|
#
|
194
224
|
# The phrase "Updating salaries..." would then be printed, along with the
|
195
225
|
# benchmark for the block when the block completes.
|
226
|
+
#
|
227
|
+
# == About the schema_migrations table
|
228
|
+
#
|
229
|
+
# Rails versions 2.0 and prior used to create a table called
|
230
|
+
# <tt>schema_info</tt> when using migrations. This table contained the
|
231
|
+
# version of the schema as of the last applied migration.
|
232
|
+
#
|
233
|
+
# Starting with Rails 2.1, the <tt>schema_info</tt> table is
|
234
|
+
# (automatically) replaced by the <tt>schema_migrations</tt> table, which
|
235
|
+
# contains the version numbers of all the migrations applied.
|
236
|
+
#
|
237
|
+
# As a result, it is now possible to add migration files that are numbered
|
238
|
+
# lower than the current schema version: when migrating up, those
|
239
|
+
# never-applied "interleaved" migrations will be automatically applied, and
|
240
|
+
# when migrating down, never-applied "interleaved" migrations will be skipped.
|
196
241
|
class Migration
|
197
242
|
@@verbose = true
|
198
243
|
cattr_accessor :verbose
|
@@ -291,18 +336,23 @@ module ActiveRecord
|
|
291
336
|
class Migrator#:nodoc:
|
292
337
|
class << self
|
293
338
|
def migrate(migrations_path, target_version = nil)
|
294
|
-
Base.connection.initialize_schema_information
|
295
|
-
|
296
339
|
case
|
297
|
-
when target_version.nil
|
298
|
-
|
299
|
-
|
300
|
-
down(migrations_path, target_version)
|
301
|
-
when current_version == target_version
|
302
|
-
return # You're on the right version
|
340
|
+
when target_version.nil? then up(migrations_path, target_version)
|
341
|
+
when current_version > target_version then down(migrations_path, target_version)
|
342
|
+
else up(migrations_path, target_version)
|
303
343
|
end
|
304
344
|
end
|
305
345
|
|
346
|
+
def rollback(migrations_path, steps=1)
|
347
|
+
migrator = self.new(:down, migrations_path)
|
348
|
+
start_index = migrator.migrations.index(migrator.current_migration)
|
349
|
+
|
350
|
+
return unless start_index
|
351
|
+
|
352
|
+
finish = migrator.migrations[start_index + steps]
|
353
|
+
down(migrations_path, finish ? finish.version : 0)
|
354
|
+
end
|
355
|
+
|
306
356
|
def up(migrations_path, target_version = nil)
|
307
357
|
self.new(:up, migrations_path, target_version).migrate
|
308
358
|
end
|
@@ -310,90 +360,129 @@ module ActiveRecord
|
|
310
360
|
def down(migrations_path, target_version = nil)
|
311
361
|
self.new(:down, migrations_path, target_version).migrate
|
312
362
|
end
|
363
|
+
|
364
|
+
def run(direction, migrations_path, target_version)
|
365
|
+
self.new(direction, migrations_path, target_version).run
|
366
|
+
end
|
313
367
|
|
314
|
-
def
|
315
|
-
Base.table_name_prefix +
|
368
|
+
def schema_migrations_table_name
|
369
|
+
Base.table_name_prefix + 'schema_migrations' + Base.table_name_suffix
|
316
370
|
end
|
317
371
|
|
318
372
|
def current_version
|
319
|
-
Base.connection.
|
373
|
+
version = Base.connection.select_values(
|
374
|
+
"SELECT version FROM #{schema_migrations_table_name}"
|
375
|
+
).map(&:to_i).max rescue nil
|
376
|
+
version || 0
|
320
377
|
end
|
321
378
|
|
322
379
|
def proper_table_name(name)
|
323
|
-
# Use the
|
380
|
+
# Use the Active Record objects own table_name, or pre/suffix from ActiveRecord::Base if name is a symbol/string
|
324
381
|
name.table_name rescue "#{ActiveRecord::Base.table_name_prefix}#{name}#{ActiveRecord::Base.table_name_suffix}"
|
325
382
|
end
|
326
383
|
end
|
327
384
|
|
328
385
|
def initialize(direction, migrations_path, target_version = nil)
|
329
386
|
raise StandardError.new("This database does not yet support migrations") unless Base.connection.supports_migrations?
|
330
|
-
|
331
|
-
|
387
|
+
Base.connection.initialize_schema_migrations_table
|
388
|
+
@direction, @migrations_path, @target_version = direction, migrations_path, target_version
|
332
389
|
end
|
333
390
|
|
334
391
|
def current_version
|
335
392
|
self.class.current_version
|
336
393
|
end
|
394
|
+
|
395
|
+
def current_migration
|
396
|
+
migrations.detect { |m| m.version == current_version }
|
397
|
+
end
|
398
|
+
|
399
|
+
def run
|
400
|
+
target = migrations.detect { |m| m.version == @target_version }
|
401
|
+
raise UnknownMigrationVersionError.new(@target_version) if target.nil?
|
402
|
+
target.migrate(@direction)
|
403
|
+
end
|
337
404
|
|
338
405
|
def migrate
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
406
|
+
current = migrations.detect { |m| m.version == current_version }
|
407
|
+
target = migrations.detect { |m| m.version == @target_version }
|
408
|
+
|
409
|
+
if target.nil? && !@target_version.nil? && @target_version > 0
|
410
|
+
raise UnknownMigrationVersionError.new(@target_version)
|
411
|
+
end
|
412
|
+
|
413
|
+
start = up? ? 0 : (migrations.index(current) || 0)
|
414
|
+
finish = migrations.index(target) || migrations.size - 1
|
415
|
+
runnable = migrations[start..finish]
|
416
|
+
|
417
|
+
# skip the last migration if we're headed down, but not ALL the way down
|
418
|
+
runnable.pop if down? && !target.nil?
|
419
|
+
|
420
|
+
runnable.each do |migration|
|
421
|
+
Base.logger.info "Migrating to #{migration} (#{migration.version})"
|
422
|
+
|
423
|
+
# On our way up, we skip migrating the ones we've already migrated
|
424
|
+
# On our way down, we skip reverting the ones we've never migrated
|
425
|
+
next if up? && migrated.include?(migration.version.to_i)
|
426
|
+
|
427
|
+
if down? && !migrated.include?(migration.version.to_i)
|
428
|
+
migration.announce 'never migrated, skipping'; migration.write
|
429
|
+
else
|
430
|
+
migration.migrate(@direction)
|
431
|
+
record_version_state_after_migrating(migration.version)
|
343
432
|
end
|
433
|
+
end
|
434
|
+
end
|
344
435
|
|
345
|
-
|
436
|
+
def migrations
|
437
|
+
@migrations ||= begin
|
438
|
+
files = Dir["#{@migrations_path}/[0-9]*_*.rb"]
|
439
|
+
|
440
|
+
migrations = files.inject([]) do |klasses, file|
|
441
|
+
version, name = file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
442
|
+
|
443
|
+
raise IllegalMigrationNameError.new(file) unless version
|
444
|
+
version = version.to_i
|
445
|
+
|
446
|
+
if klasses.detect { |m| m.version == version }
|
447
|
+
raise DuplicateMigrationVersionError.new(version)
|
448
|
+
end
|
346
449
|
|
347
|
-
|
348
|
-
|
349
|
-
|
450
|
+
if klasses.detect { |m| m.name == name.camelize }
|
451
|
+
raise DuplicateMigrationNameError.new(name.camelize)
|
452
|
+
end
|
453
|
+
|
454
|
+
load(file)
|
455
|
+
|
456
|
+
klasses << returning(name.camelize.constantize) do |klass|
|
457
|
+
class << klass; attr_accessor :version end
|
458
|
+
klass.version = version
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
migrations = migrations.sort_by(&:version)
|
463
|
+
down? ? migrations.reverse : migrations
|
350
464
|
end
|
351
465
|
end
|
352
466
|
|
353
467
|
def pending_migrations
|
354
|
-
|
468
|
+
already_migrated = migrated
|
469
|
+
migrations.reject { |m| already_migrated.include?(m.version.to_i) }
|
355
470
|
end
|
356
471
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
version, name = migration_version_and_name(migration_file)
|
362
|
-
assert_unique_migration_version(migrations, version.to_i)
|
363
|
-
migrations << migration_class(name, version.to_i)
|
364
|
-
end.sort_by(&:version)
|
365
|
-
|
366
|
-
down? ? classes.reverse : classes
|
367
|
-
end
|
472
|
+
def migrated
|
473
|
+
sm_table = self.class.schema_migrations_table_name
|
474
|
+
Base.connection.select_values("SELECT version FROM #{sm_table}").map(&:to_i).sort
|
475
|
+
end
|
368
476
|
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
end
|
373
|
-
end
|
477
|
+
private
|
478
|
+
def record_version_state_after_migrating(version)
|
479
|
+
sm_table = self.class.schema_migrations_table_name
|
374
480
|
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
m.first.to_i
|
481
|
+
if down?
|
482
|
+
Base.connection.update("DELETE FROM #{sm_table} WHERE version = '#{version}'")
|
483
|
+
else
|
484
|
+
Base.connection.insert("INSERT INTO #{sm_table} (version) VALUES ('#{version}')")
|
380
485
|
end
|
381
|
-
down? ? files.reverse : files
|
382
|
-
end
|
383
|
-
|
384
|
-
def migration_class(migration_name, version)
|
385
|
-
klass = migration_name.camelize.constantize
|
386
|
-
class << klass; attr_accessor :version end
|
387
|
-
klass.version = version
|
388
|
-
klass
|
389
|
-
end
|
390
|
-
|
391
|
-
def migration_version_and_name(migration_file)
|
392
|
-
return *migration_file.scan(/([0-9]+)_([_a-z0-9]*).rb/).first
|
393
|
-
end
|
394
|
-
|
395
|
-
def set_schema_version(version)
|
396
|
-
Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i}")
|
397
486
|
end
|
398
487
|
|
399
488
|
def up?
|
@@ -403,14 +492,5 @@ module ActiveRecord
|
|
403
492
|
def down?
|
404
493
|
@direction == :down
|
405
494
|
end
|
406
|
-
|
407
|
-
def reached_target_version?(version)
|
408
|
-
return false if @target_version == nil
|
409
|
-
(up? && version.to_i - 1 >= @target_version) || (down? && version.to_i <= @target_version)
|
410
|
-
end
|
411
|
-
|
412
|
-
def irrelevant_migration?(version)
|
413
|
-
(up? && version.to_i <= current_version) || (down? && version.to_i > current_version)
|
414
|
-
end
|
415
495
|
end
|
416
496
|
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module NamedScope
|
3
|
+
# All subclasses of ActiveRecord::Base have two named_scopes:
|
4
|
+
# * <tt>all</tt>, which is similar to a <tt>find(:all)</tt> query, and
|
5
|
+
# * <tt>scoped</tt>, which allows for the creation of anonymous scopes, on the fly: <tt>Shirt.scoped(:conditions => {:color => 'red'}).scoped(:include => :washing_instructions)</tt>
|
6
|
+
#
|
7
|
+
# These anonymous scopes tend to be useful when procedurally generating complex queries, where passing
|
8
|
+
# intermediate values (scopes) around as first-class objects is convenient.
|
9
|
+
def self.included(base)
|
10
|
+
base.class_eval do
|
11
|
+
extend ClassMethods
|
12
|
+
named_scope :scoped, lambda { |scope| scope }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def scopes
|
18
|
+
read_inheritable_attribute(:scopes) || write_inheritable_attribute(:scopes, {})
|
19
|
+
end
|
20
|
+
|
21
|
+
# Adds a class method for retrieving and querying objects. A scope represents a narrowing of a database query,
|
22
|
+
# such as <tt>:conditions => {:color => :red}, :select => 'shirts.*', :include => :washing_instructions</tt>.
|
23
|
+
#
|
24
|
+
# class Shirt < ActiveRecord::Base
|
25
|
+
# named_scope :red, :conditions => {:color => 'red'}
|
26
|
+
# named_scope :dry_clean_only, :joins => :washing_instructions, :conditions => ['washing_instructions.dry_clean_only = ?', true]
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# The above calls to <tt>named_scope</tt> define class methods <tt>Shirt.red</tt> and <tt>Shirt.dry_clean_only</tt>. <tt>Shirt.red</tt>,
|
30
|
+
# in effect, represents the query <tt>Shirt.find(:all, :conditions => {:color => 'red'})</tt>.
|
31
|
+
#
|
32
|
+
# Unlike Shirt.find(...), however, the object returned by <tt>Shirt.red</tt> is not an Array; it resembles the association object
|
33
|
+
# constructed by a <tt>has_many</tt> declaration. For instance, you can invoke <tt>Shirt.red.find(:first)</tt>, <tt>Shirt.red.count</tt>,
|
34
|
+
# <tt>Shirt.red.find(:all, :conditions => {:size => 'small'})</tt>. Also, just
|
35
|
+
# as with the association objects, name scopes acts like an Array, implementing Enumerable; <tt>Shirt.red.each(&block)</tt>,
|
36
|
+
# <tt>Shirt.red.first</tt>, and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if Shirt.red really were an Array.
|
37
|
+
#
|
38
|
+
# These named scopes are composable. For instance, <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are both red and dry clean only.
|
39
|
+
# Nested finds and calculations also work with these compositions: <tt>Shirt.red.dry_clean_only.count</tt> returns the number of garments
|
40
|
+
# for which these criteria obtain. Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
|
41
|
+
#
|
42
|
+
# All scopes are available as class methods on the ActiveRecord::Base descendent upon which the scopes were defined. But they are also available to
|
43
|
+
# <tt>has_many</tt> associations. If,
|
44
|
+
#
|
45
|
+
# class Person < ActiveRecord::Base
|
46
|
+
# has_many :shirts
|
47
|
+
# end
|
48
|
+
#
|
49
|
+
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of Elton's red, dry clean
|
50
|
+
# only shirts.
|
51
|
+
#
|
52
|
+
# Named scopes can also be procedural.
|
53
|
+
#
|
54
|
+
# class Shirt < ActiveRecord::Base
|
55
|
+
# named_scope :colored, lambda { |color|
|
56
|
+
# { :conditions => { :color => color } }
|
57
|
+
# }
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# In this example, <tt>Shirt.colored('puce')</tt> finds all puce shirts.
|
61
|
+
#
|
62
|
+
# Named scopes can also have extensions, just as with <tt>has_many</tt> declarations:
|
63
|
+
#
|
64
|
+
# class Shirt < ActiveRecord::Base
|
65
|
+
# named_scope :red, :conditions => {:color => 'red'} do
|
66
|
+
# def dom_id
|
67
|
+
# 'red_shirts'
|
68
|
+
# end
|
69
|
+
# end
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
#
|
73
|
+
# For testing complex named scopes, you can examine the scoping options using the
|
74
|
+
# <tt>proxy_options</tt> method on the proxy itself.
|
75
|
+
#
|
76
|
+
# class Shirt < ActiveRecord::Base
|
77
|
+
# named_scope :colored, lambda { |color|
|
78
|
+
# { :conditions => { :color => color } }
|
79
|
+
# }
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# expected_options = { :conditions => { :colored => 'red' } }
|
83
|
+
# assert_equal expected_options, Shirt.colored('red').proxy_options
|
84
|
+
def named_scope(name, options = {}, &block)
|
85
|
+
scopes[name] = lambda do |parent_scope, *args|
|
86
|
+
Scope.new(parent_scope, case options
|
87
|
+
when Hash
|
88
|
+
options
|
89
|
+
when Proc
|
90
|
+
options.call(*args)
|
91
|
+
end, &block)
|
92
|
+
end
|
93
|
+
(class << self; self end).instance_eval do
|
94
|
+
define_method name do |*args|
|
95
|
+
scopes[name].call(self, *args)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class Scope
|
102
|
+
attr_reader :proxy_scope, :proxy_options
|
103
|
+
|
104
|
+
[].methods.each do |m|
|
105
|
+
unless m =~ /(^__|^nil\?|^send|^object_id$|class|extend|find|count|sum|average|maximum|minimum|paginate|first|last|empty?)/
|
106
|
+
delegate m, :to => :proxy_found
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
delegate :scopes, :with_scope, :to => :proxy_scope
|
111
|
+
|
112
|
+
def initialize(proxy_scope, options, &block)
|
113
|
+
[options[:extend]].flatten.each { |extension| extend extension } if options[:extend]
|
114
|
+
extend Module.new(&block) if block_given?
|
115
|
+
@proxy_scope, @proxy_options = proxy_scope, options.except(:extend)
|
116
|
+
end
|
117
|
+
|
118
|
+
def reload
|
119
|
+
load_found; self
|
120
|
+
end
|
121
|
+
|
122
|
+
def first(*args)
|
123
|
+
if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
|
124
|
+
proxy_found.first(*args)
|
125
|
+
else
|
126
|
+
find(:first, *args)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def last(*args)
|
131
|
+
if args.first.kind_of?(Integer) || (@found && !args.first.kind_of?(Hash))
|
132
|
+
proxy_found.last(*args)
|
133
|
+
else
|
134
|
+
find(:last, *args)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def empty?
|
139
|
+
@found ? @found.empty? : count.zero?
|
140
|
+
end
|
141
|
+
|
142
|
+
protected
|
143
|
+
def proxy_found
|
144
|
+
@found || load_found
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
def method_missing(method, *args, &block)
|
149
|
+
if scopes.include?(method)
|
150
|
+
scopes[method].call(self, *args)
|
151
|
+
else
|
152
|
+
with_scope :find => proxy_options do
|
153
|
+
proxy_scope.send(method, *args, &block)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def load_found
|
159
|
+
@found = find(:all)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|