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
@@ -3,6 +3,71 @@ require 'set'
|
|
3
3
|
module ActiveRecord
|
4
4
|
module Associations
|
5
5
|
class AssociationCollection < AssociationProxy #:nodoc:
|
6
|
+
def initialize(owner, reflection)
|
7
|
+
super
|
8
|
+
construct_sql
|
9
|
+
end
|
10
|
+
|
11
|
+
def find(*args)
|
12
|
+
options = args.extract_options!
|
13
|
+
|
14
|
+
# If using a custom finder_sql, scan the entire collection.
|
15
|
+
if @reflection.options[:finder_sql]
|
16
|
+
expects_array = args.first.kind_of?(Array)
|
17
|
+
ids = args.flatten.compact.uniq.map(&:to_i)
|
18
|
+
|
19
|
+
if ids.size == 1
|
20
|
+
id = ids.first
|
21
|
+
record = load_target.detect { |r| id == r.id }
|
22
|
+
expects_array ? [ record ] : record
|
23
|
+
else
|
24
|
+
load_target.select { |r| ids.include?(r.id) }
|
25
|
+
end
|
26
|
+
else
|
27
|
+
conditions = "#{@finder_sql}"
|
28
|
+
if sanitized_conditions = sanitize_sql(options[:conditions])
|
29
|
+
conditions << " AND (#{sanitized_conditions})"
|
30
|
+
end
|
31
|
+
|
32
|
+
options[:conditions] = conditions
|
33
|
+
|
34
|
+
if options[:order] && @reflection.options[:order]
|
35
|
+
options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
|
36
|
+
elsif @reflection.options[:order]
|
37
|
+
options[:order] = @reflection.options[:order]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Build options specific to association
|
41
|
+
construct_find_options!(options)
|
42
|
+
|
43
|
+
merge_options_from_reflection!(options)
|
44
|
+
|
45
|
+
# Pass through args exactly as we received them.
|
46
|
+
args << options
|
47
|
+
@reflection.klass.find(*args)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# Fetches the first one using SQL if possible.
|
52
|
+
def first(*args)
|
53
|
+
if fetch_first_or_last_using_find? args
|
54
|
+
find(:first, *args)
|
55
|
+
else
|
56
|
+
load_target unless loaded?
|
57
|
+
@target.first(*args)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Fetches the last one using SQL if possible.
|
62
|
+
def last(*args)
|
63
|
+
if fetch_first_or_last_using_find? args
|
64
|
+
find(:last, *args)
|
65
|
+
else
|
66
|
+
load_target unless loaded?
|
67
|
+
@target.last(*args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
6
71
|
def to_ary
|
7
72
|
load_target
|
8
73
|
@target.to_ary
|
@@ -13,6 +78,14 @@ module ActiveRecord
|
|
13
78
|
@loaded = false
|
14
79
|
end
|
15
80
|
|
81
|
+
def build(attributes = {})
|
82
|
+
if attributes.is_a?(Array)
|
83
|
+
attributes.collect { |attr| build(attr) }
|
84
|
+
else
|
85
|
+
build_record(attributes) { |record| set_belongs_to_association_for(record) }
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
16
89
|
# Add +records+ to this association. Returns +self+ so method calls may be chained.
|
17
90
|
# Since << flattens its argument list and inserts each record, +push+ and +concat+ behave identically.
|
18
91
|
def <<(*records)
|
@@ -22,10 +95,9 @@ module ActiveRecord
|
|
22
95
|
@owner.transaction do
|
23
96
|
flatten_deeper(records).each do |record|
|
24
97
|
raise_on_type_mismatch(record)
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
callback(:after_add, record)
|
98
|
+
add_record_to_target_with_callbacks(record) do |r|
|
99
|
+
result &&= insert_record(record) unless @owner.new_record?
|
100
|
+
end
|
29
101
|
end
|
30
102
|
end
|
31
103
|
|
@@ -41,22 +113,27 @@ module ActiveRecord
|
|
41
113
|
delete(@target)
|
42
114
|
reset_target!
|
43
115
|
end
|
44
|
-
|
116
|
+
|
45
117
|
# Calculate sum using SQL, not Enumerable
|
46
|
-
def sum(*args
|
47
|
-
|
118
|
+
def sum(*args)
|
119
|
+
if block_given?
|
120
|
+
calculate(:sum, *args) { |*block_args| yield(*block_args) }
|
121
|
+
else
|
122
|
+
calculate(:sum, *args)
|
123
|
+
end
|
48
124
|
end
|
49
125
|
|
50
126
|
# Remove +records+ from this association. Does not destroy +records+.
|
51
127
|
def delete(*records)
|
52
128
|
records = flatten_deeper(records)
|
53
129
|
records.each { |record| raise_on_type_mismatch(record) }
|
54
|
-
records.reject! { |record| @target.delete(record) if record.new_record? }
|
55
|
-
return if records.empty?
|
56
130
|
|
57
131
|
@owner.transaction do
|
58
132
|
records.each { |record| callback(:before_remove, record) }
|
59
|
-
|
133
|
+
|
134
|
+
old_records = records.reject {|r| r.new_record? }
|
135
|
+
delete_records(old_records) if old_records.any?
|
136
|
+
|
60
137
|
records.each do |record|
|
61
138
|
@target.delete(record)
|
62
139
|
callback(:after_remove, record)
|
@@ -89,12 +166,18 @@ module ActiveRecord
|
|
89
166
|
if attrs.is_a?(Array)
|
90
167
|
attrs.collect { |attr| create(attr) }
|
91
168
|
else
|
92
|
-
create_record(attrs)
|
169
|
+
create_record(attrs) do |record|
|
170
|
+
yield(record) if block_given?
|
171
|
+
record.save
|
172
|
+
end
|
93
173
|
end
|
94
174
|
end
|
95
175
|
|
96
176
|
def create!(attrs = {})
|
97
|
-
create_record(attrs)
|
177
|
+
create_record(attrs) do |record|
|
178
|
+
yield(record) if block_given?
|
179
|
+
record.save!
|
180
|
+
end
|
98
181
|
end
|
99
182
|
|
100
183
|
# Returns the size of the collection by executing a SELECT COUNT(*) query if the collection hasn't been loaded and
|
@@ -121,9 +204,9 @@ module ActiveRecord
|
|
121
204
|
size.zero?
|
122
205
|
end
|
123
206
|
|
124
|
-
def any?
|
207
|
+
def any?
|
125
208
|
if block_given?
|
126
|
-
method_missing(:any
|
209
|
+
method_missing(:any?) { |*block_args| yield(*block_args) }
|
127
210
|
else
|
128
211
|
!empty?
|
129
212
|
end
|
@@ -155,13 +238,53 @@ module ActiveRecord
|
|
155
238
|
end
|
156
239
|
end
|
157
240
|
|
241
|
+
def include?(record)
|
242
|
+
return false unless record.is_a?(@reflection.klass)
|
243
|
+
load_target if @reflection.options[:finder_sql] && !loaded?
|
244
|
+
return @target.include?(record) if loaded?
|
245
|
+
exists?(record)
|
246
|
+
end
|
158
247
|
|
159
248
|
protected
|
160
|
-
def
|
249
|
+
def construct_find_options!(options)
|
250
|
+
end
|
251
|
+
|
252
|
+
def load_target
|
253
|
+
if !@owner.new_record? || foreign_key_present
|
254
|
+
begin
|
255
|
+
if !loaded?
|
256
|
+
if @target.is_a?(Array) && @target.any?
|
257
|
+
@target = find_target + @target.find_all {|t| t.new_record? }
|
258
|
+
else
|
259
|
+
@target = find_target
|
260
|
+
end
|
261
|
+
end
|
262
|
+
rescue ActiveRecord::RecordNotFound
|
263
|
+
reset
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
loaded if target
|
268
|
+
target
|
269
|
+
end
|
270
|
+
|
271
|
+
def method_missing(method, *args)
|
161
272
|
if @target.respond_to?(method) || (!@reflection.klass.respond_to?(method) && Class.respond_to?(method))
|
162
|
-
|
163
|
-
|
164
|
-
|
273
|
+
if block_given?
|
274
|
+
super { |*block_args| yield(*block_args) }
|
275
|
+
else
|
276
|
+
super
|
277
|
+
end
|
278
|
+
elsif @reflection.klass.scopes.include?(method)
|
279
|
+
@reflection.klass.scopes[method].call(self, *args)
|
280
|
+
else
|
281
|
+
with_scope(construct_scope) do
|
282
|
+
if block_given?
|
283
|
+
@reflection.klass.send(method, *args) { |*block_args| yield(*block_args) }
|
284
|
+
else
|
285
|
+
@reflection.klass.send(method, *args)
|
286
|
+
end
|
287
|
+
end
|
165
288
|
end
|
166
289
|
end
|
167
290
|
|
@@ -187,15 +310,25 @@ module ActiveRecord
|
|
187
310
|
|
188
311
|
private
|
189
312
|
|
190
|
-
def create_record(attrs
|
313
|
+
def create_record(attrs)
|
314
|
+
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
|
191
315
|
ensure_owner_is_not_new
|
192
316
|
record = @reflection.klass.send(:with_scope, :create => construct_scope[:create]) { @reflection.klass.new(attrs) }
|
193
|
-
|
317
|
+
if block_given?
|
318
|
+
add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
|
319
|
+
else
|
320
|
+
add_record_to_target_with_callbacks(record)
|
321
|
+
end
|
194
322
|
end
|
195
323
|
|
196
|
-
def build_record(attrs
|
324
|
+
def build_record(attrs)
|
325
|
+
attrs.update(@reflection.options[:conditions]) if @reflection.options[:conditions].is_a?(Hash)
|
197
326
|
record = @reflection.klass.new(attrs)
|
198
|
-
|
327
|
+
if block_given?
|
328
|
+
add_record_to_target_with_callbacks(record) { |*block_args| yield(*block_args) }
|
329
|
+
else
|
330
|
+
add_record_to_target_with_callbacks(record)
|
331
|
+
end
|
199
332
|
end
|
200
333
|
|
201
334
|
def add_record_to_target_with_callbacks(record)
|
@@ -209,21 +342,10 @@ module ActiveRecord
|
|
209
342
|
|
210
343
|
def callback(method, record)
|
211
344
|
callbacks_for(method).each do |callback|
|
212
|
-
|
213
|
-
when Symbol
|
214
|
-
@owner.send(callback, record)
|
215
|
-
when Proc, Method
|
216
|
-
callback.call(@owner, record)
|
217
|
-
else
|
218
|
-
if callback.respond_to?(method)
|
219
|
-
callback.send(method, @owner, record)
|
220
|
-
else
|
221
|
-
raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
|
222
|
-
end
|
223
|
-
end
|
345
|
+
ActiveSupport::Callbacks::Callback.new(method, callback, record).call(@owner, record)
|
224
346
|
end
|
225
347
|
end
|
226
|
-
|
348
|
+
|
227
349
|
def callbacks_for(callback_name)
|
228
350
|
full_callback_name = "#{callback_name}_for_#{@reflection.name}"
|
229
351
|
@owner.class.read_inheritable_attribute(full_callback_name.to_sym) || []
|
@@ -234,7 +356,10 @@ module ActiveRecord
|
|
234
356
|
raise ActiveRecord::RecordNotSaved, "You cannot call create unless the parent is saved"
|
235
357
|
end
|
236
358
|
end
|
237
|
-
|
359
|
+
|
360
|
+
def fetch_first_or_last_using_find?(args)
|
361
|
+
args.first.kind_of?(Hash) || !(loaded? || @owner.new_record? || @reflection.options[:finder_sql] || !@target.blank? || args.first.kind_of?(Integer))
|
362
|
+
end
|
238
363
|
end
|
239
364
|
end
|
240
|
-
end
|
365
|
+
end
|
@@ -1,11 +1,55 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
|
+
# This is the root class of all association proxies:
|
4
|
+
#
|
5
|
+
# AssociationProxy
|
6
|
+
# BelongsToAssociation
|
7
|
+
# HasOneAssociation
|
8
|
+
# BelongsToPolymorphicAssociation
|
9
|
+
# AssociationCollection
|
10
|
+
# HasAndBelongsToManyAssociation
|
11
|
+
# HasManyAssociation
|
12
|
+
# HasManyThroughAssociation
|
13
|
+
# HasOneThroughAssociation
|
14
|
+
#
|
15
|
+
# Association proxies in Active Record are middlemen between the object that
|
16
|
+
# holds the association, known as the <tt>@owner</tt>, and the actual associated
|
17
|
+
# object, known as the <tt>@target</tt>. The kind of association any proxy is
|
18
|
+
# about is available in <tt>@reflection</tt>. That's an instance of the class
|
19
|
+
# ActiveRecord::Reflection::AssociationReflection.
|
20
|
+
#
|
21
|
+
# For example, given
|
22
|
+
#
|
23
|
+
# class Blog < ActiveRecord::Base
|
24
|
+
# has_many :posts
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# blog = Blog.find(:first)
|
28
|
+
#
|
29
|
+
# the association proxy in <tt>blog.posts</tt> has the object in +blog+ as
|
30
|
+
# <tt>@owner</tt>, the collection of its posts as <tt>@target</tt>, and
|
31
|
+
# the <tt>@reflection</tt> object represents a <tt>:has_many</tt> macro.
|
32
|
+
#
|
33
|
+
# This class has most of the basic instance methods removed, and delegates
|
34
|
+
# unknown methods to <tt>@target</tt> via <tt>method_missing</tt>. As a
|
35
|
+
# corner case, it even removes the +class+ method and that's why you get
|
36
|
+
#
|
37
|
+
# blog.posts.class # => Array
|
38
|
+
#
|
39
|
+
# though the object behind <tt>blog.posts</tt> is not an Array, but an
|
40
|
+
# ActiveRecord::Associations::HasManyAssociation.
|
41
|
+
#
|
42
|
+
# The <tt>@target</tt> object is not loaded until needed. For example,
|
43
|
+
#
|
44
|
+
# blog.posts.count
|
45
|
+
#
|
46
|
+
# is computed directly through SQL and does not trigger by itself the
|
47
|
+
# instantiation of the actual post records.
|
3
48
|
class AssociationProxy #:nodoc:
|
4
|
-
attr_reader :reflection
|
5
49
|
alias_method :proxy_respond_to?, :respond_to?
|
6
50
|
alias_method :proxy_extend, :extend
|
7
51
|
delegate :to_param, :to => :proxy_target
|
8
|
-
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_)/ }
|
52
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
9
53
|
|
10
54
|
def initialize(owner, reflection)
|
11
55
|
@owner, @reflection = owner, reflection
|
@@ -74,7 +118,7 @@ module ActiveRecord
|
|
74
118
|
end
|
75
119
|
|
76
120
|
def inspect
|
77
|
-
|
121
|
+
load_target
|
78
122
|
@target.inspect
|
79
123
|
end
|
80
124
|
|
@@ -115,17 +159,36 @@ module ActiveRecord
|
|
115
159
|
:offset => @reflection.options[:offset],
|
116
160
|
:joins => @reflection.options[:joins],
|
117
161
|
:include => @reflection.options[:include],
|
118
|
-
:select => @reflection.options[:select]
|
162
|
+
:select => @reflection.options[:select],
|
163
|
+
:readonly => @reflection.options[:readonly]
|
119
164
|
)
|
120
165
|
end
|
121
166
|
|
167
|
+
def with_scope(*args, &block)
|
168
|
+
@reflection.klass.send :with_scope, *args, &block
|
169
|
+
end
|
170
|
+
|
122
171
|
private
|
123
|
-
def method_missing(method, *args
|
172
|
+
def method_missing(method, *args)
|
124
173
|
if load_target
|
125
|
-
|
174
|
+
if block_given?
|
175
|
+
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
176
|
+
else
|
177
|
+
@target.send(method, *args)
|
178
|
+
end
|
126
179
|
end
|
127
180
|
end
|
128
181
|
|
182
|
+
# Loads the target if needed and returns it.
|
183
|
+
#
|
184
|
+
# This method is abstract in the sense that it relies on +find_target+,
|
185
|
+
# which is expected to be provided by descendants.
|
186
|
+
#
|
187
|
+
# If the target is already loaded it is just returned. Thus, you can call
|
188
|
+
# +load_target+ unconditionally to get the target.
|
189
|
+
#
|
190
|
+
# ActiveRecord::RecordNotFound is rescued within the method, and it is
|
191
|
+
# not reraised. The proxy is reset and +nil+ is the return value.
|
129
192
|
def load_target
|
130
193
|
return nil unless defined?(@loaded)
|
131
194
|
|
@@ -147,7 +210,8 @@ module ActiveRecord
|
|
147
210
|
|
148
211
|
def raise_on_type_mismatch(record)
|
149
212
|
unless record.is_a?(@reflection.klass)
|
150
|
-
|
213
|
+
message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
|
214
|
+
raise ActiveRecord::AssociationTypeMismatch, message
|
151
215
|
end
|
152
216
|
end
|
153
217
|
|
@@ -13,7 +13,7 @@ module ActiveRecord
|
|
13
13
|
counter_cache_name = @reflection.counter_cache_column
|
14
14
|
|
15
15
|
if record.nil?
|
16
|
-
if counter_cache_name &&
|
16
|
+
if counter_cache_name && !@owner.new_record?
|
17
17
|
@reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
|
18
18
|
end
|
19
19
|
|
@@ -42,9 +42,11 @@ module ActiveRecord
|
|
42
42
|
private
|
43
43
|
def find_target
|
44
44
|
@reflection.klass.find(
|
45
|
-
@owner[@reflection.primary_key_name],
|
45
|
+
@owner[@reflection.primary_key_name],
|
46
|
+
:select => @reflection.options[:select],
|
46
47
|
:conditions => conditions,
|
47
|
-
:include => @reflection.options[:include]
|
48
|
+
:include => @reflection.options[:include],
|
49
|
+
:readonly => @reflection.options[:readonly]
|
48
50
|
)
|
49
51
|
end
|
50
52
|
|
@@ -7,10 +7,8 @@ module ActiveRecord
|
|
7
7
|
else
|
8
8
|
@target = (AssociationProxy === record ? record.target : record)
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
@owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
|
13
|
-
end
|
10
|
+
@owner[@reflection.primary_key_name] = record.id
|
11
|
+
@owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
|
14
12
|
|
15
13
|
@updated = true
|
16
14
|
end
|
@@ -29,12 +27,13 @@ module ActiveRecord
|
|
29
27
|
|
30
28
|
if @reflection.options[:conditions]
|
31
29
|
association_class.find(
|
32
|
-
@owner[@reflection.primary_key_name],
|
30
|
+
@owner[@reflection.primary_key_name],
|
31
|
+
:select => @reflection.options[:select],
|
33
32
|
:conditions => conditions,
|
34
33
|
:include => @reflection.options[:include]
|
35
34
|
)
|
36
35
|
else
|
37
|
-
association_class.find(@owner[@reflection.primary_key_name], :include => @reflection.options[:include])
|
36
|
+
association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include])
|
38
37
|
end
|
39
38
|
end
|
40
39
|
|