activerecord 1.13.2 → 1.14.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 (144) hide show
  1. data/CHANGELOG +452 -10
  2. data/RUNNING_UNIT_TESTS +1 -1
  3. data/lib/active_record.rb +5 -2
  4. data/lib/active_record/acts/list.rb +1 -1
  5. data/lib/active_record/acts/tree.rb +29 -25
  6. data/lib/active_record/aggregations.rb +3 -2
  7. data/lib/active_record/associations.rb +783 -337
  8. data/lib/active_record/associations/association_collection.rb +7 -12
  9. data/lib/active_record/associations/association_proxy.rb +62 -24
  10. data/lib/active_record/associations/belongs_to_association.rb +27 -46
  11. data/lib/active_record/associations/belongs_to_polymorphic_association.rb +50 -0
  12. data/lib/active_record/associations/has_and_belongs_to_many_association.rb +38 -38
  13. data/lib/active_record/associations/has_many_association.rb +61 -56
  14. data/lib/active_record/associations/has_many_through_association.rb +144 -0
  15. data/lib/active_record/associations/has_one_association.rb +22 -16
  16. data/lib/active_record/base.rb +482 -182
  17. data/lib/active_record/calculations.rb +225 -0
  18. data/lib/active_record/callbacks.rb +7 -7
  19. data/lib/active_record/connection_adapters/abstract/connection_specification.rb +162 -47
  20. data/lib/active_record/connection_adapters/abstract/quoting.rb +1 -1
  21. data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +2 -1
  22. data/lib/active_record/connection_adapters/abstract/schema_statements.rb +21 -1
  23. data/lib/active_record/connection_adapters/abstract_adapter.rb +34 -2
  24. data/lib/active_record/connection_adapters/db2_adapter.rb +107 -61
  25. data/lib/active_record/connection_adapters/mysql_adapter.rb +29 -6
  26. data/lib/active_record/connection_adapters/openbase_adapter.rb +349 -0
  27. data/lib/active_record/connection_adapters/{oci_adapter.rb → oracle_adapter.rb} +125 -59
  28. data/lib/active_record/connection_adapters/postgresql_adapter.rb +24 -21
  29. data/lib/active_record/connection_adapters/sqlite_adapter.rb +47 -8
  30. data/lib/active_record/connection_adapters/sqlserver_adapter.rb +36 -16
  31. data/lib/active_record/connection_adapters/sybase_adapter.rb +684 -0
  32. data/lib/active_record/fixtures.rb +42 -17
  33. data/lib/active_record/locking.rb +36 -15
  34. data/lib/active_record/migration.rb +111 -8
  35. data/lib/active_record/observer.rb +25 -1
  36. data/lib/active_record/reflection.rb +103 -41
  37. data/lib/active_record/schema.rb +2 -2
  38. data/lib/active_record/schema_dumper.rb +55 -18
  39. data/lib/active_record/timestamp.rb +6 -6
  40. data/lib/active_record/validations.rb +65 -40
  41. data/lib/active_record/vendor/db2.rb +10 -5
  42. data/lib/active_record/vendor/simple.rb +693 -702
  43. data/lib/active_record/version.rb +2 -2
  44. data/rakefile +4 -4
  45. data/test/aaa_create_tables_test.rb +25 -6
  46. data/test/abstract_unit.rb +39 -1
  47. data/test/adapter_test.rb +31 -4
  48. data/test/associations_cascaded_eager_loading_test.rb +106 -0
  49. data/test/associations_go_eager_test.rb +85 -16
  50. data/test/associations_join_model_test.rb +338 -0
  51. data/test/associations_test.rb +129 -50
  52. data/test/base_test.rb +204 -49
  53. data/test/binary_test.rb +1 -1
  54. data/test/calculations_test.rb +169 -0
  55. data/test/callbacks_test.rb +5 -23
  56. data/test/class_inheritable_attributes_test.rb +1 -1
  57. data/test/column_alias_test.rb +1 -1
  58. data/test/connections/native_mysql/connection.rb +1 -0
  59. data/test/connections/native_openbase/connection.rb +22 -0
  60. data/test/connections/{native_oci → native_oracle}/connection.rb +7 -9
  61. data/test/connections/native_sqlite/connection.rb +1 -1
  62. data/test/connections/native_sqlite3/connection.rb +1 -0
  63. data/test/connections/native_sqlite3/in_memory_connection.rb +1 -0
  64. data/test/connections/native_sybase/connection.rb +24 -0
  65. data/test/defaults_test.rb +18 -0
  66. data/test/deprecated_associations_test.rb +2 -2
  67. data/test/deprecated_finder_test.rb +0 -6
  68. data/test/finder_test.rb +26 -23
  69. data/test/fixtures/accounts.yml +10 -0
  70. data/test/fixtures/author.rb +31 -6
  71. data/test/fixtures/author_favorites.yml +4 -0
  72. data/test/fixtures/categories/special_categories.yml +9 -0
  73. data/test/fixtures/categories/subsubdir/arbitrary_filename.yml +4 -0
  74. data/test/fixtures/categories_posts.yml +4 -0
  75. data/test/fixtures/categorization.rb +5 -0
  76. data/test/fixtures/categorizations.yml +11 -0
  77. data/test/fixtures/category.rb +6 -0
  78. data/test/fixtures/company.rb +17 -5
  79. data/test/fixtures/company_in_module.rb +19 -5
  80. data/test/fixtures/db_definitions/db2.drop.sql +3 -0
  81. data/test/fixtures/db_definitions/db2.sql +121 -100
  82. data/test/fixtures/db_definitions/db22.sql +2 -2
  83. data/test/fixtures/db_definitions/firebird.drop.sql +4 -0
  84. data/test/fixtures/db_definitions/firebird.sql +26 -0
  85. data/test/fixtures/db_definitions/mysql.drop.sql +3 -0
  86. data/test/fixtures/db_definitions/mysql.sql +21 -1
  87. data/test/fixtures/db_definitions/openbase.drop.sql +2 -0
  88. data/test/fixtures/db_definitions/openbase.sql +282 -0
  89. data/test/fixtures/db_definitions/openbase2.drop.sql +2 -0
  90. data/test/fixtures/db_definitions/openbase2.sql +7 -0
  91. data/test/fixtures/db_definitions/{oci.drop.sql → oracle.drop.sql} +6 -0
  92. data/test/fixtures/db_definitions/{oci.sql → oracle.sql} +25 -4
  93. data/test/fixtures/db_definitions/{oci2.drop.sql → oracle2.drop.sql} +0 -0
  94. data/test/fixtures/db_definitions/{oci2.sql → oracle2.sql} +0 -0
  95. data/test/fixtures/db_definitions/postgresql.drop.sql +4 -0
  96. data/test/fixtures/db_definitions/postgresql.sql +22 -1
  97. data/test/fixtures/db_definitions/schema.rb +32 -0
  98. data/test/fixtures/db_definitions/sqlite.drop.sql +3 -0
  99. data/test/fixtures/db_definitions/sqlite.sql +18 -0
  100. data/test/fixtures/db_definitions/sqlserver.drop.sql +3 -0
  101. data/test/fixtures/db_definitions/sqlserver.sql +23 -3
  102. data/test/fixtures/db_definitions/sybase.drop.sql +31 -0
  103. data/test/fixtures/db_definitions/sybase.sql +204 -0
  104. data/test/fixtures/db_definitions/sybase2.drop.sql +4 -0
  105. data/test/fixtures/db_definitions/sybase2.sql +5 -0
  106. data/test/fixtures/developers.yml +6 -1
  107. data/test/fixtures/developers_projects.yml +4 -0
  108. data/test/fixtures/funny_jokes.yml +14 -0
  109. data/test/fixtures/joke.rb +6 -0
  110. data/test/fixtures/legacy_thing.rb +3 -0
  111. data/test/fixtures/legacy_things.yml +3 -0
  112. data/test/fixtures/mixin.rb +1 -1
  113. data/test/fixtures/person.rb +4 -1
  114. data/test/fixtures/post.rb +26 -1
  115. data/test/fixtures/project.rb +1 -0
  116. data/test/fixtures/reader.rb +4 -0
  117. data/test/fixtures/readers.yml +4 -0
  118. data/test/fixtures/reply.rb +2 -1
  119. data/test/fixtures/tag.rb +5 -0
  120. data/test/fixtures/tagging.rb +6 -0
  121. data/test/fixtures/taggings.yml +18 -0
  122. data/test/fixtures/tags.yml +7 -0
  123. data/test/fixtures/tasks.yml +2 -2
  124. data/test/fixtures/topic.rb +2 -2
  125. data/test/fixtures/topics.yml +1 -0
  126. data/test/fixtures_test.rb +47 -13
  127. data/test/inheritance_test.rb +2 -2
  128. data/test/locking_test.rb +15 -1
  129. data/test/method_scoping_test.rb +248 -13
  130. data/test/migration_test.rb +68 -11
  131. data/test/mixin_nested_set_test.rb +1 -1
  132. data/test/modules_test.rb +6 -1
  133. data/test/readonly_test.rb +1 -1
  134. data/test/reflection_test.rb +63 -9
  135. data/test/schema_dumper_test.rb +41 -0
  136. data/test/{synonym_test_oci.rb → synonym_test_oracle.rb} +1 -1
  137. data/test/threaded_connections_test.rb +10 -0
  138. data/test/unconnected_test.rb +12 -5
  139. data/test/validations_test.rb +197 -10
  140. metadata +295 -260
  141. data/test/fixtures/db_definitions/create_oracle_db.bat +0 -0
  142. data/test/fixtures/db_definitions/create_oracle_db.sh +0 -0
  143. data/test/fixtures/fixture_database.sqlite +0 -0
  144. data/test/fixtures/fixture_database_2.sqlite +0 -0
