Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/tags/rel_2-0-0_RC2/activerecord/lib/active_record/base.rb

Revision 8231, 97.9 kB (checked in by bitsweat, 1 year ago)

attr_protected and _accessible use sets of strings instead of arrays of symbols internally. Closes #10300.

  • Property svn:executable set to *
Line 
1 require 'base64'
2 require 'yaml'
3 require 'set'
4
5 module ActiveRecord #:nodoc:
6   class ActiveRecordError < StandardError #:nodoc:
7   end
8   class SubclassNotFound < ActiveRecordError #:nodoc:
9   end
10   class AssociationTypeMismatch < ActiveRecordError #:nodoc:
11   end
12   class SerializationTypeMismatch < ActiveRecordError #:nodoc:
13   end
14   class AdapterNotSpecified < ActiveRecordError # :nodoc:
15   end
16   class AdapterNotFound < ActiveRecordError # :nodoc:
17   end
18   class ConnectionNotEstablished < ActiveRecordError #:nodoc:
19   end
20   class ConnectionFailed < ActiveRecordError #:nodoc:
21   end
22   class RecordNotFound < ActiveRecordError #:nodoc:
23   end
24   class RecordNotSaved < ActiveRecordError #:nodoc:
25   end
26   class StatementInvalid < ActiveRecordError #:nodoc:
27   end
28   class PreparedStatementInvalid < ActiveRecordError #:nodoc:
29   end
30   class StaleObjectError < ActiveRecordError #:nodoc:
31   end
32   class ConfigurationError < ActiveRecordError #:nodoc:
33   end
34   class ReadOnlyRecord < ActiveRecordError #:nodoc:
35   end
36   class Rollback < ActiveRecordError #:nodoc:
37   end
38   class DangerousAttributeError < ActiveRecordError #:nodoc:
39   end
40
41   # Raised when you've tried to access a column which wasn't
42   # loaded by your finder.  Typically this is because :select
43   # has been specified
44   class MissingAttributeError < NoMethodError
45   end
46
47   class AttributeAssignmentError < ActiveRecordError #:nodoc:
48     attr_reader :exception, :attribute
49     def initialize(message, exception, attribute)
50       @exception = exception
51       @attribute = attribute
52       @message = message
53     end
54   end
55
56   class MultiparameterAssignmentErrors < ActiveRecordError #:nodoc:
57     attr_reader :errors
58     def initialize(errors)
59       @errors = errors
60     end
61   end
62
63   # Active Record objects don't specify their attributes directly, but rather infer them from the table definition with
64   # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
65   # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
66   # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
67   #
68   # See the mapping rules in table_name and the full example in link:files/README.html for more insight.
69   #
70   # == Creation
71   #
72   # Active Records accept constructor parameters either in a hash or as a block. The hash method is especially useful when
73   # you're receiving the data from somewhere else, like an HTTP request. It works like this:
74   #
75   #   user = User.new(:name => "David", :occupation => "Code Artist")
76   #   user.name # => "David"
77   #
78   # You can also use block initialization:
79   #
80   #   user = User.new do |u|
81   #     u.name = "David"
82   #     u.occupation = "Code Artist"
83   #   end
84   #
85   # And of course you can just create a bare object and specify the attributes after the fact:
86   #
87   #   user = User.new
88   #   user.name = "David"
89   #   user.occupation = "Code Artist"
90   #
91   # == Conditions
92   #
93   # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
94   # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
95   # be used for statements that don't involve tainted data. The hash form works much like the array form, except
96   # only equality and range is possible. Examples:
97   #
98   #   class User < ActiveRecord::Base
99   #     def self.authenticate_unsafely(user_name, password)
100   #       find(:first, :conditions => "user_name = '#{user_name}' AND password = '#{password}'")
101   #     end
102   #
103   #     def self.authenticate_safely(user_name, password)
104   #       find(:first, :conditions => [ "user_name = ? AND password = ?", user_name, password ])
105   #     end
106   #
107   #     def self.authenticate_safely_simply(user_name, password)
108   #       find(:first, :conditions => { :user_name => user_name, :password => password })
109   #     end
110   #   end
111   #
112   # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
113   # attacks if the <tt>user_name</tt> and +password+ parameters come directly from an HTTP request. The <tt>authenticate_safely</tt>  and
114   # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+ before inserting them in the query,
115   # which will ensure that an attacker can't escape the query and fake the login (or worse).
116   #
117   # When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
118   # question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
119   # the question marks with symbols and supplying a hash with values for the matching symbol keys:
120   #
121   #   Company.find(:first, [
122   #     "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
123   #     { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
124   #   ])
125   #
126   # Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
127   # operator. For instance:
128   #
129   #   Student.find(:all, :conditions => { :first_name => "Harvey", :status => 1 })
130   #   Student.find(:all, :conditions => params[:student])
131   #
132   # A range may be used in the hash to use the SQL BETWEEN operator:
133   #
134   #   Student.find(:all, :conditions => { :grade => 9..12 })
135   #
136   # == Overwriting default accessors
137   #
138   # All column values are automatically available through basic accessors on the Active Record object, but sometimes you
139   # want to specialize this behavior. This can be done by overwriting the default accessors (using the same
140   # name as the attribute) and calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
141   # Example:
142   #
143   #   class Song < ActiveRecord::Base
144   #     # Uses an integer of seconds to hold the length of the song
145   #
146   #     def length=(minutes)
147   #       write_attribute(:length, minutes * 60)
148   #     end
149   #
150   #     def length
151   #       read_attribute(:length) / 60
152   #     end
153   #   end
154   #
155   # You can alternatively use self[:attribute]=(value) and self[:attribute] instead of write_attribute(:attribute, value) and
156   # read_attribute(:attribute) as a shorter form.
157   #
158   # == Accessing attributes before they have been typecasted
159   #
160   # Sometimes you want to be able to read the raw attribute data without having the column-determined typecast run its course first.
161   # That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
162   # has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
163   #
164   # This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
165   # the original string back in an error message. Accessing the attribute normally would typecast the string to 0, which isn't what you
166   # want.
167   #
168   # == Dynamic attribute-based finders
169   #
170   # Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects by simple queries without turning to SQL. They work by
171   # appending the name of an attribute to <tt>find_by_</tt> or <tt>find_all_by_</tt>, so you get finders like Person.find_by_user_name,
172   # Person.find_all_by_last_name, Payment.find_by_transaction_id. So instead of writing
173   # <tt>Person.find(:first, ["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
174   # And instead of writing <tt>Person.find(:all, ["last_name = ?", last_name])</tt>, you just do <tt>Person.find_all_by_last_name(last_name)</tt>.
175   #
176   # It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
177   # <tt>Person.find_by_user_name_and_password</tt> or even <tt>Payment.find_by_purchaser_and_state_and_country</tt>. So instead of writing
178   # <tt>Person.find(:first, ["user_name = ? AND password = ?", user_name, password])</tt>, you just do
179   # <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
180   #
181   # It's even possible to use all the additional parameters to find. For example, the full interface for Payment.find_all_by_amount
182   # is actually Payment.find_all_by_amount(amount, options). And the full interface to Person.find_by_user_name is
183   # actually Person.find_by_user_name(user_name, options). So you could call <tt>Payment.find_all_by_amount(50, :order => "created_on")</tt>.
184   #
185   # The same dynamic finder style can be used to create the object if it doesn't already exist. This dynamic finder is called with
186   # <tt>find_or_create_by_</tt> and will return the object if it already exists and otherwise creates it, then returns it. Example:
187   #
188   #   # No 'Summer' tag exists
189   #   Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
190   #   
191   #   # Now the 'Summer' tag does exist
192   #   Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
193   #
194   # Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without saving it first. Example:
195   #
196   #   # No 'Winter' tag exists
197   #   winter = Tag.find_or_initialize_by_name("Winter")
198   #   winter.new_record? # true
199   #
200   # To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
201   # a list of parameters. For example:
202   #
203   #   Tag.find_or_create_by_name(:name => "rails", :creator => current_user)
204   #
205   # That will either find an existing tag named "rails", or create a new one while setting the user that created it.
206   #
207   # == Saving arrays, hashes, and other non-mappable objects in text columns
208   #
209   # 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+.
210   # This makes it possible to store arrays, hashes, and other non-mappable objects without doing any additional work. Example:
211   #
212   #   class User < ActiveRecord::Base
213   #     serialize :preferences
214   #   end
215   #
216   #   user = User.create(:preferences => { "background" => "black", "display" => large })
217   #   User.find(user.id).preferences # => { "background" => "black", "display" => large }
218   #
219   # You can also specify a class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
220   # descendent of a class not in the hierarchy. Example:
221   #
222   #   class User < ActiveRecord::Base
223   #     serialize :preferences, Hash
224   #   end
225   #
226   #   user = User.create(:preferences => %w( one two three ))
227   #   User.find(user.id).preferences    # raises SerializationTypeMismatch
228   #
229   # == Single table inheritance
230   #
231   # Active Record allows inheritance by storing the name of the class in a column that by default is named "type" (can be changed
232   # by overwriting <tt>Base.inheritance_column</tt>). This means that an inheritance looking like this:
233   #
234   #   class Company < ActiveRecord::Base; end
235   #   class Firm < Company; end
236   #   class Client < Company; end
237   #   class PriorityClient < Client; end
238   #
239   # When you do Firm.create(:name => "37signals"), this record will be saved in the companies table with type = "Firm". You can then
240   # fetch this row again using Company.find(:first, "name = '37signals'") and it will return a Firm object.
241   #
242   # If you don't have a type column defined in your table, single-table inheritance won't be triggered. In that case, it'll work just
243   # like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
244   #
245   # Note, all the attributes for all the cases are kept in the same table. Read more:
246   # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
247   #
248   # == Connection to multiple databases in different models
249   #
250   # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
251   # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
252   # For example, if Course is an ActiveRecord::Base, but resides in a different database, you can just say Course.establish_connection
253   # and Course *and all its subclasses* will use this connection instead.
254   #
255   # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
256   # requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
257   #
258   # == Exceptions
259   #
260   # * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
261   # * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include an
262   #   <tt>:adapter</tt> key.
263   # * +AdapterNotFound+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a non-existent adapter
264   #   (or a bad spelling of an existing one).
265   # * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
266   # * +SerializationTypeMismatch+ -- the serialized object wasn't of the class specified as the second parameter.
267   # * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
268   # * +RecordNotFound+ -- no record responded to the find* method.
269   #   Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
270   # * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the  message.
271   #   Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions.
272   # * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
273   #   +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
274   #   objects that should be inspected to determine which attributes triggered the errors.
275   # * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
276   #   You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
277   #
278   # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
279   # So it's possible to assign a logger to the class through Base.logger= which will then be used by all
280   # instances in the current object space.
281   class Base
282     # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
283     # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
284     cattr_accessor :logger, :instance_writer => false
285
286     def self.inherited(child) #:nodoc:
287       @@subclasses[self] ||= []
288       @@subclasses[self] << child
289       super
290     end
291
292     def self.reset_subclasses #:nodoc:
293       nonreloadables = []
294       subclasses.each do |klass|
295         unless Dependencies.autoloaded? klass
296           nonreloadables << klass
297           next
298         end
299         klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
300         klass.instance_methods(false).each { |m| klass.send :undef_method, m }
301       end
302       @@subclasses = {}
303       nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
304     end
305
306     @@subclasses = {}
307
308     cattr_accessor :configurations, :instance_writer => false
309     @@configurations = {}
310
311     # Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
312     # :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
313     # the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
314     # that this is a global setting for all Active Records.
315     cattr_accessor :primary_key_prefix_type, :instance_writer => false
316     @@primary_key_prefix_type = nil
317
318     # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
319     # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
320     # for tables in a shared database. By default, the prefix is the empty string.
321     cattr_accessor :table_name_prefix, :instance_writer => false
322     @@table_name_prefix = ""
323
324     # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
325     # "people_basecamp"). By default, the suffix is the empty string.
326     cattr_accessor :table_name_suffix, :instance_writer => false
327     @@table_name_suffix = ""
328
329     # Indicates whether table names should be the pluralized versions of the corresponding class names.
330     # If true, the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
331     # See table_name for the full rules on table/class naming. This is true, by default.
332     cattr_accessor :pluralize_table_names, :instance_writer => false
333     @@pluralize_table_names = true
334
335     # Determines whether to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
336     # make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
337     # may complicate matters if you use software like syslog. This is true, by default.
338     cattr_accessor :colorize_logging, :instance_writer => false
339     @@colorize_logging = true
340
341     # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
342     # This is set to :local by default.
343     cattr_accessor :default_timezone, :instance_writer => false
344     @@default_timezone = :local
345
346     # Determines whether to use a connection for each thread, or a single shared connection for all threads.
347     # Defaults to false. Set to true if you're writing a threaded application.
348     cattr_accessor :allow_concurrency, :instance_writer => false
349     @@allow_concurrency = false
350
351     # Specifies the format to use when dumping the database schema with Rails'
352     # Rakefile.  If :sql, the schema is dumped as (potentially database-
353     # specific) SQL statements.  If :ruby, the schema is dumped as an
354     # ActiveRecord::Schema file which can be loaded into any database that
355     # supports migrations.  Use :ruby if you want to have different database
356     # adapters for, e.g., your development and test environments.
357     cattr_accessor :schema_format , :instance_writer => false
358     @@schema_format = :ruby
359
360     class << self # Class methods
361       # Find operates with three different retrieval approaches:
362       #
363       # * Find by id: This can either be a specific id (1), a list of ids (1, 5, 6), or an array of ids ([5, 6, 10]).
364       #   If no record can be found for all of the listed ids, then RecordNotFound will be raised.
365       # * Find first: This will return the first record matched by the options used. These options can either be specific
366       #   conditions or merely an order. If no record can be matched, nil is returned.
367       # * Find all: This will return all the records matched by the options used. If no records are found, an empty array is returned.
368       #
369       # All approaches accept an options hash as their last parameter. The options are:
370       #
371       # * <tt>:conditions</tt>: An SQL fragment like "administrator = 1" or [ "user_name = ?", username ]. See conditions in the intro.
372       # * <tt>:order</tt>: An SQL fragment like "created_at DESC, name".
373       # * <tt>:group</tt>: An attribute name by which the result should be grouped. Uses the GROUP BY SQL-clause.
374       # * <tt>:limit</tt>: An integer determining the limit on the number of rows that should be returned.
375       # * <tt>:offset</tt>: An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
376       # * <tt>:joins</tt>: An SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (Rarely needed).
377       #   Accepts named associations in the form of :include, which will perform an INNER JOIN on the associated table(s).
378       #   The records will be returned read-only since they will have attributes that do not correspond to the table's columns.
379       #   Pass :readonly => false to override.
380       #   See adding joins for associations under Associations.
381       # * <tt>:include</tt>: Names associations that should be loaded alongside using LEFT OUTER JOINs. The symbols named refer
382       #   to already defined associations. See eager loading under Associations.
383       # * <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
384       #   include the joined columns.
385       # * <tt>:from</tt>: By default, this is the table name of the class, but can be changed to an alternate table name (or even the name
386       #   of a database view).
387       # * <tt>:readonly</tt>: Mark the returned records read-only so they cannot be saved or updated.
388       # * <tt>:lock</tt>: An SQL fragment like "FOR UPDATE" or "LOCK IN SHARE MODE".
389       #   :lock => true gives connection's default exclusive lock, usually "FOR UPDATE".
390       #
391       # Examples for find by id:
392       #   Person.find(1)       # returns the object for ID = 1
393       #   Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
394       #   Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
395       #   Person.find([1])     # returns an array for the object with ID = 1
396       #   Person.find(1, :conditions => "administrator = 1", :order => "created_on DESC")
397       #
398       # Note that returned records may not be in the same order as the ids you
399       # provide since database rows are unordered. Give an explicit :order
400       # to ensure the results are sorted.
401       #
402       # Examples for find first:
403       #   Person.find(:first) # returns the first object fetched by SELECT * FROM people
404       #   Person.find(:first, :conditions => [ "user_name = ?", user_name])
405       #   Person.find(:first, :order => "created_on DESC", :offset => 5)
406       #
407       # Examples for find all:
408       #   Person.find(:all) # returns an array of objects for all the rows fetched by SELECT * FROM people
409       #   Person.find(:all, :conditions => [ "category IN (?)", categories], :limit => 50)
410       #   Person.find(:all, :offset => 10, :limit => 10)
411       #   Person.find(:all, :include => [ :account, :friends ])
412       #   Person.find(:all, :group => "category")
413       #
414       # Example for find with a lock. Imagine two concurrent transactions:
415       # each will read person.visits == 2, add 1 to it, and save, resulting
416       # in two saves of person.visits = 3.  By locking the row, the second
417       # transaction has to wait until the first is finished; we get the
418       # expected person.visits == 4.
419       #   Person.transaction do
420       #     person = Person.find(1, :lock => true)
421       #     person.visits += 1
422       #     person.save!
423       #   end
424       def find(*args)
425         options = args.extract_options!
426         # Note:  we extract any :joins option with a non-string value from the options, and turn it into
427         #  an internal option :ar_joins.  This allows code called from here to find the ar_joins, and
428         #  it bypasses marking the result as read_only.
429         #  A normal string join marks the result as read-only because it contains attributes from joined tables
430         #  which are not in the base table and therefore prevent the result from being saved.
431         #  In the case of an ar_join, the JoinDependency created to instantiate the results eliminates these
432         #  bogus attributes.  See JoinDependency#instantiate, and JoinBase#instantiate in associations.rb.
433         validate_find_options(options)
434         set_readonly_option!(options)
435
436         case args.first
437           when :first then find_initial(options)
438           when :all   then find_every(options)
439           else             find_from_ids(args, options)
440         end
441       end
442      
443       # Works like find(:all), but requires a complete SQL string. Examples:
444       #   Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
445       #   Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
446       def find_by_sql(sql)
447         connection.select_all(sanitize_sql(sql), "#{name} Load").collect! { |record| instantiate(record) }
448       end
449
450       # Returns true if the given +id+ represents the primary key of a record in the database, false otherwise.
451       # You can also pass a set of SQL conditions.
452       # Example:
453       #   Person.exists?(5)
454       #   Person.exists?('5')
455       #   Person.exists?(:name => "David")
456       #   Person.exists?(['name LIKE ?', "%#{query}%"])
457       def exists?(id_or_conditions)
458         !find(:first, :select => "#{table_name}.#{primary_key}", :conditions => expand_id_conditions(id_or_conditions)).nil?
459       rescue ActiveRecord::ActiveRecordError
460         false
461       end
462
463       # Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
464       # fails under validations, the unsaved object is still returned.
465       def create(attributes = nil)
466         if attributes.is_a?(Array)
467           attributes.collect { |attr| create(attr) }
468         else
469           object = new(attributes)
470           object.save
471           object
472         end
473       end
474
475       # Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
476       # and returns it. If the save fails under validations, the unsaved object is still returned.
477       #
478       # The arguments may also be given as arrays in which case the update method is called for each pair of +id+ and
479       # +attributes+ and an array of objects is returned.
480       #
481       # Example of updating one record:
482       #   Person.update(15, {:user_name => 'Samuel', :group => 'expert'})
483       #
484       # Example of updating multiple records:
485       #   people = { 1 => { "first_name" => "David" }, 2 => { "first_name" => "Jeremy"} }       
486       #   Person.update(people.keys, people.values)
487       def update(id, attributes)
488         if id.is_a?(Array)
489           idx = -1
490           id.collect { |id| idx += 1; update(id, attributes[idx]) }
491         else
492           object = find(id)
493           object.update_attributes(attributes)
494           object
495         end
496       end
497
498       # Deletes the record with the given +id+ without instantiating an object first. If an array of ids is provided, all of them
499       # are deleted.
500       def delete(id)
501         delete_all([ "#{connection.quote_column_name(primary_key)} IN (?)", id ])
502       end
503
504       # Destroys the record with the given +id+ by instantiating the object and calling #destroy (all the callbacks are the triggered).
505       # If an array of ids is provided, all of them are destroyed.
506       def destroy(id)
507         id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy
508       end
509
510       # Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows updated.
511       # A subset of the records can be selected by specifying +conditions+. Example:
512       #   Billing.update_all "category = 'authorized', approved = 1", "author = 'David'"
513       #
514       # Optional :order and :limit options may be given as the third parameter,
515       # but their behavior is database-specific.
516       def update_all(updates, conditions = nil, options = {})
517         sql  = "UPDATE #{table_name} SET #{sanitize_sql_for_assignment(updates)} "
518         scope = scope(:find)
519         add_conditions!(sql, conditions, scope)
520         add_order!(sql, options[:order], scope)
521         add_limit!(sql, options, scope)
522         connection.update(sql, "#{name} Update")
523       end
524
525       # Destroys the objects for all the records that match the +conditions+ by instantiating each object and calling
526       # the destroy method. Example:
527       #   Person.destroy_all "last_login < '2004-04-04'"
528       def destroy_all(conditions = nil)
529         find(:all, :conditions => conditions).each { |object| object.destroy }
530       end
531
532       # Deletes all the records that match the +conditions+ without instantiating the objects first (and hence not
533       # calling the destroy method). Example:
534       #   Post.delete_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
535       def delete_all(conditions = nil)
536         sql = "DELETE FROM #{quoted_table_name} "
537         add_conditions!(sql, conditions, scope(:find))
538         connection.delete(sql, "#{name} Delete all")
539       end
540
541       # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
542       # The use of this method should be restricted to complicated SQL queries that can't be executed
543       # using the ActiveRecord::Calculations class methods.  Look into those before using this.
544       #
545       # ==== Options
546       #
547       # +sql+: An SQL statement which should return a count query from the database, see the example below
548       #
549       # ==== Examples
550       #
551       #   Product.count_by_sql "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
552       def count_by_sql(sql)
553         sql = sanitize_conditions(sql)
554         connection.select_value(sql, "#{name} Count").to_i
555       end
556
557       # A generic "counter updater" implementation, intended primarily to be
558       # used by increment_counter and decrement_counter, but which may also
559       # be useful on its own. It simply does a direct SQL update for the record
560       # with the given ID, altering the given hash of counters by the amount
561       # given by the corresponding value:
562       #
563       #   Post.update_counters 5, :comment_count => -1, :action_count => 1
564       #   # UPDATE posts
565       #   #    SET comment_count = comment_count - 1,
566       #   #        action_count = action_count + 1
567       #   #  WHERE id = 5
568       def update_counters(id, counters)
569         updates = counters.inject([]) { |list, (counter_name, increment)|
570           sign = increment < 0 ? "-" : "+"
571           list << "#{connection.quote_column_name(counter_name)} = #{connection.quote_column_name(counter_name)} #{sign} #{increment.abs}"
572         }.join(", ")
573         update_all(updates, "#{connection.quote_column_name(primary_key)} = #{quote_value(id)}")
574       end
575
576       # Increment a number field by one, usually representing a count.
577       #
578       # This is used for caching aggregate values, so that they don't need to be computed every time.
579       # For example, a DiscussionBoard may cache post_count and comment_count otherwise every time the board is
580       # shown it would have to run an SQL query to find how many posts and comments there are.
581       #
582       # ==== Options
583       #
584       # +counter_name+  The name of the field that should be incremented
585       # +id+            The id of the object that should be incremented
586       #
587       # ==== Examples
588       #
589       #   # Increment the post_count column for the record with an id of 5
590       #   DiscussionBoard.increment_counter(:post_count, 5)
591       def increment_counter(counter_name, id)
592         update_counters(id, counter_name => 1)
593       end
594
595       # Decrement a number field by one, usually representing a count.
596       #
597       # This works the same as increment_counter but reduces the column value by 1 instead of increasing it.
598       #
599       # ==== Options
600       #
601       # +counter_name+  The name of the field that should be decremented
602       # +id+            The id of the object that should be decremented
603       #
604       # ==== Examples
605       #
606       #   # Decrement the post_count column for the record with an id of 5
607       #   DiscussionBoard.decrement_counter(:post_count, 5)
608       def decrement_counter(counter_name, id)
609         update_counters(id, counter_name => -1)
610       end
611
612
613       # Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
614       # <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
615       # methods to do assignment. This is meant to protect sensitive attributes from being overwritten by URL/form hackers. Example:
616       #
617       #   class Customer < ActiveRecord::Base
618       #     attr_protected :credit_rating
619       #   end
620       #
621       #   customer = Customer.new("name" => David, "credit_rating" => "Excellent")
622       #   customer.credit_rating # => nil
623       #   customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
624       #   customer.credit_rating # => nil
625       #
626       #   customer.credit_rating = "Average"
627       #   customer.credit_rating # => "Average"
628       #
629       # To start from an all-closed default and enable attributes as needed, have a look at attr_accessible.
630       def attr_protected(*attributes)
631         write_inheritable_attribute("attr_protected", Set.new(attributes.map(&:to_s)) + (protected_attributes || []))
632       end
633
634       # Returns an array of all the attributes that have been protected from mass-assignment.
635       def protected_attributes # :nodoc:
636         read_inheritable_attribute("attr_protected")
637       end
638
639       # Similar to the attr_protected macro, this protects attributes of your model from mass-assignment,
640       # such as <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>
641       # however, it does it in the opposite way.  This locks all attributes and only allows access to the
642       # attributes specified.  Assignment to attributes not in this list will be ignored and need to be set
643       # using the direct writer methods instead.  This is meant to protect sensitive attributes from being
644       # overwritten by URL/form hackers. If you'd rather start from an all-open default and restrict
645       # attributes as needed, have a look at attr_protected.
646