activerecord 1.0.0 → 3.0.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 +5518 -76
- data/README.rdoc +222 -0
- data/examples/performance.rb +162 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record/aggregations.rb +192 -80
- data/lib/active_record/association_preload.rb +403 -0
- data/lib/active_record/associations/association_collection.rb +545 -53
- data/lib/active_record/associations/association_proxy.rb +295 -0
- data/lib/active_record/associations/belongs_to_association.rb +91 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
- data/lib/active_record/associations/has_many_association.rb +108 -84
- data/lib/active_record/associations/has_many_through_association.rb +116 -0
- data/lib/active_record/associations/has_one_association.rb +143 -0
- data/lib/active_record/associations/has_one_through_association.rb +40 -0
- data/lib/active_record/associations/through_association_scope.rb +154 -0
- data/lib/active_record/associations.rb +2086 -368
- data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
- data/lib/active_record/attribute_methods/dirty.rb +95 -0
- data/lib/active_record/attribute_methods/primary_key.rb +50 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +116 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
- data/lib/active_record/attribute_methods/write.rb +37 -0
- data/lib/active_record/attribute_methods.rb +60 -0
- data/lib/active_record/autosave_association.rb +369 -0
- data/lib/active_record/base.rb +1603 -721
- data/lib/active_record/callbacks.rb +176 -225
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
- data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
- data/lib/active_record/counter_cache.rb +115 -0
- data/lib/active_record/dynamic_finder_match.rb +53 -0
- data/lib/active_record/dynamic_scope_match.rb +32 -0
- data/lib/active_record/errors.rb +172 -0
- data/lib/active_record/fixtures.rb +941 -105
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +172 -0
- data/lib/active_record/locking/pessimistic.rb +55 -0
- data/lib/active_record/log_subscriber.rb +48 -0
- data/lib/active_record/migration.rb +617 -0
- data/lib/active_record/named_scope.rb +138 -0
- data/lib/active_record/nested_attributes.rb +417 -0
- data/lib/active_record/observer.rb +105 -36
- data/lib/active_record/persistence.rb +291 -0
- data/lib/active_record/query_cache.rb +36 -0
- data/lib/active_record/railtie.rb +91 -0
- data/lib/active_record/railties/controller_runtime.rb +38 -0
- data/lib/active_record/railties/databases.rake +512 -0
- data/lib/active_record/reflection.rb +364 -87
- data/lib/active_record/relation/batches.rb +89 -0
- data/lib/active_record/relation/calculations.rb +286 -0
- data/lib/active_record/relation/finder_methods.rb +355 -0
- data/lib/active_record/relation/predicate_builder.rb +41 -0
- data/lib/active_record/relation/query_methods.rb +261 -0
- data/lib/active_record/relation/spawn_methods.rb +112 -0
- data/lib/active_record/relation.rb +393 -0
- data/lib/active_record/schema.rb +59 -0
- data/lib/active_record/schema_dumper.rb +195 -0
- data/lib/active_record/serialization.rb +60 -0
- data/lib/active_record/serializers/xml_serializer.rb +244 -0
- data/lib/active_record/session_store.rb +340 -0
- data/lib/active_record/test_case.rb +67 -0
- data/lib/active_record/timestamp.rb +88 -0
- data/lib/active_record/transactions.rb +329 -75
- data/lib/active_record/validations/associated.rb +48 -0
- data/lib/active_record/validations/uniqueness.rb +185 -0
- data/lib/active_record/validations.rb +58 -179
- data/lib/active_record/version.rb +9 -0
- data/lib/active_record.rb +100 -24
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
- data/lib/rails/generators/active_record.rb +27 -0
- metadata +216 -158
- data/README +0 -361
- data/RUNNING_UNIT_TESTS +0 -36
- data/dev-utils/eval_debugger.rb +0 -9
- data/examples/associations.rb +0 -87
- data/examples/shared_setup.rb +0 -15
- data/examples/validation.rb +0 -88
- data/install.rb +0 -60
- data/lib/active_record/deprecated_associations.rb +0 -70
- data/lib/active_record/support/class_attribute_accessors.rb +0 -43
- data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
- data/lib/active_record/support/clean_logger.rb +0 -10
- data/lib/active_record/support/inflector.rb +0 -70
- data/lib/active_record/vendor/mysql.rb +0 -1117
- data/lib/active_record/vendor/simple.rb +0 -702
- data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
- data/lib/active_record/wrappings.rb +0 -59
- data/rakefile +0 -122
- data/test/abstract_unit.rb +0 -16
- data/test/aggregations_test.rb +0 -34
- data/test/all.sh +0 -8
- data/test/associations_test.rb +0 -477
- data/test/base_test.rb +0 -513
- data/test/class_inheritable_attributes_test.rb +0 -33
- data/test/connections/native_mysql/connection.rb +0 -24
- data/test/connections/native_postgresql/connection.rb +0 -24
- data/test/connections/native_sqlite/connection.rb +0 -24
- data/test/deprecated_associations_test.rb +0 -336
- data/test/finder_test.rb +0 -67
- data/test/fixtures/accounts/signals37 +0 -3
- data/test/fixtures/accounts/unknown +0 -2
- data/test/fixtures/auto_id.rb +0 -4
- data/test/fixtures/column_name.rb +0 -3
- data/test/fixtures/companies/first_client +0 -6
- data/test/fixtures/companies/first_firm +0 -4
- data/test/fixtures/companies/second_client +0 -6
- data/test/fixtures/company.rb +0 -37
- data/test/fixtures/company_in_module.rb +0 -33
- data/test/fixtures/course.rb +0 -3
- data/test/fixtures/courses/java +0 -2
- data/test/fixtures/courses/ruby +0 -2
- data/test/fixtures/customer.rb +0 -30
- data/test/fixtures/customers/david +0 -6
- data/test/fixtures/db_definitions/mysql.sql +0 -96
- data/test/fixtures/db_definitions/mysql2.sql +0 -4
- data/test/fixtures/db_definitions/postgresql.sql +0 -113
- data/test/fixtures/db_definitions/postgresql2.sql +0 -4
- data/test/fixtures/db_definitions/sqlite.sql +0 -85
- data/test/fixtures/db_definitions/sqlite2.sql +0 -4
- data/test/fixtures/default.rb +0 -2
- data/test/fixtures/developer.rb +0 -8
- data/test/fixtures/developers/david +0 -2
- data/test/fixtures/developers/jamis +0 -2
- data/test/fixtures/developers_projects/david_action_controller +0 -2
- data/test/fixtures/developers_projects/david_active_record +0 -2
- data/test/fixtures/developers_projects/jamis_active_record +0 -2
- data/test/fixtures/entrant.rb +0 -3
- data/test/fixtures/entrants/first +0 -3
- data/test/fixtures/entrants/second +0 -3
- data/test/fixtures/entrants/third +0 -3
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/movie.rb +0 -5
- data/test/fixtures/movies/first +0 -2
- data/test/fixtures/movies/second +0 -2
- data/test/fixtures/project.rb +0 -3
- data/test/fixtures/projects/action_controller +0 -2
- data/test/fixtures/projects/active_record +0 -2
- data/test/fixtures/reply.rb +0 -21
- data/test/fixtures/subscriber.rb +0 -5
- data/test/fixtures/subscribers/first +0 -2
- data/test/fixtures/subscribers/second +0 -2
- data/test/fixtures/topic.rb +0 -20
- data/test/fixtures/topics/first +0 -9
- data/test/fixtures/topics/second +0 -8
- data/test/fixtures_test.rb +0 -20
- data/test/inflector_test.rb +0 -104
- data/test/inheritance_test.rb +0 -125
- data/test/lifecycle_test.rb +0 -110
- data/test/modules_test.rb +0 -21
- data/test/multiple_db_test.rb +0 -46
- data/test/pk_test.rb +0 -57
- data/test/reflection_test.rb +0 -78
- data/test/thread_safety_test.rb +0 -33
- data/test/transactions_test.rb +0 -83
- data/test/unconnected_test.rb +0 -24
- data/test/validations_test.rb +0 -126
@@ -1,126 +1,403 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
# = Active Record Reflection
|
2
3
|
module Reflection # :nodoc:
|
3
|
-
|
4
|
-
super
|
5
|
-
base.extend(ClassMethods)
|
4
|
+
extend ActiveSupport::Concern
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
6
|
+
# Reflection enables to interrogate Active Record classes and objects
|
7
|
+
# about their associations and aggregations. This information can,
|
8
|
+
# for example, be used in a form builder that takes an Active Record object
|
9
|
+
# and creates input fields for all of the attributes depending on their type
|
10
|
+
# and displays the associations to other objects.
|
11
|
+
#
|
12
|
+
# MacroReflection class has info for AggregateReflection and AssociationReflection
|
13
|
+
# classes.
|
14
|
+
module ClassMethods
|
15
|
+
def create_reflection(macro, name, options, active_record)
|
16
|
+
case macro
|
17
|
+
when :has_many, :belongs_to, :has_one, :has_and_belongs_to_many
|
18
|
+
klass = options[:through] ? ThroughReflection : AssociationReflection
|
19
|
+
reflection = klass.new(macro, name, options, active_record)
|
20
|
+
when :composed_of
|
21
|
+
reflection = AggregateReflection.new(macro, name, options, active_record)
|
17
22
|
end
|
23
|
+
write_inheritable_hash :reflections, name => reflection
|
24
|
+
reflection
|
18
25
|
end
|
19
|
-
|
20
|
-
for association_type in %w( belongs_to has_one has_many has_and_belongs_to_many )
|
21
|
-
base.module_eval <<-"end_eval"
|
22
|
-
class << self
|
23
|
-
alias_method :#{association_type}_without_reflection, :#{association_type}
|
24
|
-
|
25
|
-
def #{association_type}_with_reflection(association_id, options = {})
|
26
|
-
#{association_type}_without_reflection(association_id, options)
|
27
|
-
write_inheritable_array "associations", [ AssociationReflection.new(association_id, options, self) ]
|
28
|
-
end
|
29
26
|
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
# Returns a hash containing all AssociationReflection objects for the current class.
|
28
|
+
# Example:
|
29
|
+
#
|
30
|
+
# Invoice.reflections
|
31
|
+
# Account.reflections
|
32
|
+
#
|
33
|
+
def reflections
|
34
|
+
read_inheritable_attribute(:reflections) || write_inheritable_attribute(:reflections, {})
|
33
35
|
end
|
34
|
-
end
|
35
36
|
|
36
|
-
# Reflection allows you to interrogate Active Record classes and objects about their associations and aggregations.
|
37
|
-
# This information can for example be used in a form builder that took an Active Record object and created input
|
38
|
-
# fields for all of the attributes depending on their type and displayed the associations to other objects.
|
39
|
-
#
|
40
|
-
# You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
|
41
|
-
module ClassMethods
|
42
37
|
# Returns an array of AggregateReflection objects for all the aggregations in the class.
|
43
38
|
def reflect_on_all_aggregations
|
44
|
-
|
39
|
+
reflections.values.select { |reflection| reflection.is_a?(AggregateReflection) }
|
45
40
|
end
|
46
|
-
|
47
|
-
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
|
48
|
-
#
|
41
|
+
|
42
|
+
# Returns the AggregateReflection object for the named +aggregation+ (use the symbol).
|
43
|
+
#
|
44
|
+
# Account.reflect_on_aggregation(:balance) # => the balance AggregateReflection
|
45
|
+
#
|
49
46
|
def reflect_on_aggregation(aggregation)
|
50
|
-
|
47
|
+
reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
|
51
48
|
end
|
52
49
|
|
53
|
-
# Returns an array of AssociationReflection objects for all the
|
54
|
-
|
55
|
-
|
50
|
+
# Returns an array of AssociationReflection objects for all the
|
51
|
+
# associations in the class. If you only want to reflect on a certain
|
52
|
+
# association type, pass in the symbol (<tt>:has_many</tt>, <tt>:has_one</tt>,
|
53
|
+
# <tt>:belongs_to</tt>) as the first parameter.
|
54
|
+
#
|
55
|
+
# Example:
|
56
|
+
#
|
57
|
+
# Account.reflect_on_all_associations # returns an array of all associations
|
58
|
+
# Account.reflect_on_all_associations(:has_many) # returns an array of all has_many associations
|
59
|
+
#
|
60
|
+
def reflect_on_all_associations(macro = nil)
|
61
|
+
association_reflections = reflections.values.select { |reflection| reflection.is_a?(AssociationReflection) }
|
62
|
+
macro ? association_reflections.select { |reflection| reflection.macro == macro } : association_reflections
|
56
63
|
end
|
57
|
-
|
58
|
-
# Returns the AssociationReflection object for the
|
59
|
-
#
|
64
|
+
|
65
|
+
# Returns the AssociationReflection object for the +association+ (use the symbol).
|
66
|
+
#
|
67
|
+
# Account.reflect_on_association(:owner) # returns the owner AssociationReflection
|
68
|
+
# Invoice.reflect_on_association(:line_items).macro # returns :has_many
|
69
|
+
#
|
60
70
|
def reflect_on_association(association)
|
61
|
-
|
71
|
+
reflections[association].is_a?(AssociationReflection) ? reflections[association] : nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Returns an array of AssociationReflection objects for all associations which have <tt>:autosave</tt> enabled.
|
75
|
+
def reflect_on_all_autosave_associations
|
76
|
+
reflections.values.select { |reflection| reflection.options[:autosave] }
|
62
77
|
end
|
63
78
|
end
|
64
|
-
|
65
79
|
|
66
|
-
|
67
|
-
#
|
80
|
+
|
81
|
+
# Abstract base class for AggregateReflection and AssociationReflection. Objects of
|
82
|
+
# AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
|
68
83
|
class MacroReflection
|
69
84
|
attr_reader :active_record
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
# Returns the name of the macro, so it would return :balance for "composed_of :balance, :class_name => 'Money'" or
|
75
|
-
# :clients for "has_many :clients".
|
76
|
-
def name
|
77
|
-
@name
|
78
|
-
end
|
79
|
-
|
80
|
-
# Returns the hash of options used for the macro, so it would return { :class_name => "Money" } for
|
81
|
-
# "composed_of :balance, :class_name => 'Money'" or {} for "has_many :clients".
|
82
|
-
def options
|
83
|
-
@options
|
84
|
-
end
|
85
|
-
|
86
|
-
# Returns the class for the macro, so "composed_of :balance, :class_name => 'Money'" would return the Money class and
|
87
|
-
# "has_many :clients" would return the Client class.
|
88
|
-
def klass() end
|
89
|
-
|
90
|
-
def ==(other_aggregation)
|
91
|
-
name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
|
85
|
+
|
86
|
+
def initialize(macro, name, options, active_record)
|
87
|
+
@macro, @name, @options, @active_record = macro, name, options, active_record
|
92
88
|
end
|
93
|
-
end
|
94
89
|
|
90
|
+
# Returns the name of the macro.
|
91
|
+
#
|
92
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:balance</tt>
|
93
|
+
# <tt>has_many :clients</tt> returns <tt>:clients</tt>
|
94
|
+
attr_reader :name
|
95
95
|
|
96
|
-
|
97
|
-
|
96
|
+
# Returns the macro type.
|
97
|
+
#
|
98
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>:composed_of</tt>
|
99
|
+
# <tt>has_many :clients</tt> returns <tt>:has_many</tt>
|
100
|
+
attr_reader :macro
|
101
|
+
|
102
|
+
# Returns the hash of options used for the macro.
|
103
|
+
#
|
104
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>{ :class_name => "Money" }</tt>
|
105
|
+
# <tt>has_many :clients</tt> returns +{}+
|
106
|
+
attr_reader :options
|
107
|
+
|
108
|
+
# Returns the class for the macro.
|
109
|
+
#
|
110
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns the Money class
|
111
|
+
# <tt>has_many :clients</tt> returns the Client class
|
98
112
|
def klass
|
99
|
-
|
113
|
+
@klass ||= class_name.constantize
|
114
|
+
end
|
115
|
+
|
116
|
+
# Returns the class name for the macro.
|
117
|
+
#
|
118
|
+
# <tt>composed_of :balance, :class_name => 'Money'</tt> returns <tt>'Money'</tt>
|
119
|
+
# <tt>has_many :clients</tt> returns <tt>'Client'</tt>
|
120
|
+
def class_name
|
121
|
+
@class_name ||= options[:class_name] || derive_class_name
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns +true+ if +self+ and +other_aggregation+ have the same +name+ attribute, +active_record+ attribute,
|
125
|
+
# and +other_aggregation+ has an options hash assigned to it.
|
126
|
+
def ==(other_aggregation)
|
127
|
+
other_aggregation.kind_of?(self.class) && name == other_aggregation.name && other_aggregation.options && active_record == other_aggregation.active_record
|
100
128
|
end
|
101
|
-
|
129
|
+
|
130
|
+
def sanitized_conditions #:nodoc:
|
131
|
+
@sanitized_conditions ||= klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
132
|
+
end
|
133
|
+
|
102
134
|
private
|
103
|
-
def
|
104
|
-
name.
|
135
|
+
def derive_class_name
|
136
|
+
name.to_s.camelize
|
105
137
|
end
|
106
138
|
end
|
107
139
|
|
108
|
-
|
140
|
+
|
141
|
+
# Holds all the meta-data about an aggregation as it was specified in the
|
142
|
+
# Active Record class.
|
143
|
+
class AggregateReflection < MacroReflection #:nodoc:
|
144
|
+
end
|
145
|
+
|
146
|
+
# Holds all the meta-data about an association as it was specified in the
|
147
|
+
# Active Record class.
|
109
148
|
class AssociationReflection < MacroReflection #:nodoc:
|
149
|
+
# Returns the target association's class.
|
150
|
+
#
|
151
|
+
# class Author < ActiveRecord::Base
|
152
|
+
# has_many :books
|
153
|
+
# end
|
154
|
+
#
|
155
|
+
# Author.reflect_on_association(:books).klass
|
156
|
+
# # => Book
|
157
|
+
#
|
158
|
+
# <b>Note:</b> Do not call +klass.new+ or +klass.create+ to instantiate
|
159
|
+
# a new association object. Use +build_association+ or +create_association+
|
160
|
+
# instead. This allows plugins to hook into association object creation.
|
110
161
|
def klass
|
111
|
-
active_record.send(:compute_type,
|
162
|
+
@klass ||= active_record.send(:compute_type, class_name)
|
163
|
+
end
|
164
|
+
|
165
|
+
def initialize(macro, name, options, active_record)
|
166
|
+
super
|
167
|
+
@collection = [:has_many, :has_and_belongs_to_many].include?(macro)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Returns a new, unsaved instance of the associated class. +options+ will
|
171
|
+
# be passed to the class's constructor.
|
172
|
+
def build_association(*options)
|
173
|
+
klass.new(*options)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Creates a new instance of the associated class, and immediately saves it
|
177
|
+
# with ActiveRecord::Base#save. +options+ will be passed to the class's
|
178
|
+
# creation method. Returns the newly created object.
|
179
|
+
def create_association(*options)
|
180
|
+
klass.create(*options)
|
181
|
+
end
|
182
|
+
|
183
|
+
# Creates a new instance of the associated class, and immediately saves it
|
184
|
+
# with ActiveRecord::Base#save!. +options+ will be passed to the class's
|
185
|
+
# creation method. If the created record doesn't pass validations, then an
|
186
|
+
# exception will be raised.
|
187
|
+
#
|
188
|
+
# Returns the newly created object.
|
189
|
+
def create_association!(*options)
|
190
|
+
klass.create!(*options)
|
191
|
+
end
|
192
|
+
|
193
|
+
def table_name
|
194
|
+
@table_name ||= klass.table_name
|
195
|
+
end
|
196
|
+
|
197
|
+
def quoted_table_name
|
198
|
+
@quoted_table_name ||= klass.quoted_table_name
|
199
|
+
end
|
200
|
+
|
201
|
+
def primary_key_name
|
202
|
+
@primary_key_name ||= options[:foreign_key] || derive_primary_key_name
|
203
|
+
end
|
204
|
+
|
205
|
+
def primary_key_column
|
206
|
+
@primary_key_column ||= klass.columns.find { |c| c.name == klass.primary_key }
|
207
|
+
end
|
208
|
+
|
209
|
+
def association_foreign_key
|
210
|
+
@association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
|
211
|
+
end
|
212
|
+
|
213
|
+
def counter_cache_column
|
214
|
+
if options[:counter_cache] == true
|
215
|
+
"#{active_record.name.demodulize.underscore.pluralize}_count"
|
216
|
+
elsif options[:counter_cache]
|
217
|
+
options[:counter_cache]
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
def columns(tbl_name, log_msg)
|
222
|
+
@columns ||= klass.connection.columns(tbl_name, log_msg)
|
223
|
+
end
|
224
|
+
|
225
|
+
def reset_column_information
|
226
|
+
@columns = nil
|
227
|
+
end
|
228
|
+
|
229
|
+
def check_validity!
|
230
|
+
check_validity_of_inverse!
|
231
|
+
end
|
232
|
+
|
233
|
+
def check_validity_of_inverse!
|
234
|
+
unless options[:polymorphic]
|
235
|
+
if has_inverse? && inverse_of.nil?
|
236
|
+
raise InverseOfAssociationNotFoundError.new(self)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def through_reflection
|
242
|
+
false
|
243
|
+
end
|
244
|
+
|
245
|
+
def through_reflection_primary_key_name
|
246
|
+
end
|
247
|
+
|
248
|
+
def source_reflection
|
249
|
+
nil
|
250
|
+
end
|
251
|
+
|
252
|
+
def has_inverse?
|
253
|
+
!@options[:inverse_of].nil?
|
254
|
+
end
|
255
|
+
|
256
|
+
def inverse_of
|
257
|
+
if has_inverse?
|
258
|
+
@inverse_of ||= klass.reflect_on_association(options[:inverse_of])
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def polymorphic_inverse_of(associated_class)
|
263
|
+
if has_inverse?
|
264
|
+
if inverse_relationship = associated_class.reflect_on_association(options[:inverse_of])
|
265
|
+
inverse_relationship
|
266
|
+
else
|
267
|
+
raise InverseOfAssociationNotFoundError.new(self, associated_class)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
# Returns whether or not this association reflection is for a collection
|
273
|
+
# association. Returns +true+ if the +macro+ is either +has_many+ or
|
274
|
+
# +has_and_belongs_to_many+, +false+ otherwise.
|
275
|
+
def collection?
|
276
|
+
@collection
|
277
|
+
end
|
278
|
+
|
279
|
+
# Returns whether or not the association should be validated as part of
|
280
|
+
# the parent's validation.
|
281
|
+
#
|
282
|
+
# Unless you explicitly disable validation with
|
283
|
+
# <tt>:validate => false</tt>, validation will take place when:
|
284
|
+
#
|
285
|
+
# * you explicitly enable validation; <tt>:validate => true</tt>
|
286
|
+
# * you use autosave; <tt>:autosave => true</tt>
|
287
|
+
# * the association is a +has_many+ association
|
288
|
+
def validate?
|
289
|
+
!options[:validate].nil? ? options[:validate] : (options[:autosave] == true || macro == :has_many)
|
290
|
+
end
|
291
|
+
|
292
|
+
def dependent_conditions(record, base_class, extra_conditions)
|
293
|
+
dependent_conditions = []
|
294
|
+
dependent_conditions << "#{primary_key_name} = #{record.send(name).send(:owner_quoted_id)}"
|
295
|
+
dependent_conditions << "#{options[:as]}_type = '#{base_class.name}'" if options[:as]
|
296
|
+
dependent_conditions << klass.send(:sanitize_sql, options[:conditions]) if options[:conditions]
|
297
|
+
dependent_conditions << extra_conditions if extra_conditions
|
298
|
+
dependent_conditions = dependent_conditions.collect {|where| "(#{where})" }.join(" AND ")
|
299
|
+
dependent_conditions = dependent_conditions.gsub('@', '\@')
|
300
|
+
dependent_conditions
|
301
|
+
end
|
302
|
+
|
303
|
+
# Returns +true+ if +self+ is a +belongs_to+ reflection.
|
304
|
+
def belongs_to?
|
305
|
+
macro == :belongs_to
|
112
306
|
end
|
113
307
|
|
114
308
|
private
|
115
|
-
def
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
309
|
+
def derive_class_name
|
310
|
+
class_name = name.to_s.camelize
|
311
|
+
class_name = class_name.singularize if collection?
|
312
|
+
class_name
|
313
|
+
end
|
314
|
+
|
315
|
+
def derive_primary_key_name
|
316
|
+
if belongs_to?
|
317
|
+
"#{name}_id"
|
318
|
+
elsif options[:as]
|
319
|
+
"#{options[:as]}_id"
|
320
|
+
else
|
321
|
+
active_record.name.foreign_key
|
121
322
|
end
|
122
|
-
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
# Holds all the meta-data about a :through association as it was specified
|
327
|
+
# in the Active Record class.
|
328
|
+
class ThroughReflection < AssociationReflection #:nodoc:
|
329
|
+
# Gets the source of the through reflection. It checks both a singularized
|
330
|
+
# and pluralized form for <tt>:belongs_to</tt> or <tt>:has_many</tt>.
|
331
|
+
#
|
332
|
+
# class Post < ActiveRecord::Base
|
333
|
+
# has_many :taggings
|
334
|
+
# has_many :tags, :through => :taggings
|
335
|
+
# end
|
336
|
+
#
|
337
|
+
def source_reflection
|
338
|
+
@source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
|
339
|
+
end
|
340
|
+
|
341
|
+
# Returns the AssociationReflection object specified in the <tt>:through</tt> option
|
342
|
+
# of a HasManyThrough or HasOneThrough association.
|
343
|
+
#
|
344
|
+
# class Post < ActiveRecord::Base
|
345
|
+
# has_many :taggings
|
346
|
+
# has_many :tags, :through => :taggings
|
347
|
+
# end
|
348
|
+
#
|
349
|
+
# tags_reflection = Post.reflect_on_association(:tags)
|
350
|
+
# taggings_reflection = tags_reflection.through_reflection
|
351
|
+
#
|
352
|
+
def through_reflection
|
353
|
+
@through_reflection ||= active_record.reflect_on_association(options[:through])
|
354
|
+
end
|
355
|
+
|
356
|
+
# Gets an array of possible <tt>:through</tt> source reflection names:
|
357
|
+
#
|
358
|
+
# [:singularized, :pluralized]
|
359
|
+
#
|
360
|
+
def source_reflection_names
|
361
|
+
@source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
|
362
|
+
end
|
363
|
+
|
364
|
+
def check_validity!
|
365
|
+
if through_reflection.nil?
|
366
|
+
raise HasManyThroughAssociationNotFoundError.new(active_record.name, self)
|
367
|
+
end
|
368
|
+
|
369
|
+
if source_reflection.nil?
|
370
|
+
raise HasManyThroughSourceAssociationNotFoundError.new(self)
|
371
|
+
end
|
372
|
+
|
373
|
+
if options[:source_type] && source_reflection.options[:polymorphic].nil?
|
374
|
+
raise HasManyThroughAssociationPointlessSourceTypeError.new(active_record.name, self, source_reflection)
|
375
|
+
end
|
376
|
+
|
377
|
+
if source_reflection.options[:polymorphic] && options[:source_type].nil?
|
378
|
+
raise HasManyThroughAssociationPolymorphicError.new(active_record.name, self, source_reflection)
|
379
|
+
end
|
380
|
+
|
381
|
+
unless [:belongs_to, :has_many, :has_one].include?(source_reflection.macro) && source_reflection.options[:through].nil?
|
382
|
+
raise HasManyThroughSourceAssociationMacroError.new(self)
|
383
|
+
end
|
384
|
+
|
385
|
+
check_validity_of_inverse!
|
386
|
+
end
|
387
|
+
|
388
|
+
def through_reflection_primary_key
|
389
|
+
through_reflection.belongs_to? ? through_reflection.klass.primary_key : through_reflection.primary_key_name
|
390
|
+
end
|
391
|
+
|
392
|
+
def through_reflection_primary_key_name
|
393
|
+
through_reflection.primary_key_name if through_reflection.belongs_to?
|
394
|
+
end
|
395
|
+
|
396
|
+
private
|
397
|
+
def derive_class_name
|
398
|
+
# get the class_name of the belongs_to association of the through reflection
|
399
|
+
options[:source_type] || source_reflection.class_name
|
123
400
|
end
|
124
401
|
end
|
125
402
|
end
|
126
|
-
end
|
403
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Batches # :nodoc:
|
5
|
+
# Yields each record that was found by the find +options+. The find is
|
6
|
+
# performed by find_in_batches with a batch size of 1000 (or as
|
7
|
+
# specified by the <tt>:batch_size</tt> option).
|
8
|
+
#
|
9
|
+
# Example:
|
10
|
+
#
|
11
|
+
# Person.where("age > 21").find_each do |person|
|
12
|
+
# person.party_all_night!
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# Note: This method is only intended to use for batch processing of
|
16
|
+
# large amounts of records that wouldn't fit in memory all at once. If
|
17
|
+
# you just need to loop over less than 1000 records, it's probably
|
18
|
+
# better just to use the regular find methods.
|
19
|
+
def find_each(options = {})
|
20
|
+
find_in_batches(options) do |records|
|
21
|
+
records.each { |record| yield record }
|
22
|
+
end
|
23
|
+
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# Yields each batch of records that was found by the find +options+ as
|
28
|
+
# an array. The size of each batch is set by the <tt>:batch_size</tt>
|
29
|
+
# option; the default is 1000.
|
30
|
+
#
|
31
|
+
# You can control the starting point for the batch processing by
|
32
|
+
# supplying the <tt>:start</tt> option. This is especially useful if you
|
33
|
+
# want multiple workers dealing with the same processing queue. You can
|
34
|
+
# make worker 1 handle all the records between id 0 and 10,000 and
|
35
|
+
# worker 2 handle from 10,000 and beyond (by setting the <tt>:start</tt>
|
36
|
+
# option on that worker).
|
37
|
+
#
|
38
|
+
# It's not possible to set the order. That is automatically set to
|
39
|
+
# ascending on the primary key ("id ASC") to make the batch ordering
|
40
|
+
# work. This also mean that this method only works with integer-based
|
41
|
+
# primary keys. You can't set the limit either, that's used to control
|
42
|
+
# the the batch sizes.
|
43
|
+
#
|
44
|
+
# Example:
|
45
|
+
#
|
46
|
+
# Person.where("age > 21").find_in_batches do |group|
|
47
|
+
# sleep(50) # Make sure it doesn't get too crowded in there!
|
48
|
+
# group.each { |person| person.party_all_night! }
|
49
|
+
# end
|
50
|
+
def find_in_batches(options = {})
|
51
|
+
relation = self
|
52
|
+
|
53
|
+
if orders.present? || taken.present?
|
54
|
+
ActiveRecord::Base.logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
|
55
|
+
end
|
56
|
+
|
57
|
+
if (finder_options = options.except(:start, :batch_size)).present?
|
58
|
+
raise "You can't specify an order, it's forced to be #{batch_order}" if options[:order].present?
|
59
|
+
raise "You can't specify a limit, it's forced to be the batch_size" if options[:limit].present?
|
60
|
+
|
61
|
+
relation = apply_finder_options(finder_options)
|
62
|
+
end
|
63
|
+
|
64
|
+
start = options.delete(:start).to_i
|
65
|
+
batch_size = options.delete(:batch_size) || 1000
|
66
|
+
|
67
|
+
relation = relation.except(:order).order(batch_order).limit(batch_size)
|
68
|
+
records = relation.where(primary_key.gteq(start)).all
|
69
|
+
|
70
|
+
while records.any?
|
71
|
+
yield records
|
72
|
+
|
73
|
+
break if records.size < batch_size
|
74
|
+
|
75
|
+
if primary_key_offset = records.last.id
|
76
|
+
records = relation.where(primary_key.gt(primary_key_offset)).to_a
|
77
|
+
else
|
78
|
+
raise "Primary key not included in the custom select clause"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def batch_order
|
86
|
+
"#{@klass.table_name}.#{@klass.primary_key} ASC"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|