@@ -0,0 +1,225 @@
1
+ module ActiveRecord
2
+ module Calculations #:nodoc:
3
+ CALCULATIONS_OPTIONS = [:conditions, :joins, :order, :select, :group, :having, :distinct]
4
+ def self.included(base)
5
+ base.extend(ClassMethods)
6
+ end
7
+
8
+ module ClassMethods
9
+ # Count operates using three different approaches.
10
+ #
11
+ # * Count all: By not passing any parameters to count, it will return a count of all the rows for the model.
12
+ # * Count by conditions or joins: For backwards compatibility, you can pass in +conditions+ and +joins+ as individual parameters.
13
+ # * Count using options will find the row count matched by the options used.
14
+ #
15
+ # The last approach, count using options, accepts an option hash as the only parameter. The options are:
16
+ #
17
+ # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
18
+ # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
19
+ # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
20
+ # * <tt>:include</tt>: Named associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
21
+ # to already defined associations. When using named associations count returns the number DISTINCT items for the model you're counting.
22
+ # See eager loading under Associations.
23
+ # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
24
+ # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
25
+ # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
26
+ # include the joined columns.
27
+ # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
28
+ #
29
+ # Examples for counting all:
30
+ # Person.count # returns the total count of all people
31
+ #
32
+ # Examples for count by +conditions+ and +joins+ (for backwards compatibility):
33
+ # Person.count("age > 26") # returns the number of people older than 26
34
+ # Person.find("age > 26 AND job.salary > 60000", "LEFT JOIN jobs on jobs.person_id = person.id") # returns the total number of rows matching the conditions and joins fetched by SELECT COUNT(*).
35
+ #
36
+ # Examples for count with options:
37
+ # Person.count(:conditions => "age > 26")
38
+ # Person.count(:conditions => "age > 26 AND job.salary > 60000", :include => :job) # because of the named association, it finds the DISTINCT count using LEFT OUTER JOIN.
39
+ # Person.count(:conditions => "age > 26 AND job.salary > 60000", :joins => "LEFT JOIN jobs on jobs.person_id = person.id") # finds the number of rows matching the conditions and joins.
40
+ # Person.count('id', :conditions => "age > 26") # Performs a COUNT(id)
41
+ # Person.count(:all, :conditions => "age > 26") # Performs a COUNT(*) (:all is an alias for '*')
42
+ #
43
+ # Note: Person.count(:all) will not work because it will use :all as the condition. Use Person.count instead.
44
+ def count(*args)
45
+ options = {}
46
+
47
+ #For backwards compatibility, we need to handle both count(conditions=nil, joins=nil) or count(options={}).
48
+ if args.size >= 0 and args.size <= 2
49
+ if args.first.is_a?(Hash)
50
+ options = args.first
51
+ #should we verify the options hash???
52
+ elsif args[1].is_a?(Hash)
53
+ column_name = args.first
54
+ options = args[1]
55
+ else
56
+ # Handle legacy paramter options: def count(conditions=nil, joins=nil)
57
+ options.merge!(:conditions => args[0]) if args.length > 0
58
+ options.merge!(:joins => args[1]) if args.length > 1
59
+ end
60
+ else
61
+ raise(ArgumentError, "Unexpected parameters passed to count(*args): expected either count(conditions=nil, joins=nil) or count(options={})")
62
+ end
63
+
64
+ (scope(:find, :include) || options[:include]) ? count_with_associations(options) : calculate(:count, :all, options)
65
+ end
66
+
67
+ # Calculates average value on a given column. The value is returned as a float. See #calculate for examples with options.
68
+ #
69
+ # Person.average('age')
70
+ def average(column_name, options = {})
71
+ calculate(:avg, column_name, options)
72
+ end
73
+
74
+ # Calculates the minimum value on a given column. The value is returned with the same data type of the column.. See #calculate for examples with options.
75
+ #
76
+ # Person.minimum('age')
77
+ def minimum(column_name, options = {})
78
+ calculate(:min, column_name, options)
79
+ end
80
+
81
+ # Calculates the maximum value on a given column. The value is returned with the same data type of the column.. See #calculate for examples with options.
82
+ #
83
+ # Person.maximum('age')
84
+ def maximum(column_name, options = {})
85
+ calculate(:max, column_name, options)
86
+ end
87
+
88
+ # Calculates the sum value on a given column. The value is returned with the same data type of the column.. See #calculate for examples with options.
89
+ #
90
+ # Person.sum('age')
91
+ def sum(column_name, options = {})
92
+ calculate(:sum, column_name, options)
93
+ end
94
+
95
+ # This calculates aggregate values in the given column: Methods for count, sum, average, minimum, and maximum have been added as shortcuts.
96
+ # Options such as :conditions, :order, :group, :having, and :joins can be passed to customize the query.
97
+ #
98
+ # There are two basic forms of output:
99
+ # * Single aggregate value: The single value is type cast to Fixnum for COUNT, Float for AVG, and the given column's type for everything else.
100
+ # * Grouped values: This returns an ordered hash of the values and groups them by the :group option. It takes either a column name, or the name
101
+ # of a belongs_to association.
102
+ #
103
+ # values = Person.maximum(:age, :group => 'last_name')
104
+ # puts values["Drake"]
105
+ # => 43
106
+ #
107
+ # drake = Family.find_by_last_name('Drake')
108
+ # values = Person.maximum(:age, :group => :family) # Person belongs_to :family
109
+ # puts values[drake]
110
+ # => 43
111
+ #
112
+ # values.each do |family, max_age|
113
+ # ...
114
+ # end
115
+ #
116
+ # Options:
117
+ # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
118
+ # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id". (Rarely needed).
119
+ # The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
120
+ # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name" (really only used with GROUP BY calculations).
121
+ # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
122
+ # * <tt>:select</tt>: By default, this is * as in SELECT * FROM, but can be changed if you for example want to do a join, but not
123
+ # include the joined columns.
124
+ # * <tt>:distinct</tt>: Set this to true to make this a distinct calculation, such as SELECT COUNT(DISTINCT posts.id) ...
125
+ #
126
+ # Examples:
127
+ # Person.calculate(:count, :all) # The same as Person.count
128
+ # Person.average(:age) # SELECT AVG(age) FROM people...
129
+ # Person.minimum(:age, :conditions => ['last_name != ?', 'Drake']) # Selects the minimum age for everyone with a last name other than 'Drake'
130
+ # Person.minimum(:age, :having => 'min(age) > 17', :group => :last_name) # Selects the minimum age for any family without any minors
131
+ def calculate(operation, column_name, options = {})
132
+ validate_calculation_options(operation, options)
133
+ column_name = options[:select] if options[:select]
134
+ column_name = '*' if column_name == :all
135
+ column = column_for column_name
136
+ aggregate = select_aggregate(operation, column_name, options)
137
+ aggregate_alias = column_alias_for(operation, column_name)
138
+ if options[:group]
139
+ execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias, options)
140
+ else
141
+ execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias, options)
142
+ end
143
+ end
144
+
145
+ protected
146
+ def construct_calculation_sql(aggregate, aggregate_alias, options) #:nodoc:
147
+ scope = scope(:find)
148
+ sql = ["SELECT #{aggregate} AS #{aggregate_alias}"]
149
+ sql << ", #{options[:group_field]} AS #{options[:group_alias]}" if options[:group]
150
+ sql << " FROM #{table_name} "
151
+ add_joins!(sql, options, scope)
152
+ add_conditions!(sql, options[:conditions], scope)
153
+ sql << " GROUP BY #{options[:group_field]}" if options[:group]
154
+ sql << " HAVING #{options[:having]}" if options[:group] && options[:having]
155
+ sql << " ORDER BY #{options[:order]}" if options[:order]
156
+ sql.join
157
+ end
158
+
159
+ def execute_simple_calculation(operation, column_name, column, aggregate, aggregate_alias, options) #:nodoc:
160
+ value = connection.select_value(construct_calculation_sql(aggregate, aggregate_alias, options))
161
+ type_cast_calculated_value(value, column, operation)
162
+ end
163
+
164
+ def execute_grouped_calculation(operation, column_name, column, aggregate, aggregate_alias, options) #:nodoc:
165
+ group_attr = options[:group].to_s
166
+ association = reflect_on_association(group_attr.to_sym)
167
+ associated = association && association.macro == :belongs_to # only count belongs_to associations
168
+ group_field = (associated ? "#{options[:group]}_id" : options[:group]).to_s
169
+ group_alias = column_alias_for(group_field)
170
+ group_column = column_for group_field
171
+ sql = construct_calculation_sql(aggregate, aggregate_alias, options.merge(:group_field => group_field, :group_alias => group_alias))
172
+ calculated_data = connection.select_all(sql)
173
+
174
+ if association
175
+ key_ids = calculated_data.collect { |row| row[group_alias] }
176
+ key_records = association.klass.base_class.find(key_ids)
177
+ key_records = key_records.inject({}) { |hsh, r| hsh.merge(r.id => r) }
178
+ end
179
+
180
+ calculated_data.inject(OrderedHash.new) do |all, row|
181
+ key = associated ? key_records[row[group_alias].to_i] : type_cast_calculated_value(row[group_alias], group_column)
182
+ value = row[aggregate_alias]
183
+ all << [key, type_cast_calculated_value(value, column, operation)]
184
+ end
185
+ end
186
+
187
+ private
188
+ def validate_calculation_options(operation, options = {})
189
+ if operation.to_s == 'count'
190
+ options.assert_valid_keys(CALCULATIONS_OPTIONS + [:include])
191
+ else
192
+ options.assert_valid_keys(CALCULATIONS_OPTIONS)
193
+ end
194
+ end
195
+
196
+ def select_aggregate(operation, column_name, options)
197
+ "#{operation}(#{'DISTINCT ' if options[:distinct]}#{column_name})"
198
+ end
199
+
200
+ # converts a given key to the value that the database adapter returns as
201
+ #
202
+ # users.id #=> users_id
203
+ # sum(id) #=> sum_id
204
+ # count(distinct users.id) #=> count_distinct_users_id
205
+ # count(*) #=> count_all
206
+ def column_alias_for(*keys)
207
+ keys.join(' ').downcase.gsub(/\*/, 'all').gsub(/\W+/, ' ').strip.gsub(/ +/, '_')
208
+ end
209
+
210
+ def column_for(field)
211
+ field_name = field.to_s.split('.').last
212
+ columns.detect { |c| c.name.to_s == field_name }
213
+ end
214
+
215
+ def type_cast_calculated_value(value, column, operation = nil)
216
+ operation = operation.to_s.downcase
217
+ case operation
218
+ when 'count' then value.to_i
219
+ when 'avg' then value.to_f
220
+ else column ? column.type_cast(value) : value
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
@@ -13,15 +13,15 @@ module ActiveRecord
13
13
  # * (2) before_validation_on_create
