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.

Files changed (178) hide show
  1. data/CHANGELOG +5518 -76
  2. data/README.rdoc +222 -0
  3. data/examples/performance.rb +162 -0
  4. data/examples/simple.rb +14 -0
  5. data/lib/active_record/aggregations.rb +192 -80
  6. data/lib/active_record/association_preload.rb +403 -0
  7. data/lib/active_record/associations/association_collection.rb +545 -53
  8. data/lib/active_record/associations/association_proxy.rb +295 -0
  9. data/lib/active_record/associations/belongs_to_association.rb +91 -0
  10. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +78 -0
  11. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +127 -36
  12. data/lib/active_record/associations/has_many_association.rb +108 -84
  13. data/lib/active_record/associations/has_many_through_association.rb +116 -0
  14. data/lib/active_record/associations/has_one_association.rb +143 -0
  15. data/lib/active_record/associations/has_one_through_association.rb +40 -0
  16. data/lib/active_record/associations/through_association_scope.rb +154 -0
  17. data/lib/active_record/associations.rb +2086 -368
  18. data/lib/active_record/attribute_methods/before_type_cast.rb +33 -0
  19. data/lib/active_record/attribute_methods/dirty.rb +95 -0
  20. data/lib/active_record/attribute_methods/primary_key.rb +50 -0
  21. data/lib/active_record/attribute_methods/query.rb +39 -0
  22. data/lib/active_record/attribute_methods/read.rb +116 -0
  23. data/lib/active_record/attribute_methods/time_zone_conversion.rb +61 -0
  24. data/lib/active_record/attribute_methods/write.rb +37 -0
  25. data/lib/active_record/attribute_methods.rb +60 -0
  26. data/lib/active_record/autosave_association.rb +369 -0
  27. data/lib/active_record/base.rb +1603 -721
  28. data/lib/active_record/callbacks.rb +176 -225
  29. data/lib/active_record/connection_adapters/abstract/connection_pool.rb +365 -0
  30. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +113 -0
  31. data/lib/active_record/connection_adapters/abstract/database_limits.rb +57 -0
  32. data/lib/active_record/connection_adapters/abstract/database_statements.rb +329 -0
  33. data/lib/active_record/connection_adapters/abstract/query_cache.rb +81 -0
  34. data/lib/active_record/connection_adapters/abstract/quoting.rb +72 -0
  35. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +739 -0
  36. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +543 -0
  37. data/lib/active_record/connection_adapters/abstract_adapter.rb +165 -279
  38. data/lib/active_record/connection_adapters/mysql_adapter.rb +594 -82
  39. data/lib/active_record/connection_adapters/postgresql_adapter.rb +988 -135
  40. data/lib/active_record/connection_adapters/sqlite3_adapter.rb +53 -0
  41. data/lib/active_record/connection_adapters/sqlite_adapter.rb +365 -71
  42. data/lib/active_record/counter_cache.rb +115 -0
  43. data/lib/active_record/dynamic_finder_match.rb +53 -0
  44. data/lib/active_record/dynamic_scope_match.rb +32 -0
  45. data/lib/active_record/errors.rb +172 -0
  46. data/lib/active_record/fixtures.rb +941 -105
  47. data/lib/active_record/locale/en.yml +40 -0
  48. data/lib/active_record/locking/optimistic.rb +172 -0
  49. data/lib/active_record/locking/pessimistic.rb +55 -0
  50. data/lib/active_record/log_subscriber.rb +48 -0
  51. data/lib/active_record/migration.rb +617 -0
  52. data/lib/active_record/named_scope.rb +138 -0
  53. data/lib/active_record/nested_attributes.rb +417 -0
  54. data/lib/active_record/observer.rb +105 -36
  55. data/lib/active_record/persistence.rb +291 -0
  56. data/lib/active_record/query_cache.rb +36 -0
  57. data/lib/active_record/railtie.rb +91 -0
  58. data/lib/active_record/railties/controller_runtime.rb +38 -0
  59. data/lib/active_record/railties/databases.rake +512 -0
  60. data/lib/active_record/reflection.rb +364 -87
  61. data/lib/active_record/relation/batches.rb +89 -0
  62. data/lib/active_record/relation/calculations.rb +286 -0
  63. data/lib/active_record/relation/finder_methods.rb +355 -0
  64. data/lib/active_record/relation/predicate_builder.rb +41 -0
  65. data/lib/active_record/relation/query_methods.rb +261 -0
  66. data/lib/active_record/relation/spawn_methods.rb +112 -0
  67. data/lib/active_record/relation.rb +393 -0
  68. data/lib/active_record/schema.rb +59 -0
  69. data/lib/active_record/schema_dumper.rb +195 -0
  70. data/lib/active_record/serialization.rb +60 -0
  71. data/lib/active_record/serializers/xml_serializer.rb +244 -0
  72. data/lib/active_record/session_store.rb +340 -0
  73. data/lib/active_record/test_case.rb +67 -0
  74. data/lib/active_record/timestamp.rb +88 -0
  75. data/lib/active_record/transactions.rb +329 -75
  76. data/lib/active_record/validations/associated.rb +48 -0
  77. data/lib/active_record/validations/uniqueness.rb +185 -0
  78. data/lib/active_record/validations.rb +58 -179
  79. data/lib/active_record/version.rb +9 -0
  80. data/lib/active_record.rb +100 -24
  81. data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
  82. data/lib/rails/generators/active_record/migration/templates/migration.rb +17 -0
  83. data/lib/rails/generators/active_record/model/model_generator.rb +38 -0
  84. data/lib/rails/generators/active_record/model/templates/migration.rb +16 -0
  85. data/lib/rails/generators/active_record/model/templates/model.rb +5 -0
  86. data/lib/rails/generators/active_record/model/templates/module.rb +5 -0
  87. data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
  88. data/lib/rails/generators/active_record/observer/templates/observer.rb +2 -0
  89. data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +24 -0
  90. data/lib/rails/generators/active_record/session_migration/templates/migration.rb +16 -0
  91. data/lib/rails/generators/active_record.rb +27 -0
  92. metadata +216 -158
  93. data/README +0 -361
  94. data/RUNNING_UNIT_TESTS +0 -36
  95. data/dev-utils/eval_debugger.rb +0 -9
  96. data/examples/associations.rb +0 -87
  97. data/examples/shared_setup.rb +0 -15
  98. data/examples/validation.rb +0 -88
  99. data/install.rb +0 -60
  100. data/lib/active_record/deprecated_associations.rb +0 -70
  101. data/lib/active_record/support/class_attribute_accessors.rb +0 -43
  102. data/lib/active_record/support/class_inheritable_attributes.rb +0 -37
  103. data/lib/active_record/support/clean_logger.rb +0 -10
  104. data/lib/active_record/support/inflector.rb +0 -70
  105. data/lib/active_record/vendor/mysql.rb +0 -1117
  106. data/lib/active_record/vendor/simple.rb +0 -702
  107. data/lib/active_record/wrappers/yaml_wrapper.rb +0 -15
  108. data/lib/active_record/wrappings.rb +0 -59
  109. data/rakefile +0 -122
  110. data/test/abstract_unit.rb +0 -16
  111. data/test/aggregations_test.rb +0 -34
  112. data/test/all.sh +0 -8
  113. data/test/associations_test.rb +0 -477
  114. data/test/base_test.rb +0 -513
  115. data/test/class_inheritable_attributes_test.rb +0 -33
  116. data/test/connections/native_mysql/connection.rb +0 -24
  117. data/test/connections/native_postgresql/connection.rb +0 -24
  118. data/test/connections/native_sqlite/connection.rb +0 -24
  119. data/test/deprecated_associations_test.rb +0 -336
  120. data/test/finder_test.rb +0 -67
  121. data/test/fixtures/accounts/signals37 +0 -3
  122. data/test/fixtures/accounts/unknown +0 -2
  123. data/test/fixtures/auto_id.rb +0 -4
  124. data/test/fixtures/column_name.rb +0 -3
  125. data/test/fixtures/companies/first_client +0 -6
  126. data/test/fixtures/companies/first_firm +0 -4
  127. data/test/fixtures/companies/second_client +0 -6
  128. data/test/fixtures/company.rb +0 -37
  129. data/test/fixtures/company_in_module.rb +0 -33
  130. data/test/fixtures/course.rb +0 -3
  131. data/test/fixtures/courses/java +0 -2
  132. data/test/fixtures/courses/ruby +0 -2
  133. data/test/fixtures/customer.rb +0 -30
  134. data/test/fixtures/customers/david +0 -6
  135. data/test/fixtures/db_definitions/mysql.sql +0 -96
  136. data/test/fixtures/db_definitions/mysql2.sql +0 -4
  137. data/test/fixtures/db_definitions/postgresql.sql +0 -113
  138. data/test/fixtures/db_definitions/postgresql2.sql +0 -4
  139. data/test/fixtures/db_definitions/sqlite.sql +0 -85
  140. data/test/fixtures/db_definitions/sqlite2.sql +0 -4
  141. data/test/fixtures/default.rb +0 -2
  142. data/test/fixtures/developer.rb +0 -8
  143. data/test/fixtures/developers/david +0 -2
  144. data/test/fixtures/developers/jamis +0 -2
  145. data/test/fixtures/developers_projects/david_action_controller +0 -2
  146. data/test/fixtures/developers_projects/david_active_record +0 -2
  147. data/test/fixtures/developers_projects/jamis_active_record +0 -2
  148. data/test/fixtures/entrant.rb +0 -3
  149. data/test/fixtures/entrants/first +0 -3
  150. data/test/fixtures/entrants/second +0 -3
  151. data/test/fixtures/entrants/third +0 -3
  152. data/test/fixtures/fixture_database.sqlite +0 -0
  153. data/test/fixtures/fixture_database_2.sqlite +0 -0
  154. data/test/fixtures/movie.rb +0 -5
  155. data/test/fixtures/movies/first +0 -2
  156. data/test/fixtures/movies/second +0 -2
  157. data/test/fixtures/project.rb +0 -3
  158. data/test/fixtures/projects/action_controller +0 -2
  159. data/test/fixtures/projects/active_record +0 -2
  160. data/test/fixtures/reply.rb +0 -21
  161. data/test/fixtures/subscriber.rb +0 -5
  162. data/test/fixtures/subscribers/first +0 -2
  163. data/test/fixtures/subscribers/second +0 -2
  164. data/test/fixtures/topic.rb +0 -20
  165. data/test/fixtures/topics/first +0 -9
  166. data/test/fixtures/topics/second +0 -8
  167. data/test/fixtures_test.rb +0 -20
  168. data/test/inflector_test.rb +0 -104
  169. data/test/inheritance_test.rb +0 -125
  170. data/test/lifecycle_test.rb +0 -110
  171. data/test/modules_test.rb +0 -21
  172. data/test/multiple_db_test.rb +0 -46
  173. data/test/pk_test.rb +0 -57
  174. data/test/reflection_test.rb +0 -78
  175. data/test/thread_safety_test.rb +0 -33
  176. data/test/transactions_test.rb +0 -83
  177. data/test/unconnected_test.rb +0 -24
  178. 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
