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
@@ -51,9 +51,11 @@ module ActiveRecord
|
|
51
51
|
private
|
52
52
|
def find_target
|
53
53
|
@reflection.klass.find(:first,
|
54
|
-
:conditions => @finder_sql,
|
54
|
+
:conditions => @finder_sql,
|
55
|
+
:select => @reflection.options[:select],
|
55
56
|
:order => @reflection.options[:order],
|
56
|
-
:include => @reflection.options[:include]
|
57
|
+
:include => @reflection.options[:include],
|
58
|
+
:readonly => @reflection.options[:readonly]
|
57
59
|
)
|
58
60
|
end
|
59
61
|
|
@@ -61,10 +63,10 @@ module ActiveRecord
|
|
61
63
|
case
|
62
64
|
when @reflection.options[:as]
|
63
65
|
@finder_sql =
|
64
|
-
"#{@reflection.
|
65
|
-
"#{@reflection.
|
66
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
67
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
66
68
|
else
|
67
|
-
@finder_sql = "#{@reflection.
|
69
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
68
70
|
end
|
69
71
|
@finder_sql << " AND (#{conditions})" if conditions
|
70
72
|
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Associations
|
3
|
+
class HasOneThroughAssociation < HasManyThroughAssociation
|
4
|
+
|
5
|
+
def create_through_record(new_value) #nodoc:
|
6
|
+
klass = @reflection.through_reflection.klass
|
7
|
+
|
8
|
+
current_object = @owner.send(@reflection.through_reflection.name)
|
9
|
+
|
10
|
+
if current_object
|
11
|
+
klass.destroy(current_object)
|
12
|
+
@owner.clear_association_cache
|
13
|
+
end
|
14
|
+
|
15
|
+
@owner.send(@reflection.through_reflection.name, klass.send(:create, construct_join_attributes(new_value)))
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def find(*args)
|
20
|
+
super(args.merge(:limit => 1))
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_target
|
24
|
+
super.first
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -8,20 +8,28 @@ module ActiveRecord
|
|
8
8
|
base.attribute_method_suffix(*DEFAULT_SUFFIXES)
|
9
9
|
base.cattr_accessor :attribute_types_cached_by_default, :instance_writer => false
|
10
10
|
base.attribute_types_cached_by_default = ATTRIBUTE_TYPES_CACHED_BY_DEFAULT
|
11
|
+
base.cattr_accessor :time_zone_aware_attributes, :instance_writer => false
|
12
|
+
base.time_zone_aware_attributes = false
|
13
|
+
base.cattr_accessor :skip_time_zone_conversion_for_attributes, :instance_writer => false
|
14
|
+
base.skip_time_zone_conversion_for_attributes = []
|
11
15
|
end
|
12
16
|
|
13
17
|
# Declare and check for suffixed attribute methods.
|
14
18
|
module ClassMethods
|
15
|
-
#
|
16
|
-
# Uses method_missing and respond_to
|
19
|
+
# Declares a method available for all attributes with the given suffix.
|
20
|
+
# Uses +method_missing+ and <tt>respond_to?</tt> to rewrite the method
|
21
|
+
#
|
17
22
|
# #{attr}#{suffix}(*args, &block)
|
23
|
+
#
|
18
24
|
# to
|
25
|
+
#
|
19
26
|
# attribute#{suffix}(#{attr}, *args, &block)
|
20
27
|
#
|
21
|
-
# An attribute#{suffix} instance method must exist and accept at least
|
22
|
-
# the attr argument.
|
28
|
+
# An <tt>attribute#{suffix}</tt> instance method must exist and accept at least
|
29
|
+
# the +attr+ argument.
|
23
30
|
#
|
24
31
|
# For example:
|
32
|
+
#
|
25
33
|
# class Person < ActiveRecord::Base
|
26
34
|
# attribute_method_suffix '_changed?'
|
27
35
|
#
|
@@ -56,21 +64,27 @@ module ActiveRecord
|
|
56
64
|
!generated_methods.empty?
|
57
65
|
end
|
58
66
|
|
59
|
-
#
|
60
|
-
# accessors, mutators and query methods
|
67
|
+
# Generates all the attribute related methods for columns in the database
|
68
|
+
# accessors, mutators and query methods.
|
61
69
|
def define_attribute_methods
|
62
70
|
return if generated_methods?
|
63
71
|
columns_hash.each do |name, column|
|
64
72
|
unless instance_method_already_implemented?(name)
|
65
73
|
if self.serialized_attributes[name]
|
66
74
|
define_read_method_for_serialized_attribute(name)
|
75
|
+
elsif create_time_zone_conversion_attribute?(name, column)
|
76
|
+
define_read_method_for_time_zone_conversion(name)
|
67
77
|
else
|
68
78
|
define_read_method(name.to_sym, name, column)
|
69
79
|
end
|
70
80
|
end
|
71
81
|
|
72
82
|
unless instance_method_already_implemented?("#{name}=")
|
73
|
-
|
83
|
+
if create_time_zone_conversion_attribute?(name, column)
|
84
|
+
define_write_method_for_time_zone_conversion(name)
|
85
|
+
else
|
86
|
+
define_write_method(name.to_sym)
|
87
|
+
end
|
74
88
|
end
|
75
89
|
|
76
90
|
unless instance_method_already_implemented?("#{name}?")
|
@@ -79,8 +93,9 @@ module ActiveRecord
|
|
79
93
|
end
|
80
94
|
end
|
81
95
|
|
82
|
-
#
|
83
|
-
#
|
96
|
+
# Checks whether the method is defined in the model or any of its subclasses
|
97
|
+
# that also derive from Active Record. Raises DangerousAttributeError if the
|
98
|
+
# method is defined by Active Record though.
|
84
99
|
def instance_method_already_implemented?(method_name)
|
85
100
|
method_name = method_name.to_s
|
86
101
|
return true if method_name =~ /^id(=$|\?$|$)/
|
@@ -94,17 +109,19 @@ module ActiveRecord
|
|
94
109
|
|
95
110
|
# +cache_attributes+ allows you to declare which converted attribute values should
|
96
111
|
# be cached. Usually caching only pays off for attributes with expensive conversion
|
97
|
-
# methods, like
|
112
|
+
# methods, like time related columns (e.g. +created_at+, +updated_at+).
|
98
113
|
def cache_attributes(*attribute_names)
|
99
114
|
attribute_names.each {|attr| cached_attributes << attr.to_s}
|
100
115
|
end
|
101
116
|
|
102
|
-
#
|
117
|
+
# Returns the attributes which are cached. By default time related columns
|
118
|
+
# with datatype <tt>:datetime, :timestamp, :time, :date</tt> are cached.
|
103
119
|
def cached_attributes
|
104
120
|
@cached_attributes ||=
|
105
121
|
columns.select{|c| attribute_types_cached_by_default.include?(c.type)}.map(&:name).to_set
|
106
122
|
end
|
107
123
|
|
124
|
+
# Returns +true+ if the provided attribute is being cached.
|
108
125
|
def cache_attribute?(attr_name)
|
109
126
|
cached_attributes.include?(attr_name)
|
110
127
|
end
|
@@ -121,6 +138,10 @@ module ActiveRecord
|
|
121
138
|
@@attribute_method_suffixes ||= []
|
122
139
|
end
|
123
140
|
|
141
|
+
def create_time_zone_conversion_attribute?(name, column)
|
142
|
+
time_zone_aware_attributes && !skip_time_zone_conversion_for_attributes.include?(name.to_sym) && [:datetime, :timestamp].include?(column.type)
|
143
|
+
end
|
144
|
+
|
124
145
|
# Define an attribute reader method. Cope with nil column.
|
125
146
|
def define_read_method(symbol, attr_name, column)
|
126
147
|
cast_code = column.type_cast_code('v') if column
|
@@ -140,8 +161,22 @@ module ActiveRecord
|
|
140
161
|
def define_read_method_for_serialized_attribute(attr_name)
|
141
162
|
evaluate_attribute_method attr_name, "def #{attr_name}; unserialize_attribute('#{attr_name}'); end"
|
142
163
|
end
|
164
|
+
|
165
|
+
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
|
166
|
+
# This enhanced read method automatically converts the UTC time stored in the database to the time zone stored in Time.zone.
|
167
|
+
def define_read_method_for_time_zone_conversion(attr_name)
|
168
|
+
method_body = <<-EOV
|
169
|
+
def #{attr_name}(reload = false)
|
170
|
+
cached = @attributes_cache['#{attr_name}']
|
171
|
+
return cached if cached && !reload
|
172
|
+
time = read_attribute('#{attr_name}')
|
173
|
+
@attributes_cache['#{attr_name}'] = time.acts_like?(:time) ? time.in_time_zone : time
|
174
|
+
end
|
175
|
+
EOV
|
176
|
+
evaluate_attribute_method attr_name, method_body
|
177
|
+
end
|
143
178
|
|
144
|
-
#
|
179
|
+
# Defines a predicate method <tt>attr_name?</tt>.
|
145
180
|
def define_question_method(attr_name)
|
146
181
|
evaluate_attribute_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end", "#{attr_name}?"
|
147
182
|
end
|
@@ -149,6 +184,21 @@ module ActiveRecord
|
|
149
184
|
def define_write_method(attr_name)
|
150
185
|
evaluate_attribute_method attr_name, "def #{attr_name}=(new_value);write_attribute('#{attr_name}', new_value);end", "#{attr_name}="
|
151
186
|
end
|
187
|
+
|
188
|
+
# Defined for all +datetime+ and +timestamp+ attributes when +time_zone_aware_attributes+ are enabled.
|
189
|
+
# This enhanced write method will automatically convert the time passed to it to the zone stored in Time.zone.
|
190
|
+
def define_write_method_for_time_zone_conversion(attr_name)
|
191
|
+
method_body = <<-EOV
|
192
|
+
def #{attr_name}=(time)
|
193
|
+
unless time.acts_like?(:time)
|
194
|
+
time = time.is_a?(String) ? Time.zone.parse(time) : time.to_time rescue time
|
195
|
+
end
|
196
|
+
time = time.in_time_zone rescue nil if time
|
197
|
+
write_attribute(:#{attr_name}, time)
|
198
|
+
end
|
199
|
+
EOV
|
200
|
+
evaluate_attribute_method attr_name, method_body, "#{attr_name}="
|
201
|
+
end
|
152
202
|
|
153
203
|
# Evaluate the definition for an attribute related method
|
154
204
|
def evaluate_attribute_method(attr_name, method_definition, method_name=attr_name)
|
@@ -171,14 +221,14 @@ module ActiveRecord
|
|
171
221
|
end # ClassMethods
|
172
222
|
|
173
223
|
|
174
|
-
# Allows access to the object attributes, which are held in the
|
224
|
+
# Allows access to the object attributes, which are held in the <tt>@attributes</tt> hash, as though they
|
175
225
|
# were first-class methods. So a Person class with a name attribute can use Person#name and
|
176
226
|
# Person#name= and never directly use the attributes hash -- except for multiple assigns with
|
177
227
|
# ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
|
178
|
-
# the completed attribute is not nil or 0.
|
228
|
+
# the completed attribute is not +nil+ or 0.
|
179
229
|
#
|
180
230
|
# It's also possible to instantiate related objects, so a Client class belonging to the clients
|
181
|
-
# table with a master_id foreign key can instantiate master through Client#master.
|
231
|
+
# table with a +master_id+ foreign key can instantiate master through Client#master.
|
182
232
|
def method_missing(method_id, *args, &block)
|
183
233
|
method_name = method_id.to_s
|
184
234
|
|
@@ -249,7 +299,7 @@ module ActiveRecord
|
|
249
299
|
|
250
300
|
|
251
301
|
# Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
|
252
|
-
# columns are turned into nil
|
302
|
+
# columns are turned into +nil+.
|
253
303
|
def write_attribute(attr_name, value)
|
254
304
|
attr_name = attr_name.to_s
|
255
305
|
@attributes_cache.delete(attr_name)
|
@@ -280,8 +330,9 @@ module ActiveRecord
|
|
280
330
|
end
|
281
331
|
end
|
282
332
|
|
283
|
-
# A Person object with a name attribute can ask person.respond_to?("name")
|
284
|
-
# person.respond_to?("name
|
333
|
+
# A Person object with a name attribute can ask <tt>person.respond_to?("name")</tt>,
|
334
|
+
# <tt>person.respond_to?("name=")</tt>, and <tt>person.respond_to?("name?")</tt>
|
335
|
+
# which will all return +true+.
|
285
336
|
alias :respond_to_without_attributes? :respond_to?
|
286
337
|
def respond_to?(method, include_priv = false)
|
287
338
|
method_name = method.to_s
|
@@ -303,7 +354,6 @@ module ActiveRecord
|
|
303
354
|
end
|
304
355
|
super
|
305
356
|
end
|
306
|
-
|
307
357
|
|
308
358
|
private
|
309
359
|
|
data/lib/active_record/base.rb
CHANGED
@@ -2,7 +2,7 @@ require 'yaml'
|
|
2
2
|
require 'set'
|
3
3
|
|
4
4
|
module ActiveRecord #:nodoc:
|
5
|
-
# Generic
|
5
|
+
# Generic Active Record exception class.
|
6
6
|
class ActiveRecordError < StandardError
|
7
7
|
end
|
8
8
|
|
@@ -11,22 +11,18 @@ module ActiveRecord #:nodoc:
|
|
11
11
|
class SubclassNotFound < ActiveRecordError #:nodoc:
|
12
12
|
end
|
13
13
|
|
14
|
-
# Raised when object assigned to association
|
14
|
+
# Raised when an object assigned to an association has an incorrect type.
|
15
15
|
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
# has_many :patches
|
20
|
-
# end
|
21
|
-
#
|
22
|
-
# class Patch < ActiveRecord::Base
|
23
|
-
# belongs_to :ticket
|
24
|
-
# end
|
16
|
+
# class Ticket < ActiveRecord::Base
|
17
|
+
# has_many :patches
|
18
|
+
# end
|
25
19
|
#
|
26
|
-
#
|
20
|
+
# class Patch < ActiveRecord::Base
|
21
|
+
# belongs_to :ticket
|
22
|
+
# end
|
27
23
|
#
|
28
|
-
#
|
29
|
-
#
|
24
|
+
# # Comments are not patches, this assignment raises AssociationTypeMismatch.
|
25
|
+
# @ticket.patches << Comment.new(:content => "Please attach tests to your patch.")
|
30
26
|
class AssociationTypeMismatch < ActiveRecordError
|
31
27
|
end
|
32
28
|
|
@@ -34,19 +30,19 @@ module ActiveRecord #:nodoc:
|
|
34
30
|
class SerializationTypeMismatch < ActiveRecordError
|
35
31
|
end
|
36
32
|
|
37
|
-
# Raised when adapter not specified on connection (or configuration file config/database.yml misses adapter field).
|
33
|
+
# Raised when adapter not specified on connection (or configuration file <tt>config/database.yml</tt> misses adapter field).
|
38
34
|
class AdapterNotSpecified < ActiveRecordError
|
39
35
|
end
|
40
36
|
|
41
|
-
# Raised when
|
37
|
+
# Raised when Active Record cannot find database adapter specified in <tt>config/database.yml</tt> or programmatically.
|
42
38
|
class AdapterNotFound < ActiveRecordError
|
43
39
|
end
|
44
40
|
|
45
|
-
# Raised when connection to the database could not been established (for example when connection
|
41
|
+
# Raised when connection to the database could not been established (for example when <tt>connection=</tt> is given a nil object).
|
46
42
|
class ConnectionNotEstablished < ActiveRecordError
|
47
43
|
end
|
48
44
|
|
49
|
-
# Raised when
|
45
|
+
# Raised when Active Record cannot find record by given id or set of ids.
|
50
46
|
class RecordNotFound < ActiveRecordError
|
51
47
|
end
|
52
48
|
|
@@ -59,14 +55,14 @@ module ActiveRecord #:nodoc:
|
|
59
55
|
class StatementInvalid < ActiveRecordError
|
60
56
|
end
|
61
57
|
|
62
|
-
# Raised when number of bind variables in statement given to
|
58
|
+
# Raised when number of bind variables in statement given to <tt>:condition</tt> key (for example, when using +find+ method)
|
63
59
|
# does not match number of expected variables.
|
64
60
|
#
|
65
|
-
#
|
61
|
+
# For example, in
|
66
62
|
#
|
67
|
-
#
|
63
|
+
# Location.find :all, :conditions => ["lat = ? AND lng = ?", 53.7362]
|
68
64
|
#
|
69
|
-
#
|
65
|
+
# two placeholders are given but only one variable to fill them.
|
70
66
|
class PreparedStatementInvalid < ActiveRecordError
|
71
67
|
end
|
72
68
|
|
@@ -74,7 +70,7 @@ module ActiveRecord #:nodoc:
|
|
74
70
|
# instantiation, for example, when two users edit the same wiki page and one starts editing and saves
|
75
71
|
# the page before the other.
|
76
72
|
#
|
77
|
-
# Read more about optimistic locking in
|
73
|
+
# Read more about optimistic locking in ActiveRecord::Locking module RDoc.
|
78
74
|
class StaleObjectError < ActiveRecordError
|
79
75
|
end
|
80
76
|
|
@@ -87,22 +83,24 @@ module ActiveRecord #:nodoc:
|
|
87
83
|
class ReadOnlyRecord < ActiveRecordError
|
88
84
|
end
|
89
85
|
|
90
|
-
# Used by
|
86
|
+
# Used by Active Record transaction mechanism to distinguish rollback from other exceptional situations.
|
91
87
|
# You can use it to roll your transaction back explicitly in the block passed to +transaction+ method.
|
92
88
|
class Rollback < ActiveRecordError
|
93
89
|
end
|
94
90
|
|
95
|
-
# Raised when attribute has a name reserved by
|
91
|
+
# Raised when attribute has a name reserved by Active Record (when attribute has name of one of Active Record instance methods).
|
96
92
|
class DangerousAttributeError < ActiveRecordError
|
97
93
|
end
|
98
94
|
|
99
|
-
# Raised when you've tried to access a column which wasn't
|
100
|
-
#
|
101
|
-
# has been specified
|
95
|
+
# Raised when you've tried to access a column which wasn't loaded by your finder.
|
96
|
+
# Typically this is because <tt>:select</tt> has been specified.
|
102
97
|
class MissingAttributeError < NoMethodError
|
103
98
|
end
|
104
99
|
|
105
|
-
|
100
|
+
# Raised when an error occured while doing a mass assignment to an attribute through the
|
101
|
+
# <tt>attributes=</tt> method. The exception has an +attribute+ property that is the name of the
|
102
|
+
# offending attribute.
|
103
|
+
class AttributeAssignmentError < ActiveRecordError
|
106
104
|
attr_reader :exception, :attribute
|
107
105
|
def initialize(message, exception, attribute)
|
108
106
|
@exception = exception
|
@@ -111,7 +109,10 @@ module ActiveRecord #:nodoc:
|
|
111
109
|
end
|
112
110
|
end
|
113
111
|
|
114
|
-
|
112
|
+
# Raised when there are multiple errors while doing a mass assignment through the +attributes+
|
113
|
+
# method. The exception has an +errors+ property that contains an array of AttributeAssignmentError
|
114
|
+
# objects, each corresponding to the error while assigning to an attribute.
|
115
|
+
class MultiparameterAssignmentErrors < ActiveRecordError
|
115
116
|
attr_reader :errors
|
116
117
|
def initialize(errors)
|
117
118
|
@errors = errors
|
@@ -191,18 +192,22 @@ module ActiveRecord #:nodoc:
|
|
191
192
|
#
|
192
193
|
# Student.find(:all, :conditions => { :grade => 9..12 })
|
193
194
|
#
|
195
|
+
# An array may be used in the hash to use the SQL IN operator:
|
196
|
+
#
|
197
|
+
# Student.find(:all, :conditions => { :grade => [9,11,12] })
|
198
|
+
#
|
194
199
|
# == Overwriting default accessors
|
195
200
|
#
|
196
201
|
# All column values are automatically available through basic accessors on the Active Record object, but sometimes you
|
197
202
|
# want to specialize this behavior. This can be done by overwriting the default accessors (using the same
|
198
|
-
# name as the attribute) and calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
|
203
|
+
# name as the attribute) and calling <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually change things.
|
199
204
|
# Example:
|
200
205
|
#
|
201
206
|
# class Song < ActiveRecord::Base
|
202
207
|
# # Uses an integer of seconds to hold the length of the song
|
203
208
|
#
|
204
209
|
# def length=(minutes)
|
205
|
-
# write_attribute(:length, minutes * 60)
|
210
|
+
# write_attribute(:length, minutes.to_i * 60)
|
206
211
|
# end
|
207
212
|
#
|
208
213
|
# def length
|
@@ -210,8 +215,8 @@ module ActiveRecord #:nodoc:
|
|
210
215
|
# end
|
211
216
|
# end
|
212
217
|
#
|
213
|
-
# You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, value) and
|
214
|
-
# read_attribute(:attribute) as a shorter form.
|
218
|
+
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt> instead of <tt>write_attribute(:attribute, value)</tt> and
|
219
|
+
# <tt>read_attribute(:attribute)</tt> as a shorter form.
|
215
220
|
#
|
216
221
|
# == Attribute query methods
|
217
222
|
#
|
@@ -230,8 +235,8 @@ module ActiveRecord #:nodoc:
|
|
230
235
|
# == Accessing attributes before they have been typecasted
|
231
236
|
#
|
232
237
|
# Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
|
233
|
-
# That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
|
234
|
-
# has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast
|
238
|
+
# That can be done by using the <tt><attribute>_before_type_cast</tt> accessors that all attributes have. For example, if your Account model
|
239
|
+
# has a <tt>balance</tt> attribute, you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
|
235
240
|
#
|
236
241
|
# This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
|
237
242
|
# the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
|
@@ -240,8 +245,8 @@ module ActiveRecord #:nodoc:
|
|
240
245
|
# == Dynamic attribute-based finders
|
241
246
|
#
|
242
247
|
# Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
|
243
|
-
# appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like Person.find_by_user_name
|
244
|
-
# Person.find_all_by_last_name
|
248
|
+
# appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like <tt>Person.find_by_user_name</tt>,
|
249
|
+
# <tt>Person.find_all_by_last_name</tt>, and <tt>Payment.find_by_transaction_id</tt>. So instead of writing
|
245
250
|
# <tt>Person.find(:first, :conditions => ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
|
246
251
|
# And instead of writing <tt>Person.find(:all, :conditions => ["last_name = ?", last_name])</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
|
247
252
|
#
|
@@ -250,12 +255,12 @@ module ActiveRecord #:nodoc:
|
|
250
255
|
# <tt>Person.find(:first, :conditions => ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
|
251
256
|
# <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
|
252
257
|
#
|
253
|
-
# It's even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount
|
254
|
-
# is actually Payment.find_all_by_amount(amount, options)
|
255
|
-
# actually Person.find_by_user_name(user_name, options)
|
258
|
+
# It's even possible to use all the additional parameters to find. For example, the full interface for <tt>Payment.find_all_by_amount</tt>
|
259
|
+
# is actually <tt>Payment.find_all_by_amount(amount, options)</tt>. And the full interface to <tt>Person.find_by_user_name</tt> is
|
260
|
+
# actually <tt>Person.find_by_user_name(user_name, options)</tt>. So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
|
256
261
|
#
|
257
262
|
# The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
|
258
|
-
# <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it.
|
263
|
+
# <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Protected attributes won't be set unless they are given in a block. For example:
|
259
264
|
#
|
260
265
|
# # No 'Summer' tag exists
|
261
266
|
# Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
|
@@ -263,7 +268,10 @@ module ActiveRecord #:nodoc:
|
|
263
268
|
# # Now the 'Summer' tag does exist
|
264
269
|
# Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
|
265
270
|
#
|
266
|
-
#
|
271
|
+
# # Now 'Bob' exist and is an 'admin'
|
272
|
+
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
|
273
|
+
#
|
274
|
+
# Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Protected attributes won't be setted unless they are given in a block. For example:
|
267
275
|
#
|
268
276
|
# # No 'Winter' tag exists
|
269
277
|
# winter = Tag.find_or_initialize_by_name("Winter")
|
@@ -308,8 +316,8 @@ module ActiveRecord #:nodoc:
|
|
308
316
|
# class Client < Company; end
|
309
317
|
# class PriorityClient < Client; end
|
310
318
|
#
|
311
|
-
# When you do Firm.create(:name => "37signals")
|
312
|
-
# fetch this row again using Company.find(:first, "name = '37signals'") and it will return a Firm object.
|
319
|
+
# When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in the companies table with type = "Firm". You can then
|
320
|
+
# fetch this row again using <tt>Company.find(:first, "name = '37signals'")</tt> and it will return a Firm object.
|
313
321
|
#
|
314
322
|
# If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
|
315
323
|
# like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
|
@@ -321,34 +329,34 @@ module ActiveRecord #:nodoc:
|
|
321
329
|
#
|
322
330
|
# Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
|
323
331
|
# All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
|
324
|
-
# For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say Course.establish_connection
|
325
|
-
# and Course
|
332
|
+
# For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
|
333
|
+
# and Course and all of its subclasses will use this connection instead.
|
326
334
|
#
|
327
335
|
# This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
|
328
336
|
# requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
|
329
337
|
#
|
330
338
|
# == Exceptions
|
331
339
|
#
|
332
|
-
# *
|
333
|
-
# *
|
340
|
+
# * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
|
341
|
+
# * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
|
334
342
|
# <tt>:adapter</tt> key.
|
335
|
-
# *
|
343
|
+
# * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a non-existent adapter
|
336
344
|
# (or a bad spelling of an existing one).
|
337
|
-
# *
|
338
|
-
# *
|
339
|
-
# *
|
340
|
-
# *
|
341
|
-
#
|
342
|
-
#
|
343
|
-
#
|
344
|
-
# *
|
345
|
-
#
|
345
|
+
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type specified in the association definition.
|
346
|
+
# * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
|
347
|
+
# * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt> before querying.
|
348
|
+
# * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
|
349
|
+
# or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
|
350
|
+
# nothing was found, please check its documentation for further details.
|
351
|
+
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
|
352
|
+
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
|
353
|
+
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of AttributeAssignmentError
|
346
354
|
# objects that should be inspected to determine which attributes triggered the errors.
|
347
|
-
# *
|
355
|
+
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the <tt>attributes=</tt> method.
|
348
356
|
# You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
|
349
357
|
#
|
350
358
|
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
|
351
|
-
# So it's possible to assign a logger to the class through Base.logger
|
359
|
+
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
|
352
360
|
# instances in the current object space.
|
353
361
|
class Base
|
354
362
|
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
|
@@ -399,7 +407,7 @@ module ActiveRecord #:nodoc:
|
|
399
407
|
@@table_name_suffix = ""
|
400
408
|
|
401
409
|
# Indicates whether table names should be the pluralized versions of the corresponding class names.
|
402
|
-
# If true, the default table name for a
|
410
|
+
# If true, the default table name for a Product class will be +products+. If false, it would just be +product+.
|
403
411
|
# See table_name for the full rules on table/class naming. This is true, by default.
|
404
412
|
cattr_accessor :pluralize_table_names, :instance_writer => false
|
405
413
|
@@pluralize_table_names = true
|
@@ -416,7 +424,9 @@ module ActiveRecord #:nodoc:
|
|
416
424
|
@@default_timezone = :local
|
417
425
|
|
418
426
|
# Determines whether to use a connection for each thread, or a single shared connection for all threads.
|
419
|
-
# Defaults to false.
|
427
|
+
# Defaults to false. If you're writing a threaded application, set to true
|
428
|
+
# and periodically call verify_active_connections! to clear out connections
|
429
|
+
# assigned to stale threads.
|
420
430
|
cattr_accessor :allow_concurrency, :instance_writer => false
|
421
431
|
@@allow_concurrency = false
|
422
432
|
|
@@ -429,37 +439,51 @@ module ActiveRecord #:nodoc:
|
|
429
439
|
cattr_accessor :schema_format , :instance_writer => false
|
430
440
|
@@schema_format = :ruby
|
431
441
|
|
442
|
+
# Determine whether to store the full constant name including namespace when using STI
|
443
|
+
superclass_delegating_accessor :store_full_sti_class
|
444
|
+
self.store_full_sti_class = false
|
445
|
+
|
432
446
|
class << self # Class methods
|
433
|
-
# Find operates with
|
447
|
+
# Find operates with four different retrieval approaches:
|
434
448
|
#
|
435
|
-
# * Find by id
|
449
|
+
# * Find by id - This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
|
436
450
|
# If no record can be found for all of the listed ids, then RecordNotFound will be raised.
|
437
|
-
# * Find first
|
438
|
-
# conditions or merely an order. If no record can be matched, nil is returned.
|
439
|
-
#
|
440
|
-
#
|
441
|
-
#
|
442
|
-
#
|
443
|
-
# *
|
444
|
-
#
|
445
|
-
# *
|
446
|
-
#
|
447
|
-
#
|
448
|
-
#
|
449
|
-
#
|
451
|
+
# * Find first - This will return the first record matched by the options used. These options can either be specific
|
452
|
+
# conditions or merely an order. If no record can be matched, +nil+ is returned. Use
|
453
|
+
# <tt>Model.find(:first, *args)</tt> or its shortcut <tt>Model.first(*args)</tt>.
|
454
|
+
# * Find last - This will return the last record matched by the options used. These options can either be specific
|
455
|
+
# conditions or merely an order. If no record can be matched, +nil+ is returned. Use
|
456
|
+
# <tt>Model.find(:last, *args)</tt> or its shortcut <tt>Model.last(*args)</tt>.
|
457
|
+
# * Find all - This will return all the records matched by the options used.
|
458
|
+
# If no records are found, an empty array is returned. Use
|
459
|
+
# <tt>Model.find(:all, *args)</tt> or its shortcut <tt>Model.all(*args)</tt>.
|
460
|
+
#
|
461
|
+
# All approaches accept an options hash as their last parameter.
|
462
|
+
#
|
463
|
+
# ==== Attributes
|
464
|
+
#
|
465
|
+
# * <tt>:conditions</tt> - An SQL fragment like "administrator = 1" or <tt>[ "user_name = ?", username ]</tt>. See conditions in the intro.
|
466
|
+
# * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
|
467
|
+
# * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
|
468
|
+
# * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
|
469
|
+
# * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
|
470
|
+
# * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
|
471
|
+
# or named associations in the same form used for the <tt>:include</tt> option, which will perform an <tt>INNER JOIN</tt> on the associated table(s).
|
450
472
|
# If the value is a string, then the records will be returned read-only since they will have attributes that do not correspond to the table's columns.
|
451
|
-
# Pass
|
452
|
-
# * <tt>:include</tt
|
473
|
+
# Pass <tt>:readonly => false</tt> to override.
|
474
|
+
# * <tt>:include</tt> - Names associations that should be loaded alongside. The symbols named refer
|
453
475
|
# to already defined associations. See eager loading under Associations.
|
454
|
-
# * <tt>:select</tt
|
476
|
+
# * <tt>:select</tt> - By default, this is "*" as in "SELECT * FROM", but can be changed if you, for example, want to do a join but not
|
455
477
|
# include the joined columns.
|
456
|
-
# * <tt>:from</tt
|
478
|
+
# * <tt>:from</tt> - By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
|
457
479
|
# of a database view).
|
458
|
-
# * <tt>:readonly</tt
|
459
|
-
# * <tt>:lock</tt
|
460
|
-
#
|
480
|
+
# * <tt>:readonly</tt> - Mark the returned records read-only so they cannot be saved or updated.
|
481
|
+
# * <tt>:lock</tt> - An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
|
482
|
+
# <tt>:lock => true</tt> gives connection's default exclusive lock, usually "FOR UPDATE".
|
483
|
+
#
|
484
|
+
# ==== Examples
|
461
485
|
#
|
462
|
-
#
|
486
|
+
# # find by id
|
463
487
|
# Person.find(1) # returns the object for ID = 1
|
464
488
|
# Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
|
465
489
|
# Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
|
@@ -467,26 +491,35 @@ module ActiveRecord #:nodoc:
|
|
467
491
|
# Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
|
468
492
|
#
|
469
493
|
# Note that returned records may not be in the same order as the ids you
|
470
|
-
# provide since database rows are unordered. Give an explicit
|
494
|
+
# provide since database rows are unordered. Give an explicit <tt>:order</tt>
|
471
495
|
# to ensure the results are sorted.
|
472
496
|
#
|
473
|
-
# Examples
|
497
|
+
# ==== Examples
|
498
|
+
#
|
499
|
+
# # find first
|
474
500
|
# Person.find(:first) # returns the first object fetched by SELECT * FROM people
|
475
501
|
# Person.find(:first, :conditions => [ "user_name = ?", user_name])
|
476
502
|
# Person.find(:first, :order => "created_on DESC", :offset => 5)
|
477
503
|
#
|
478
|
-
#
|
504
|
+
# # find last
|
505
|
+
# Person.find(:last) # returns the last object fetched by SELECT * FROM people
|
506
|
+
# Person.find(:last, :conditions => [ "user_name = ?", user_name])
|
507
|
+
# Person.find(:last, :order => "created_on DESC", :offset => 5)
|
508
|
+
#
|
509
|
+
# # find all
|
479
510
|
# Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
|
480
511
|
# Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
|
512
|
+
# Person.find(:all, :conditions => { :friends => ["Bob", "Steve", "Fred"] }
|
481
513
|
# Person.find(:all, :offset => 10, :limit => 10)
|
482
514
|
# Person.find(:all, :include => [ :account, :friends ])
|
483
515
|
# Person.find(:all, :group => "category")
|
484
516
|
#
|
485
|
-
# Example for find with a lock
|
486
|
-
# each will read person.visits == 2
|
487
|
-
# in two saves of person.visits = 3
|
517
|
+
# Example for find with a lock: Imagine two concurrent transactions:
|
518
|
+
# each will read <tt>person.visits == 2</tt>, add 1 to it, and save, resulting
|
519
|
+
# in two saves of <tt>person.visits = 3</tt>. By locking the row, the second
|
488
520
|
# transaction has to wait until the first is finished; we get the
|
489
|
-
# expected person.visits == 4
|
521
|
+
# expected <tt>person.visits == 4</tt>.
|
522
|
+
#
|
490
523
|
# Person.transaction do
|
491
524
|
# person = Person.find(1, :lock => true)
|
492
525
|
# person.visits += 1
|
@@ -499,13 +532,31 @@ module ActiveRecord #:nodoc:
|
|
499
532
|
|
500
533
|
case args.first
|
501
534
|
when :first then find_initial(options)
|
535
|
+
when :last then find_last(options)
|
502
536
|
when :all then find_every(options)
|
503
537
|
else find_from_ids(args, options)
|
504
538
|
end
|
505
539
|
end
|
506
540
|
|
507
|
-
#
|
508
|
-
#
|
541
|
+
# A convenience wrapper for <tt>find(:first, *args)</tt>. You can pass in all the
|
542
|
+
# same arguments to this method as you can to <tt>find(:first)</tt>.
|
543
|
+
def first(*args)
|
544
|
+
find(:first, *args)
|
545
|
+
end
|
546
|
+
|
547
|
+
# A convenience wrapper for <tt>find(:last, *args)</tt>. You can pass in all the
|
548
|
+
# same arguments to this method as you can to <tt>find(:last)</tt>.
|
549
|
+
def last(*args)
|
550
|
+
find(:last, *args)
|
551
|
+
end
|
552
|
+
|
553
|
+
# This is an alias for find(:all). You can pass in all the same arguments to this method as you can
|
554
|
+
# to find(:all)
|
555
|
+
def all(*args)
|
556
|
+
find(:all, *args)
|
557
|
+
end
|
558
|
+
|
559
|
+
# Executes a custom SQL query against your database and returns all the results. The results will
|
509
560
|
# be returned as an array with columns requested encapsulated as attributes of the model you call
|
510
561
|
# this method from. If you call +Product.find_by_sql+ then the results will be returned in a Product
|
511
562
|
# object with the attributes you specified in the SQL query.
|
@@ -514,13 +565,13 @@ module ActiveRecord #:nodoc:
|
|
514
565
|
# SELECT will be attributes of the model, whether or not they are columns of the corresponding
|
515
566
|
# table.
|
516
567
|
#
|
517
|
-
# The +sql+ parameter is a full
|
568
|
+
# The +sql+ parameter is a full SQL query as a string. It will be called as is, there will be
|
518
569
|
# no database agnostic conversions performed. This should be a last resort because using, for example,
|
519
570
|
# MySQL specific terms will lock you to using that particular database engine or require you to
|
520
571
|
# change your call if you switch engines
|
521
572
|
#
|
522
573
|
# ==== Examples
|
523
|
-
# # A simple
|
574
|
+
# # A simple SQL query spanning multiple tables
|
524
575
|
# Post.find_by_sql "SELECT p.title, c.author FROM posts p, comments c WHERE p.id = c.post_id"
|
525
576
|
# > [#<Post:0x36bff9c @attributes={"title"=>"Ruby Meetup", "first_name"=>"Quentin"}>, ...]
|
526
577
|
#
|
@@ -550,10 +601,10 @@ module ActiveRecord #:nodoc:
|
|
550
601
|
def exists?(id_or_conditions)
|
551
602
|
connection.select_all(
|
552
603
|
construct_finder_sql(
|
553
|
-
:select => "#{quoted_table_name}.#{primary_key}",
|
554
|
-
:conditions => expand_id_conditions(id_or_conditions),
|
604
|
+
:select => "#{quoted_table_name}.#{primary_key}",
|
605
|
+
:conditions => expand_id_conditions(id_or_conditions),
|
555
606
|
:limit => 1
|
556
|
-
),
|
607
|
+
),
|
557
608
|
"#{name} Exists"
|
558
609
|
).size > 0
|
559
610
|
end
|
@@ -567,13 +618,25 @@ module ActiveRecord #:nodoc:
|
|
567
618
|
# ==== Examples
|
568
619
|
# # Create a single new object
|
569
620
|
# User.create(:first_name => 'Jamie')
|
621
|
+
#
|
570
622
|
# # Create an Array of new objects
|
571
|
-
# User.create([{:first_name => 'Jamie'}, {:first_name => 'Jeremy'}])
|
572
|
-
|
623
|
+
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }])
|
624
|
+
#
|
625
|
+
# # Create a single object and pass it into a block to set other attributes.
|
626
|
+
# User.create(:first_name => 'Jamie') do |u|
|
627
|
+
# u.is_admin = false
|
628
|
+
# end
|
629
|
+
#
|
630
|
+
# # Creating an Array of new objects using a block, where the block is executed for each object:
|
631
|
+
# User.create([{ :first_name => 'Jamie' }, { :first_name => 'Jeremy' }]) do |u|
|
632
|
+
# u.is_admin = false
|
633
|
+
# end
|
634
|
+
def create(attributes = nil, &block)
|
573
635
|
if attributes.is_a?(Array)
|
574
|
-
attributes.collect { |attr| create(attr) }
|
636
|
+
attributes.collect { |attr| create(attr, &block) }
|
575
637
|
else
|
576
638
|
object = new(attributes)
|
639
|
+
yield(object) if block_given?
|
577
640
|
object.save
|
578
641
|
object
|
579
642
|
end
|
@@ -582,18 +645,18 @@ module ActiveRecord #:nodoc:
|
|
582
645
|
# Updates an object (or multiple objects) and saves it to the database, if validations pass.
|
583
646
|
# The resulting object is returned whether the object was saved successfully to the database or not.
|
584
647
|
#
|
585
|
-
# ====
|
648
|
+
# ==== Attributes
|
586
649
|
#
|
587
|
-
# +id+
|
588
|
-
# +attributes+
|
650
|
+
# * +id+ - This should be the id or an array of ids to be updated.
|
651
|
+
# * +attributes+ - This should be a Hash of attributes to be set on the object, or an array of Hashes.
|
589
652
|
#
|
590
653
|
# ==== Examples
|
591
654
|
#
|
592
655
|
# # Updating one record:
|
593
|
-
# Person.update(15, {:user_name => 'Samuel', :group => 'expert'})
|
656
|
+
# Person.update(15, { :user_name => 'Samuel', :group => 'expert' })
|
594
657
|
#
|
595
658
|
# # Updating multiple records:
|
596
|
-
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }
|
659
|
+
# people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy" } }
|
597
660
|
# Person.update(people.keys, people.values)
|
598
661
|
def update(id, attributes)
|
599
662
|
if id.is_a?(Array)
|
@@ -612,9 +675,9 @@ module ActiveRecord #:nodoc:
|
|
612
675
|
#
|
613
676
|
# Objects are _not_ instantiated with this method.
|
614
677
|
#
|
615
|
-
# ====
|
678
|
+
# ==== Attributes
|
616
679
|
#
|
617
|
-
# +id+
|
680
|
+
# * +id+ - Can be either an Integer or an Array of Integers.
|
618
681
|
#
|
619
682
|
# ==== Examples
|
620
683
|
#
|
@@ -635,9 +698,9 @@ module ActiveRecord #:nodoc:
|
|
635
698
|
# This essentially finds the object (or multiple objects) with the given id, creates a new object
|
636
699
|
# from the attributes, and then calls destroy on it.
|
637
700
|
#
|
638
|
-
# ====
|
701
|
+
# ==== Attributes
|
639
702
|
#
|
640
|
-
# +id+
|
703
|
+
# * +id+ - Can be either an Integer or an Array of Integers.
|
641
704
|
#
|
642
705
|
# ==== Examples
|
643
706
|
#
|
@@ -658,12 +721,12 @@ module ActiveRecord #:nodoc:
|
|
658
721
|
# Updates all records with details given if they match a set of conditions supplied, limits and order can
|
659
722
|
# also be supplied.
|
660
723
|
#
|
661
|
-
# ====
|
724
|
+
# ==== Attributes
|
662
725
|
#
|
663
|
-
# +updates+
|
664
|
-
# +conditions+
|
665
|
-
#
|
666
|
-
# +options+
|
726
|
+
# * +updates+ - A String of column and value pairs that will be set on any records that match conditions.
|
727
|
+
# * +conditions+ - An SQL fragment like "administrator = 1" or [ "user_name = ?", username ].
|
728
|
+
# See conditions in the intro for more info.
|
729
|
+
# * +options+ - Additional options are <tt>:limit</tt> and/or <tt>:order</tt>, see the examples for usage.
|
667
730
|
#
|
668
731
|
# ==== Examples
|
669
732
|
#
|
@@ -677,7 +740,7 @@ module ActiveRecord #:nodoc:
|
|
677
740
|
# Billing.update_all( "author = 'David'", "title LIKE '%Rails%'",
|
678
741
|
# :order => 'created_at', :limit => 5 )
|
679
742
|
def update_all(updates, conditions = nil, options = {})
|
680
|
-
sql = "UPDATE #{
|
743
|
+
sql = "UPDATE #{quoted_table_name} SET #{sanitize_sql_for_assignment(updates)} "
|
681
744
|
scope = scope(:find)
|
682
745
|
add_conditions!(sql, conditions, scope)
|
683
746
|
add_order!(sql, options[:order], nil)
|
@@ -690,9 +753,9 @@ module ActiveRecord #:nodoc:
|
|
690
753
|
# many records. If you want to simply delete records without worrying about dependent associations or
|
691
754
|
# callbacks, use the much faster +delete_all+ method instead.
|
692
755
|
#
|
693
|
-
# ====
|
756
|
+
# ==== Attributes
|
694
757
|
#
|
695
|
-
# +conditions+
|
758
|
+
# * +conditions+ - Conditions are specified the same way as with +find+ method.
|
696
759
|
#
|
697
760
|
# ==== Example
|
698
761
|
#
|
@@ -708,9 +771,9 @@ module ActiveRecord #:nodoc:
|
|
708
771
|
# calling the destroy method and invoking callbacks. This is a single SQL query, much more efficient
|
709
772
|
# than destroy_all.
|
710
773
|
#
|
711
|
-
# ====
|
774
|
+
# ==== Attributes
|
712
775
|
#
|
713
|
-
# +conditions+
|
776
|
+
# * +conditions+ - Conditions are specified the same way as with +find+ method.
|
714
777
|
#
|
715
778
|
# ==== Example
|
716
779
|
#
|
@@ -728,9 +791,9 @@ module ActiveRecord #:nodoc:
|
|
728
791
|
# The use of this method should be restricted to complicated SQL queries that can't be executed
|
729
792
|
# using the ActiveRecord::Calculations class methods. Look into those before using this.
|
730
793
|
#
|
731
|
-
# ====
|
794
|
+
# ==== Attributes
|
732
795
|
#
|
733
|
-
# +sql
|
796
|
+
# * +sql+ - An SQL statement which should return a count query from the database, see the example below.
|
734
797
|
#
|
735
798
|
# ==== Examples
|
736
799
|
#
|
@@ -746,12 +809,11 @@ module ActiveRecord #:nodoc:
|
|
746
809
|
# with the given ID, altering the given hash of counters by the amount
|
747
810
|
# given by the corresponding value:
|
748
811
|
#
|
749
|
-
# ====
|
812
|
+
# ==== Attributes
|
750
813
|
#
|
751
|
-
# +id+
|
752
|
-
# +counters+
|
753
|
-
#
|
754
|
-
# values
|
814
|
+
# * +id+ - The id of the object you wish to update a counter on.
|
815
|
+
# * +counters+ - An Array of Hashes containing the names of the fields
|
816
|
+
# to update as keys and the amount to update the field by as values.
|
755
817
|
#
|
756
818
|
# ==== Examples
|
757
819
|
#
|
@@ -777,10 +839,10 @@ module ActiveRecord #:nodoc:
|
|
777
839
|
# For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
|
778
840
|
# shown it would have to run an SQL query to find how many posts and comments there are.
|
779
841
|
#
|
780
|
-
# ====
|
842
|
+
# ==== Attributes
|
781
843
|
#
|
782
|
-
# +counter_name+
|
783
|
-
# +id+
|
844
|
+
# * +counter_name+ - The name of the field that should be incremented.
|
845
|
+
# * +id+ - The id of the object that should be incremented.
|
784
846
|
#
|
785
847
|
# ==== Examples
|
786
848
|
#
|
@@ -794,10 +856,10 @@ module ActiveRecord #:nodoc:
|
|
794
856
|
#
|
795
857
|
# This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
|
796
858
|
#
|
797
|
-
# ====
|
859
|
+
# ==== Attributes
|
798
860
|
#
|
799
|
-
# +counter_name+
|
800
|
-
# +id+
|
861
|
+
# * +counter_name+ - The name of the field that should be decremented.
|
862
|
+
# * +id+ - The id of the object that should be decremented.
|
801
863
|
#
|
802
864
|
# ==== Examples
|
803
865
|
#
|
@@ -808,9 +870,15 @@ module ActiveRecord #:nodoc:
|
|
808
870
|
end
|
809
871
|
|
810
872
|
|
811
|
-
# Attributes named in this macro are protected from mass-assignment,
|
812
|
-
# <tt>
|
813
|
-
#
|
873
|
+
# Attributes named in this macro are protected from mass-assignment,
|
874
|
+
# such as <tt>new(attributes)</tt>,
|
875
|
+
# <tt>update_attributes(attributes)</tt>, or
|
876
|
+
# <tt>attributes=(attributes)</tt>.
|
877
|
+
#
|
878
|
+
# Mass-assignment to these attributes will simply be ignored, to assign
|
879
|
+
# to them you can use direct writer methods. This is meant to protect
|
880
|
+
# sensitive attributes from being overwritten by malicious users
|
881
|
+
# tampering with URLs or forms.
|
814
882
|
#
|
815
883
|
# class Customer < ActiveRecord::Base
|
816
884
|
# attr_protected :credit_rating
|
@@ -824,7 +892,8 @@ module ActiveRecord #:nodoc:
|
|
824
892
|
# customer.credit_rating = "Average"
|
825
893
|
# customer.credit_rating # => "Average"
|
826
894
|
#
|
827
|
-
# To start from an all-closed default and enable attributes as needed,
|
895
|
+
# To start from an all-closed default and enable attributes as needed,
|
896
|
+
# have a look at +attr_accessible+.
|
828
897
|
def attr_protected(*attributes)
|
829
898
|
write_inheritable_attribute("attr_protected", Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
|
830
899
|
end
|
@@ -834,19 +903,18 @@ module ActiveRecord #:nodoc:
|
|
834
903
|
read_inheritable_attribute("attr_protected")
|
835
904
|
end
|
836
905
|
|
837
|
-
#
|
838
|
-
# such as <tt>new(attributes)</tt
|
839
|
-
#
|
840
|
-
# attributes
|
841
|
-
# using the direct writer methods instead. This is meant to protect sensitive attributes from being
|
842
|
-
# overwritten by URL/form hackers. If you'd rather start from an all-open default and restrict
|
843
|
-
# attributes as needed, have a look at attr_protected.
|
844
|
-
#
|
845
|
-
# ==== Options
|
906
|
+
# Specifies a white list of model attributes that can be set via
|
907
|
+
# mass-assignment, such as <tt>new(attributes)</tt>,
|
908
|
+
# <tt>update_attributes(attributes)</tt>, or
|
909
|
+
# <tt>attributes=(attributes)</tt>
|
846
910
|
#
|
847
|
-
#
|
848
|
-
#
|
849
|
-
#
|
911
|
+
# This is the opposite of the +attr_protected+ macro: Mass-assignment
|
912
|
+
# will only set attributes in this list, to assign to the rest of
|
913
|
+
# attributes you can use direct writer methods. This is meant to protect
|
914
|
+
# sensitive attributes from being overwritten by malicious users
|
915
|
+
# tampering with URLs or forms. If you'd rather start from an all-open
|
916
|
+
# default and restrict attributes as needed, have a look at
|
917
|
+
# +attr_protected+.
|
850
918
|
#
|
851
919
|
# class Customer < ActiveRecord::Base
|
852
920
|
# attr_accessible :name, :nickname
|
@@ -881,12 +949,12 @@ module ActiveRecord #:nodoc:
|
|
881
949
|
# If you have an attribute that needs to be saved to the database as an object, and retrieved as the same object,
|
882
950
|
# then specify the name of that attribute using this method and it will be handled automatically.
|
883
951
|
# The serialization is done through YAML. If +class_name+ is specified, the serialized object must be of that
|
884
|
-
# class on retrieval or
|
952
|
+
# class on retrieval or SerializationTypeMismatch will be raised.
|
885
953
|
#
|
886
|
-
# ====
|
954
|
+
# ==== Attributes
|
887
955
|
#
|
888
|
-
# +attr_name+
|
889
|
-
# +class_name+
|
956
|
+
# * +attr_name+ - The field name that should be serialized.
|
957
|
+
# * +class_name+ - Optional, class name that the object type should be equal to.
|
890
958
|
#
|
891
959
|
# ==== Example
|
892
960
|
# # Serialize a preferences attribute
|
@@ -904,12 +972,14 @@ module ActiveRecord #:nodoc:
|
|
904
972
|
|
905
973
|
|
906
974
|
# Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
|
907
|
-
# directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
|
975
|
+
# directly from ActiveRecord::Base. So if the hierarchy looks like: Reply < Message < ActiveRecord::Base, then Message is used
|
908
976
|
# to guess the table name even when called on Reply. The rules used to do the guess are handled by the Inflector class
|
909
977
|
# in Active Support, which knows almost all common English inflections. You can add new inflections in config/initializers/inflections.rb.
|
910
978
|
#
|
911
979
|
# Nested classes are given table names prefixed by the singular form of
|
912
|
-
# the parent's table name. Enclosing modules are not considered.
|
980
|
+
# the parent's table name. Enclosing modules are not considered.
|
981
|
+
#
|
982
|
+
# ==== Examples
|
913
983
|
#
|
914
984
|
# class Invoice < ActiveRecord::Base; end;
|
915
985
|
# file class table_name
|
@@ -923,8 +993,8 @@ module ActiveRecord #:nodoc:
|
|
923
993
|
# file class table_name
|
924
994
|
# invoice/lineitem.rb Invoice::Lineitem lineitems
|
925
995
|
#
|
926
|
-
# Additionally, the class-level table_name_prefix is prepended and the
|
927
|
-
# table_name_suffix is appended. So if you have "myapp_" as a prefix,
|
996
|
+
# Additionally, the class-level +table_name_prefix+ is prepended and the
|
997
|
+
# +table_name_suffix+ is appended. So if you have "myapp_" as a prefix,
|
928
998
|
# the table name guess for an Invoice class becomes "myapp_invoices".
|
929
999
|
# Invoice::Lineitem becomes "myapp_invoice_lineitems".
|
930
1000
|
#
|
@@ -975,9 +1045,9 @@ module ActiveRecord #:nodoc:
|
|
975
1045
|
key = 'id'
|
976
1046
|
case primary_key_prefix_type
|
977
1047
|
when :table_name
|
978
|
-
key =
|
1048
|
+
key = base_name.to_s.foreign_key(false)
|
979
1049
|
when :table_name_with_underscore
|
980
|
-
key =
|
1050
|
+
key = base_name.to_s.foreign_key
|
981
1051
|
end
|
982
1052
|
key
|
983
1053
|
end
|
@@ -1003,8 +1073,6 @@ module ActiveRecord #:nodoc:
|
|
1003
1073
|
# Sets the table name to use to the given value, or (if the value
|
1004
1074
|
# is nil or false) to the value returned by the given block.
|
1005
1075
|
#
|
1006
|
-
# Example:
|
1007
|
-
#
|
1008
1076
|
# class Project < ActiveRecord::Base
|
1009
1077
|
# set_table_name "project"
|
1010
1078
|
# end
|
@@ -1017,8 +1085,6 @@ module ActiveRecord #:nodoc:
|
|
1017
1085
|
# or (if the value is nil or false) to the value returned by the given
|
1018
1086
|
# block.
|
1019
1087
|
#
|
1020
|
-
# Example:
|
1021
|
-
#
|
1022
1088
|
# class Project < ActiveRecord::Base
|
1023
1089
|
# set_primary_key "sysid"
|
1024
1090
|
# end
|
@@ -1031,8 +1097,6 @@ module ActiveRecord #:nodoc:
|
|
1031
1097
|
# or (if the value # is nil or false) to the value returned by the
|
1032
1098
|
# given block.
|
1033
1099
|
#
|
1034
|
-
# Example:
|
1035
|
-
#
|
1036
1100
|
# class Project < ActiveRecord::Base
|
1037
1101
|
# set_inheritance_column do
|
1038
1102
|
# original_inheritance_column + "_id"
|
@@ -1054,8 +1118,6 @@ module ActiveRecord #:nodoc:
|
|
1054
1118
|
# If a sequence name is not explicitly set when using PostgreSQL, it
|
1055
1119
|
# will discover the sequence corresponding to your primary key for you.
|
1056
1120
|
#
|
1057
|
-
# Example:
|
1058
|
-
#
|
1059
1121
|
# class Project < ActiveRecord::Base
|
1060
1122
|
# set_sequence_name "projectseq" # default would have been "project_seq"
|
1061
1123
|
# end
|
@@ -1074,18 +1136,7 @@ module ActiveRecord #:nodoc:
|
|
1074
1136
|
|
1075
1137
|
# Indicates whether the table associated with this class exists
|
1076
1138
|
def table_exists?
|
1077
|
-
|
1078
|
-
connection.tables.include? table_name
|
1079
|
-
else
|
1080
|
-
# if the connection adapter hasn't implemented tables, there are two crude tests that can be
|
1081
|
-
# used - see if getting column info raises an error, or if the number of columns returned is zero
|
1082
|
-
begin
|
1083
|
-
reset_column_information
|
1084
|
-
columns.size > 0
|
1085
|
-
rescue ActiveRecord::StatementInvalid
|
1086
|
-
false
|
1087
|
-
end
|
1088
|
-
end
|
1139
|
+
connection.table_exists?(table_name)
|
1089
1140
|
end
|
1090
1141
|
|
1091
1142
|
# Returns an array of column objects for the table associated with this class.
|
@@ -1226,7 +1277,7 @@ module ActiveRecord #:nodoc:
|
|
1226
1277
|
class_of_active_record_descendant(self)
|
1227
1278
|
end
|
1228
1279
|
|
1229
|
-
# Set this to true if this is an abstract class (see
|
1280
|
+
# Set this to true if this is an abstract class (see <tt>abstract_class?</tt>).
|
1230
1281
|
attr_accessor :abstract_class
|
1231
1282
|
|
1232
1283
|
# Returns whether this class is a base AR class. If A is a base class and
|
@@ -1235,16 +1286,63 @@ module ActiveRecord #:nodoc:
|
|
1235
1286
|
defined?(@abstract_class) && @abstract_class == true
|
1236
1287
|
end
|
1237
1288
|
|
1289
|
+
def respond_to?(method_id, include_private = false)
|
1290
|
+
if match = matches_dynamic_finder?(method_id) || matches_dynamic_finder_with_initialize_or_create?(method_id)
|
1291
|
+
return true if all_attributes_exists?(extract_attribute_names_from_match(match))
|
1292
|
+
end
|
1293
|
+
super
|
1294
|
+
end
|
1295
|
+
|
1296
|
+
def sti_name
|
1297
|
+
store_full_sti_class ? name : name.demodulize
|
1298
|
+
end
|
1299
|
+
|
1238
1300
|
private
|
1239
1301
|
def find_initial(options)
|
1240
|
-
options.update(:limit => 1)
|
1302
|
+
options.update(:limit => 1)
|
1241
1303
|
find_every(options).first
|
1242
1304
|
end
|
1243
1305
|
|
1306
|
+
def find_last(options)
|
1307
|
+
order = options[:order]
|
1308
|
+
|
1309
|
+
if order
|
1310
|
+
order = reverse_sql_order(order)
|
1311
|
+
elsif !scoped?(:find, :order)
|
1312
|
+
order = "#{table_name}.#{primary_key} DESC"
|
1313
|
+
end
|
1314
|
+
|
1315
|
+
if scoped?(:find, :order)
|
1316
|
+
scoped_order = reverse_sql_order(scope(:find, :order))
|
1317
|
+
scoped_methods.select { |s| s[:find].update(:order => scoped_order) }
|
1318
|
+
end
|
1319
|
+
|
1320
|
+
find_initial(options.merge({ :order => order }))
|
1321
|
+
end
|
1322
|
+
|
1323
|
+
def reverse_sql_order(order_query)
|
1324
|
+
reversed_query = order_query.split(/,/).each { |s|
|
1325
|
+
if s.match(/\s(asc|ASC)$/)
|
1326
|
+
s.gsub!(/\s(asc|ASC)$/, ' DESC')
|
1327
|
+
elsif s.match(/\s(desc|DESC)$/)
|
1328
|
+
s.gsub!(/\s(desc|DESC)$/, ' ASC')
|
1329
|
+
elsif !s.match(/\s(asc|ASC|desc|DESC)$/)
|
1330
|
+
s.concat(' DESC')
|
1331
|
+
end
|
1332
|
+
}.join(',')
|
1333
|
+
end
|
1334
|
+
|
1244
1335
|
def find_every(options)
|
1245
|
-
|
1246
|
-
|
1247
|
-
|
1336
|
+
include_associations = merge_includes(scope(:find, :include), options[:include])
|
1337
|
+
|
1338
|
+
if include_associations.any? && references_eager_loaded_tables?(options)
|
1339
|
+
records = find_with_associations(options)
|
1340
|
+
else
|
1341
|
+
records = find_by_sql(construct_finder_sql(options))
|
1342
|
+
if include_associations.any?
|
1343
|
+
preload_associations(records, include_associations)
|
1344
|
+
end
|
1345
|
+
end
|
1248
1346
|
|
1249
1347
|
records.each { |record| record.readonly! } if options[:readonly]
|
1250
1348
|
|
@@ -1358,12 +1456,16 @@ module ActiveRecord #:nodoc:
|
|
1358
1456
|
# Nest the type name in the same module as this class.
|
1359
1457
|
# Bar is "MyApp::Business::Bar" relative to MyApp::Business::Foo
|
1360
1458
|
def type_name_with_module(type_name)
|
1361
|
-
|
1459
|
+
if store_full_sti_class
|
1460
|
+
type_name
|
1461
|
+
else
|
1462
|
+
(/^::/ =~ type_name) ? type_name : "#{parent.name}::#{type_name}"
|
1463
|
+
end
|
1362
1464
|
end
|
1363
1465
|
|
1364
1466
|
def construct_finder_sql(options)
|
1365
1467
|
scope = scope(:find)
|
1366
|
-
sql = "SELECT #{(scope && scope[:select]) ||
|
1468
|
+
sql = "SELECT #{options[:select] || (scope && scope[:select]) || (options[:joins] && quoted_table_name + '.*') || '*'} "
|
1367
1469
|
sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
|
1368
1470
|
|
1369
1471
|
add_joins!(sql, options, scope)
|
@@ -1382,6 +1484,20 @@ module ActiveRecord #:nodoc:
|
|
1382
1484
|
(safe_to_array(first) + safe_to_array(second)).uniq
|
1383
1485
|
end
|
1384
1486
|
|
1487
|
+
# Merges conditions so that the result is a valid +condition+
|
1488
|
+
def merge_conditions(*conditions)
|
1489
|
+
segments = []
|
1490
|
+
|
1491
|
+
conditions.each do |condition|
|
1492
|
+
unless condition.blank?
|
1493
|
+
sql = sanitize_sql(condition)
|
1494
|
+
segments << sql unless sql.blank?
|
1495
|
+
end
|
1496
|
+
end
|
1497
|
+
|
1498
|
+
"(#{segments.join(') AND (')})" unless segments.empty?
|
1499
|
+
end
|
1500
|
+
|
1385
1501
|
# Object#to_a is deprecated, though it does have the desired behavior
|
1386
1502
|
def safe_to_array(o)
|
1387
1503
|
case o
|
@@ -1416,7 +1532,7 @@ module ActiveRecord #:nodoc:
|
|
1416
1532
|
end
|
1417
1533
|
end
|
1418
1534
|
|
1419
|
-
# The optional scope argument is for the current
|
1535
|
+
# The optional scope argument is for the current <tt>:find</tt> scope.
|
1420
1536
|
def add_limit!(sql, options, scope = :auto)
|
1421
1537
|
scope = scope(:find) if :auto == scope
|
1422
1538
|
|
@@ -1428,43 +1544,43 @@ module ActiveRecord #:nodoc:
|
|
1428
1544
|
connection.add_limit_offset!(sql, options)
|
1429
1545
|
end
|
1430
1546
|
|
1431
|
-
# The optional scope argument is for the current
|
1432
|
-
# The
|
1547
|
+
# The optional scope argument is for the current <tt>:find</tt> scope.
|
1548
|
+
# The <tt>:lock</tt> option has precedence over a scoped <tt>:lock</tt>.
|
1433
1549
|
def add_lock!(sql, options, scope = :auto)
|
1434
1550
|
scope = scope(:find) if :auto == scope
|
1435
1551
|
options = options.reverse_merge(:lock => scope[:lock]) if scope
|
1436
1552
|
connection.add_lock!(sql, options)
|
1437
1553
|
end
|
1438
1554
|
|
1439
|
-
# The optional scope argument is for the current
|
1555
|
+
# The optional scope argument is for the current <tt>:find</tt> scope.
|
1440
1556
|
def add_joins!(sql, options, scope = :auto)
|
1441
1557
|
scope = scope(:find) if :auto == scope
|
1442
|
-
|
1443
|
-
|
1444
|
-
|
1445
|
-
|
1446
|
-
|
1447
|
-
|
1448
|
-
|
1558
|
+
[(scope && scope[:joins]), options[:joins]].each do |join|
|
1559
|
+
case join
|
1560
|
+
when Symbol, Hash, Array
|
1561
|
+
join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
|
1562
|
+
sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
|
1563
|
+
else
|
1564
|
+
sql << " #{join} "
|
1565
|
+
end
|
1449
1566
|
end
|
1450
1567
|
end
|
1451
1568
|
|
1452
1569
|
# Adds a sanitized version of +conditions+ to the +sql+ string. Note that the passed-in +sql+ string is changed.
|
1453
|
-
# The optional scope argument is for the current
|
1570
|
+
# The optional scope argument is for the current <tt>:find</tt> scope.
|
1454
1571
|
def add_conditions!(sql, conditions, scope = :auto)
|
1455
1572
|
scope = scope(:find) if :auto == scope
|
1456
|
-
|
1457
|
-
|
1458
|
-
|
1459
|
-
|
1460
|
-
|
1461
|
-
sql << "WHERE (#{segments.join(") AND (")}) " unless segments.empty?
|
1573
|
+
conditions = [conditions]
|
1574
|
+
conditions << scope[:conditions] if scope
|
1575
|
+
conditions << type_condition if finder_needs_type_condition?
|
1576
|
+
merged_conditions = merge_conditions(*conditions)
|
1577
|
+
sql << "WHERE #{merged_conditions} " unless merged_conditions.blank?
|
1462
1578
|
end
|
1463
1579
|
|
1464
1580
|
def type_condition
|
1465
1581
|
quoted_inheritance_column = connection.quote_column_name(inheritance_column)
|
1466
|
-
type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{
|
1467
|
-
condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.
|
1582
|
+
type_condition = subclasses.inject("#{quoted_table_name}.#{quoted_inheritance_column} = '#{sti_name}' ") do |condition, subclass|
|
1583
|
+
condition << "OR #{quoted_table_name}.#{quoted_inheritance_column} = '#{subclass.sti_name}' "
|
1468
1584
|
end
|
1469
1585
|
|
1470
1586
|
" (#{type_condition}) "
|
@@ -1472,8 +1588,8 @@ module ActiveRecord #:nodoc:
|
|
1472
1588
|
|
1473
1589
|
# Guesses the table name, but does not decorate it with prefix and suffix information.
|
1474
1590
|
def undecorated_table_name(class_name = base_class.name)
|
1475
|
-
table_name =
|
1476
|
-
table_name =
|
1591
|
+
table_name = class_name.to_s.demodulize.underscore
|
1592
|
+
table_name = table_name.pluralize if pluralize_table_names
|
1477
1593
|
table_name
|
1478
1594
|
end
|
1479
1595
|
|
@@ -1490,7 +1606,7 @@ module ActiveRecord #:nodoc:
|
|
1490
1606
|
# Each dynamic finder or initializer/creator is also defined in the class after it is first invoked, so that future
|
1491
1607
|
# attempts to use it do not run through method_missing.
|
1492
1608
|
def method_missing(method_id, *arguments)
|
1493
|
-
if match =
|
1609
|
+
if match = matches_dynamic_finder?(method_id)
|
1494
1610
|
finder = determine_finder(match)
|
1495
1611
|
|
1496
1612
|
attribute_names = extract_attribute_names_from_match(match)
|
@@ -1514,14 +1630,17 @@ module ActiveRecord #:nodoc:
|
|
1514
1630
|
end
|
1515
1631
|
}, __FILE__, __LINE__
|
1516
1632
|
send(method_id, *arguments)
|
1517
|
-
elsif match =
|
1633
|
+
elsif match = matches_dynamic_finder_with_initialize_or_create?(method_id)
|
1518
1634
|
instantiator = determine_instantiator(match)
|
1519
1635
|
attribute_names = extract_attribute_names_from_match(match)
|
1520
1636
|
super unless all_attributes_exists?(attribute_names)
|
1521
1637
|
|
1522
1638
|
self.class_eval %{
|
1523
1639
|
def self.#{method_id}(*args)
|
1640
|
+
guard_protected_attributes = false
|
1641
|
+
|
1524
1642
|
if args[0].is_a?(Hash)
|
1643
|
+
guard_protected_attributes = true
|
1525
1644
|
attributes = args[0].with_indifferent_access
|
1526
1645
|
find_attributes = attributes.slice(*[:#{attribute_names.join(',:')}])
|
1527
1646
|
else
|
@@ -1532,8 +1651,10 @@ module ActiveRecord #:nodoc:
|
|
1532
1651
|
set_readonly_option!(options)
|
1533
1652
|
|
1534
1653
|
record = find_initial(options)
|
1535
|
-
|
1536
|
-
|
1654
|
+
|
1655
|
+
if record.nil?
|
1656
|
+
record = self.new { |r| r.send(:attributes=, attributes, guard_protected_attributes) }
|
1657
|
+
#{'yield(record) if block_given?'}
|
1537
1658
|
#{'record.save' if instantiator == :create}
|
1538
1659
|
record
|
1539
1660
|
else
|
@@ -1547,6 +1668,14 @@ module ActiveRecord #:nodoc:
|
|
1547
1668
|
end
|
1548
1669
|
end
|
1549
1670
|
|
1671
|
+
def matches_dynamic_finder?(method_id)
|
1672
|
+
/^find_(all_by|by)_([_a-zA-Z]\w*)$/.match(method_id.to_s)
|
1673
|
+
end
|
1674
|
+
|
1675
|
+
def matches_dynamic_finder_with_initialize_or_create?(method_id)
|
1676
|
+
/^find_or_(initialize|create)_by_([_a-zA-Z]\w*)$/.match(method_id.to_s)
|
1677
|
+
end
|
1678
|
+
|
1550
1679
|
def determine_finder(match)
|
1551
1680
|
match.captures.first == 'all_by' ? :find_every : :find_initial
|
1552
1681
|
end
|
@@ -1565,7 +1694,23 @@ module ActiveRecord #:nodoc:
|
|
1565
1694
|
attributes
|
1566
1695
|
end
|
1567
1696
|
|
1697
|
+
# Similar in purpose to +expand_hash_conditions_for_aggregates+.
|
1698
|
+
def expand_attribute_names_for_aggregates(attribute_names)
|
1699
|
+
expanded_attribute_names = []
|
1700
|
+
attribute_names.each do |attribute_name|
|
1701
|
+
unless (aggregation = reflect_on_aggregation(attribute_name.to_sym)).nil?
|
1702
|
+
aggregate_mapping(aggregation).each do |field_attr, aggregate_attr|
|
1703
|
+
expanded_attribute_names << field_attr
|
1704
|
+
end
|
1705
|
+
else
|
1706
|
+
expanded_attribute_names << attribute_name
|
1707
|
+
end
|
1708
|
+
end
|
1709
|
+
expanded_attribute_names
|
1710
|
+
end
|
1711
|
+
|
1568
1712
|
def all_attributes_exists?(attribute_names)
|
1713
|
+
attribute_names = expand_attribute_names_for_aggregates(attribute_names)
|
1569
1714
|
attribute_names.all? { |name| column_methods_hash.include?(name.to_sym) }
|
1570
1715
|
end
|
1571
1716
|
|
@@ -1587,8 +1732,8 @@ module ActiveRecord #:nodoc:
|
|
1587
1732
|
end
|
1588
1733
|
|
1589
1734
|
|
1590
|
-
# Defines an "attribute" method (like
|
1591
|
-
#
|
1735
|
+
# Defines an "attribute" method (like +inheritance_column+ or
|
1736
|
+
# +table_name+). A new (class) method will be created with the
|
1592
1737
|
# given name. If a value is specified, the new method will
|
1593
1738
|
# return that value (as a string). Otherwise, the given block
|
1594
1739
|
# will be used to compute the value of the method.
|
@@ -1619,8 +1764,8 @@ module ActiveRecord #:nodoc:
|
|
1619
1764
|
|
1620
1765
|
protected
|
1621
1766
|
# Scope parameters to method calls within the block. Takes a hash of method_name => parameters hash.
|
1622
|
-
# method_name may be
|
1623
|
-
# <tt>:include</tt>, <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> options.
|
1767
|
+
# method_name may be <tt>:find</tt> or <tt>:create</tt>. <tt>:find</tt> parameters may include the <tt>:conditions</tt>, <tt>:joins</tt>,
|
1768
|
+
# <tt>:include</tt>, <tt>:offset</tt>, <tt>:limit</tt>, and <tt>:readonly</tt> options. <tt>:create</tt> parameters are an attributes hash.
|
1624
1769
|
#
|
1625
1770
|
# class Article < ActiveRecord::Base
|
1626
1771
|
# def self.create_with_scope
|
@@ -1633,12 +1778,12 @@ module ActiveRecord #:nodoc:
|
|
1633
1778
|
# end
|
1634
1779
|
#
|
1635
1780
|
# In nested scopings, all previous parameters are overwritten by the innermost rule, with the exception of
|
1636
|
-
#
|
1781
|
+
# <tt>:conditions</tt> and <tt>:include</tt> options in <tt>:find</tt>, which are merged.
|
1637
1782
|
#
|
1638
1783
|
# class Article < ActiveRecord::Base
|
1639
1784
|
# def self.find_with_scope
|
1640
1785
|
# with_scope(:find => { :conditions => "blog_id = 1", :limit => 1 }, :create => { :blog_id => 1 }) do
|
1641
|
-
# with_scope(:find => { :limit => 10})
|
1786
|
+
# with_scope(:find => { :limit => 10 })
|
1642
1787
|
# find(:all) # => SELECT * from articles WHERE blog_id = 1 LIMIT 10
|
1643
1788
|
# end
|
1644
1789
|
# with_scope(:find => { :conditions => "author_id = 3" })
|
@@ -1684,7 +1829,7 @@ module ActiveRecord #:nodoc:
|
|
1684
1829
|
(hash[method].keys + params.keys).uniq.each do |key|
|
1685
1830
|
merge = hash[method][key] && params[key] # merge if both scopes have the same key
|
1686
1831
|
if key == :conditions && merge
|
1687
|
-
hash[method][key] =
|
1832
|
+
hash[method][key] = merge_conditions(params[key], hash[method][key])
|
1688
1833
|
elsif key == :include && merge
|
1689
1834
|
hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
|
1690
1835
|
else
|
@@ -1765,7 +1910,7 @@ module ActiveRecord #:nodoc:
|
|
1765
1910
|
end
|
1766
1911
|
end
|
1767
1912
|
|
1768
|
-
# Returns the class descending directly from
|
1913
|
+
# Returns the class descending directly from Active Record in the inheritance hierarchy.
|
1769
1914
|
def class_of_active_record_descendant(klass)
|
1770
1915
|
if klass.superclass == Base || klass.superclass.abstract_class?
|
1771
1916
|
klass
|
@@ -1776,17 +1921,19 @@ module ActiveRecord #:nodoc:
|
|
1776
1921
|
end
|
1777
1922
|
end
|
1778
1923
|
|
1779
|
-
# Returns the name of the class descending directly from
|
1924
|
+
# Returns the name of the class descending directly from Active Record in the inheritance hierarchy.
|
1780
1925
|
def class_name_of_active_record_descendant(klass) #:nodoc:
|
1781
1926
|
klass.base_class.name
|
1782
1927
|
end
|
1783
1928
|
|
1784
|
-
# Accepts an array, hash, or string of
|
1929
|
+
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
1785
1930
|
# them into a valid SQL fragment for a WHERE clause.
|
1786
1931
|
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
1787
1932
|
# { :name => "foo'bar", :group_id => 4 } returns "name='foo''bar' and group_id='4'"
|
1788
1933
|
# "name='foo''bar' and group_id='4'" returns "name='foo''bar' and group_id='4'"
|
1789
1934
|
def sanitize_sql_for_conditions(condition)
|
1935
|
+
return nil if condition.blank?
|
1936
|
+
|
1790
1937
|
case condition
|
1791
1938
|
when Array; sanitize_sql_array(condition)
|
1792
1939
|
when Hash; sanitize_sql_hash_for_conditions(condition)
|
@@ -1795,7 +1942,7 @@ module ActiveRecord #:nodoc:
|
|
1795
1942
|
end
|
1796
1943
|
alias_method :sanitize_sql, :sanitize_sql_for_conditions
|
1797
1944
|
|
1798
|
-
# Accepts an array, hash, or string of
|
1945
|
+
# Accepts an array, hash, or string of SQL conditions and sanitizes
|
1799
1946
|
# them into a valid SQL fragment for a SET clause.
|
1800
1947
|
# { :name => nil, :group_id => 4 } returns "name = NULL , group_id='4'"
|
1801
1948
|
def sanitize_sql_for_assignment(assignments)
|
@@ -1806,6 +1953,41 @@ module ActiveRecord #:nodoc:
|
|
1806
1953
|
end
|
1807
1954
|
end
|
1808
1955
|
|
1956
|
+
def aggregate_mapping(reflection)
|
1957
|
+
mapping = reflection.options[:mapping] || [reflection.name, reflection.name]
|
1958
|
+
mapping.first.is_a?(Array) ? mapping : [mapping]
|
1959
|
+
end
|
1960
|
+
|
1961
|
+
# Accepts a hash of SQL conditions and replaces those attributes
|
1962
|
+
# that correspond to a +composed_of+ relationship with their expanded
|
1963
|
+
# aggregate attribute values.
|
1964
|
+
# Given:
|
1965
|
+
# class Person < ActiveRecord::Base
|
1966
|
+
# composed_of :address, :class_name => "Address",
|
1967
|
+
# :mapping => [%w(address_street street), %w(address_city city)]
|
1968
|
+
# end
|
1969
|
+
# Then:
|
1970
|
+
# { :address => Address.new("813 abc st.", "chicago") }
|
1971
|
+
# # => { :address_street => "813 abc st.", :address_city => "chicago" }
|
1972
|
+
def expand_hash_conditions_for_aggregates(attrs)
|
1973
|
+
expanded_attrs = {}
|
1974
|
+
attrs.each do |attr, value|
|
1975
|
+
unless (aggregation = reflect_on_aggregation(attr.to_sym)).nil?
|
1976
|
+
mapping = aggregate_mapping(aggregation)
|
1977
|
+
mapping.each do |field_attr, aggregate_attr|
|
1978
|
+
if mapping.size == 1 && !value.respond_to?(aggregate_attr)
|
1979
|
+
expanded_attrs[field_attr] = value
|
1980
|
+
else
|
1981
|
+
expanded_attrs[field_attr] = value.send(aggregate_attr)
|
1982
|
+
end
|
1983
|
+
end
|
1984
|
+
else
|
1985
|
+
expanded_attrs[attr] = value
|
1986
|
+
end
|
1987
|
+
end
|
1988
|
+
expanded_attrs
|
1989
|
+
end
|
1990
|
+
|
1809
1991
|
# Sanitizes a hash of attribute/value pairs into SQL conditions for a WHERE clause.
|
1810
1992
|
# { :name => "foo'bar", :group_id => 4 }
|
1811
1993
|
# # => "name='foo''bar' and group_id= 4"
|
@@ -1815,7 +1997,12 @@ module ActiveRecord #:nodoc:
|
|
1815
1997
|
# # => "age BETWEEN 13 AND 18"
|
1816
1998
|
# { 'other_records.id' => 7 }
|
1817
1999
|
# # => "`other_records`.`id` = 7"
|
2000
|
+
# And for value objects on a composed_of relationship:
|
2001
|
+
# { :address => Address.new("123 abc st.", "chicago") }
|
2002
|
+
# # => "address_street='123 abc st.' and address_city='chicago'"
|
1818
2003
|
def sanitize_sql_hash_for_conditions(attrs)
|
2004
|
+
attrs = expand_hash_conditions_for_aggregates(attrs)
|
2005
|
+
|
1819
2006
|
conditions = attrs.map do |attr, value|
|
1820
2007
|
attr = attr.to_s
|
1821
2008
|
|
@@ -1838,13 +2025,13 @@ module ActiveRecord #:nodoc:
|
|
1838
2025
|
# { :status => nil, :group_id => 1 }
|
1839
2026
|
# # => "status = NULL , group_id = 1"
|
1840
2027
|
def sanitize_sql_hash_for_assignment(attrs)
|
1841
|
-
|
2028
|
+
attrs.map do |attr, value|
|
1842
2029
|
"#{connection.quote_column_name(attr)} = #{quote_bound_value(value)}"
|
1843
2030
|
end.join(', ')
|
1844
2031
|
end
|
1845
2032
|
|
1846
2033
|
# Accepts an array of conditions. The array has each value
|
1847
|
-
# sanitized and interpolated into the
|
2034
|
+
# sanitized and interpolated into the SQL statement.
|
1848
2035
|
# ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
|
1849
2036
|
def sanitize_sql_array(ary)
|
1850
2037
|
statement, *values = ary
|
@@ -1866,7 +2053,7 @@ module ActiveRecord #:nodoc:
|
|
1866
2053
|
end
|
1867
2054
|
|
1868
2055
|
def replace_named_bind_variables(statement, bind_vars) #:nodoc:
|
1869
|
-
statement.gsub(/:(\w
|
2056
|
+
statement.gsub(/:([a-zA-Z]\w*)/) do
|
1870
2057
|
match = $1.to_sym
|
1871
2058
|
if bind_vars.include?(match)
|
1872
2059
|
quote_bound_value(bind_vars[match])
|
@@ -1965,6 +2152,24 @@ module ActiveRecord #:nodoc:
|
|
1965
2152
|
(id = self.id) ? id.to_s : nil # Be sure to stringify the id for routes
|
1966
2153
|
end
|
1967
2154
|
|
2155
|
+
# Returns a cache key that can be used to identify this record.
|
2156
|
+
#
|
2157
|
+
# ==== Examples
|
2158
|
+
#
|
2159
|
+
# Product.new.cache_key # => "products/new"
|
2160
|
+
# Product.find(5).cache_key # => "products/5" (updated_at not available)
|
2161
|
+
# Person.find(5).cache_key # => "people/5-20071224150000" (updated_at available)
|
2162
|
+
def cache_key
|
2163
|
+
case
|
2164
|
+
when new_record?
|
2165
|
+
"#{self.class.name.tableize}/new"
|
2166
|
+
when self[:updated_at]
|
2167
|
+
"#{self.class.name.tableize}/#{id}-#{updated_at.to_s(:number)}"
|
2168
|
+
else
|
2169
|
+
"#{self.class.name.tableize}/#{id}"
|
2170
|
+
end
|
2171
|
+
end
|
2172
|
+
|
1968
2173
|
def id_before_type_cast #:nodoc:
|
1969
2174
|
read_attribute_before_type_cast(self.class.primary_key)
|
1970
2175
|
end
|
@@ -1985,6 +2190,12 @@ module ActiveRecord #:nodoc:
|
|
1985
2190
|
|
1986
2191
|
# * No record exists: Creates a new record with values matching those of the object attributes.
|
1987
2192
|
# * A record does exist: Updates the record with values matching those of the object attributes.
|
2193
|
+
#
|
2194
|
+
# Note: If your model specifies any validations then the method declaration dynamically
|
2195
|
+
# changes to:
|
2196
|
+
# save(perform_validation=true)
|
2197
|
+
# Calling save(false) saves the model without running validations.
|
2198
|
+
# See ActiveRecord::Validations for more information.
|
1988
2199
|
def save
|
1989
2200
|
create_or_update
|
1990
2201
|
end
|
@@ -2014,16 +2225,16 @@ module ActiveRecord #:nodoc:
|
|
2014
2225
|
# The extent of a "deep" clone is application-specific and is therefore
|
2015
2226
|
# left to the application to implement according to its need.
|
2016
2227
|
def clone
|
2017
|
-
attrs =
|
2228
|
+
attrs = clone_attributes(:read_attribute_before_type_cast)
|
2018
2229
|
attrs.delete(self.class.primary_key)
|
2019
2230
|
record = self.class.new
|
2020
2231
|
record.send :instance_variable_set, '@attributes', attrs
|
2021
2232
|
record
|
2022
2233
|
end
|
2023
2234
|
|
2024
|
-
# Returns an instance of the specified klass with the attributes of the current record. This is mostly useful in relation to
|
2235
|
+
# Returns an instance of the specified +klass+ with the attributes of the current record. This is mostly useful in relation to
|
2025
2236
|
# single-table inheritance structures where you want a subclass to appear as the superclass. This can be used along with record
|
2026
|
-
# identification in Action Pack to allow, say, Client < Company to do something like render
|
2237
|
+
# identification in Action Pack to allow, say, <tt>Client < Company</tt> to do something like render <tt>:partial => @client.becomes(Company)</tt>
|
2027
2238
|
# to render that instance using the companies/company partial instead of clients/client.
|
2028
2239
|
#
|
2029
2240
|
# Note: The new instance will share a link to the same attributes as the original class. So any change to the attributes in either
|
@@ -2057,37 +2268,53 @@ module ActiveRecord #:nodoc:
|
|
2057
2268
|
save!
|
2058
2269
|
end
|
2059
2270
|
|
2060
|
-
# Initializes
|
2271
|
+
# Initializes +attribute+ to zero if +nil+ and adds the value passed as +by+ (default is 1).
|
2272
|
+
# The increment is performed directly on the underlying attribute, no setter is invoked.
|
2273
|
+
# Only makes sense for number-based attributes. Returns +self+.
|
2061
2274
|
def increment(attribute, by = 1)
|
2062
2275
|
self[attribute] ||= 0
|
2063
2276
|
self[attribute] += by
|
2064
2277
|
self
|
2065
2278
|
end
|
2066
2279
|
|
2067
|
-
#
|
2280
|
+
# Wrapper around +increment+ that saves the record. This method differs from
|
2281
|
+
# its non-bang version in that it passes through the attribute setter.
|
2282
|
+
# Saving is not subjected to validation checks. Returns +true+ if the
|
2283
|
+
# record could be saved.
|
2068
2284
|
def increment!(attribute, by = 1)
|
2069
2285
|
increment(attribute, by).update_attribute(attribute, self[attribute])
|
2070
2286
|
end
|
2071
2287
|
|
2072
|
-
# Initializes
|
2288
|
+
# Initializes +attribute+ to zero if +nil+ and subtracts the value passed as +by+ (default is 1).
|
2289
|
+
# The decrement is performed directly on the underlying attribute, no setter is invoked.
|
2290
|
+
# Only makes sense for number-based attributes. Returns +self+.
|
2073
2291
|
def decrement(attribute, by = 1)
|
2074
2292
|
self[attribute] ||= 0
|
2075
2293
|
self[attribute] -= by
|
2076
2294
|
self
|
2077
2295
|
end
|
2078
2296
|
|
2079
|
-
#
|
2297
|
+
# Wrapper around +decrement+ that saves the record. This method differs from
|
2298
|
+
# its non-bang version in that it passes through the attribute setter.
|
2299
|
+
# Saving is not subjected to validation checks. Returns +true+ if the
|
2300
|
+
# record could be saved.
|
2080
2301
|
def decrement!(attribute, by = 1)
|
2081
2302
|
decrement(attribute, by).update_attribute(attribute, self[attribute])
|
2082
2303
|
end
|
2083
2304
|
|
2084
|
-
#
|
2305
|
+
# Assigns to +attribute+ the boolean opposite of <tt>attribute?</tt>. So
|
2306
|
+
# if the predicate returns +true+ the attribute will become +false+. This
|
2307
|
+
# method toggles directly the underlying value without calling any setter.
|
2308
|
+
# Returns +self+.
|
2085
2309
|
def toggle(attribute)
|
2086
2310
|
self[attribute] = !send("#{attribute}?")
|
2087
2311
|
self
|
2088
2312
|
end
|
2089
2313
|
|
2090
|
-
#
|
2314
|
+
# Wrapper around +toggle+ that saves the record. This method differs from
|
2315
|
+
# its non-bang version in that it passes through the attribute setter.
|
2316
|
+
# Saving is not subjected to validation checks. Returns +true+ if the
|
2317
|
+
# record could be saved.
|
2091
2318
|
def toggle!(attribute)
|
2092
2319
|
toggle(attribute).update_attribute(attribute, self[attribute])
|
2093
2320
|
end
|
@@ -2138,31 +2365,20 @@ module ActiveRecord #:nodoc:
|
|
2138
2365
|
end
|
2139
2366
|
|
2140
2367
|
|
2141
|
-
# Returns a hash of all the attributes with their names as keys and
|
2142
|
-
def attributes
|
2143
|
-
|
2144
|
-
|
2145
|
-
|
2146
|
-
attributes
|
2147
|
-
else
|
2148
|
-
ActiveSupport::Deprecation.warn "Passing options to Base#attributes is deprecated and will be removed in Rails 2.1. Please use Hash#slice or Hash#except instead"
|
2149
|
-
if except = options[:except]
|
2150
|
-
except = Array(except).collect { |attribute| attribute.to_s }
|
2151
|
-
except.each { |attribute_name| attributes.delete(attribute_name) }
|
2152
|
-
attributes
|
2153
|
-
elsif only = options[:only]
|
2154
|
-
only = Array(only).collect { |attribute| attribute.to_s }
|
2155
|
-
attributes.delete_if { |key, value| !only.include?(key) }
|
2156
|
-
attributes
|
2157
|
-
else
|
2158
|
-
raise ArgumentError, "Options does not specify :except or :only (#{options.keys.inspect})"
|
2159
|
-
end
|
2368
|
+
# Returns a hash of all the attributes with their names as keys and the values of the attributes as values.
|
2369
|
+
def attributes
|
2370
|
+
self.attribute_names.inject({}) do |attrs, name|
|
2371
|
+
attrs[name] = read_attribute(name)
|
2372
|
+
attrs
|
2160
2373
|
end
|
2161
2374
|
end
|
2162
2375
|
|
2163
|
-
# Returns a hash of
|
2376
|
+
# Returns a hash of attributes before typecasting and deserialization.
|
2164
2377
|
def attributes_before_type_cast
|
2165
|
-
|
2378
|
+
self.attribute_names.inject({}) do |attrs, name|
|
2379
|
+
attrs[name] = read_attribute_before_type_cast(name)
|
2380
|
+
attrs
|
2381
|
+
end
|
2166
2382
|
end
|
2167
2383
|
|
2168
2384
|
# Format attributes nicely for inspect.
|
@@ -2259,8 +2475,8 @@ module ActiveRecord #:nodoc:
|
|
2259
2475
|
|
2260
2476
|
# Updates the associated record with values matching those of the instance attributes.
|
2261
2477
|
# Returns the number of affected rows.
|
2262
|
-
def update
|
2263
|
-
quoted_attributes = attributes_with_quotes(false, false)
|
2478
|
+
def update(attribute_names = @attributes.keys)
|
2479
|
+
quoted_attributes = attributes_with_quotes(false, false, attribute_names)
|
2264
2480
|
return 0 if quoted_attributes.empty?
|
2265
2481
|
connection.update(
|
2266
2482
|
"UPDATE #{self.class.quoted_table_name} " +
|
@@ -2294,13 +2510,13 @@ module ActiveRecord #:nodoc:
|
|
2294
2510
|
id
|
2295
2511
|
end
|
2296
2512
|
|
2297
|
-
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendent.
|
2298
|
-
# Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
|
2299
|
-
# set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
|
2513
|
+
# Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord::Base descendent.
|
2514
|
+
# Considering the hierarchy Reply < Message < ActiveRecord::Base, this makes it possible to do Reply.new without having to
|
2515
|
+
# set <tt>Reply[Reply.inheritance_column] = "Reply"</tt> yourself. No such attribute would be set for objects of the
|
2300
2516
|
# Message class in that example.
|
2301
2517
|
def ensure_proper_type
|
2302
2518
|
unless self.class.descends_from_active_record?
|
2303
|
-
write_attribute(self.class.inheritance_column,
|
2519
|
+
write_attribute(self.class.inheritance_column, self.class.sti_name)
|
2304
2520
|
end
|
2305
2521
|
end
|
2306
2522
|
|
@@ -2352,12 +2568,13 @@ module ActiveRecord #:nodoc:
|
|
2352
2568
|
|
2353
2569
|
# Returns a copy of the attributes hash where all the values have been safely quoted for use in
|
2354
2570
|
# an SQL statement.
|
2355
|
-
def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true)
|
2356
|
-
quoted =
|
2571
|
+
def attributes_with_quotes(include_primary_key = true, include_readonly_attributes = true, attribute_names = @attributes.keys)
|
2572
|
+
quoted = {}
|
2573
|
+
connection = self.class.connection
|
2574
|
+
attribute_names.each do |name|
|
2357
2575
|
if column = column_for_attribute(name)
|
2358
|
-
|
2576
|
+
quoted[name] = connection.quote(read_attribute(name), column) unless !include_primary_key && column.primary
|
2359
2577
|
end
|
2360
|
-
result
|
2361
2578
|
end
|
2362
2579
|
include_readonly_attributes ? quoted : remove_readonly_attributes(quoted)
|
2363
2580
|
end
|
@@ -2367,7 +2584,7 @@ module ActiveRecord #:nodoc:
|
|
2367
2584
|
self.class.connection.quote(value, column)
|
2368
2585
|
end
|
2369
2586
|
|
2370
|
-
# Interpolate custom
|
2587
|
+
# Interpolate custom SQL string in instance context.
|
2371
2588
|
# Optional record argument is meant for custom insert_sql.
|
2372
2589
|
def interpolate_sql(sql, record = nil)
|
2373
2590
|
instance_eval("%@#{sql.gsub('@', '\@')}@")
|
@@ -2396,9 +2613,12 @@ module ActiveRecord #:nodoc:
|
|
2396
2613
|
)
|
2397
2614
|
end
|
2398
2615
|
|
2399
|
-
|
2400
|
-
|
2401
|
-
|
2616
|
+
def instantiate_time_object(name, values)
|
2617
|
+
if self.class.time_zone_aware_attributes && !self.class.skip_time_zone_conversion_for_attributes.include?(name.to_sym)
|
2618
|
+
Time.zone.local(*values)
|
2619
|
+
else
|
2620
|
+
Time.time_with_datetime_fallback(@@default_timezone, *values)
|
2621
|
+
end
|
2402
2622
|
end
|
2403
2623
|
|
2404
2624
|
def execute_callstack_for_multiparameter_attributes(callstack)
|
@@ -2410,12 +2630,12 @@ module ActiveRecord #:nodoc:
|
|
2410
2630
|
else
|
2411
2631
|
begin
|
2412
2632
|
value = if Time == klass
|
2413
|
-
instantiate_time_object(
|
2633
|
+
instantiate_time_object(name, values)
|
2414
2634
|
elsif Date == klass
|
2415
2635
|
begin
|
2416
2636
|
Date.new(*values)
|
2417
2637
|
rescue ArgumentError => ex # if Date.new raises an exception on an invalid date
|
2418
|
-
instantiate_time_object(
|
2638
|
+
instantiate_time_object(name, values).to_date # we instantiate Time object and convert it back to a date thus using Time's logic in handling invalid dates
|
2419
2639
|
end
|
2420
2640
|
else
|
2421
2641
|
klass.new(*values)
|
@@ -2463,8 +2683,9 @@ module ActiveRecord #:nodoc:
|
|
2463
2683
|
end
|
2464
2684
|
|
2465
2685
|
def quoted_column_names(attributes = attributes_with_quotes)
|
2686
|
+
connection = self.class.connection
|
2466
2687
|
attributes.keys.collect do |column_name|
|
2467
|
-
|
2688
|
+
connection.quote_column_name(column_name)
|
2468
2689
|
end
|
2469
2690
|
end
|
2470
2691
|
|