14
14
  # * (-) validate
15
15
  # * (-) validate_on_create
16
- # * (4) after_validation
17
- # * (5) after_validation_on_create
18
- # * (6) before_save
19
- # * (7) before_create
16
+ # * (3) after_validation
17
+ # * (4) after_validation_on_create
18
+ # * (5) before_save
19
+ # * (6) before_create
20
20
  # * (-) create
21
- # * (8) after_create
22
- # * (9) after_save
21
+ # * (7) after_create
22
+ # * (8) after_save
23
23
  #
24
- # That's a total of nine callbacks, which gives you immense power to react and prepare for each state in the
24
+ # That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the
25
25
  # Active Record lifecycle.
26
26
  #
27
27
  # Examples:
@@ -1,3 +1,5 @@
1
+ require 'set'
2
+
1
3
  module ActiveRecord
2
4
  class Base
3
5
  class ConnectionSpecification #:nodoc:
@@ -7,22 +9,139 @@ module ActiveRecord
7
9
  end
8
10
  end
9
11
 
12
+ # Check for activity after at least +verification_timeout+ seconds.
13
+ # Defaults to 0 (always check.)
14
+ cattr_accessor :verification_timeout
15
+ @@verification_timeout = 0
16
+
10
17
  # The class -> [adapter_method, config] map