- def self.append_features(base)
4
- super
5
- base.extend(ClassMethods)
4
+ extend ActiveSupport::Concern
6
5
 
7
- base.class_eval do
8
- class << self
9
- alias_method :composed_of_without_reflection, :composed_of
10
-
11
- def composed_of_with_reflection(part_id, options = {})
12
- composed_of_without_reflection(part_id, options)
13
- write_inheritable_array "aggregations", [ AggregateReflection.new(part_id, options, self) ]
14
- end
15
-
16
- alias_method :composed_of, :composed_of_with_reflection
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
- alias_method :#{association_type}, :#{association_type}_with_reflection
31
- end
32
- end_eval
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
- read_inheritable_attribute "aggregations"
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). Example:
48
- # Account.reflect_on_aggregation(:balance) # returns the balance AggregateReflection
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
- reflect_on_all_aggregations.find { |reflection| reflection.name == aggregation } unless reflect_on_all_aggregations.nil?
47
+ reflections[aggregation].is_a?(AggregateReflection) ? reflections[aggregation] : nil
51
48
  end
52
49
 
53
- # Returns an array of AssociationReflection objects for all the aggregations in the class.
54
- def reflect_on_all_associations
55
- read_inheritable_attribute "associations"
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 named +aggregation+ (use the symbol). Example:
59
- # Account.reflect_on_association(:owner) # returns the owner AssociationReflection
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
- reflect_on_all_associations.find { |reflection| reflection.name == association } unless reflect_on_all_associations.nil?
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
- # Abstract base class for AggregateReflection and AssociationReflection that describes the interface available for both of
67
- # those classes. Objects of AggregateReflection and AssociationReflection are returned by the Reflection::ClassMethods.
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
- def initialize(name, options, active_record)
71
- @name, @options, @active_record = name, options, active_record
72
- end
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
- # Holds all the meta-data about an aggregation as it was specified in the Active Record class.
97
- class AggregateReflection < MacroReflection #:nodoc:
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
- Object.const_get(options[:class_name] || name_to_class_name(name.id2name))
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 name_to_class_name(name)
104
- name.capitalize.gsub(/_(.)/) { |s| $1.capitalize }
135
+ def derive_class_name
136
+ name.to_s.camelize
105
137
  end
106
138
  end
107
139
 
108
- # Holds all the meta-data about an association as it was specified in the Active Record class.
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, (name_to_class_name(name.id2name)))
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 name_to_class_name(name)
116
- if name !~ /::/
117
- class_name = active_record.send(
118
- :type_name_with_module,
119
- (options[:class_name] || active_record.class_name(active_record.table_name_prefix + name + active_record.table_name_suffix))
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
- return class_name || name
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