Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100755 1065 lines (937 sloc) 50.411 kb
db045db David Heinemeier Hansson Initial
dhh authored
1 require 'active_record/support/class_attribute_accessors'
2 require 'active_record/support/class_inheritable_attributes'
3 require 'active_record/support/inflector'
4 require 'yaml'
5
d6d875b David Heinemeier Hansson Moved the global require_* out of the classes so they actually work with...
dhh authored
6 unless Object.respond_to?(:require_association)
7 Object.send(:define_method, :require_association) { |file_name| ActiveRecord::Base.require_association(file_name) }
8 end
9
db045db David Heinemeier Hansson Initial
dhh authored
10 module ActiveRecord #:nodoc:
11 class ActiveRecordError < StandardError #:nodoc:
12 end
13 class AssociationTypeMismatch < ActiveRecordError #:nodoc:
14 end
15 class SerializationTypeMismatch < ActiveRecordError #:nodoc:
16 end
17 class AdapterNotSpecified < ActiveRecordError # :nodoc:
18 end
19 class AdapterNotFound < ActiveRecordError # :nodoc:
20 end
21 class ConnectionNotEstablished < ActiveRecordError #:nodoc:
22 end
23 class ConnectionFailed < ActiveRecordError #:nodoc:
24 end
25 class RecordNotFound < ActiveRecordError #:nodoc:
26 end
27 class StatementInvalid < ActiveRecordError #:nodoc:
28 end
29
30 # Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with
31 # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
32 # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
33 # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
34 #
35 # See the mapping rules in table_name and the full example in link:files/README.html for more insight.
36 #
37 # == Creation
38 #
39 # Active Records accepts constructor parameters either in a hash or as a block. The hash method is especially useful when
40 # you're receiving the data from somewhere else, like a HTTP request. It works like this:
41 #
42 # user = User.new("name" => "David", "occupation" => "Code Artist")
43 # user.name # => "David"
44 #
45 # You can also use block initialization:
46 #
47 # user = User.new do |u|
48 # u.name = "David"
49 # u.occupation = "Code Artist"
50 # end
51 #
52 # And of course you can just create a bare object and specify the attributes after the fact:
53 #
54 # user = User.new
55 # user.name = "David"
56 # user.occupation = "Code Artist"
57 #
58 # == Conditions
59 #
60 # Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
61 # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
62 # be used for statements that doesn't involve tainted data. Examples:
63 #
64 # User < ActiveRecord::Base
65 # def self.authenticate_unsafely(user_name, password)
66 # find_first("user_name = '#{user_name}' AND password = '#{password}'")
67 # end
68 #
69 # def self.authenticate_safely(user_name, password)
70 # find_first([ "user_name = '%s' AND password = '%s'", user_name, password ])
71 # end
72 # end
73 #
2575b3b David Heinemeier Hansson Added extra words of caution for guarding against SQL-injection attacks
dhh authored
74 # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
75 # attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt> method,
76 # on the other hand, will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query, which will ensure that
db045db David Heinemeier Hansson Initial
dhh authored
77 # an attacker can't escape the query and fake the login (or worse).
2575b3b David Heinemeier Hansson Added extra words of caution for guarding against SQL-injection attacks
dhh authored
78 #
79 # Beware, that the approach used in <tt>authenticate_unsafely</tt> is basically just a wrapped call to sprintf. This means that you
80 # still have to quote when using %s or use %d instead. So find_first([ "firm_id = %s", firm_id ]) is _not_ safe while both
81 # find_first([ "firm_id = '%s'", firm_id ]) and find_first([ "firm_id = %d", firm_id ]) are.
db045db David Heinemeier Hansson Initial
dhh authored
82 #
83 # == Overwriting default accessors
84 #
85 # All column values are automatically available through basic accessors on the Active Record object, but some times you
86 # want to specialize this behavior. This can be done by either by overwriting the default accessors (using the same
87 # name as the attribute) calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
88 # Example:
89 #
90 # class Song < ActiveRecord::Base
91 # # Uses an integer of seconds to hold the length of the song
92 #
93 # def length=(minutes)
94 # write_attribute("length", minutes * 60)
95 # end
96 #
97 # def length
98 # read_attribute("length") / 60
99 # end
100 # end
101 #
102 # == Saving arrays, hashes, and other non-mappeable objects in text columns
103 #
104 # Active Record can serialize any object in text columns using YAML. To do so, you must specify this with a call to the class method +serialize+.
105 # This makes it possible to store arrays, hashes, and other non-mappeable objects without doing any additional work. Example:
106 #
107 # class User < ActiveRecord::Base
108 # serialize :preferences
109 # end
110 #
111 # user = User.create("preferences" => { "background" => "black", "display" => large })
112 # User.find(user.id).preferences # => { "background" => "black", "display" => large }
113 #
114 # You can also specify an optional :class_name option that'll raise an exception if a serialized object is retrieved as a
115 # descendent of a class not in the hierarchy. Example:
116 #
117 # class User < ActiveRecord::Base
118 # serialize :preferences, :class_name => "Hash"
119 # end
120 #
121 # user = User.create("preferences" => %w( one two three ))
122 # User.find(user.id).preferences # raises SerializationTypeMismatch
123 #
124 # == Single table inheritance
125 #
126 # Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed
127 # by overwriting <tt>Base.inheritance_column</tt>). This means that an inheritance looking like this:
128 #
129 # class Company < ActiveRecord::Base; end
130 # class Firm < Company; end
131 # class Client < Company; end
132 # class PriorityClient < Client; end
133 #
134 # When you do Firm.create("name" => "37signals"), this record with be saved in the companies table with type = "Firm". You can then
135 # fetch this row again using Company.find_first "name = '37signals'" and it will return a Firm object.
136 #
137 # Note, all the attributes for all the cases are kept in the same table. Read more:
138 # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
139 #
140 # == Connection to multiple databases in different models
141 #
142 # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
143 # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
144 # For example, if Course is a ActiveRecord::Base, but resides in a different database you can just say Course.establish_connection
145 # and Course *and all its subclasses* will use this connection instead.
146 #
147 # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
148 # requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
149 #
150 # == Exceptions
151 #
152 # * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
153 # * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include a
154 # <tt>:adapter</tt> key.
155 # * +AdapterNotSpecified+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an unexisting adapter
156 # (or a bad spelling of an existing one).
157 # * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
158 # * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified in the <tt>:class_name</tt> option of
159 # the serialize definition.
160 # * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
161 # * +RecordNotFound+ -- no record responded to the find* method.
162 # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
163 # * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the message.
164 # Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions.
165 #
166 # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
167 # So it's possible to assign a logger to the class through Base.logger= which will then be used by all
168 # instances in the current object space.
169 class Base
170 include ClassInheritableAttributes
171
172 # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
173 # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
174 cattr_accessor :logger
175
176 # Returns the connection currently associated with the class. This can
177 # also be used to "borrow" the connection to do database work unrelated
178 # to any of the specific Active Records.
179 def self.connection
180 retrieve_connection
181 end
182
183 # Returns the connection currently associated with the class. This can
184 # also be used to "borrow" the connection to do database work that isn't
185 # easily done without going straight to SQL.
186 def connection
187 self.class.connection
188 end
189
190 def self.inherited(child) #:nodoc:
191 @@subclasses[self] ||= []
192 @@subclasses[self] << child
193 super
194 end
195
196 @@subclasses = {}
197
198 cattr_accessor :configurations
199 @@primary_key_prefix_type = {}
200
201 # Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
202 # :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
203 # the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
204 # that this is a global setting for all Active Records.
205 cattr_accessor :primary_key_prefix_type
206 @@primary_key_prefix_type = nil
207
208 # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
209 # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convinient way of creating a namespace
210 # for tables in a shared database. By default, the prefix is the empty string.
211 cattr_accessor :table_name_prefix
212 @@table_name_prefix = ""
213
214 # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
215 # "people_basecamp"). By default, the suffix is the empty string.
216 cattr_accessor :table_name_suffix
217 @@table_name_suffix = ""
218
219 # Indicate whether or not table names should be the pluralized versions of the corresponding class names.
220 # If true, this the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
221 # See table_name for the full rules on table/class naming. This is true, by default.
222 cattr_accessor :pluralize_table_names
223 @@pluralize_table_names = true
224
225 # When turned on (which is default), all associations are included using "load". This mean that any change is instant in cached
226 # environments like mod_ruby or FastCGI. When set to false, "require" is used, which is faster but requires server restart to
227 # be effective.
228 @@reload_associations = true
229 cattr_accessor :reload_associations
230
231 @@associations_loaded = []
232 cattr_accessor :associations_loaded
233
234 class << self # Class methods
235 # Returns objects for the records responding to either a specific id (1), a list of ids (1, 5, 6) or an array of ids.
236 # If only one ID is specified, that object is returned directly. If more than one ID is specified, an array is returned.
237 # Examples:
238 # Person.find(1) # returns the object for ID = 1
239 # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
240 # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
241 # +RecordNotFound+ is raised if no record can be found.
242 def find(*ids)
243 ids = ids.flatten.compact.uniq
244
245 if ids.length > 1
246 ids_list = ids.map{ |id| "'#{sanitize(id)}'" }.join(", ")
247 objects = find_all("#{primary_key} IN (#{ids_list})", primary_key)
248
249 if objects.length == ids.length
250 return objects
251 else
252 raise RecordNotFound, "Couldn't find #{name} with ID in (#{ids_list})"
253 end
254 elsif ids.length == 1
255 id = ids.first
256 sql = "SELECT * FROM #{table_name} WHERE #{primary_key} = '#{sanitize(id)}'"
257 sql << " AND #{type_condition}" unless descends_from_active_record?
258
259 if record = connection.select_one(sql, "#{name} Find")
260 instantiate(record)
261 else
262 raise RecordNotFound, "Couldn't find #{name} with ID = #{id}"
263 end
264 else
265 raise RecordNotFound, "Couldn't find #{name} without an ID"
266 end
267 end
268
269 # Works like find, but the record matching +id+ must also meet the +conditions+.
270 # +RecordNotFound+ is raised if no record can be found matching the +id+ or meeting the condition.
271 # Example:
272 # Person.find_on_conditions 5, "first_name LIKE '%dav%' AND last_name = 'heinemeier'"
273 def find_on_conditions(id, conditions)
274 find_first("#{primary_key} = '#{sanitize(id)}' AND #{sanitize_conditions(conditions)}") ||
275 raise(RecordNotFound, "Couldn't find #{name} with #{primary_key} = #{id} on the condition of #{conditions}")
276 end
277
278 # Returns an array of all the objects that could be instantiated from the associated
279 # table in the database. The +conditions+ can be used to narrow the selection of objects (WHERE-part),
280 # such as by "color = 'red'", and arrangement of the selection can be done through +orderings+ (ORDER BY-part),
281 # such as by "last_name, first_name DESC". A maximum of returned objects can be specified in +limit+. Example:
282 # Project.find_all "category = 'accounts'", "last_accessed DESC", 15
283 def find_all(conditions = nil, orderings = nil, limit = nil, joins = nil)
284 sql = "SELECT * FROM #{table_name} "
285 sql << "#{joins} " if joins
286 add_conditions!(sql, conditions)
287 sql << "ORDER BY #{orderings} " unless orderings.nil?
288 sql << "LIMIT #{limit} " unless limit.nil?
289
290 find_by_sql(sql)
291 end
292
293 # Works like find_all, but requires a complete SQL string. Example:
294 # Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
295 def find_by_sql(sql)
296 connection.select_all(sql, "#{name} Load").inject([]) { |objects, record| objects << instantiate(record) }
297 end
298
299 # Returns the object for the first record responding to the conditions in +conditions+,
300 # such as "group = 'master'". If more than one record is returned from the query, it's the first that'll
301 # be used to create the object. In such cases, it might be beneficial to also specify
302 # +orderings+, like "income DESC, name", to control exactly which record is to be used. Example:
303 # Employee.find_first "income > 50000", "income DESC, name"
304 def find_first(conditions = nil, orderings = nil)
305 sql = "SELECT * FROM #{table_name} "
306 add_conditions!(sql, conditions)
307 sql << "ORDER BY #{orderings} " unless orderings.nil?
308 sql << "LIMIT 1"
309
310 record = connection.select_one(sql, "#{name} Load First")
311 instantiate(record) unless record.nil?
312 end
313
314 # Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
315 # fail under validations, the unsaved object is still returned.
316 def create(attributes = nil)
317 object = new(attributes)
318 object.save
319 object
320 end
321
322 # Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
323 # and returns it. If the save fail under validations, the unsaved object is still returned.
324 def update(id, attributes)
325 object = find(id)
326 object.attributes = attributes
327 object.save
328 object
329 end
330
331 # Updates all records with the SET-part of an SQL update statement in +updates+. A subset of the records can be selected
332 # by specifying +conditions+. Example:
333 # Billing.update_all "category = 'authorized', approved = 1", "author = 'David'"
334 def update_all(updates, conditions = nil)
335 sql = "UPDATE #{table_name} SET #{updates} "
336 add_conditions!(sql, conditions)
337 connection.update(sql, "#{name} Update")
338 end
339
340 # Destroys the objects for all the records that matches the +condition+ by instantiating each object and calling
341 # the destroy method. Example:
342 # Person.destroy_all "last_login < '2004-04-04'"
343 def destroy_all(conditions = nil)
344 find_all(conditions).each { |object| object.destroy }
345 end
346
347 # Deletes all the records that matches the +condition+ without instantiating the objects first (and hence not
348 # calling the destroy method). Example:
349 # Post.destroy_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
350 def delete_all(conditions = nil)
351 sql = "DELETE FROM #{table_name} "
352 add_conditions!(sql, conditions)
353 connection.delete(sql, "#{name} Delete all")
354 end
355
356 # Returns the number of records that meets the +conditions+. Zero is returned if no records match. Example:
357 # Product.count "sales > 1"
358 def count(conditions = nil)
359 sql = "SELECT COUNT(*) FROM #{table_name} "
360 add_conditions!(sql, conditions)
361 count_by_sql(sql)
362 end
363
364 # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
365 # Product.count "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
366 def count_by_sql(sql)
367 count = connection.select_one(sql, "#{name} Count").values.first
368 return count ? count.to_i : 0
369 end
370
371 # Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
372 # discussion_board_id)</tt> would increment the "post_count" counter on the board responding to discussion_board_id.
373 # This is used for caching aggregate values, so that they doesn't need to be computed every time. Especially important
374 # for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
375 # that needs to list both the number of posts and comments.
376 def increment_counter(counter_name, id)
377 update_all "#{counter_name} = #{counter_name} + 1", "#{primary_key} = #{id}"
378 end
379
380 # Works like increment_counter, but decrements instead.
381 def decrement_counter(counter_name, id)
382 update_all "#{counter_name} = #{counter_name} - 1", "#{primary_key} = #{id}"
383 end
384
385 # Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
386 # <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
387 # methods to do assignment. This is meant to protect sensitive attributes to be overwritten by URL/form hackers. Example:
388 #
389 # class Customer < ActiveRecord::Base
390 # attr_protected :credit_rating
391 # end
392 #
393 # customer = Customer.new("name" => David, "credit_rating" => "Excellent")
394 # customer.credit_rating # => nil
395 # customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
396 # customer.credit_rating # => nil
397 #
398 # customer.credit_rating = "Average"
399 # customer.credit_rating # => "Average"
400 def attr_protected(*attributes)
401 write_inheritable_array("attr_protected", attributes)
402 end
403
404 # Returns an array of all the attributes that have been protected from mass-assigment.
405 def protected_attributes # :nodoc:
406 read_inheritable_attribute("attr_protected")
407 end
408
409 # If this macro is used, only those attributed named in it will be accessible for mass-assignment, such as
410 # <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>. This is the more conservative choice for mass-assignment
411 # protection. If you'd rather start from an all-open default and restrict attributes as needed, have a look at
412 # attr_protected.
413 def attr_accessible(*attributes)
414 write_inheritable_array("attr_accessible", attributes)
415 end
416
417 # Returns an array of all the attributes that have been made accessible to mass-assigment.
418 def accessible_attributes # :nodoc:
419 read_inheritable_attribute("attr_accessible")
420 end
421
422 # Specifies that the attribute by the name of +attr_name+ should be serialized before saving to the database and unserialized
423 # after loading from the database. The serialization is done through YAML. If +class_name+ is specified, the serialized
424 # object must be of that class on retrival or +SerializationTypeMismatch+ will be raised.
425 def serialize(attr_name, class_name = Object)
426 write_inheritable_attribute("attr_serialized", serialized_attributes.update(attr_name.to_s => class_name))
427 end
428
429 # Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
430 def serialized_attributes
431 read_inheritable_attribute("attr_serialized") || { }
432 end
433
434 # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
435 # directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
436 # to guess the table name from even when called on Reply. The guessing rules are as follows:
437 #
438 # * Class name ends in "x", "ch" or "ss": "es" is appended, so a Search class becomes a searches table.
439 # * Class name ends in "y" preceded by a consonant or "qu": The "y" is replaced with "ies", so a Category class becomes a categories table.
440 # * Class name ends in "fe": The "fe" is replaced with "ves", so a Wife class becomes a wives table.
441 # * Class name ends in "lf" or "rf": The "f" is replaced with "ves", so a Half class becomes a halves table.
442 # * Class name ends in "person": The "person" is replaced with "people", so a Salesperson class becomes a salespeople table.
443 # * Class name ends in "man": The "man" is replaced with "men", so a Spokesman class becomes a spokesmen table.
444 # * Class name ends in "sis": The "i" is replaced with an "e", so a Basis class becomes a bases table.
445 # * Class name ends in "tum" or "ium": The "um" is replaced with an "a", so a Datum class becomes a data table.
446 # * Class name ends in "child": The "child" is replaced with "children", so a NodeChild class becomes a node_children table.
447 # * Class name ends in an "s": No additional characters are added or removed.
448 # * Class name doesn't end in "s": An "s" is appended, so a Comment class becomes a comments table.
449 # * Class name with word compositions: Compositions are underscored, so CreditCard class becomes a credit_cards table.
450 #
451 # Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended.
452 # So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts".
453 #
454 # You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a
455 # "mice" table. Example:
456 #
457 # class Mouse < ActiveRecord::Base
458 # def self.table_name() "mice" end
459 # end
460 def table_name(class_name = nil)
461 if class_name.nil?
462 class_name = class_name_of_active_record_descendant(self)
463 table_name_prefix + undecorated_table_name(class_name) + table_name_suffix
464 else
465 table_name_prefix + undecorated_table_name(class_name) + table_name_suffix
466 end
467 end
468
469 # Defines the primary key field -- can be overridden in subclasses. Overwritting will negate any effect of the
470 # primary_key_prefix_type setting, though.
471 def primary_key
472 case primary_key_prefix_type
473 when :table_name
474 Inflector.foreign_key(class_name_of_active_record_descendant(self), false)
475 when :table_name_with_underscore
476 Inflector.foreign_key(class_name_of_active_record_descendant(self))
477 else
478 "id"
479 end
480 end
481
482 # Defines the column name for use with single table inheritance -- can be overridden in subclasses.
483 def inheritance_column
484 "type"
485 end
486
487 # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
488 def class_name(table_name = table_name) # :nodoc:
489 # remove any prefix and/or suffix from the table name
490 class_name = Inflector.camelize(table_name[table_name_prefix.length..-(table_name_suffix.length + 1)])
491 class_name = Inflector.singularize(class_name) if pluralize_table_names
492 return class_name
493 end
494
495 # Returns an array of column objects for the table associated with this class.
496 def columns
497 @columns ||= connection.columns(table_name, "#{name} Columns")
498 end
499
500 # Returns an array of column objects for the table associated with this class.
501 def columns_hash
502 @columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
503 end
504
505 # Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count",
506 # and columns used for single table inheritance has been removed.
507 def content_columns
508 @content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
509 end
510
511 # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
512 # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
513 # is available.
514 def column_methods_hash
515 @dynamic_methods_hash ||= columns_hash.keys.inject(Hash.new(false)) do |methods, attr|
516 methods[attr.to_sym] = true
517 methods["#{attr}=".to_sym] = true
518 methods["#{attr}?".to_sym] = true
519 methods
520 end
521 end
522
523 # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
524 # Person.human_attribute_name("first_name") # => "First name"
525 def human_attribute_name(attribute_key_name)
526 attribute_key_name.gsub(/_/, " ").capitalize unless attribute_key_name.nil?
527 end
528
529 def descends_from_active_record? # :nodoc:
530 superclass == Base
531 end
532
533 # Used to sanitize objects before they're used in an SELECT SQL-statement.
534 def sanitize(object) # :nodoc:
535 return object if Fixnum === object
536 object.to_s.gsub(/([;:])/, "").gsub('##', '\#\#').gsub(/'/, "''") # ' (for ruby-mode)
537 end
538
539 # Used to aggregate logging and benchmark, so you can measure and represent multiple statements in a single block.
540 # Usage (hides all the SQL calls for the individual actions and calculates total runtime for them all):
541 #
542 # Project.benchmark("Creating project") do
543 # project = Project.create("name" => "stuff")
544 # project.create_manager("name" => "David")
545 # project.milestones << Milestone.find_all
546 # end
547 def benchmark(title)
548 result = nil
549 logger.level = Logger::ERROR
550 bm = Benchmark.measure { result = yield }
551 logger.level = Logger::DEBUG
552 logger.info "#{title} (#{sprintf("%f", bm.real)})"
553 return result
554 end
555
556 # Loads the <tt>file_name</tt> if reload_associations is true or requires if it's false.
d82f73e David Heinemeier Hansson Abolished ActionController::Base.require_or_load in favor of require_dep...
dhh authored
557 def require_association(file_name)
db045db David Heinemeier Hansson Initial
dhh authored
558 if !associations_loaded.include?(file_name)
559 associations_loaded << file_name
ac3c8a5 David Heinemeier Hansson Silence errors occurring when reloading classes
dhh authored
560 reload_associations ? silence_warnings { load("#{file_name}.rb") } : require(file_name)
db045db David Heinemeier Hansson Initial
dhh authored
561 end
562 end
563
d6d875b David Heinemeier Hansson Moved the global require_* out of the classes so they actually work with...
dhh authored
564 # Resets the list of dependencies loaded (typically to be called by the end of a request), so when require_association is
db045db David Heinemeier Hansson Initial
dhh authored
565 # called for that dependency it'll be loaded anew.
566 def reset_associations_loaded
567 associations_loaded = []
568 end
569
570 private
571 # Finder methods must instantiate through this method to work with the single-table inheritance model
572 # that makes it possible to create objects of different types from the same table.
573 def instantiate(record)
574 object = record_with_type?(record) ? compute_type(record[inheritance_column]).allocate : allocate
575 object.instance_variable_set("@attributes", record)
576 return object
577 end
578
579 # Returns true if the +record+ has a single table inheritance column and is using it.
580 def record_with_type?(record)
581 record.include?(inheritance_column) && !record[inheritance_column].nil? &&
582 !record[inheritance_column].empty?
583 end
584
585 # Returns the name of the type of the record using the current module as a prefix. So descendents of
586 # MyApp::Business::Account would be appear as "MyApp::Business::AccountSubclass".
587 def type_name_with_module(type_name)
588 self.name =~ /::/ ? self.name.scan(/(.*)::/).first.first + "::" + type_name : type_name
589 end
590
591 # Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed.
592 def add_conditions!(sql, conditions)
593 sql << "WHERE #{sanitize_conditions(conditions)} " unless conditions.nil?
594 sql << (conditions.nil? ? "WHERE " : " AND ") + type_condition unless descends_from_active_record?
595 end
596
597 def type_condition
598 " (" + subclasses.inject("#{inheritance_column} = '#{Inflector.demodulize(name)}' ") do |condition, subclass|
599 condition << "OR #{inheritance_column} = '#{Inflector.demodulize(subclass.name)}'"
600 end + ") "
601 end
602
603 # Guesses the table name, but does not decorate it with prefix and suffix information.
604 def undecorated_table_name(class_name = class_name_of_active_record_descendant(self))
605 table_name = Inflector.underscore(Inflector.demodulize(class_name))
606 table_name = Inflector.pluralize(table_name) if pluralize_table_names
607 return table_name
608 end
609
610
611 protected
612 def subclasses
613 @@subclasses[self] ||= []
614 @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
615 end
616
617 # Returns the class type of the record using the current module as a prefix. So descendents of
618 # MyApp::Business::Account would be appear as MyApp::Business::AccountSubclass.
619 def compute_type(type_name)
620 type_name_with_module(type_name).split("::").inject(Object) do |final_type, part|
621 final_type = final_type.const_get(part)
622 end
623 end
624
625 # Returns the name of the class descending directly from ActiveRecord in the inheritance hierarchy.
626 def class_name_of_active_record_descendant(klass)
627 if klass.superclass == Base
628 return klass.name
629 elsif klass.superclass.nil?
630 raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
631 else
632 class_name_of_active_record_descendant(klass.superclass)
633 end
634 end
635
636 # Accepts either a condition array or string. The string is returned untouched, but the array has each of
637 # the condition values sanitized.
638 def sanitize_conditions(conditions)
639 if Array === conditions
640 statement, values = conditions[0], conditions[1..-1]
641 values.collect! { |value| sanitize(value) }
642 conditions = statement % values
643 end
644
645 return conditions
646 end
647 end
648
649 public
650 # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
651 # attributes but not yet saved (pass a hash with key names matching the associated table column names).
652 # In both instances, valid attribute keys are determined by the column names of the associated table --
653 # hence you can't have attributes that aren't part of the table columns.
654 def initialize(attributes = nil)
655 @attributes = attributes_from_column_definition
656 @new_record = true
657 ensure_proper_type
658 self.attributes = attributes unless attributes.nil?
659 yield self if block_given?
660 end
661
662 # Every Active Record class must use "id" as their primary ID. This getter overwrites the native
663 # id method, which isn't being used in this context.
664 def id
665 read_attribute(self.class.primary_key)
666 end
667
668 # Sets the primary ID.
669 def id=(value)
670 write_attribute(self.class.primary_key, value)
671 end
672
673 # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
674 def new_record?
675 @new_record
676 end
677
678 # * No record exists: Creates a new record with values matching those of the object attributes.
679 # * A record does exist: Updates the record with values matching those of the object attributes.
680 def save
681 create_or_update
682 return true
683 end
684
685 # Deletes the record in the database and freezes this instance to reflect that no changes should
686 # be made (since they can't be persisted).
687 def destroy
688 unless new_record?
689 connection.delete(
690 "DELETE FROM #{self.class.table_name} " +
691 "WHERE #{self.class.primary_key} = '#{id}'",
692 "#{self.class.name} Destroy"
693 )
694 end
695
696 freeze
697 end
698
699 # Returns a clone of the record that hasn't been assigned an id yet and is treated as a new record.
700 def clone
701 attr = Hash.new
702
703 self.attribute_names.each do |name|
704 begin
705 attr[name] = read_attribute(name).clone
706 rescue TypeError
707 attr[name] = read_attribute(name)
708 end
709 end
710
711 cloned_record = self.class.new(attr)
712 cloned_record.instance_variable_set "@new_record", true
713 cloned_record.id = nil
714 cloned_record
715 end
716
717 # Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
718 def update_attribute(name, value)
719 self[name] = value
720 save
721 end
722
723 # Returns the value of attribute identified by <tt>attr_name</tt> after it has been type cast (for example,
724 # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
725 # (Alias for the protected read_attribute method).
726 def [](attr_name)
727 read_attribute(attr_name)
728 end
729
730 # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
731 # (Alias for the protected write_attribute method).
732 def []= (attr_name, value)
733 write_attribute(attr_name, value)
734 end
735
736 # Allows you to set all the attributes at once by passing in a hash with keys
737 # matching the attribute names (which again matches the column names). Sensitive attributes can be protected
738 # from this form of mass-assignment by using the +attr_protected+ macro. Or you can alternatively
739 # specify which attributes *can* be accessed in with the +attr_accessible+ macro. Then all the
740 # attributes not included in that won't be allowed to be mass-assigned.
741 def attributes=(attributes)
742 return if attributes.nil?
743
744 multi_parameter_attributes = []
745 remove_attributes_protected_from_mass_assignment(attributes).each do |k, v|
746 k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
747 end
748 assign_multiparameter_attributes(multi_parameter_attributes)
749 end
750
751 # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
752 # nil nor empty? (the latter only applies to objects that responds to empty?, most notably Strings).
753 def attribute_present?(attribute)
754 is_empty = read_attribute(attribute).respond_to?("empty?") ? read_attribute(attribute).empty? : false
755 @attributes.include?(attribute) && !@attributes[attribute].nil? && !is_empty
756 end
757
758 # Returns an array of names for the attributes available on this object sorted alphabetically.
759 def attribute_names
760 @attributes.keys.sort
761 end
762
763 # Returns the column object for the named attribute.
764 def column_for_attribute(name)
765 self.class.columns_hash[name]
766 end
767
768 # Returns true if the +comparison_object+ is of the same type and has the same id.
769 def ==(comparison_object)
770 comparison_object.instance_of?(self.class) && comparison_object.id == id
771 end
772
773 # Delegates to ==
774 def eql?(comparison_object)
775 self == (comparison_object)
776 end
777
778 # Delegates to id in order to allow two records of the same type and id to work with something like:
779 # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
780 def hash
781 id
782 end
783
784 # For checking respond_to? without searching the attributes (which is faster).
785 alias_method :respond_to_without_attributes?, :respond_to?
786
787 # A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
788 # person.respond_to?("name?") which will all return true.
789 def respond_to?(method)
790 self.class.column_methods_hash[method.to_sym] || respond_to_without_attributes?(method)
791 end
792
d82f73e David Heinemeier Hansson Abolished ActionController::Base.require_or_load in favor of require_dep...
dhh authored
793 # Loads the <tt>file_name</tt> if reload_associations is true or requires if it's false.
794 def require_association(file_name)
795 if !associations_loaded.include?(file_name)
796 associations_loaded << file_name
797 reload_associations ? silence_warnings { load("#{file_name}.rb") } : require(file_name)
798 end
db045db David Heinemeier Hansson Initial
dhh authored
799 end
800
d82f73e David Heinemeier Hansson Abolished ActionController::Base.require_or_load in favor of require_dep...
dhh authored
801 Object.send(:define_method, :require_association) { |file_name| ActiveRecord::Base.require_association(file_name) }
802
db045db David Heinemeier Hansson Initial
dhh authored
803 private
804 def create_or_update
805 if new_record? then create else update end
806 end
807
808 # Updates the associated record with values matching those of the instant attributes.
809 def update
810 connection.update(
811 "UPDATE #{self.class.table_name} " +
9a248a8 David Heinemeier Hansson Dont include the primary key in updates -- its unneeded and SQL Server c...
dhh authored
812 "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
db045db David Heinemeier Hansson Initial
dhh authored
813 "WHERE #{self.class.primary_key} = '#{id}'",
814 "#{self.class.name} Update"
815 )
816 end
817
818 # Creates a new record with values matching those of the instant attributes.
819 def create
820 self.id = connection.insert(
821 "INSERT INTO #{self.class.table_name} " +
822 "(#{quoted_column_names.join(', ')}) " +
823 "VALUES(#{attributes_with_quotes.values.join(', ')})",
824 "#{self.class.name} Create",
825 self.class.primary_key, self.id
826 )
827
828 @new_record = false
829 end
830
831 # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendant.
832 # Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
833 # set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
834 # Message class in that example.
835 def ensure_proper_type
836 unless self.class.descends_from_active_record?
837 write_attribute(self.class.inheritance_column, Inflector.demodulize(self.class.name))
838 end
839 end
840
841 # Allows access to the object attributes, which are held in the @attributes hash, as were
842 # they first-class methods. So a Person class with a name attribute can use Person#name and
843 # Person#name= and never directly use the attributes hash -- except for multiple assigns with
844 # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
845 # the completed attribute is not nil or 0.
846 #
847 # It's also possible to instantiate related objects, so a Client class belonging to the clients
848 # table with a master_id foreign key can instantiate master through Client#master.
849 def method_missing(method_id, *arguments)
850 method_name = method_id.id2name
851
852
853
854 if method_name =~ read_method? && @attributes.include?($1)
855 return read_attribute($1)
856 elsif method_name =~ write_method? && @attributes.include?($1)
857 write_attribute($1, arguments[0])
858 elsif method_name =~ query_method? && @attributes.include?($1)
859 return query_attribute($1)
860 else
861 super
862 end
863 end
864
865 def read_method?() /^([a-zA-Z][-_\w]*)[^=?]*$/ end
866 def write_method?() /^([a-zA-Z][-_\w]*)=.*$/ end
867 def query_method?() /^([a-zA-Z][-_\w]*)\?$/ end
868
869 # Returns the value of attribute identified by <tt>attr_name</tt> after it has been type cast (for example,
870 # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
871 def read_attribute(attr_name) #:doc:
872 if @attributes.keys.include? attr_name
873 if column = column_for_attribute(attr_name)
874 @attributes[attr_name] = unserializable_attribute?(attr_name, column) ?
875 unserialize_attribute(attr_name) : column.type_cast(@attributes[attr_name])
876 end
877
878 @attributes[attr_name]
879 else
880 nil
881 end
882 end
883
884 # Returns true if the attribute is of a text column and marked for serialization.
885 def unserializable_attribute?(attr_name, column)
886 @attributes[attr_name] && column.send(:type) == :text && @attributes[attr_name].is_a?(String) && self.class.serialized_attributes[attr_name]
887 end
888
889 # Returns the unserialized object of the attribute.
890 def unserialize_attribute(attr_name)
891 unserialized_object = object_from_yaml(@attributes[attr_name])
892
893 if unserialized_object.is_a?(self.class.serialized_attributes[attr_name])
894 @attributes[attr_name] = unserialized_object
895 else
896 raise(
897 SerializationTypeMismatch,
898 "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, " +
899 "but was a #{unserialized_object.class.to_s}"
900 )
901 end
902 end
903
904 # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
905 # columns are turned into nil.
906 def write_attribute(attr_name, value) #:doc:
907 @attributes[attr_name] = empty_string_for_number_column?(attr_name, value) ? nil : value
908 end
909
910 def empty_string_for_number_column?(attr_name, value)
911 column = column_for_attribute(attr_name)
912 column && (column.klass == Fixnum || column.klass == Float) && value == ""
913 end
914
915 def query_attribute(attr_name)
916 attribute = @attributes[attr_name]
917 if attribute.kind_of?(Fixnum) && attribute == 0
918 false
919 elsif attribute.kind_of?(String) && attribute == "0"
920 false
921 elsif attribute.kind_of?(String) && attribute.empty?
922 false
923 elsif attribute.nil?
924 false
925 elsif attribute == false
926 false
927 elsif attribute == "f"
928 false
929 elsif attribute == "false"
930 false
931 else
932 true
933 end
934 end
935
936 def remove_attributes_protected_from_mass_assignment(attributes)
937 if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
938 attributes.reject { |key, value| key == self.class.primary_key }
939 elsif self.class.protected_attributes.nil?
940 attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.intern) || key == self.class.primary_key }
941 elsif self.class.accessible_attributes.nil?
942 attributes.reject { |key, value| self.class.protected_attributes.include?(key.intern) || key == self.class.primary_key }
943 end
944 end
945
946 # Returns copy of the attributes hash where all the values have been safely quoted for use in
947 # an SQL statement.
9a248a8 David Heinemeier Hansson Dont include the primary key in updates -- its unneeded and SQL Server c...
dhh authored
948 def attributes_with_quotes(include_primary_key = true)
db045db David Heinemeier Hansson Initial
dhh authored
949 columns_hash = self.class.columns_hash
950 @attributes.inject({}) do |attrs_quoted, pair|
9a248a8 David Heinemeier Hansson Dont include the primary key in updates -- its unneeded and SQL Server c...
dhh authored
951 attrs_quoted[pair.first] = quote(pair.last, columns_hash[pair.first]) unless !include_primary_key && pair.first == self.class.primary_key
db045db David Heinemeier Hansson Initial
dhh authored
952 attrs_quoted
953 end
954 end
955
956 # Quote strings appropriately for SQL statements.
957 def quote(value, column = nil)
958 connection.quote(value, column)
959 end
960
961 # Interpolate custom sql string in instance context.
962 # Optional record argument is meant for custom insert_sql.
963 def interpolate_sql(sql, record = nil)
964 instance_eval("%(#{sql})")
965 end
966
967 # Initializes the attributes array with keys matching the columns from the linked table and
968 # the values matching the corresponding default value of that column, so
969 # that a new instance, or one populated from a passed-in Hash, still has all the attributes
970 # that instances loaded from the database would.
971 def attributes_from_column_definition
972 connection.columns(self.class.table_name, "#{self.class.name} Columns").inject({}) do |attributes, column|
973 attributes[column.name] = column.default unless column.name == self.class.primary_key
974 attributes
975 end
976 end
977
978 # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
979 # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
980 # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
981 # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
982 # parenteses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
983 # s for String, and a for Array. If all the values for a given attribute is empty, the attribute will be set to nil.
984 def assign_multiparameter_attributes(pairs)
985 execute_callstack_for_multiparameter_attributes(
986 extract_callstack_for_multiparameter_attributes(pairs)
987 )
988 end
989
990 # Includes an ugly hack for Time.local instead of Time.new because the latter is reserved by Time itself.
991 def execute_callstack_for_multiparameter_attributes(callstack)
992 callstack.each do |name, values|
993 klass = (self.class.reflect_on_aggregation(name) || column_for_attribute(name)).klass
994 if values.empty?
995 send(name + "=", nil)
996 else
997 send(name + "=", Time == klass ? klass.local(*values) : klass.new(*values))
998 end
999 end
1000 end
1001
1002 def extract_callstack_for_multiparameter_attributes(pairs)
1003 attributes = { }
1004
1005 for pair in pairs
1006 multiparameter_name, value = pair
1007 attribute_name = multiparameter_name.split("(").first
1008 attributes[attribute_name] = [] unless attributes.include?(attribute_name)
1009
1010 unless value.empty?
1011 attributes[attribute_name] <<
1012 [find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value)]
1013 end
1014 end
1015
1016 attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
1017 end
1018
1019 def type_cast_attribute_value(multiparameter_name, value)
1020 multiparameter_name =~ /\([0-9]*([a-z])\)/ ? value.send("to_" + $1) : value
1021 end
1022
1023 def find_parameter_position(multiparameter_name)
1024 multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
1025 end
1026
1027 # Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
1028 def comma_pair_list(hash)
1029 hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
1030 end
1031
1032 def quoted_column_names(attributes = attributes_with_quotes)
1033 attributes.keys.collect { |column_name| connection.quote_column_name(column_name) }
1034 end
1035
1036 def quote_columns(column_quoter, hash)
1037 hash.inject({}) {|list, pair|
1038 list[column_quoter.quote_column_name(pair.first)] = pair.last
1039 list
1040 }
1041 end
1042
1043 def quoted_comma_pair_list(column_quoter, hash)
1044 comma_pair_list(quote_columns(column_quoter, hash))
1045 end
1046
1047 def object_from_yaml(string)
1048 return string unless String === string
1049 if has_yaml_encoding_header?(string)
1050 begin
1051 YAML::load(string)
1052 rescue Object
1053 # Apparently wasn't YAML anyway
1054 string
1055 end
1056 else
1057 string
1058 end
1059 end
1060
1061 def has_yaml_encoding_header?(string)
1062 string[0..3] == "--- "
1063 end
1064 end
ac3c8a5 David Heinemeier Hansson Silence errors occurring when reloading classes
dhh authored
1065 end
Something went wrong with that request. Please try again.