11
18
  @@defined_connections = {}
12
19
 
13
- # The class -> thread id -> adapter cache.
14
- @@connection_cache = Hash.new { |h, k| h[k] = Hash.new }
20
+ # The class -> thread id -> adapter cache. (class -> adapter if not allow_concurrency)
21
+ @@active_connections = {}
15
22
 
16
- # Returns the connection currently associated with the class. This can
17
- # also be used to "borrow" the connection to do database work unrelated
18
- # to any of the specific Active Records.
19
- def self.connection
20
- @@connection_cache[Thread.current.object_id][name] ||= retrieve_connection
21
- end
23
+ class << self
24
+ # Retrieve the connection cache.
25
+ def thread_safe_active_connections #:nodoc:
26
+ @@active_connections[Thread.current.object_id] ||= {}
27
+ end
28
+
29
+ def single_threaded_active_connections #:nodoc:
30
+ @@active_connections
31
+ end
32
+
33
+ # pick up the right active_connection method from @@allow_concurrency
34
+ if @@allow_concurrency
35
+ alias_method :active_connections, :thread_safe_active_connections
36
+ else
37
+ alias_method :active_connections, :single_threaded_active_connections
38
+ end
39
+
40
+ # set concurrency support flag (not thread safe, like most of the methods in this file)
41
+ def allow_concurrency=(threaded) #:nodoc:
42
+ logger.debug "allow_concurrency=#{threaded}" if logger
43
+ return if @@allow_concurrency == threaded
44
+ clear_all_cached_connections!
45
+ @@allow_concurrency = threaded
46
+ method_prefix = threaded ? "thread_safe" : "single_threaded"
47
+ sing = (class << self; self; end)
48
+ [:active_connections, :scoped_methods].each do |method|
49
+ sing.send(:alias_method, method, "#{method_prefix}_#{method}")
50
+ end
51
+ log_connections if logger
52
+ end
53
+
54
+ def active_connection_name #:nodoc:
55
+ @active_connection_name ||=
56
+ if active_connections[name] || @@defined_connections[name]
57
+ name
58
+ elsif self == ActiveRecord::Base
59
+ nil
60
+ else
61
+ superclass.active_connection_name
62
+ end
63
+ end
64
+
65
+ def clear_active_connection_name #:nodoc:
66
+ @active_connection_name = nil
67
+ subclasses.each { |klass| klass.clear_active_connection_name }
68
+ end
69
+
70
+ # Returns the connection currently associated with the class. This can
71
+ # also be used to "borrow" the connection to do database work unrelated
72
+ # to any of the specific Active Records.
73
+ def connection
74
+ if @active_connection_name && (conn = active_connections[@active_connection_name])
75
+ conn
76
+ else
77
+ # retrieve_connection sets the cache key.
78
+ conn = retrieve_connection
79
+ active_connections[@active_connection_name] = conn
80
+ end
81
+ end
82
+
83
+ # Clears the cache which maps classes to connections.
84
+ def clear_active_connections!
85
+ clear_cache!(@@active_connections) do |name, conn|
86
+ conn.disconnect!
87
+ end
88
+ end
89
+
90
+ # Verify active connections.
91
+ def verify_active_connections! #:nodoc:
92
+ if @@allow_concurrency
93
+ remove_stale_cached_threads!(@@active_connections) do |name, conn|
94
+ conn.disconnect!
95
+ end
96
+ end
97
+
98
+ active_connections.each_value do |connection|
99
+ connection.verify!(@@verification_timeout)
100
+ end
101
+ end
22
102
 
