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
@@ -1,16 +1,6 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
3
|
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
4
|
-
def initialize(owner, reflection)
|
5
|
-
super
|
6
|
-
construct_sql
|
7
|
-
end
|
8
|
-
|
9
|
-
def build(attributes = {})
|
10
|
-
load_target
|
11
|
-
build_record(attributes)
|
12
|
-
end
|
13
|
-
|
14
4
|
def create(attributes = {})
|
15
5
|
create_record(attributes) { |record| insert_record(record) }
|
16
6
|
end
|
@@ -19,53 +9,13 @@ module ActiveRecord
|
|
19
9
|
create_record(attributes) { |record| insert_record(record, true) }
|
20
10
|
end
|
21
11
|
|
22
|
-
|
23
|
-
|
24
|
-
end
|
25
|
-
|
26
|
-
def find(*args)
|
27
|
-
options = args.extract_options!
|
28
|
-
|
29
|
-
# If using a custom finder_sql, scan the entire collection.
|
30
|
-
if @reflection.options[:finder_sql]
|
31
|
-
expects_array = args.first.kind_of?(Array)
|
32
|
-
ids = args.flatten.compact.uniq
|
33
|
-
|
34
|
-
if ids.size == 1
|
35
|
-
id = ids.first.to_i
|
36
|
-
record = load_target.detect { |r| id == r.id }
|
37
|
-
expects_array ? [record] : record
|
38
|
-
else
|
39
|
-
load_target.select { |r| ids.include?(r.id) }
|
40
|
-
end
|
41
|
-
else
|
42
|
-
conditions = "#{@finder_sql}"
|
43
|
-
|
44
|
-
if sanitized_conditions = sanitize_sql(options[:conditions])
|
45
|
-
conditions << " AND (#{sanitized_conditions})"
|
46
|
-
end
|
47
|
-
|
48
|
-
options[:conditions] = conditions
|
12
|
+
protected
|
13
|
+
def construct_find_options!(options)
|
49
14
|
options[:joins] = @join_sql
|
50
15
|
options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
|
51
|
-
|
52
|
-
if options[:order] && @reflection.options[:order]
|
53
|
-
options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
|
54
|
-
elsif @reflection.options[:order]
|
55
|
-
options[:order] = @reflection.options[:order]
|
56
|
-
end
|
57
|
-
|
58
|
-
merge_options_from_reflection!(options)
|
59
|
-
|
60
|
-
options[:select] ||= (@reflection.options[:select] || '*')
|
61
|
-
|
62
|
-
# Pass through args exactly as we received them.
|
63
|
-
args << options
|
64
|
-
@reflection.klass.find(*args)
|
16
|
+
options[:select] ||= (@reflection.options[:select] || '*')
|
65
17
|
end
|
66
|
-
|
67
|
-
|
68
|
-
protected
|
18
|
+
|
69
19
|
def count_records
|
70
20
|
load_target.size
|
71
21
|
end
|
@@ -85,10 +35,10 @@ module ActiveRecord
|
|
85
35
|
columns = @owner.connection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
|
86
36
|
|
87
37
|
attributes = columns.inject({}) do |attrs, column|
|
88
|
-
case column.name
|
89
|
-
when @reflection.primary_key_name
|
38
|
+
case column.name.to_s
|
39
|
+
when @reflection.primary_key_name.to_s
|
90
40
|
attrs[column.name] = @owner.quoted_id
|
91
|
-
when @reflection.association_foreign_key
|
41
|
+
when @reflection.association_foreign_key.to_s
|
92
42
|
attrs[column.name] = record.quoted_id
|
93
43
|
else
|
94
44
|
if record.has_attribute?(column.name)
|
@@ -100,7 +50,7 @@ module ActiveRecord
|
|
100
50
|
end
|
101
51
|
|
102
52
|
sql =
|
103
|
-
"INSERT INTO #{@reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
|
53
|
+
"INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
|
104
54
|
"VALUES (#{attributes.values.join(', ')})"
|
105
55
|
|
106
56
|
@owner.connection.insert(sql)
|
@@ -125,11 +75,11 @@ module ActiveRecord
|
|
125
75
|
if @reflection.options[:finder_sql]
|
126
76
|
@finder_sql = @reflection.options[:finder_sql]
|
127
77
|
else
|
128
|
-
@finder_sql = "#{@reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
|
78
|
+
@finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{@owner.quoted_id} "
|
129
79
|
@finder_sql << " AND (#{conditions})" if conditions
|
130
80
|
end
|
131
81
|
|
132
|
-
@join_sql = "INNER JOIN #{@reflection.options[:join_table]} ON #{@reflection.
|
82
|
+
@join_sql = "INNER JOIN #{@owner.connection.quote_table_name @reflection.options[:join_table]} ON #{@reflection.quoted_table_name}.#{@reflection.klass.primary_key} = #{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.association_foreign_key}"
|
133
83
|
end
|
134
84
|
|
135
85
|
def construct_scope
|
@@ -148,15 +98,13 @@ module ActiveRecord
|
|
148
98
|
end
|
149
99
|
|
150
100
|
private
|
151
|
-
def create_record(attributes)
|
101
|
+
def create_record(attributes, &block)
|
152
102
|
# Can't use Base.create because the foreign key may be a protected attribute.
|
153
103
|
ensure_owner_is_not_new
|
154
104
|
if attributes.is_a?(Array)
|
155
105
|
attributes.collect { |attr| create(attr) }
|
156
106
|
else
|
157
|
-
|
158
|
-
yield(record)
|
159
|
-
record
|
107
|
+
build_record(attributes, &block)
|
160
108
|
end
|
161
109
|
end
|
162
110
|
end
|
@@ -1,19 +1,6 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
3
|
class HasManyAssociation < AssociationCollection #:nodoc:
|
4
|
-
def initialize(owner, reflection)
|
5
|
-
super
|
6
|
-
construct_sql
|
7
|
-
end
|
8
|
-
|
9
|
-
def build(attributes = {})
|
10
|
-
if attributes.is_a?(Array)
|
11
|
-
attributes.collect { |attr| build(attr) }
|
12
|
-
else
|
13
|
-
build_record(attributes) { |record| set_belongs_to_association_for(record) }
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
4
|
# Count the number of associated records. All arguments are optional.
|
18
5
|
def count(*args)
|
19
6
|
if @reflection.options[:counter_sql]
|
@@ -22,7 +9,7 @@ module ActiveRecord
|
|
22
9
|
@reflection.klass.count_by_sql(@finder_sql)
|
23
10
|
else
|
24
11
|
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
|
25
|
-
options[:conditions] = options[:conditions].
|
12
|
+
options[:conditions] = options[:conditions].blank? ?
|
26
13
|
@finder_sql :
|
27
14
|
@finder_sql + " AND (#{sanitize_sql(options[:conditions])})"
|
28
15
|
options[:include] ||= @reflection.options[:include]
|
@@ -31,62 +18,7 @@ module ActiveRecord
|
|
31
18
|
end
|
32
19
|
end
|
33
20
|
|
34
|
-
def find(*args)
|
35
|
-
options = args.extract_options!
|
36
|
-
|
37
|
-
# If using a custom finder_sql, scan the entire collection.
|
38
|
-
if @reflection.options[:finder_sql]
|
39
|
-
expects_array = args.first.kind_of?(Array)
|
40
|
-
ids = args.flatten.compact.uniq.map(&:to_i)
|
41
|
-
|
42
|
-
if ids.size == 1
|
43
|
-
id = ids.first
|
44
|
-
record = load_target.detect { |r| id == r.id }
|
45
|
-
expects_array ? [ record ] : record
|
46
|
-
else
|
47
|
-
load_target.select { |r| ids.include?(r.id) }
|
48
|
-
end
|
49
|
-
else
|
50
|
-
conditions = "#{@finder_sql}"
|
51
|
-
if sanitized_conditions = sanitize_sql(options[:conditions])
|
52
|
-
conditions << " AND (#{sanitized_conditions})"
|
53
|
-
end
|
54
|
-
options[:conditions] = conditions
|
55
|
-
|
56
|
-
if options[:order] && @reflection.options[:order]
|
57
|
-
options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
|
58
|
-
elsif @reflection.options[:order]
|
59
|
-
options[:order] = @reflection.options[:order]
|
60
|
-
end
|
61
|
-
|
62
|
-
merge_options_from_reflection!(options)
|
63
|
-
|
64
|
-
# Pass through args exactly as we received them.
|
65
|
-
args << options
|
66
|
-
@reflection.klass.find(*args)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
21
|
protected
|
71
|
-
def load_target
|
72
|
-
if !@owner.new_record? || foreign_key_present
|
73
|
-
begin
|
74
|
-
if !loaded?
|
75
|
-
if @target.is_a?(Array) && @target.any?
|
76
|
-
@target = (find_target + @target).uniq
|
77
|
-
else
|
78
|
-
@target = find_target
|
79
|
-
end
|
80
|
-
end
|
81
|
-
rescue ActiveRecord::RecordNotFound
|
82
|
-
reset
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
loaded if target
|
87
|
-
target
|
88
|
-
end
|
89
|
-
|
90
22
|
def count_records
|
91
23
|
count = if has_cached_counter?
|
92
24
|
@owner.send(:read_attribute, cached_counter_attribute_name)
|
@@ -144,12 +76,12 @@ module ActiveRecord
|
|
144
76
|
|
145
77
|
when @reflection.options[:as]
|
146
78
|
@finder_sql =
|
147
|
-
"#{@reflection.
|
148
|
-
"#{@reflection.
|
79
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{@owner.quoted_id} AND " +
|
80
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
149
81
|
@finder_sql << " AND (#{conditions})" if conditions
|
150
82
|
|
151
83
|
else
|
152
|
-
@finder_sql = "#{@reflection.
|
84
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
153
85
|
@finder_sql << " AND (#{conditions})" if conditions
|
154
86
|
end
|
155
87
|
|
@@ -167,7 +99,10 @@ module ActiveRecord
|
|
167
99
|
def construct_scope
|
168
100
|
create_scoping = {}
|
169
101
|
set_belongs_to_association_for(create_scoping)
|
170
|
-
{
|
102
|
+
{
|
103
|
+
:find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit] },
|
104
|
+
:create => create_scoping
|
105
|
+
}
|
171
106
|
end
|
172
107
|
end
|
173
108
|
end
|
@@ -1,104 +1,23 @@
|
|
1
1
|
module ActiveRecord
|
2
2
|
module Associations
|
3
|
-
class HasManyThroughAssociation <
|
3
|
+
class HasManyThroughAssociation < HasManyAssociation #:nodoc:
|
4
4
|
def initialize(owner, reflection)
|
5
|
-
super
|
6
5
|
reflection.check_validity!
|
7
|
-
|
8
|
-
construct_sql
|
9
|
-
end
|
10
|
-
|
11
|
-
def find(*args)
|
12
|
-
options = args.extract_options!
|
13
|
-
|
14
|
-
conditions = "#{@finder_sql}"
|
15
|
-
if sanitized_conditions = sanitize_sql(options[:conditions])
|
16
|
-
conditions << " AND (#{sanitized_conditions})"
|
17
|
-
end
|
18
|
-
options[:conditions] = conditions
|
19
|
-
|
20
|
-
if options[:order] && @reflection.options[:order]
|
21
|
-
options[:order] = "#{options[:order]}, #{@reflection.options[:order]}"
|
22
|
-
elsif @reflection.options[:order]
|
23
|
-
options[:order] = @reflection.options[:order]
|
24
|
-
end
|
25
|
-
|
26
|
-
options[:select] = construct_select(options[:select])
|
27
|
-
options[:from] ||= construct_from
|
28
|
-
options[:joins] = construct_joins(options[:joins])
|
29
|
-
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
|
30
|
-
|
31
|
-
merge_options_from_reflection!(options)
|
32
|
-
|
33
|
-
# Pass through args exactly as we received them.
|
34
|
-
args << options
|
35
|
-
@reflection.klass.find(*args)
|
36
|
-
end
|
37
|
-
|
38
|
-
def reset
|
39
|
-
@target = []
|
40
|
-
@loaded = false
|
41
|
-
end
|
42
|
-
|
43
|
-
# Adds records to the association. The source record and its associates
|
44
|
-
# must have ids in order to create records associating them, so this
|
45
|
-
# will raise ActiveRecord::HasManyThroughCantAssociateNewRecords if
|
46
|
-
# either is a new record. Calls create! so you can rescue errors.
|
47
|
-
#
|
48
|
-
# The :before_add and :after_add callbacks are not yet supported.
|
49
|
-
def <<(*records)
|
50
|
-
return if records.empty?
|
51
|
-
through = @reflection.through_reflection
|
52
|
-
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) if @owner.new_record?
|
53
|
-
|
54
|
-
klass = through.klass
|
55
|
-
klass.transaction do
|
56
|
-
flatten_deeper(records).each do |associate|
|
57
|
-
raise_on_type_mismatch(associate)
|
58
|
-
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
|
59
|
-
|
60
|
-
@owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(associate)) { klass.create! }
|
61
|
-
@target << associate if loaded?
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
self
|
6
|
+
super
|
66
7
|
end
|
67
8
|
|
68
|
-
|
69
|
-
|
70
|
-
# Removes +records+ from this association. Does not destroy +records+.
|
71
|
-
def delete(*records)
|
72
|
-
records = flatten_deeper(records)
|
73
|
-
records.each { |associate| raise_on_type_mismatch(associate) }
|
74
|
-
|
75
|
-
through = @reflection.through_reflection
|
76
|
-
raise ActiveRecord::HasManyThroughCantDissociateNewRecords.new(@owner, through) if @owner.new_record?
|
77
|
-
|
78
|
-
load_target
|
79
|
-
|
80
|
-
klass = through.klass
|
81
|
-
klass.transaction do
|
82
|
-
flatten_deeper(records).each do |associate|
|
83
|
-
raise_on_type_mismatch(associate)
|
84
|
-
raise ActiveRecord::HasManyThroughCantDissociateNewRecords.new(@owner, through) unless associate.respond_to?(:new_record?) && !associate.new_record?
|
9
|
+
alias_method :new, :build
|
85
10
|
|
86
|
-
|
87
|
-
|
88
|
-
|
11
|
+
def create!(attrs = nil)
|
12
|
+
@reflection.klass.transaction do
|
13
|
+
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create! } : @reflection.klass.create!)
|
14
|
+
object
|
89
15
|
end
|
90
|
-
|
91
|
-
self
|
92
16
|
end
|
93
17
|
|
94
|
-
def
|
95
|
-
raise ActiveRecord::HasManyThroughCantAssociateNewRecords.new(@owner, @reflection.through_reflection)
|
96
|
-
end
|
97
|
-
alias_method :new, :build
|
98
|
-
|
99
|
-
def create!(attrs = nil)
|
18
|
+
def create(attrs = nil)
|
100
19
|
@reflection.klass.transaction do
|
101
|
-
self << (object = @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create
|
20
|
+
self << (object = attrs ? @reflection.klass.send(:with_scope, :create => attrs) { @reflection.klass.create } : @reflection.klass.create)
|
102
21
|
object
|
103
22
|
end
|
104
23
|
end
|
@@ -111,33 +30,47 @@ module ActiveRecord
|
|
111
30
|
return @target.size if loaded?
|
112
31
|
return count
|
113
32
|
end
|
114
|
-
|
115
|
-
# Calculate sum using SQL, not Enumerable
|
116
|
-
def sum(*args, &block)
|
117
|
-
calculate(:sum, *args, &block)
|
118
|
-
end
|
119
33
|
|
120
34
|
def count(*args)
|
121
35
|
column_name, options = @reflection.klass.send(:construct_count_options_from_args, *args)
|
122
36
|
if @reflection.options[:uniq]
|
123
|
-
# This is needed because 'SELECT count(DISTINCT *)..' is not valid
|
124
|
-
column_name = "#{@reflection.
|
37
|
+
# This is needed because 'SELECT count(DISTINCT *)..' is not valid SQL statement.
|
38
|
+
column_name = "#{@reflection.quoted_table_name}.#{@reflection.klass.primary_key}" if column_name == :all
|
125
39
|
options.merge!(:distinct => true)
|
126
40
|
end
|
127
41
|
@reflection.klass.send(:with_scope, construct_scope) { @reflection.klass.count(column_name, options) }
|
128
42
|
end
|
129
43
|
|
130
44
|
protected
|
131
|
-
def
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
45
|
+
def construct_find_options!(options)
|
46
|
+
options[:select] = construct_select(options[:select])
|
47
|
+
options[:from] ||= construct_from
|
48
|
+
options[:joins] = construct_joins(options[:joins])
|
49
|
+
options[:include] = @reflection.source_reflection.options[:include] if options[:include].nil?
|
50
|
+
end
|
51
|
+
|
52
|
+
def insert_record(record, force=true)
|
53
|
+
if record.new_record?
|
54
|
+
if force
|
55
|
+
record.save!
|
56
|
+
else
|
57
|
+
return false unless record.save
|
58
|
+
end
|
59
|
+
end
|
60
|
+
klass = @reflection.through_reflection.klass
|
61
|
+
@owner.send(@reflection.through_reflection.name).proxy_target << klass.send(:with_scope, :create => construct_join_attributes(record)) { klass.create! }
|
62
|
+
end
|
63
|
+
|
64
|
+
# TODO - add dependent option support
|
65
|
+
def delete_records(records)
|
66
|
+
klass = @reflection.through_reflection.klass
|
67
|
+
records.each do |associate|
|
68
|
+
klass.delete_all(construct_join_attributes(associate))
|
136
69
|
end
|
137
70
|
end
|
138
71
|
|
139
72
|
def find_target
|
140
|
-
|
73
|
+
@reflection.klass.find(:all,
|
141
74
|
:select => construct_select,
|
142
75
|
:conditions => construct_conditions,
|
143
76
|
:from => construct_from,
|
@@ -145,11 +78,9 @@ module ActiveRecord
|
|
145
78
|
:order => @reflection.options[:order],
|
146
79
|
:limit => @reflection.options[:limit],
|
147
80
|
:group => @reflection.options[:group],
|
81
|
+
:readonly => @reflection.options[:readonly],
|
148
82
|
:include => @reflection.options[:include] || @reflection.source_reflection.options[:include]
|
149
83
|
)
|
150
|
-
|
151
|
-
records.uniq! if @reflection.options[:uniq]
|
152
|
-
records
|
153
84
|
end
|
154
85
|
|
155
86
|
# Construct attributes for associate pointing to owner.
|
@@ -164,6 +95,8 @@ module ActiveRecord
|
|
164
95
|
|
165
96
|
# Construct attributes for :through pointing to owner and associate.
|
166
97
|
def construct_join_attributes(associate)
|
98
|
+
# TODO: revist this to allow it for deletion, supposing dependent option is supported
|
99
|
+
raise ActiveRecord::HasManyThroughCantAssociateThroughHasManyReflection.new(@owner, @reflection) if @reflection.source_reflection.macro == :has_many
|
167
100
|
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
|
168
101
|
if @reflection.options[:source_type]
|
169
102
|
join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.base_class.name.to_s)
|
@@ -185,7 +118,7 @@ module ActiveRecord
|
|
185
118
|
|
186
119
|
# Build SQL conditions from attributes, qualified by table name.
|
187
120
|
def construct_conditions
|
188
|
-
table_name = @reflection.through_reflection.
|
121
|
+
table_name = @reflection.through_reflection.quoted_table_name
|
189
122
|
conditions = construct_quoted_owner_attributes(@reflection.through_reflection).map do |attr, value|
|
190
123
|
"#{table_name}.#{attr} = #{value}"
|
191
124
|
end
|
@@ -194,21 +127,22 @@ module ActiveRecord
|
|
194
127
|
end
|
195
128
|
|
196
129
|
def construct_from
|
197
|
-
@reflection.
|
130
|
+
@reflection.quoted_table_name
|
198
131
|
end
|
199
132
|
|
200
133
|
def construct_select(custom_select = nil)
|
201
|
-
|
134
|
+
distinct = "DISTINCT " if @reflection.options[:uniq]
|
135
|
+
selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*"
|
202
136
|
end
|
203
137
|
|
204
138
|
def construct_joins(custom_joins = nil)
|
205
139
|
polymorphic_join = nil
|
206
|
-
if @reflection.
|
140
|
+
if @reflection.source_reflection.macro == :belongs_to
|
207
141
|
reflection_primary_key = @reflection.klass.primary_key
|
208
142
|
source_primary_key = @reflection.source_reflection.primary_key_name
|
209
143
|
if @reflection.options[:source_type]
|
210
144
|
polymorphic_join = "AND %s.%s = %s" % [
|
211
|
-
@reflection.through_reflection.
|
145
|
+
@reflection.through_reflection.quoted_table_name, "#{@reflection.source_reflection.options[:foreign_type]}",
|
212
146
|
@owner.class.quote_value(@reflection.options[:source_type])
|
213
147
|
]
|
214
148
|
end
|
@@ -217,7 +151,7 @@ module ActiveRecord
|
|
217
151
|
source_primary_key = @reflection.klass.primary_key
|
218
152
|
if @reflection.source_reflection.options[:as]
|
219
153
|
polymorphic_join = "AND %s.%s = %s" % [
|
220
|
-
@reflection.
|
154
|
+
@reflection.quoted_table_name, "#{@reflection.source_reflection.options[:as]}_type",
|
221
155
|
@owner.class.quote_value(@reflection.through_reflection.klass.name)
|
222
156
|
]
|
223
157
|
end
|
@@ -236,9 +170,12 @@ module ActiveRecord
|
|
236
170
|
:find => { :from => construct_from,
|
237
171
|
:conditions => construct_conditions,
|
238
172
|
:joins => construct_joins,
|
173
|
+
:include => @reflection.options[:include],
|
239
174
|
:select => construct_select,
|
240
175
|
:order => @reflection.options[:order],
|
241
|
-
:limit => @reflection.options[:limit]
|
176
|
+
:limit => @reflection.options[:limit],
|
177
|
+
:readonly => @reflection.options[:readonly],
|
178
|
+
} }
|
242
179
|
end
|
243
180
|
|
244
181
|
def construct_sql
|
@@ -246,8 +183,10 @@ module ActiveRecord
|
|
246
183
|
when @reflection.options[:finder_sql]
|
247
184
|
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
248
185
|
|
249
|
-
@finder_sql = "#{@reflection.
|
186
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{@owner.quoted_id}"
|
250
187
|
@finder_sql << " AND (#{conditions})" if conditions
|
188
|
+
else
|
189
|
+
@finder_sql = construct_conditions
|
251
190
|
end
|
252
191
|
|
253
192
|
if @reflection.options[:counter_sql]
|
@@ -268,25 +207,37 @@ module ActiveRecord
|
|
268
207
|
|
269
208
|
def build_conditions
|
270
209
|
association_conditions = @reflection.options[:conditions]
|
271
|
-
through_conditions =
|
210
|
+
through_conditions = build_through_conditions
|
272
211
|
source_conditions = @reflection.source_reflection.options[:conditions]
|
273
212
|
uses_sti = !@reflection.through_reflection.klass.descends_from_active_record?
|
274
213
|
|
275
214
|
if association_conditions || through_conditions || source_conditions || uses_sti
|
276
215
|
all = []
|
277
216
|
|
278
|
-
[association_conditions,
|
217
|
+
[association_conditions, source_conditions].each do |conditions|
|
279
218
|
all << interpolate_sql(sanitize_sql(conditions)) if conditions
|
280
219
|
end
|
281
220
|
|
221
|
+
all << through_conditions if through_conditions
|
282
222
|
all << build_sti_condition if uses_sti
|
283
223
|
|
284
224
|
all.map { |sql| "(#{sql})" } * ' AND '
|
285
225
|
end
|
286
226
|
end
|
287
227
|
|
228
|
+
def build_through_conditions
|
229
|
+
conditions = @reflection.through_reflection.options[:conditions]
|
230
|
+
if conditions.is_a?(Hash)
|
231
|
+
interpolate_sql(sanitize_sql(conditions)).gsub(
|
232
|
+
@reflection.quoted_table_name,
|
233
|
+
@reflection.through_reflection.quoted_table_name)
|
234
|
+
elsif conditions
|
235
|
+
interpolate_sql(sanitize_sql(conditions))
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
288
239
|
def build_sti_condition
|
289
|
-
"#{@reflection.through_reflection.
|
240
|
+
"#{@reflection.through_reflection.quoted_table_name}.#{@reflection.through_reflection.klass.inheritance_column} = #{@reflection.klass.quote_value(@reflection.through_reflection.klass.sti_name)}"
|
290
241
|
end
|
291
242
|
|
292
243
|
alias_method :sql_conditions, :conditions
|