activerecord 1.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 +581 -0
- data/README +361 -0
- data/RUNNING_UNIT_TESTS +36 -0
- data/dev-utils/eval_debugger.rb +9 -0
- data/examples/associations.png +0 -0
- data/examples/associations.rb +87 -0
- data/examples/shared_setup.rb +15 -0
- data/examples/validation.rb +88 -0
- data/install.rb +60 -0
- data/lib/active_record.rb +48 -0
- data/lib/active_record/aggregations.rb +165 -0
- data/lib/active_record/associations.rb +536 -0
- data/lib/active_record/associations/association_collection.rb +70 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +46 -0
- data/lib/active_record/associations/has_many_association.rb +104 -0
- data/lib/active_record/base.rb +985 -0
- data/lib/active_record/callbacks.rb +337 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +326 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +131 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +177 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +107 -0
- data/lib/active_record/deprecated_associations.rb +70 -0
- data/lib/active_record/fixtures.rb +172 -0
- data/lib/active_record/observer.rb +71 -0
- data/lib/active_record/reflection.rb +126 -0
- data/lib/active_record/support/class_attribute_accessors.rb +43 -0
- data/lib/active_record/support/class_inheritable_attributes.rb +37 -0
- data/lib/active_record/support/clean_logger.rb +10 -0
- data/lib/active_record/support/inflector.rb +70 -0
- data/lib/active_record/transactions.rb +102 -0
- data/lib/active_record/validations.rb +205 -0
- data/lib/active_record/vendor/mysql.rb +1117 -0
- data/lib/active_record/vendor/simple.rb +702 -0
- data/lib/active_record/wrappers/yaml_wrapper.rb +15 -0
- data/lib/active_record/wrappings.rb +59 -0
- data/rakefile +122 -0
- data/test/abstract_unit.rb +16 -0
- data/test/aggregations_test.rb +34 -0
- data/test/all.sh +8 -0
- data/test/associations_test.rb +477 -0
- data/test/base_test.rb +513 -0
- data/test/class_inheritable_attributes_test.rb +33 -0
- data/test/connections/native_mysql/connection.rb +24 -0
- data/test/connections/native_postgresql/connection.rb +24 -0
- data/test/connections/native_sqlite/connection.rb +24 -0
- data/test/deprecated_associations_test.rb +336 -0
- data/test/finder_test.rb +67 -0
- data/test/fixtures/accounts/signals37 +3 -0
- data/test/fixtures/accounts/unknown +2 -0
- data/test/fixtures/auto_id.rb +4 -0
- data/test/fixtures/column_name.rb +3 -0
- data/test/fixtures/companies/first_client +6 -0
- data/test/fixtures/companies/first_firm +4 -0
- data/test/fixtures/companies/second_client +6 -0
- data/test/fixtures/company.rb +37 -0
- data/test/fixtures/company_in_module.rb +33 -0
- data/test/fixtures/course.rb +3 -0
- data/test/fixtures/courses/java +2 -0
- data/test/fixtures/courses/ruby +2 -0
- data/test/fixtures/customer.rb +30 -0
- data/test/fixtures/customers/david +6 -0
- data/test/fixtures/db_definitions/mysql.sql +96 -0
- data/test/fixtures/db_definitions/mysql2.sql +4 -0
- data/test/fixtures/db_definitions/postgresql.sql +113 -0
- data/test/fixtures/db_definitions/postgresql2.sql +4 -0
- data/test/fixtures/db_definitions/sqlite.sql +85 -0
- data/test/fixtures/db_definitions/sqlite2.sql +4 -0
- data/test/fixtures/default.rb +2 -0
- data/test/fixtures/developer.rb +8 -0
- data/test/fixtures/developers/david +2 -0
- data/test/fixtures/developers/jamis +2 -0
- data/test/fixtures/developers_projects/david_action_controller +2 -0
- data/test/fixtures/developers_projects/david_active_record +2 -0
- data/test/fixtures/developers_projects/jamis_active_record +2 -0
- data/test/fixtures/entrant.rb +3 -0
- data/test/fixtures/entrants/first +3 -0
- data/test/fixtures/entrants/second +3 -0
- data/test/fixtures/entrants/third +3 -0
- data/test/fixtures/fixture_database.sqlite +0 -0
- data/test/fixtures/fixture_database_2.sqlite +0 -0
- data/test/fixtures/movie.rb +5 -0
- data/test/fixtures/movies/first +2 -0
- data/test/fixtures/movies/second +2 -0
- data/test/fixtures/project.rb +3 -0
- data/test/fixtures/projects/action_controller +2 -0
- data/test/fixtures/projects/active_record +2 -0
- data/test/fixtures/reply.rb +21 -0
- data/test/fixtures/subscriber.rb +5 -0
- data/test/fixtures/subscribers/first +2 -0
- data/test/fixtures/subscribers/second +2 -0
- data/test/fixtures/topic.rb +20 -0
- data/test/fixtures/topics/first +9 -0
- data/test/fixtures/topics/second +8 -0
- data/test/fixtures_test.rb +20 -0
- data/test/inflector_test.rb +104 -0
- data/test/inheritance_test.rb +125 -0
- data/test/lifecycle_test.rb +110 -0
- data/test/modules_test.rb +21 -0
- data/test/multiple_db_test.rb +46 -0
- data/test/pk_test.rb +57 -0
- data/test/reflection_test.rb +78 -0
- data/test/thread_safety_test.rb +33 -0
- data/test/transactions_test.rb +83 -0
- data/test/unconnected_test.rb +24 -0
- data/test/validations_test.rb +126 -0
- metadata +166 -0
@@ -0,0 +1,337 @@
|
|
1
|
+
require 'observer'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
# Callbacks are hooks into the lifecycle of an Active Record object that allows you to trigger logic
|
5
|
+
# before or after an alteration of the object state. This can be used to make sure that associated and
|
6
|
+
# dependent objects are deleted when destroy is called (by overwriting before_destroy) or to massage attributes
|
7
|
+
# before they're validated (by overwriting before_validation). As an example of the callbacks initiated, consider
|
8
|
+
# the Base#save call:
|
9
|
+
#
|
10
|
+
# * (-) save
|
11
|
+
# * (-) valid?
|
12
|
+
# * (1) before_validation
|
13
|
+
# * (2) before_validation_on_create
|
14
|
+
# * (-) validate
|
15
|
+
# * (-) validate_on_create
|
16
|
+
# * (4) after_validation
|
17
|
+
# * (5) after_validation_on_create
|
18
|
+
# * (6) before_save
|
19
|
+
# * (7) before_create
|
20
|
+
# * (-) create
|
21
|
+
# * (8) after_create
|
22
|
+
# * (9) after_save
|
23
|
+
#
|
24
|
+
# That's a total of nine callbacks, which gives you immense power to react and prepare for each state in the
|
25
|
+
# Active Record lifecyle.
|
26
|
+
#
|
27
|
+
# Examples:
|
28
|
+
# class CreditCard < ActiveRecord::Base
|
29
|
+
# # Strip everything but digits, so the user can specify "555 234 34" or
|
30
|
+
# # "5552-3434" or both will mean "55523434"
|
31
|
+
# def before_validation_on_create
|
32
|
+
# self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
#
|
36
|
+
# class Subscription < ActiveRecord::Base
|
37
|
+
# # Automatically assign the signup date
|
38
|
+
# def before_create
|
39
|
+
# self.signed_up_on = Date.today
|
40
|
+
# end
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# class Firm < ActiveRecord::Base
|
44
|
+
# # Destroys the associated clients and people when the firm is destroyed
|
45
|
+
# def before_destroy
|
46
|
+
# Client.destroy_all "client_of = #{id}"
|
47
|
+
# Person.destroy_all "firm_id = #{id}"
|
48
|
+
# end
|
49
|
+
#
|
50
|
+
# == Inheritable callback queues
|
51
|
+
#
|
52
|
+
# Besides the overwriteable callback methods, it's also possible to register callbacks through the use of the callback macros.
|
53
|
+
# Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
|
54
|
+
# hierarchy. Example:
|
55
|
+
#
|
56
|
+
# class Topic < ActiveRecord::Base
|
57
|
+
# before_destroy :destroy_author
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# class Reply < Topic
|
61
|
+
# before_destroy :destroy_readers
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# Now, when Topic#destroy is run only +destroy_author+ is called. When Reply#destroy is run both +destroy_author+ and
|
65
|
+
# +destroy_readers+ is called. Contrast this to the situation where we've implemented the save behavior through overwriteable
|
66
|
+
# methods:
|
67
|
+
#
|
68
|
+
# class Topic < ActiveRecord::Base
|
69
|
+
# def before_destroy() destroy_author end
|
70
|
+
# end
|
71
|
+
#
|
72
|
+
# class Reply < Topic
|
73
|
+
# def before_destroy() destroy_readers end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. So use the callback macros when
|
77
|
+
# you want to ensure that a certain callback is called for the entire hierarchy and the regular overwriteable methods when you
|
78
|
+
# want to leave it up to each descendent to decide whether they want to call +super+ and trigger the inherited callbacks.
|
79
|
+
#
|
80
|
+
# == Types of callbacks
|
81
|
+
#
|
82
|
+
# There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
|
83
|
+
# inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
|
84
|
+
# recommended approaches, inline methods using a proc is some times appropriate (such as for creating mix-ins), and inline
|
85
|
+
# eval methods are deprecated.
|
86
|
+
#
|
87
|
+
# The method reference callbacks work by specifying a protected or private method available in the object, like this:
|
88
|
+
#
|
89
|
+
# class Topic < ActiveRecord::Base
|
90
|
+
# before_destroy :delete_parents
|
91
|
+
#
|
92
|
+
# private
|
93
|
+
# def delete_parents
|
94
|
+
# self.class.delete_all "parent_id = #{id}"
|
95
|
+
# end
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# The callback objects have methods named after the callback called with the record as the only parameter, such as:
|
99
|
+
#
|
100
|
+
# class BankAccount < ActiveRecord::Base
|
101
|
+
# before_save EncryptionWrapper.new("credit_card_number")
|
102
|
+
# after_save EncryptionWrapper.new("credit_card_number")
|
103
|
+
# after_initialize EncryptionWrapper.new("credit_card_number")
|
104
|
+
# end
|
105
|
+
#
|
106
|
+
# class EncryptionWrapper
|
107
|
+
# def initialize(attribute)
|
108
|
+
# @attribute = attribute
|
109
|
+
# end
|
110
|
+
#
|
111
|
+
# def before_save(record)
|
112
|
+
# record.credit_card_number = encrypt(record.credit_card_number)
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
# def after_save(record)
|
116
|
+
# record.credit_card_number = decrypt(record.credit_card_number)
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# alias_method :after_initialize, :after_save
|
120
|
+
#
|
121
|
+
# private
|
122
|
+
# def encrypt(value)
|
123
|
+
# # Secrecy is committed
|
124
|
+
# end
|
125
|
+
#
|
126
|
+
# def decrypt(value)
|
127
|
+
# # Secrecy is unvieled
|
128
|
+
# end
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
|
132
|
+
# a method by the name of the callback messaged.
|
133
|
+
#
|
134
|
+
# The callback macros usually accept a symbol for the method they're supposed to run, but you can also pass a "method string",
|
135
|
+
# which will then be evaluated within the binding of the callback. Example:
|
136
|
+
#
|
137
|
+
# class Topic < ActiveRecord::Base
|
138
|
+
# before_destroy 'self.class.delete_all "parent_id = #{id}"'
|
139
|
+
# end
|
140
|
+
#
|
141
|
+
# Notice that single plings (') are used so the #{id} part isn't evaluated until the callback is triggered. Also note that these
|
142
|
+
# inline callbacks can be stacked just like the regular ones:
|
143
|
+
#
|
144
|
+
# class Topic < ActiveRecord::Base
|
145
|
+
# before_destroy 'self.class.delete_all "parent_id = #{id}"',
|
146
|
+
# 'puts "Evaluated after parents are destroyed"'
|
147
|
+
# end
|
148
|
+
#
|
149
|
+
# == The after_find and after_initialize exceptions
|
150
|
+
#
|
151
|
+
# Because after_find and after_initialize is called for each object instantiated found by a finder, such as Base.find_all, we've had
|
152
|
+
# to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, after_find and
|
153
|
+
# after_initialize can only be declared using an explicit implementation. So using the inheritable callback queue for after_find and
|
154
|
+
# after_initialize won't work.
|
155
|
+
module Callbacks
|
156
|
+
CALLBACKS = %w(
|
157
|
+
after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
|
158
|
+
after_validation before_validation_on_create after_validation_on_create before_validation_on_update
|
159
|
+
after_validation_on_update before_destroy after_destroy
|
160
|
+
)
|
161
|
+
|
162
|
+
def self.append_features(base) #:nodoc:
|
163
|
+
super
|
164
|
+
|
165
|
+
base.extend(ClassMethods)
|
166
|
+
base.class_eval do
|
167
|
+
class << self
|
168
|
+
include Observable
|
169
|
+
alias_method :instantiate_without_callbacks, :instantiate
|
170
|
+
alias_method :instantiate, :instantiate_with_callbacks
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
base.class_eval do
|
175
|
+
alias_method :initialize_without_callbacks, :initialize
|
176
|
+
alias_method :initialize, :initialize_with_callbacks
|
177
|
+
|
178
|
+
alias_method :create_or_update_without_callbacks, :create_or_update
|
179
|
+
alias_method :create_or_update, :create_or_update_with_callbacks
|
180
|
+
|
181
|
+
alias_method :valid_without_callbacks, :valid?
|
182
|
+
alias_method :valid?, :valid_with_callbacks
|
183
|
+
|
184
|
+
alias_method :create_without_callbacks, :create
|
185
|
+
alias_method :create, :create_with_callbacks
|
186
|
+
|
187
|
+
alias_method :update_without_callbacks, :update
|
188
|
+
alias_method :update, :update_with_callbacks
|
189
|
+
|
190
|
+
alias_method :destroy_without_callbacks, :destroy
|
191
|
+
alias_method :destroy, :destroy_with_callbacks
|
192
|
+
end
|
193
|
+
|
194
|
+
CALLBACKS.each { |cb| base.class_eval("def self.#{cb}(*methods) write_inheritable_array(\"#{cb}\", methods) end") }
|
195
|
+
end
|
196
|
+
|
197
|
+
module ClassMethods #:nodoc:
|
198
|
+
def instantiate_with_callbacks(record)
|
199
|
+
object = instantiate_without_callbacks(record)
|
200
|
+
object.callback(:after_find) if object.respond_to_without_attributes?(:after_find)
|
201
|
+
object.callback(:after_initialize) if object.respond_to_without_attributes?(:after_initialize)
|
202
|
+
object
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
# Is called when the object was instantiated by one of the finders, like Base.find.
|
207
|
+
# def after_find() end
|
208
|
+
|
209
|
+
# Is called after the object has been instantiated by a call to Base.new.
|
210
|
+
# def after_initialize() end
|
211
|
+
def initialize_with_callbacks(attributes = nil) #:nodoc:
|
212
|
+
initialize_without_callbacks(attributes)
|
213
|
+
yield self if block_given?
|
214
|
+
after_initialize if respond_to_without_attributes?(:after_initialize)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Is called _before_ Base.save (regardless of whether it's a create or update save).
|
218
|
+
def before_save() end
|
219
|
+
|
220
|
+
# Is called _after_ Base.save (regardless of whether it's a create or update save).
|
221
|
+
def after_save() end
|
222
|
+
def create_or_update_with_callbacks #:nodoc:
|
223
|
+
callback(:before_save)
|
224
|
+
create_or_update_without_callbacks
|
225
|
+
callback(:after_save)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Is called _before_ Base.save on new objects that haven't been saved yet (no record exists).
|
229
|
+
def before_create() end
|
230
|
+
|
231
|
+
# Is called _after_ Base.save on new objects that haven't been saved yet (no record exists).
|
232
|
+
def after_create() end
|
233
|
+
def create_with_callbacks #:nodoc:
|
234
|
+
callback(:before_create)
|
235
|
+
create_without_callbacks
|
236
|
+
callback(:after_create)
|
237
|
+
end
|
238
|
+
|
239
|
+
# Is called _before_ Base.save on existing objects that has a record.
|
240
|
+
def before_update() end
|
241
|
+
|
242
|
+
# Is called _after_ Base.save on existing objects that has a record.
|
243
|
+
def after_update() end
|
244
|
+
|
245
|
+
def update_with_callbacks #:nodoc:
|
246
|
+
callback(:before_update)
|
247
|
+
update_without_callbacks
|
248
|
+
callback(:after_update)
|
249
|
+
end
|
250
|
+
|
251
|
+
# Is called _before_ Validations.validate (which is part of the Base.save call).
|
252
|
+
def before_validation() end
|
253
|
+
|
254
|
+
# Is called _after_ Validations.validate (which is part of the Base.save call).
|
255
|
+
def after_validation() end
|
256
|
+
|
257
|
+
# Is called _before_ Validations.validate (which is part of the Base.save call) on new objects
|
258
|
+
# that haven't been saved yet (no record exists).
|
259
|
+
def before_validation_on_create() end
|
260
|
+
|
261
|
+
# Is called _after_ Validations.validate (which is part of the Base.save call) on new objects
|
262
|
+
# that haven't been saved yet (no record exists).
|
263
|
+
def after_validation_on_create() end
|
264
|
+
|
265
|
+
# Is called _before_ Validations.validate (which is part of the Base.save call) on
|
266
|
+
# existing objects that has a record.
|
267
|
+
def before_validation_on_update() end
|
268
|
+
|
269
|
+
# Is called _after_ Validations.validate (which is part of the Base.save call) on
|
270
|
+
# existing objects that has a record.
|
271
|
+
def after_validation_on_update() end
|
272
|
+
|
273
|
+
def valid_with_callbacks #:nodoc:
|
274
|
+
callback(:before_validation)
|
275
|
+
if new_record? then callback(:before_validation_on_create) else callback(:before_validation_on_update) end
|
276
|
+
|
277
|
+
result = valid_without_callbacks
|
278
|
+
|
279
|
+
callback(:after_validation)
|
280
|
+
if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end
|
281
|
+
|
282
|
+
return result
|
283
|
+
end
|
284
|
+
|
285
|
+
# Is called _before_ Base.destroy.
|
286
|
+
def before_destroy() end
|
287
|
+
|
288
|
+
# Is called _after_ Base.destroy (and all the attributes have been frozen).
|
289
|
+
def after_destroy() end
|
290
|
+
def destroy_with_callbacks #:nodoc:
|
291
|
+
callback(:before_destroy)
|
292
|
+
destroy_without_callbacks
|
293
|
+
callback(:after_destroy)
|
294
|
+
end
|
295
|
+
|
296
|
+
def callback(callback_method) #:nodoc:
|
297
|
+
run_callbacks(callback_method)
|
298
|
+
send(callback_method)
|
299
|
+
notify(callback_method)
|
300
|
+
end
|
301
|
+
|
302
|
+
def run_callbacks(callback_method)
|
303
|
+
filters = self.class.read_inheritable_attribute(callback_method.to_s)
|
304
|
+
if filters.nil? then return end
|
305
|
+
filters.each do |filter|
|
306
|
+
if Symbol === filter
|
307
|
+
self.send(filter)
|
308
|
+
elsif String === filter
|
309
|
+
eval(filter, binding)
|
310
|
+
elsif filter_block?(filter)
|
311
|
+
filter.call(self)
|
312
|
+
elsif filter_class?(filter, callback_method)
|
313
|
+
filter.send(callback_method, self)
|
314
|
+
else
|
315
|
+
raise(
|
316
|
+
ActiveRecordError,
|
317
|
+
"Filters need to be either a symbol, string (to be eval'ed), proc/method, or " +
|
318
|
+
"class implementing a static filter method"
|
319
|
+
)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
def filter_block?(filter)
|
325
|
+
filter.respond_to?("call") && (filter.arity == 1 || filter.arity == -1)
|
326
|
+
end
|
327
|
+
|
328
|
+
def filter_class?(filter, callback_method)
|
329
|
+
filter.respond_to?(callback_method)
|
330
|
+
end
|
331
|
+
|
332
|
+
def notify(callback_method) #:nodoc:
|
333
|
+
self.class.changed
|
334
|
+
self.class.notify_observers(callback_method, self)
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
@@ -0,0 +1,326 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
class Base
|
6
|
+
class ConnectionSpecification #:nodoc:
|
7
|
+
attr_reader :config, :adapter_method
|
8
|
+
def initialize (config, adapter_method)
|
9
|
+
@config, @adapter_method = config, adapter_method
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# The class -> [adapter_method, config] map
|
14
|
+
@@defined_connections = {}
|
15
|
+
|
16
|
+
# Establishes the connection to the database. Accepts a hash as input where
|
17
|
+
# the :adapter key must be specified with the name of a database adapter (in lower-case)
|
18
|
+
# example for regular databases (MySQL, Postgresql, etc):
|
19
|
+
#
|
20
|
+
# ActiveRecord::Base.establish_connection(
|
21
|
+
# :adapter => "mysql",
|
22
|
+
# :host => "localhost",
|
23
|
+
# :username => "myuser",
|
24
|
+
# :password => "mypass",
|
25
|
+
# :database => "somedatabase"
|
26
|
+
# )
|
27
|
+
#
|
28
|
+
# Example for SQLite database:
|
29
|
+
#
|
30
|
+
# ActiveRecord::Base.establish_connection(
|
31
|
+
# :adapter => "sqlite",
|
32
|
+
# :dbfile => "path/to/dbfile"
|
33
|
+
# )
|
34
|
+
#
|
35
|
+
# Also accepts keys as strings (for parsing from yaml for example):
|
36
|
+
# ActiveRecord::Base.establish_connection(
|
37
|
+
# "adapter" => "sqlite",
|
38
|
+
# "dbfile" => "path/to/dbfile"
|
39
|
+
# )
|
40
|
+
#
|
41
|
+
# The exceptions AdapterNotSpecified, AdapterNotFound and ArgumentError
|
42
|
+
# may be returned on an error.
|
43
|
+
#
|
44
|
+
# == Connecting to another database for a single model
|
45
|
+
#
|
46
|
+
# To support different connections for different classes, you can
|
47
|
+
# simply call establish_connection with the classes you wish to have
|
48
|
+
# different connections for:
|
49
|
+
#
|
50
|
+
# class Courses < ActiveRecord::Base
|
51
|
+
# ...
|
52
|
+
# end
|
53
|
+
#
|
54
|
+
# Courses.establish_connection( ... )
|
55
|
+
def self.establish_connection(spec)
|
56
|
+
if spec.instance_of? ConnectionSpecification
|
57
|
+
@@defined_connections[self] = spec
|
58
|
+
else
|
59
|
+
if spec.nil? then raise AdapterNotSpecified end
|
60
|
+
symbolize_strings_in_hash(spec)
|
61
|
+
unless spec.key?(:adapter) then raise AdapterNotSpecified end
|
62
|
+
|
63
|
+
adapter_method = "#{spec[:adapter]}_connection"
|
64
|
+
unless methods.include?(adapter_method) then raise AdapterNotFound end
|
65
|
+
remove_connection
|
66
|
+
@@defined_connections[self] = ConnectionSpecification.new(spec, adapter_method)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# Locate the connection of the nearest super class. This can be an
|
71
|
+
# active or defined connections: if it is the latter, it will be
|
72
|
+
# opened and set as the active connection for the class it was defined
|
73
|
+
# for (not necessarily the current class).
|
74
|
+
def self.retrieve_connection #:nodoc:
|
75
|
+
klass = self
|
76
|
+
until klass == ActiveRecord::Base.superclass
|
77
|
+
Thread.current['active_connections'] ||= {}
|
78
|
+
if Thread.current['active_connections'][klass]
|
79
|
+
return Thread.current['active_connections'][klass]
|
80
|
+
elsif @@defined_connections[klass]
|
81
|
+
klass.connection = @@defined_connections[klass]
|
82
|
+
return self.connection
|
83
|
+
end
|
84
|
+
klass = klass.superclass
|
85
|
+
end
|
86
|
+
raise ConnectionNotEstablished
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns true if a connection that's accessible to this class have already been opened.
|
90
|
+
def self.connected?
|
91
|
+
klass = self
|
92
|
+
until klass == ActiveRecord::Base.superclass
|
93
|
+
if Thread.current['active_connections'].is_a?(Hash) && Thread.current['active_connections'][klass]
|
94
|
+
return true
|
95
|
+
else
|
96
|
+
klass = klass.superclass
|
97
|
+
end
|
98
|
+
end
|
99
|
+
return false
|
100
|
+
end
|
101
|
+
|
102
|
+
# Remove the connection for this class. This will close the active
|
103
|
+
# connection and the defined connection (if they exist). The result
|
104
|
+
# can be used as argument for establish_connection, for easy
|
105
|
+
# re-establishing of the connection.
|
106
|
+
def self.remove_connection(klass=self)
|
107
|
+
conn = @@defined_connections[klass]
|
108
|
+
@@defined_connections.delete(klass)
|
109
|
+
Thread.current['active_connections'] ||= {}
|
110
|
+
Thread.current['active_connections'][klass] = nil
|
111
|
+
conn.config if conn
|
112
|
+
end
|
113
|
+
|
114
|
+
# Set the connection for the class.
|
115
|
+
def self.connection=(spec)
|
116
|
+
raise ConnectionNotEstablished unless spec
|
117
|
+
conn = self.send(spec.adapter_method, spec.config)
|
118
|
+
Thread.current['active_connections'] ||= {}
|
119
|
+
Thread.current['active_connections'][self] = conn
|
120
|
+
end
|
121
|
+
|
122
|
+
# Converts all strings in a hash to symbols.
|
123
|
+
def self.symbolize_strings_in_hash(hash)
|
124
|
+
hash.each do |key, value|
|
125
|
+
if key.class == String
|
126
|
+
hash.delete key
|
127
|
+
hash[key.intern] = value
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
module ConnectionAdapters # :nodoc:
|
134
|
+
class Column # :nodoc:
|
135
|
+
attr_reader :name, :default, :type, :limit
|
136
|
+
# The name should contain the name of the column, such as "name" in "name varchar(250)"
|
137
|
+
# The default should contain the type-casted default of the column, such as 1 in "count int(11) DEFAULT 1"
|
138
|
+
# The type parameter should either contain :integer, :float, :datetime, :date, :text, or :string
|
139
|
+
# The sql_type is just used for extracting the limit, such as 10 in "varchar(10)"
|
140
|
+
def initialize(name, default, sql_type = nil)
|
141
|
+
@name, @default, @type = name, default, simplified_type(sql_type)
|
142
|
+
@limit = extract_limit(sql_type) unless sql_type.nil?
|
143
|
+
end
|
144
|
+
|
145
|
+
def default
|
146
|
+
type_cast(@default)
|
147
|
+
end
|
148
|
+
|
149
|
+
def klass
|
150
|
+
case type
|
151
|
+
when :integer then Fixnum
|
152
|
+
when :float then Float
|
153
|
+
when :datetime then Time
|
154
|
+
when :date then Date
|
155
|
+
when :text, :string then String
|
156
|
+
when :boolean then Object
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def type_cast(value)
|
161
|
+
if value.nil? then return nil end
|
162
|
+
case type
|
163
|
+
when :string then value
|
164
|
+
when :text then value
|
165
|
+
when :integer then value.to_i
|
166
|
+
when :float then value.to_f
|
167
|
+
when :datetime then string_to_time(value)
|
168
|
+
when :date then string_to_date(value)
|
169
|
+
when :boolean then (value == "t" or value == true ? true : false)
|
170
|
+
else value
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def human_name
|
175
|
+
Base.human_attribute_name(@name)
|
176
|
+
end
|
177
|
+
|
178
|
+
private
|
179
|
+
def string_to_date(string)
|
180
|
+
return string if Date === string
|
181
|
+
date_array = ParseDate.parsedate(string)
|
182
|
+
# treat 0000-00-00 as nil
|
183
|
+
Date.new(date_array[0], date_array[1], date_array[2]) rescue nil
|
184
|
+
end
|
185
|
+
|
186
|
+
def string_to_time(string)
|
187
|
+
return string if Time === string
|
188
|
+
time_array = ParseDate.parsedate(string).compact
|
189
|
+
# treat 0000-00-00 00:00:00 as nil
|
190
|
+
Time.local(*time_array) rescue nil
|
191
|
+
end
|
192
|
+
|
193
|
+
def extract_limit(sql_type)
|
194
|
+
$1.to_i if sql_type =~ /\((.*)\)/
|
195
|
+
end
|
196
|
+
|
197
|
+
def simplified_type(field_type)
|
198
|
+
case field_type
|
199
|
+
when /int/i
|
200
|
+
:integer
|
201
|
+
when /float|double|decimal|numeric/i
|
202
|
+
:float
|
203
|
+
when /time/i
|
204
|
+
:datetime
|
205
|
+
when /date/i
|
206
|
+
:date
|
207
|
+
when /(c|b)lob/i, /text/i
|
208
|
+
:text
|
209
|
+
when /char/i, /string/i
|
210
|
+
:string
|
211
|
+
when /boolean/i
|
212
|
+
:boolean
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# All the concrete database adapters follow the interface laid down in this class.
|
218
|
+
# You can use this interface directly by borrowing the database connection from the Base with
|
219
|
+
# Base.connection.
|
220
|
+
class AbstractAdapter
|
221
|
+
@@row_even = true
|
222
|
+
|
223
|
+
include Benchmark
|
224
|
+
|
225
|
+
def initialize(connection, logger = nil) # :nodoc:
|
226
|
+
@connection, @logger = connection, logger
|
227
|
+
@runtime = 0
|
228
|
+
end
|
229
|
+
|
230
|
+
# Returns an array of record hashes with the column names as a keys and fields as values.
|
231
|
+
def select_all(sql, name = nil) end
|
232
|
+
|
233
|
+
# Returns a record hash with the column names as a keys and fields as values.
|
234
|
+
def select_one(sql, name = nil) end
|
235
|
+
|
236
|
+
# Returns an array of column objects for the table specified by +table_name+.
|
237
|
+
def columns(table_name, name = nil) end
|
238
|
+
|
239
|
+
# Returns the last auto-generated ID from the affected table.
|
240
|
+
def insert(sql, name = nil, pk = nil, id_value = nil) end
|
241
|
+
|
242
|
+
# Executes the update statement.
|
243
|
+
def update(sql, name = nil) end
|
244
|
+
|
245
|
+
# Executes the delete statement.
|
246
|
+
def delete(sql, name = nil) end
|
247
|
+
|
248
|
+
def reset_runtime # :nodoc:
|
249
|
+
rt = @runtime
|
250
|
+
@runtime = 0
|
251
|
+
return rt
|
252
|
+
end
|
253
|
+
|
254
|
+
# Begins the transaction (and turns off auto-committing).
|
255
|
+
def begin_db_transaction() end
|
256
|
+
|
257
|
+
# Commits the transaction (and turns on auto-committing).
|
258
|
+
def commit_db_transaction() end
|
259
|
+
|
260
|
+
# Rollsback the transaction (and turns on auto-committing). Must be done if the transaction block
|
261
|
+
# raises an exception or returns false.
|
262
|
+
def rollback_db_transaction() end
|
263
|
+
|
264
|
+
def quote(value, column = nil)
|
265
|
+
case value
|
266
|
+
when String then "'#{value.gsub(/\\/,'\&\&').gsub(/'/, "''")}'" # ' (for ruby-mode)
|
267
|
+
when NilClass then "NULL"
|
268
|
+
when TrueClass then (column && column.type == :boolean ? "'t'" : "1")
|
269
|
+
when FalseClass then (column && column.type == :boolean ? "'f'" : "0")
|
270
|
+
when Float, Fixnum, Bignum, Date then "'#{value.to_s}'"
|
271
|
+
when Time, DateTime then "'#{value.strftime("%Y-%m-%d %H:%M:%S")}'"
|
272
|
+
else "'#{value.to_yaml.gsub(/'/, "''")}'"
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
def quote_column_name(name)
|
277
|
+
return name
|
278
|
+
end
|
279
|
+
|
280
|
+
# Returns a string of the CREATE TABLE SQL statements for recreating the entire structure of the database.
|
281
|
+
def structure_dump() end
|
282
|
+
|
283
|
+
protected
|
284
|
+
def log(sql, name, connection, &action)
|
285
|
+
begin
|
286
|
+
if @logger.nil?
|
287
|
+
action.call(connection)
|
288
|
+
else
|
289
|
+
result = nil
|
290
|
+
bm = measure { result = action.call(connection) }
|
291
|
+
@runtime += bm.real
|
292
|
+
log_info(sql, name, bm.real)
|
293
|
+
result
|
294
|
+
end
|
295
|
+
rescue => e
|
296
|
+
log_info("#{e.message}: #{sql}", name, 0)
|
297
|
+
raise ActiveRecord::StatementInvalid, "#{e.message}: #{sql}"
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
def log_info(sql, name, runtime)
|
302
|
+
if @logger.nil? then return end
|
303
|
+
|
304
|
+
@logger.info(
|
305
|
+
format_log_entry(
|
306
|
+
"#{name.nil? ? "SQL" : name} (#{sprintf("%f", runtime)})",
|
307
|
+
sql.gsub(/ +/, " ")
|
308
|
+
)
|
309
|
+
)
|
310
|
+
end
|
311
|
+
|
312
|
+
def format_log_entry(message, dump = nil)
|
313
|
+
if @@row_even then
|
314
|
+
@@row_even = false; caller_color = "1;32"; message_color = "4;33"; dump_color = "1;37"
|
315
|
+
else
|
316
|
+
@@row_even = true; caller_color = "1;36"; message_color = "4;35"; dump_color = "0;37"
|
317
|
+
end
|
318
|
+
|
319
|
+
log_entry = " \e[#{message_color}m#{message}\e[m"
|
320
|
+
log_entry << " \e[#{dump_color}m%s\e[m" % dump if dump.kind_of?(String) && !dump.nil?
|
321
|
+
log_entry << " \e[#{dump_color}m%p\e[m" % dump if !dump.kind_of?(String) && !dump.nil?
|
322
|
+
log_entry
|
323
|
+
end
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|