23
- # Clears the cache which maps classes to connections.
24
- def self.clear_connection_cache!
25
- @@connection_cache.clear
103
+ private
104
+ def clear_cache!(cache, thread_id = nil, &block)
105
+ if cache
106
+ if @@allow_concurrency
107
+ thread_id ||= Thread.current.object_id
108
+ thread_cache, cache = cache, cache[thread_id]
109
+ return unless cache
110
+ end
111
+
112
+ cache.each(&block) if block_given?
113
+ cache.clear
114
+ end
115
+ ensure
116
+ if thread_cache && @@allow_concurrency
117
+ thread_cache.delete(thread_id)
118
+ end
119
+ end
120
+
121
+ # Remove stale threads from the cache.
122
+ def remove_stale_cached_threads!(cache, &block)
123
+ stale = Set.new(cache.keys)
124
+
125
+ Thread.list.each do |thread|
126
+ stale.delete(thread.object_id) if thread.alive?
127
+ end
128
+
129
+ stale.each do |thread_id|
130
+ clear_cache!(cache, thread_id, &block)
131
+ end
132
+ end
133
+
134
+ def clear_all_cached_connections!
135
+ if @@allow_concurrency
136
+ @@active_connections.each_value do |connection_hash_for_thread|
137
+ connection_hash_for_thread.each_value {|conn| conn.disconnect! }
138
+ connection_hash_for_thread.clear
139
+ end
140
+ else
141
+ @@active_connections.each_value {|conn| conn.disconnect! }
142
+ end
143
+ @@active_connections.clear
144
+ end
26
145
  end
