activerecord_csi 2.3.5.p6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +5858 -0
- data/README +351 -0
- data/RUNNING_UNIT_TESTS +36 -0
- data/Rakefile +270 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +162 -0
- data/install.rb +30 -0
- data/lib/active_record/aggregations.rb +261 -0
- data/lib/active_record/association_preload.rb +389 -0
- data/lib/active_record/associations/association_collection.rb +475 -0
- data/lib/active_record/associations/association_proxy.rb +278 -0
- data/lib/active_record/associations/belongs_to_association.rb +76 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +53 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +143 -0
- data/lib/active_record/associations/has_many_association.rb +122 -0
- data/lib/active_record/associations/has_many_through_association.rb +266 -0
- data/lib/active_record/associations/has_one_association.rb +133 -0
- data/lib/active_record/associations/has_one_through_association.rb +37 -0
- data/lib/active_record/associations.rb +2241 -0
- data/lib/active_record/attribute_methods.rb +388 -0
- data/lib/active_record/autosave_association.rb +364 -0
- data/lib/active_record/base.rb +3171 -0
- data/lib/active_record/batches.rb +81 -0
- data/lib/active_record/calculations.rb +311 -0
- data/lib/active_record/callbacks.rb +360 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +371 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +139 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +289 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +94 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +69 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +722 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +434 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +241 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +630 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1113 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +34 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +453 -0
- data/lib/active_record/dirty.rb +183 -0
- data/lib/active_record/dynamic_finder_match.rb +41 -0
- data/lib/active_record/dynamic_scope_match.rb +25 -0
- data/lib/active_record/fixtures.rb +996 -0
- data/lib/active_record/i18n_interpolation_deprecation.rb +26 -0
- data/lib/active_record/locale/en.yml +58 -0
- data/lib/active_record/locking/optimistic.rb +148 -0
- data/lib/active_record/locking/pessimistic.rb +55 -0
- data/lib/active_record/migration.rb +566 -0
- data/lib/active_record/named_scope.rb +192 -0
- data/lib/active_record/nested_attributes.rb +392 -0
- data/lib/active_record/observer.rb +197 -0
- data/lib/active_record/query_cache.rb +33 -0
- data/lib/active_record/reflection.rb +320 -0
- data/lib/active_record/schema.rb +51 -0
- data/lib/active_record/schema_dumper.rb +182 -0
- data/lib/active_record/serialization.rb +101 -0
- data/lib/active_record/serializers/json_serializer.rb +91 -0
- data/lib/active_record/serializers/xml_serializer.rb +357 -0
- data/lib/active_record/session_store.rb +326 -0
- data/lib/active_record/test_case.rb +66 -0
- data/lib/active_record/timestamp.rb +71 -0
- data/lib/active_record/transactions.rb +235 -0
- data/lib/active_record/validations.rb +1135 -0
- data/lib/active_record/version.rb +9 -0
- data/lib/active_record.rb +84 -0
- data/lib/activerecord.rb +2 -0
- data/test/assets/example.log +1 -0
- data/test/assets/flowers.jpg +0 -0
- data/test/cases/aaa_create_tables_test.rb +24 -0
- data/test/cases/active_schema_test_mysql.rb +100 -0
- data/test/cases/active_schema_test_postgresql.rb +24 -0
- data/test/cases/adapter_test.rb +145 -0
- data/test/cases/aggregations_test.rb +167 -0
- data/test/cases/ar_schema_test.rb +32 -0
- data/test/cases/associations/belongs_to_associations_test.rb +425 -0
- data/test/cases/associations/callbacks_test.rb +161 -0
- data/test/cases/associations/cascaded_eager_loading_test.rb +131 -0
- data/test/cases/associations/eager_load_includes_full_sti_class_test.rb +36 -0
- data/test/cases/associations/eager_load_nested_include_test.rb +130 -0
- data/test/cases/associations/eager_singularization_test.rb +145 -0
- data/test/cases/associations/eager_test.rb +834 -0
- data/test/cases/associations/extension_test.rb +62 -0
- data/test/cases/associations/habtm_join_table_test.rb +56 -0
- data/test/cases/associations/has_and_belongs_to_many_associations_test.rb +822 -0
- data/test/cases/associations/has_many_associations_test.rb +1134 -0
- data/test/cases/associations/has_many_through_associations_test.rb +346 -0
- data/test/cases/associations/has_one_associations_test.rb +330 -0
- data/test/cases/associations/has_one_through_associations_test.rb +209 -0
- data/test/cases/associations/inner_join_association_test.rb +93 -0
- data/test/cases/associations/join_model_test.rb +712 -0
- data/test/cases/associations_test.rb +262 -0
- data/test/cases/attribute_methods_test.rb +305 -0
- data/test/cases/autosave_association_test.rb +1142 -0
- data/test/cases/base_test.rb +2154 -0
- data/test/cases/batches_test.rb +61 -0
- data/test/cases/binary_test.rb +30 -0
- data/test/cases/calculations_test.rb +348 -0
- data/test/cases/callbacks_observers_test.rb +38 -0
- data/test/cases/callbacks_test.rb +438 -0
- data/test/cases/class_inheritable_attributes_test.rb +32 -0
- data/test/cases/column_alias_test.rb +17 -0
- data/test/cases/column_definition_test.rb +70 -0
- data/test/cases/connection_pool_test.rb +25 -0
- data/test/cases/connection_test_firebird.rb +8 -0
- data/test/cases/connection_test_mysql.rb +64 -0
- data/test/cases/copy_table_test_sqlite.rb +80 -0
- data/test/cases/database_statements_test.rb +12 -0
- data/test/cases/datatype_test_postgresql.rb +204 -0
- data/test/cases/date_time_test.rb +37 -0
- data/test/cases/default_test_firebird.rb +16 -0
- data/test/cases/defaults_test.rb +111 -0
- data/test/cases/deprecated_finder_test.rb +30 -0
- data/test/cases/dirty_test.rb +316 -0
- data/test/cases/finder_respond_to_test.rb +76 -0
- data/test/cases/finder_test.rb +1066 -0
- data/test/cases/fixtures_test.rb +656 -0
- data/test/cases/helper.rb +68 -0
- data/test/cases/i18n_test.rb +46 -0
- data/test/cases/inheritance_test.rb +262 -0
- data/test/cases/invalid_date_test.rb +24 -0
- data/test/cases/json_serialization_test.rb +205 -0
- data/test/cases/lifecycle_test.rb +193 -0
- data/test/cases/locking_test.rb +304 -0
- data/test/cases/method_scoping_test.rb +704 -0
- data/test/cases/migration_test.rb +1523 -0
- data/test/cases/migration_test_firebird.rb +124 -0
- data/test/cases/mixin_test.rb +96 -0
- data/test/cases/modules_test.rb +81 -0
- data/test/cases/multiple_db_test.rb +85 -0
- data/test/cases/named_scope_test.rb +361 -0
- data/test/cases/nested_attributes_test.rb +581 -0
- data/test/cases/pk_test.rb +119 -0
- data/test/cases/pooled_connections_test.rb +103 -0
- data/test/cases/query_cache_test.rb +123 -0
- data/test/cases/readonly_test.rb +107 -0
- data/test/cases/reflection_test.rb +194 -0
- data/test/cases/reload_models_test.rb +22 -0
- data/test/cases/repair_helper.rb +50 -0
- data/test/cases/reserved_word_test_mysql.rb +176 -0
- data/test/cases/sanitize_test.rb +25 -0
- data/test/cases/schema_authorization_test_postgresql.rb +75 -0
- data/test/cases/schema_dumper_test.rb +211 -0
- data/test/cases/schema_test_postgresql.rb +178 -0
- data/test/cases/serialization_test.rb +47 -0
- data/test/cases/synonym_test_oracle.rb +17 -0
- data/test/cases/timestamp_test.rb +75 -0
- data/test/cases/transactions_test.rb +522 -0
- data/test/cases/unconnected_test.rb +32 -0
- data/test/cases/validations_i18n_test.rb +955 -0
- data/test/cases/validations_test.rb +1640 -0
- data/test/cases/xml_serialization_test.rb +240 -0
- data/test/config.rb +5 -0
- data/test/connections/jdbc_jdbcderby/connection.rb +18 -0
- data/test/connections/jdbc_jdbch2/connection.rb +18 -0
- data/test/connections/jdbc_jdbchsqldb/connection.rb +18 -0
- data/test/connections/jdbc_jdbcmysql/connection.rb +26 -0
- data/test/connections/jdbc_jdbcpostgresql/connection.rb +26 -0
- data/test/connections/jdbc_jdbcsqlite3/connection.rb +25 -0
- data/test/connections/native_db2/connection.rb +25 -0
- data/test/connections/native_firebird/connection.rb +26 -0
- data/test/connections/native_frontbase/connection.rb +27 -0
- data/test/connections/native_mysql/connection.rb +25 -0
- data/test/connections/native_openbase/connection.rb +21 -0
- data/test/connections/native_oracle/connection.rb +27 -0
- data/test/connections/native_postgresql/connection.rb +25 -0
- data/test/connections/native_sqlite/connection.rb +25 -0
- data/test/connections/native_sqlite3/connection.rb +25 -0
- data/test/connections/native_sqlite3/in_memory_connection.rb +18 -0
- data/test/connections/native_sybase/connection.rb +23 -0
- data/test/fixtures/accounts.yml +29 -0
- data/test/fixtures/all/developers.yml +0 -0
- data/test/fixtures/all/people.csv +0 -0
- data/test/fixtures/all/tasks.yml +0 -0
- data/test/fixtures/author_addresses.yml +5 -0
- data/test/fixtures/author_favorites.yml +4 -0
- data/test/fixtures/authors.yml +9 -0
- data/test/fixtures/binaries.yml +132 -0
- data/test/fixtures/books.yml +7 -0
- data/test/fixtures/categories/special_categories.yml +9 -0
- data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
- data/test/fixtures/categories.yml +14 -0
- data/test/fixtures/categories_ordered.yml +7 -0
- data/test/fixtures/categories_posts.yml +23 -0
- data/test/fixtures/categorizations.yml +17 -0
- data/test/fixtures/clubs.yml +6 -0
- data/test/fixtures/comments.yml +59 -0
- data/test/fixtures/companies.yml +56 -0
- data/test/fixtures/computers.yml +4 -0
- data/test/fixtures/courses.yml +7 -0
- data/test/fixtures/customers.yml +26 -0
- data/test/fixtures/developers.yml +21 -0
- data/test/fixtures/developers_projects.yml +17 -0
- data/test/fixtures/edges.yml +6 -0
- data/test/fixtures/entrants.yml +14 -0
- data/test/fixtures/fixture_database.sqlite3 +0 -0
- data/test/fixtures/fixture_database_2.sqlite3 +0 -0
- data/test/fixtures/fk_test_has_fk.yml +3 -0
- data/test/fixtures/fk_test_has_pk.yml +2 -0
- data/test/fixtures/funny_jokes.yml +10 -0
- data/test/fixtures/items.yml +4 -0
- data/test/fixtures/jobs.yml +7 -0
- data/test/fixtures/legacy_things.yml +3 -0
- data/test/fixtures/mateys.yml +4 -0
- data/test/fixtures/member_types.yml +6 -0
- data/test/fixtures/members.yml +6 -0
- data/test/fixtures/memberships.yml +20 -0
- data/test/fixtures/minimalistics.yml +2 -0
- data/test/fixtures/mixed_case_monkeys.yml +6 -0
- data/test/fixtures/mixins.yml +29 -0
- data/test/fixtures/movies.yml +7 -0
- data/test/fixtures/naked/csv/accounts.csv +1 -0
- data/test/fixtures/naked/yml/accounts.yml +1 -0
- data/test/fixtures/naked/yml/companies.yml +1 -0
- data/test/fixtures/naked/yml/courses.yml +1 -0
- data/test/fixtures/organizations.yml +5 -0
- data/test/fixtures/owners.yml +7 -0
- data/test/fixtures/parrots.yml +27 -0
- data/test/fixtures/parrots_pirates.yml +7 -0
- data/test/fixtures/people.yml +15 -0
- data/test/fixtures/pets.yml +14 -0
- data/test/fixtures/pirates.yml +9 -0
- data/test/fixtures/posts.yml +52 -0
- data/test/fixtures/price_estimates.yml +7 -0
- data/test/fixtures/projects.yml +7 -0
- data/test/fixtures/readers.yml +9 -0
- data/test/fixtures/references.yml +17 -0
- data/test/fixtures/reserved_words/distinct.yml +5 -0
- data/test/fixtures/reserved_words/distincts_selects.yml +11 -0
- data/test/fixtures/reserved_words/group.yml +14 -0
- data/test/fixtures/reserved_words/select.yml +8 -0
- data/test/fixtures/reserved_words/values.yml +7 -0
- data/test/fixtures/ships.yml +5 -0
- data/test/fixtures/sponsors.yml +9 -0
- data/test/fixtures/subscribers.yml +7 -0
- data/test/fixtures/subscriptions.yml +12 -0
- data/test/fixtures/taggings.yml +28 -0
- data/test/fixtures/tags.yml +7 -0
- data/test/fixtures/tasks.yml +7 -0
- data/test/fixtures/topics.yml +42 -0
- data/test/fixtures/toys.yml +4 -0
- data/test/fixtures/treasures.yml +10 -0
- data/test/fixtures/vertices.yml +4 -0
- data/test/fixtures/warehouse-things.yml +3 -0
- data/test/migrations/broken/100_migration_that_raises_exception.rb +10 -0
- data/test/migrations/decimal/1_give_me_big_numbers.rb +15 -0
- data/test/migrations/duplicate/1_people_have_last_names.rb +9 -0
- data/test/migrations/duplicate/2_we_need_reminders.rb +12 -0
- data/test/migrations/duplicate/3_foo.rb +7 -0
- data/test/migrations/duplicate/3_innocent_jointable.rb +12 -0
- data/test/migrations/duplicate_names/20080507052938_chunky.rb +7 -0
- data/test/migrations/duplicate_names/20080507053028_chunky.rb +7 -0
- data/test/migrations/interleaved/pass_1/3_innocent_jointable.rb +12 -0
- data/test/migrations/interleaved/pass_2/1_people_have_last_names.rb +9 -0
- data/test/migrations/interleaved/pass_2/3_innocent_jointable.rb +12 -0
- data/test/migrations/interleaved/pass_3/1_people_have_last_names.rb +9 -0
- data/test/migrations/interleaved/pass_3/2_i_raise_on_down.rb +8 -0
- data/test/migrations/interleaved/pass_3/3_innocent_jointable.rb +12 -0
- data/test/migrations/missing/1000_people_have_middle_names.rb +9 -0
- data/test/migrations/missing/1_people_have_last_names.rb +9 -0
- data/test/migrations/missing/3_we_need_reminders.rb +12 -0
- data/test/migrations/missing/4_innocent_jointable.rb +12 -0
- data/test/migrations/valid/1_people_have_last_names.rb +9 -0
- data/test/migrations/valid/2_we_need_reminders.rb +12 -0
- data/test/migrations/valid/3_innocent_jointable.rb +12 -0
- data/test/models/author.rb +146 -0
- data/test/models/auto_id.rb +4 -0
- data/test/models/binary.rb +2 -0
- data/test/models/bird.rb +3 -0
- data/test/models/book.rb +4 -0
- data/test/models/categorization.rb +5 -0
- data/test/models/category.rb +34 -0
- data/test/models/citation.rb +6 -0
- data/test/models/club.rb +13 -0
- data/test/models/column_name.rb +3 -0
- data/test/models/comment.rb +29 -0
- data/test/models/company.rb +171 -0
- data/test/models/company_in_module.rb +61 -0
- data/test/models/computer.rb +3 -0
- data/test/models/contact.rb +16 -0
- data/test/models/contract.rb +5 -0
- data/test/models/course.rb +3 -0
- data/test/models/customer.rb +73 -0
- data/test/models/default.rb +2 -0
- data/test/models/developer.rb +101 -0
- data/test/models/edge.rb +5 -0
- data/test/models/entrant.rb +3 -0
- data/test/models/essay.rb +3 -0
- data/test/models/event.rb +3 -0
- data/test/models/guid.rb +2 -0
- data/test/models/item.rb +7 -0
- data/test/models/job.rb +5 -0
- data/test/models/joke.rb +3 -0
- data/test/models/keyboard.rb +3 -0
- data/test/models/legacy_thing.rb +3 -0
- data/test/models/matey.rb +4 -0
- data/test/models/member.rb +12 -0
- data/test/models/member_detail.rb +5 -0
- data/test/models/member_type.rb +3 -0
- data/test/models/membership.rb +9 -0
- data/test/models/minimalistic.rb +2 -0
- data/test/models/mixed_case_monkey.rb +3 -0
- data/test/models/movie.rb +5 -0
- data/test/models/order.rb +4 -0
- data/test/models/organization.rb +6 -0
- data/test/models/owner.rb +5 -0
- data/test/models/parrot.rb +16 -0
- data/test/models/person.rb +16 -0
- data/test/models/pet.rb +5 -0
- data/test/models/pirate.rb +70 -0
- data/test/models/post.rb +100 -0
- data/test/models/price_estimate.rb +3 -0
- data/test/models/project.rb +30 -0
- data/test/models/reader.rb +4 -0
- data/test/models/reference.rb +4 -0
- data/test/models/reply.rb +46 -0
- data/test/models/ship.rb +10 -0
- data/test/models/ship_part.rb +5 -0
- data/test/models/sponsor.rb +4 -0
- data/test/models/subject.rb +4 -0
- data/test/models/subscriber.rb +8 -0
- data/test/models/subscription.rb +4 -0
- data/test/models/tag.rb +7 -0
- data/test/models/tagging.rb +10 -0
- data/test/models/task.rb +3 -0
- data/test/models/topic.rb +80 -0
- data/test/models/toy.rb +6 -0
- data/test/models/treasure.rb +8 -0
- data/test/models/vertex.rb +9 -0
- data/test/models/warehouse_thing.rb +5 -0
- data/test/schema/mysql_specific_schema.rb +24 -0
- data/test/schema/postgresql_specific_schema.rb +114 -0
- data/test/schema/schema.rb +493 -0
- data/test/schema/schema2.rb +6 -0
- data/test/schema/sqlite_specific_schema.rb +25 -0
- metadata +420 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
module ActiveRecord
|
|
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.
|
|
48
|
+
class AssociationProxy #:nodoc:
|
|
49
|
+
alias_method :proxy_respond_to?, :respond_to?
|
|
50
|
+
alias_method :proxy_extend, :extend
|
|
51
|
+
delegate :to_param, :to => :proxy_target
|
|
52
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^nil\?$|^send$|proxy_|^object_id$)/ }
|
|
53
|
+
|
|
54
|
+
def initialize(owner, reflection)
|
|
55
|
+
@owner, @reflection = owner, reflection
|
|
56
|
+
Array(reflection.options[:extend]).each { |ext| proxy_extend(ext) }
|
|
57
|
+
reset
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns the owner of the proxy.
|
|
61
|
+
def proxy_owner
|
|
62
|
+
@owner
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns the reflection object that represents the association handled
|
|
66
|
+
# by the proxy.
|
|
67
|
+
def proxy_reflection
|
|
68
|
+
@reflection
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Returns the \target of the proxy, same as +target+.
|
|
72
|
+
def proxy_target
|
|
73
|
+
@target
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Does the proxy or its \target respond to +symbol+?
|
|
77
|
+
def respond_to?(*args)
|
|
78
|
+
proxy_respond_to?(*args) || (load_target && @target.respond_to?(*args))
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Forwards <tt>===</tt> explicitly to the \target because the instance method
|
|
82
|
+
# removal above doesn't catch it. Loads the \target if needed.
|
|
83
|
+
def ===(other)
|
|
84
|
+
load_target
|
|
85
|
+
other === @target
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Returns the name of the table of the related class:
|
|
89
|
+
#
|
|
90
|
+
# post.comments.aliased_table_name # => "comments"
|
|
91
|
+
#
|
|
92
|
+
def aliased_table_name
|
|
93
|
+
@reflection.klass.table_name
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns the SQL string that corresponds to the <tt>:conditions</tt>
|
|
97
|
+
# option of the macro, if given, or +nil+ otherwise.
|
|
98
|
+
def conditions
|
|
99
|
+
@conditions ||= interpolate_sql(@reflection.sanitized_conditions) if @reflection.sanitized_conditions
|
|
100
|
+
end
|
|
101
|
+
alias :sql_conditions :conditions
|
|
102
|
+
|
|
103
|
+
# Resets the \loaded flag to +false+ and sets the \target to +nil+.
|
|
104
|
+
def reset
|
|
105
|
+
@loaded = false
|
|
106
|
+
@target = nil
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Reloads the \target and returns +self+ on success.
|
|
110
|
+
def reload
|
|
111
|
+
reset
|
|
112
|
+
load_target
|
|
113
|
+
self unless @target.nil?
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Has the \target been already \loaded?
|
|
117
|
+
def loaded?
|
|
118
|
+
@loaded
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Asserts the \target has been loaded setting the \loaded flag to +true+.
|
|
122
|
+
def loaded
|
|
123
|
+
@loaded = true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Returns the target of this proxy, same as +proxy_target+.
|
|
127
|
+
def target
|
|
128
|
+
@target
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Sets the target of this proxy to <tt>\target</tt>, and the \loaded flag to +true+.
|
|
132
|
+
def target=(target)
|
|
133
|
+
@target = target
|
|
134
|
+
loaded
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Forwards the call to the target. Loads the \target if needed.
|
|
138
|
+
def inspect
|
|
139
|
+
load_target
|
|
140
|
+
@target.inspect
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def send(method, *args)
|
|
144
|
+
if proxy_respond_to?(method)
|
|
145
|
+
super
|
|
146
|
+
else
|
|
147
|
+
load_target
|
|
148
|
+
@target.send(method, *args)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
protected
|
|
153
|
+
# Does the association have a <tt>:dependent</tt> option?
|
|
154
|
+
def dependent?
|
|
155
|
+
@reflection.options[:dependent]
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Returns a string with the IDs of +records+ joined with a comma, quoted
|
|
159
|
+
# if needed. The result is ready to be inserted into a SQL IN clause.
|
|
160
|
+
#
|
|
161
|
+
# quoted_record_ids(records) # => "23,56,58,67"
|
|
162
|
+
#
|
|
163
|
+
def quoted_record_ids(records)
|
|
164
|
+
records.map { |record| record.quoted_id }.join(',')
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def interpolate_sql(sql, record = nil)
|
|
168
|
+
@owner.send(:interpolate_sql, sql, record)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# Forwards the call to the reflection class.
|
|
172
|
+
def sanitize_sql(sql, table_name = @reflection.klass.quoted_table_name)
|
|
173
|
+
@reflection.klass.send(:sanitize_sql, sql, table_name)
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Assigns the ID of the owner to the corresponding foreign key in +record+.
|
|
177
|
+
# If the association is polymorphic the type of the owner is also set.
|
|
178
|
+
def set_belongs_to_association_for(record)
|
|
179
|
+
if @reflection.options[:as]
|
|
180
|
+
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
|
|
181
|
+
record["#{@reflection.options[:as]}_type"] = @owner.class.base_class.name.to_s
|
|
182
|
+
else
|
|
183
|
+
unless @owner.new_record?
|
|
184
|
+
primary_key = @reflection.options[:primary_key] || :id
|
|
185
|
+
record[@reflection.primary_key_name] = @owner.send(primary_key)
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Merges into +options+ the ones coming from the reflection.
|
|
191
|
+
def merge_options_from_reflection!(options)
|
|
192
|
+
options.reverse_merge!(
|
|
193
|
+
:group => @reflection.options[:group],
|
|
194
|
+
:having => @reflection.options[:having],
|
|
195
|
+
:limit => @reflection.options[:limit],
|
|
196
|
+
:offset => @reflection.options[:offset],
|
|
197
|
+
:joins => @reflection.options[:joins],
|
|
198
|
+
:include => @reflection.options[:include],
|
|
199
|
+
:select => @reflection.options[:select],
|
|
200
|
+
:readonly => @reflection.options[:readonly]
|
|
201
|
+
)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# Forwards +with_scope+ to the reflection.
|
|
205
|
+
def with_scope(*args, &block)
|
|
206
|
+
@reflection.klass.send :with_scope, *args, &block
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
private
|
|
210
|
+
# Forwards any missing method call to the \target.
|
|
211
|
+
def method_missing(method, *args)
|
|
212
|
+
if load_target
|
|
213
|
+
if @target.respond_to?(method)
|
|
214
|
+
if block_given?
|
|
215
|
+
@target.send(method, *args) { |*block_args| yield(*block_args) }
|
|
216
|
+
else
|
|
217
|
+
@target.send(method, *args)
|
|
218
|
+
end
|
|
219
|
+
else
|
|
220
|
+
super
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# Loads the \target if needed and returns it.
|
|
226
|
+
#
|
|
227
|
+
# This method is abstract in the sense that it relies on +find_target+,
|
|
228
|
+
# which is expected to be provided by descendants.
|
|
229
|
+
#
|
|
230
|
+
# If the \target is already \loaded it is just returned. Thus, you can call
|
|
231
|
+
# +load_target+ unconditionally to get the \target.
|
|
232
|
+
#
|
|
233
|
+
# ActiveRecord::RecordNotFound is rescued within the method, and it is
|
|
234
|
+
# not reraised. The proxy is \reset and +nil+ is the return value.
|
|
235
|
+
def load_target
|
|
236
|
+
return nil unless defined?(@loaded)
|
|
237
|
+
|
|
238
|
+
if !loaded? and (!@owner.new_record? || foreign_key_present)
|
|
239
|
+
@target = find_target
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
@loaded = true
|
|
243
|
+
@target
|
|
244
|
+
rescue ActiveRecord::RecordNotFound
|
|
245
|
+
reset
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Can be overwritten by associations that might have the foreign key
|
|
249
|
+
# available for an association without having the object itself (and
|
|
250
|
+
# still being a new record). Currently, only +belongs_to+ presents
|
|
251
|
+
# this scenario (both vanilla and polymorphic).
|
|
252
|
+
def foreign_key_present
|
|
253
|
+
false
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Raises ActiveRecord::AssociationTypeMismatch unless +record+ is of
|
|
257
|
+
# the kind of the class of the associated objects. Meant to be used as
|
|
258
|
+
# a sanity check when you are about to assign an associated record.
|
|
259
|
+
def raise_on_type_mismatch(record)
|
|
260
|
+
unless record.is_a?(@reflection.klass) || record.is_a?(@reflection.class_name.constantize)
|
|
261
|
+
message = "#{@reflection.class_name}(##{@reflection.klass.object_id}) expected, got #{record.class}(##{record.class.object_id})"
|
|
262
|
+
raise ActiveRecord::AssociationTypeMismatch, message
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Array#flatten has problems with recursive arrays. Going one level
|
|
267
|
+
# deeper solves the majority of the problems.
|
|
268
|
+
def flatten_deeper(array)
|
|
269
|
+
array.collect { |element| (element.respond_to?(:flatten) && !element.is_a?(Hash)) ? element.flatten : element }.flatten
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# Returns the ID of the owner, quoted if needed.
|
|
273
|
+
def owner_quoted_id
|
|
274
|
+
@owner.quoted_id
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module Associations
|
|
3
|
+
class BelongsToAssociation < AssociationProxy #:nodoc:
|
|
4
|
+
def create(attributes = {})
|
|
5
|
+
replace(@reflection.create_association(attributes))
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def build(attributes = {})
|
|
9
|
+
replace(@reflection.build_association(attributes))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def replace(record)
|
|
13
|
+
counter_cache_name = @reflection.counter_cache_column
|
|
14
|
+
|
|
15
|
+
if record.nil?
|
|
16
|
+
if counter_cache_name && !@owner.new_record?
|
|
17
|
+
@reflection.klass.decrement_counter(counter_cache_name, previous_record_id) if @owner[@reflection.primary_key_name]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
@target = @owner[@reflection.primary_key_name] = nil
|
|
21
|
+
else
|
|
22
|
+
raise_on_type_mismatch(record)
|
|
23
|
+
|
|
24
|
+
if counter_cache_name && !@owner.new_record?
|
|
25
|
+
@reflection.klass.increment_counter(counter_cache_name, record.id)
|
|
26
|
+
@reflection.klass.decrement_counter(counter_cache_name, @owner[@reflection.primary_key_name]) if @owner[@reflection.primary_key_name]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@target = (AssociationProxy === record ? record.target : record)
|
|
30
|
+
@owner[@reflection.primary_key_name] = record_id(record) unless record.new_record?
|
|
31
|
+
@updated = true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
loaded
|
|
35
|
+
record
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def updated?
|
|
39
|
+
@updated
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
def find_target
|
|
44
|
+
find_method = if @reflection.options[:primary_key]
|
|
45
|
+
"find_by_#{@reflection.options[:primary_key]}"
|
|
46
|
+
else
|
|
47
|
+
"find"
|
|
48
|
+
end
|
|
49
|
+
@reflection.klass.send(find_method,
|
|
50
|
+
@owner[@reflection.primary_key_name],
|
|
51
|
+
:select => @reflection.options[:select],
|
|
52
|
+
:conditions => conditions,
|
|
53
|
+
:include => @reflection.options[:include],
|
|
54
|
+
:readonly => @reflection.options[:readonly]
|
|
55
|
+
) if @owner[@reflection.primary_key_name]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def foreign_key_present
|
|
59
|
+
!@owner[@reflection.primary_key_name].nil?
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def record_id(record)
|
|
63
|
+
record.send(@reflection.options[:primary_key] || :id)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def previous_record_id
|
|
67
|
+
@previous_record_id ||= if @reflection.options[:primary_key]
|
|
68
|
+
previous_record = @owner.send(@reflection.name)
|
|
69
|
+
previous_record.nil? ? nil : previous_record.id
|
|
70
|
+
else
|
|
71
|
+
@owner[@reflection.primary_key_name]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module Associations
|
|
3
|
+
class BelongsToPolymorphicAssociation < AssociationProxy #:nodoc:
|
|
4
|
+
def replace(record)
|
|
5
|
+
if record.nil?
|
|
6
|
+
@target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
|
|
7
|
+
else
|
|
8
|
+
@target = (AssociationProxy === record ? record.target : record)
|
|
9
|
+
|
|
10
|
+
@owner[@reflection.primary_key_name] = record_id(record)
|
|
11
|
+
@owner[@reflection.options[:foreign_type]] = record.class.base_class.name.to_s
|
|
12
|
+
|
|
13
|
+
@updated = true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
loaded
|
|
17
|
+
record
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def updated?
|
|
21
|
+
@updated
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
def find_target
|
|
26
|
+
return nil if association_class.nil?
|
|
27
|
+
|
|
28
|
+
if @reflection.options[:conditions]
|
|
29
|
+
association_class.find(
|
|
30
|
+
@owner[@reflection.primary_key_name],
|
|
31
|
+
:select => @reflection.options[:select],
|
|
32
|
+
:conditions => conditions,
|
|
33
|
+
:include => @reflection.options[:include]
|
|
34
|
+
)
|
|
35
|
+
else
|
|
36
|
+
association_class.find(@owner[@reflection.primary_key_name], :select => @reflection.options[:select], :include => @reflection.options[:include])
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def foreign_key_present
|
|
41
|
+
!@owner[@reflection.primary_key_name].nil?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def record_id(record)
|
|
45
|
+
record.send(@reflection.options[:primary_key] || :id)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def association_class
|
|
49
|
+
@owner[@reflection.options[:foreign_type]] ? @owner[@reflection.options[:foreign_type]].constantize : nil
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module Associations
|
|
3
|
+
class HasAndBelongsToManyAssociation < AssociationCollection #:nodoc:
|
|
4
|
+
def initialize(owner, reflection)
|
|
5
|
+
super
|
|
6
|
+
@primary_key_list = {}
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def create(attributes = {})
|
|
10
|
+
create_record(attributes) { |record| insert_record(record) }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def create!(attributes = {})
|
|
14
|
+
create_record(attributes) { |record| insert_record(record, true) }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def columns
|
|
18
|
+
@reflection.columns(@reflection.options[:join_table], "#{@reflection.options[:join_table]} Columns")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def reset_column_information
|
|
22
|
+
@reflection.reset_column_information
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def has_primary_key?
|
|
26
|
+
return @has_primary_key unless @has_primary_key.nil?
|
|
27
|
+
@has_primary_key = (@owner.connection.supports_primary_key? &&
|
|
28
|
+
@owner.connection.primary_key(@reflection.options[:join_table]))
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
protected
|
|
32
|
+
def construct_find_options!(options)
|
|
33
|
+
options[:joins] = @join_sql
|
|
34
|
+
options[:readonly] = finding_with_ambiguous_select?(options[:select] || @reflection.options[:select])
|
|
35
|
+
options[:select] ||= (@reflection.options[:select] || '*')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def count_records
|
|
39
|
+
load_target.size
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def insert_record(record, force = true, validate = true)
|
|
43
|
+
if has_primary_key?
|
|
44
|
+
raise ActiveRecord::ConfigurationError,
|
|
45
|
+
"Primary key is not allowed in a has_and_belongs_to_many join table (#{@reflection.options[:join_table]})."
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
if record.new_record?
|
|
49
|
+
if force
|
|
50
|
+
record.save!
|
|
51
|
+
else
|
|
52
|
+
return false unless record.save(validate)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
if @reflection.options[:insert_sql]
|
|
57
|
+
@owner.connection.insert(interpolate_sql(@reflection.options[:insert_sql], record))
|
|
58
|
+
else
|
|
59
|
+
attributes = columns.inject({}) do |attrs, column|
|
|
60
|
+
case column.name.to_s
|
|
61
|
+
when @reflection.primary_key_name.to_s
|
|
62
|
+
attrs[column.name] = owner_quoted_id
|
|
63
|
+
when @reflection.association_foreign_key.to_s
|
|
64
|
+
attrs[column.name] = record.quoted_id
|
|
65
|
+
else
|
|
66
|
+
if record.has_attribute?(column.name)
|
|
67
|
+
value = @owner.send(:quote_value, record[column.name], column)
|
|
68
|
+
attrs[column.name] = value unless value.nil?
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
attrs
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
sql =
|
|
75
|
+
"INSERT INTO #{@owner.connection.quote_table_name @reflection.options[:join_table]} (#{@owner.send(:quoted_column_names, attributes).join(', ')}) " +
|
|
76
|
+
"VALUES (#{attributes.values.join(', ')})"
|
|
77
|
+
|
|
78
|
+
@owner.connection.insert(sql)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
return true
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def delete_records(records)
|
|
85
|
+
if sql = @reflection.options[:delete_sql]
|
|
86
|
+
records.each { |record| @owner.connection.delete(interpolate_sql(sql, record)) }
|
|
87
|
+
else
|
|
88
|
+
ids = quoted_record_ids(records)
|
|
89
|
+
sql = "DELETE FROM #{@owner.connection.quote_table_name @reflection.options[:join_table]} WHERE #{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.association_foreign_key} IN (#{ids})"
|
|
90
|
+
@owner.connection.delete(sql)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def construct_sql
|
|
95
|
+
if @reflection.options[:finder_sql]
|
|
96
|
+
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
|
97
|
+
else
|
|
98
|
+
@finder_sql = "#{@owner.connection.quote_table_name @reflection.options[:join_table]}.#{@reflection.primary_key_name} = #{owner_quoted_id} "
|
|
99
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
@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}"
|
|
103
|
+
|
|
104
|
+
if @reflection.options[:counter_sql]
|
|
105
|
+
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
|
106
|
+
elsif @reflection.options[:finder_sql]
|
|
107
|
+
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
|
|
108
|
+
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
|
|
109
|
+
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
|
110
|
+
else
|
|
111
|
+
@counter_sql = @finder_sql
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def construct_scope
|
|
116
|
+
{ :find => { :conditions => @finder_sql,
|
|
117
|
+
:joins => @join_sql,
|
|
118
|
+
:readonly => false,
|
|
119
|
+
:order => @reflection.options[:order],
|
|
120
|
+
:include => @reflection.options[:include],
|
|
121
|
+
:limit => @reflection.options[:limit] } }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Join tables with additional columns on top of the two foreign keys must be considered ambiguous unless a select
|
|
125
|
+
# clause has been explicitly defined. Otherwise you can get broken records back, if, for example, the join column also has
|
|
126
|
+
# an id column. This will then overwrite the id column of the records coming back.
|
|
127
|
+
def finding_with_ambiguous_select?(select_clause)
|
|
128
|
+
!select_clause && columns.size != 2
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
private
|
|
132
|
+
def create_record(attributes, &block)
|
|
133
|
+
# Can't use Base.create because the foreign key may be a protected attribute.
|
|
134
|
+
ensure_owner_is_not_new
|
|
135
|
+
if attributes.is_a?(Array)
|
|
136
|
+
attributes.collect { |attr| create(attr) }
|
|
137
|
+
else
|
|
138
|
+
build_record(attributes, &block)
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module ActiveRecord
|
|
2
|
+
module Associations
|
|
3
|
+
# This is the proxy that handles a has many association.
|
|
4
|
+
#
|
|
5
|
+
# If the association has a <tt>:through</tt> option further specialization
|
|
6
|
+
# is provided by its child HasManyThroughAssociation.
|
|
7
|
+
class HasManyAssociation < AssociationCollection #:nodoc:
|
|
8
|
+
protected
|
|
9
|
+
def owner_quoted_id
|
|
10
|
+
if @reflection.options[:primary_key]
|
|
11
|
+
quote_value(@owner.send(@reflection.options[:primary_key]))
|
|
12
|
+
else
|
|
13
|
+
@owner.quoted_id
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Returns the number of records in this collection.
|
|
18
|
+
#
|
|
19
|
+
# If the association has a counter cache it gets that value. Otherwise
|
|
20
|
+
# it will attempt to do a count via SQL, bounded to <tt>:limit</tt> if
|
|
21
|
+
# there's one. Some configuration options like :group make it impossible
|
|
22
|
+
# to do a SQL count, in those cases the array count will be used.
|
|
23
|
+
#
|
|
24
|
+
# That does not depend on whether the collection has already been loaded
|
|
25
|
+
# or not. The +size+ method is the one that takes the loaded flag into
|
|
26
|
+
# account and delegates to +count_records+ if needed.
|
|
27
|
+
#
|
|
28
|
+
# If the collection is empty the target is set to an empty array and
|
|
29
|
+
# the loaded flag is set to true as well.
|
|
30
|
+
def count_records
|
|
31
|
+
count = if has_cached_counter?
|
|
32
|
+
@owner.send(:read_attribute, cached_counter_attribute_name)
|
|
33
|
+
elsif @reflection.options[:counter_sql]
|
|
34
|
+
@reflection.klass.count_by_sql(@counter_sql)
|
|
35
|
+
else
|
|
36
|
+
@reflection.klass.count(:conditions => @counter_sql, :include => @reflection.options[:include])
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# If there's nothing in the database and @target has no new records
|
|
40
|
+
# we are certain the current target is an empty array. This is a
|
|
41
|
+
# documented side-effect of the method that may avoid an extra SELECT.
|
|
42
|
+
@target ||= [] and loaded if count == 0
|
|
43
|
+
|
|
44
|
+
if @reflection.options[:limit]
|
|
45
|
+
count = [ @reflection.options[:limit], count ].min
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
return count
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def has_cached_counter?
|
|
52
|
+
@owner.attribute_present?(cached_counter_attribute_name)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def cached_counter_attribute_name
|
|
56
|
+
"#{@reflection.name}_count"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def insert_record(record, force = false, validate = true)
|
|
60
|
+
set_belongs_to_association_for(record)
|
|
61
|
+
force ? record.save! : record.save(validate)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Deletes the records according to the <tt>:dependent</tt> option.
|
|
65
|
+
def delete_records(records)
|
|
66
|
+
case @reflection.options[:dependent]
|
|
67
|
+
when :destroy
|
|
68
|
+
records.each { |r| r.destroy }
|
|
69
|
+
when :delete_all
|
|
70
|
+
@reflection.klass.delete(records.map { |record| record.id })
|
|
71
|
+
else
|
|
72
|
+
ids = quoted_record_ids(records)
|
|
73
|
+
@reflection.klass.update_all(
|
|
74
|
+
"#{@reflection.primary_key_name} = NULL",
|
|
75
|
+
"#{@reflection.primary_key_name} = #{owner_quoted_id} AND #{@reflection.klass.primary_key} IN (#{ids})"
|
|
76
|
+
)
|
|
77
|
+
@owner.class.update_counters(@owner.id, cached_counter_attribute_name => -records.size) if has_cached_counter?
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def target_obsolete?
|
|
82
|
+
false
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def construct_sql
|
|
86
|
+
case
|
|
87
|
+
when @reflection.options[:finder_sql]
|
|
88
|
+
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
|
89
|
+
|
|
90
|
+
when @reflection.options[:as]
|
|
91
|
+
@finder_sql =
|
|
92
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
|
|
93
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type = #{@owner.class.quote_value(@owner.class.base_class.name.to_s)}"
|
|
94
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
|
95
|
+
|
|
96
|
+
else
|
|
97
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
|
98
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if @reflection.options[:counter_sql]
|
|
102
|
+
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
|
103
|
+
elsif @reflection.options[:finder_sql]
|
|
104
|
+
# replace the SELECT clause with COUNT(*), preserving any hints within /* ... */
|
|
105
|
+
@reflection.options[:counter_sql] = @reflection.options[:finder_sql].sub(/SELECT (\/\*.*?\*\/ )?(.*)\bFROM\b/im) { "SELECT #{$1}COUNT(*) FROM" }
|
|
106
|
+
@counter_sql = interpolate_sql(@reflection.options[:counter_sql])
|
|
107
|
+
else
|
|
108
|
+
@counter_sql = @finder_sql
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def construct_scope
|
|
113
|
+
create_scoping = {}
|
|
114
|
+
set_belongs_to_association_for(create_scoping)
|
|
115
|
+
{
|
|
116
|
+
:find => { :conditions => @finder_sql, :readonly => false, :order => @reflection.options[:order], :limit => @reflection.options[:limit], :include => @reflection.options[:include]},
|
|
117
|
+
:create => create_scoping
|
|
118
|
+
}
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|