27
146
 
28
147
  # Returns the connection currently associated with the class. This can
@@ -65,6 +184,8 @@ module ActiveRecord
65
184
  raise AdapterNotSpecified unless defined? RAILS_ENV
66
185
  establish_connection(RAILS_ENV)
67
186
  when ConnectionSpecification
187
+ clear_active_connection_name
188
+ @active_connection_name = name
68
189
  @@defined_connections[name] = spec
69
190
  when Symbol, String
70
191
  if configuration = configurations[spec.to_s]
@@ -82,46 +203,31 @@ module ActiveRecord
82
203
  end
83
204
  end
84
205
 
85
- def self.active_connections #:nodoc:
86
- if allow_concurrency
87
- Thread.current['active_connections'] ||= {}
88
- else
89
- @@active_connections ||= {}
90
- end
91
- end
92
-
93
206
  # Locate the connection of the nearest super class. This can be an
94
207
  # active or defined connections: if it is the latter, it will be
95
208
  # opened and set as the active connection for the class it was defined
96
209
  # for (not necessarily the current class).
97
210
  def self.retrieve_connection #:nodoc:
98
- klass = self
99
- ar_super = ActiveRecord::Base.superclass
100
- until klass == ar_super
101
- if conn = active_connections[klass.name]
102
- # Reconnect if the connection is inactive.
103
- conn.reconnect! unless conn.active?
104
- return conn
105
- elsif conn = @@defined_connections[klass.name]
106
- klass.connection = conn
107
- return self.connection
211
+ # Name is nil if establish_connection hasn't been called for
212
+ # some class along the inheritance chain up to AR::Base yet.
213
+ if name = active_connection_name
214
+ if conn = active_connections[name]
215
+ # Verify the connection.
216
+ conn.verify!(@@verification_timeout)
217
+ elsif spec = @@defined_connections[name]
218
+ # Activate this connection specification.
219
+ klass = name.constantize
220
+ klass.connection = spec
221
+ conn = active_connections[name]
108
222
  end
109
- klass = klass.superclass
110
223
  end
111
- raise ConnectionNotEstablished
224
+
225
+ conn or raise ConnectionNotEstablished
112
226
  end
113
227
 
114
228
  # Returns true if a connection that's accessible to this class have already been opened.
115
229
  def self.connected?
116
- klass = self
117
- until klass == ActiveRecord::Base.superclass
118
- if active_connections[klass.name]
119
- return true
120
- else
121
- klass = klass.superclass
122
- end
123
- end
124
- return false
230
+ active_connections[active_connection_name] ? true : false
125
231
  end
126
232
 
127
233
  # Remove the connection for this class. This will close the active
@@ -129,16 +235,16 @@ module ActiveRecord
129
235
  # can be used as argument for establish_connection, for easy
130
236
  # re-establishing of the connection.
131
237
  def self.remove_connection(klass=self)
132
- conn = @@defined_connections[klass.name]
133
- @@defined_connections.delete(klass.name)
134
- @@connection_cache[Thread.current.object_id].delete(klass.name)
135
- active_connections.delete(klass.name)
136
- @connection = nil
137
- conn.config if conn
238
+ spec = @@defined_connections[klass.name]
239
+ konn = active_connections[klass.name]
240
+ @@defined_connections.delete_if { |key, value| value == spec }
241
+ active_connections.delete_if { |key, value| value == konn }
242
+ konn.disconnect! if konn
243
+ spec.config if spec
138
244
  end
139
245
 
140
246
  # Set the connection for the class.
141
- def self.connection=(spec)
247
+ def self.connection=(spec) #:nodoc:
142
248
  if spec.kind_of?(ActiveRecord::ConnectionAdapters::AbstractAdapter)
143
249
  active_connections[name] = spec
144
250
  elsif spec.kind_of?(ConnectionSpecification)
@@ -149,5 +255,14 @@ module ActiveRecord
149
255
  establish_connection spec
150
256
  end
151
257
  end
258
+
259
+ # connection state logging
260
+ def self.log_connections #:nodoc:
261
+ if logger
262
+ logger.info "Defined connections: #{@@defined_connections.inspect}"
263
+ logger.info "Active connections: #{active_connections.inspect}"
264
+ logger.info "Active connection name: #{@active_connection_name}"
265
+ end
266
+ end
152
267
  end
153
268
  end