Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100755 1367 lines (1210 sloc) 63.494 kb
db045db David Heinemeier Hansson Initial
dhh authored
1 require 'yaml'
abc895b David Heinemeier Hansson Added new Base.find API and deprecated find_all, find_first. Added preli...
dhh authored
2 require 'active_record/deprecated_finders'
db045db David Heinemeier Hansson Initial
dhh authored
3
4 module ActiveRecord #:nodoc:
5 class ActiveRecordError < StandardError #:nodoc:
6 end
605bc77 David Heinemeier Hansson Added a better exception for when a type column is used in a table witho...
dhh authored
7 class SubclassNotFound < ActiveRecordError #:nodoc:
8 end
db045db David Heinemeier Hansson Initial
dhh authored
9 class AssociationTypeMismatch < ActiveRecordError #:nodoc:
10 end
11 class SerializationTypeMismatch < ActiveRecordError #:nodoc:
12 end
13 class AdapterNotSpecified < ActiveRecordError # :nodoc:
14 end
15 class AdapterNotFound < ActiveRecordError # :nodoc:
16 end
17 class ConnectionNotEstablished < ActiveRecordError #:nodoc:
18 end
19 class ConnectionFailed < ActiveRecordError #:nodoc:
20 end
21 class RecordNotFound < ActiveRecordError #:nodoc:
22 end
23 class StatementInvalid < ActiveRecordError #:nodoc:
24 end
554597d David Heinemeier Hansson Added named bind-style variable interpolation #281 [Michael Koziarski]
dhh authored
25 class PreparedStatementInvalid < ActiveRecordError #:nodoc:
26 end
fbf9281 David Heinemeier Hansson Added automated optimistic locking if the field lock_version is present ...
dhh authored
27 class StaleObjectError < ActiveRecordError #:nodoc:
28 end
db045db David Heinemeier Hansson Initial
dhh authored
29
d2fefbe David Heinemeier Hansson Added MultiparameterAssignmentErrors and AttributeAssignmentError except...
dhh authored
30 class AttributeAssignmentError < ActiveRecordError #:nodoc:
31 attr_reader :exception, :attribute
32 def initialize(message, exception, attribute)
33 @exception = exception
34 @attribute = attribute
35 @message = message
36 end
37 end
38
39 class MultiparameterAssignmentErrors < ActiveRecordError #:nodoc:
40 attr_reader :errors
41 def initialize(errors)
42 @errors = errors
43 end
44 end
45
db045db David Heinemeier Hansson Initial
dhh authored
46 # Active Record objects doesn't specify their attributes directly, but rather infer them from the table definition with
47 # which they're linked. Adding, removing, and changing attributes and their type is done directly in the database. Any change
48 # is instantly reflected in the Active Record objects. The mapping that binds a given Active Record class to a certain
49 # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
50 #
51 # See the mapping rules in table_name and the full example in link:files/README.html for more insight.
52 #
53 # == Creation
54 #
55 # Active Records accepts constructor parameters either in a hash or as a block. The hash method is especially useful when
56 # you're receiving the data from somewhere else, like a HTTP request. It works like this:
57 #
58 # user = User.new("name" => "David", "occupation" => "Code Artist")
59 # user.name # => "David"
60 #
61 # You can also use block initialization:
62 #
63 # user = User.new do |u|
64 # u.name = "David"
65 # u.occupation = "Code Artist"
66 # end
67 #
68 # And of course you can just create a bare object and specify the attributes after the fact:
69 #
70 # user = User.new
71 # user.name = "David"
72 # user.occupation = "Code Artist"
73 #
74 # == Conditions
75 #
76 # Conditions can either be specified as a string or an array representing the WHERE-part of an SQL statement.
77 # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
78 # be used for statements that doesn't involve tainted data. Examples:
79 #
80 # User < ActiveRecord::Base
81 # def self.authenticate_unsafely(user_name, password)
82 # find_first("user_name = '#{user_name}' AND password = '#{password}'")
83 # end
84 #
85 # def self.authenticate_safely(user_name, password)
3e7d191 David Heinemeier Hansson Added bind-style variable interpolation for the condition arrays that us...
dhh authored
86 # find_first([ "user_name = ? AND password = ?", user_name, password ])
db045db David Heinemeier Hansson Initial
dhh authored
87 # end
88 # end
89 #
2575b3b David Heinemeier Hansson Added extra words of caution for guarding against SQL-injection attacks
dhh authored
90 # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query and is thus susceptible to SQL-injection
91 # attacks if the <tt>user_name</tt> and +password+ parameters come directly from a HTTP request. The <tt>authenticate_safely</tt> method,
92 # 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
93 # 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
94 #
5cd38ca David Heinemeier Hansson Added documentation about named bind variables
dhh authored
95 # When using multiple parameters in the conditions, it can easily become hard to read exactly what the fourth or fifth
96 # question mark is supposed to represent. In those cases, you can resort to named bind variables instead. That's done by replacing
97 # the question marks with symbols and supplying a hash with values for the matching symbol keys:
98 #
99 # Company.find_first([
100 # "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
101 # { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
102 # ])
103 #
db045db David Heinemeier Hansson Initial
dhh authored
104 # == Overwriting default accessors
105 #
106 # All column values are automatically available through basic accessors on the Active Record object, but some times you
107 # want to specialize this behavior. This can be done by either by overwriting the default accessors (using the same
108 # name as the attribute) calling read_attribute(attr_name) and write_attribute(attr_name, value) to actually change things.
109 # Example:
110 #
111 # class Song < ActiveRecord::Base
112 # # Uses an integer of seconds to hold the length of the song
113 #
114 # def length=(minutes)
115 # write_attribute("length", minutes * 60)
116 # end
117 #
118 # def length
119 # read_attribute("length") / 60
120 # end
121 # end
122 #
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
123 # == Accessing attributes before they have been type casted
124 #
125 # Some times you want to be able to read the raw attribute data without having the column-determined type cast run its course first.
126 # That can be done by using the <attribute>_before_type_cast accessors that all attributes have. For example, if your Account model
127 # has a balance attribute, you can call account.balance_before_type_cast or account.id_before_type_cast.
128 #
129 # This is especially useful in validation situations where the user might supply a string for an integer field and you want to display
130 # the original string back in an error message. Accessing the attribute normally would type cast the string to 0, which isn't what you
131 # want.
132 #
ac8fd7d David Heinemeier Hansson Added dynamic attribute-based finders as a cleaner way of getting object...
dhh authored
133 # == Dynamic attribute-based finders
134 #
135 # Dynamic attribute-based finders are a cleaner way of getting objects by simple queries without turning to SQL. They work by
136 # appending the name of an attribute to <tt>find_by_</tt>, so you get finders like <tt>Person.find_by_user_name, Payment.find_by_transaction_id</tt>.
137 # So instead of writing <tt>Person.find_first(["user_name = ?", user_name])</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
138 #
139 # It's also possible to use multiple attributes in the same find by separating them with "_and_", so you get finders like
140 # <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
141 # <tt>Person.find_first(["user_name = ? AND password = ?", user_name, password])</tt>, you just do
142 # <tt>Person.find_by_user_name_and_password(user_name, password)</tt>.
569f2ea David Heinemeier Hansson Added the possibility of specifying the remaining options for find_first...
dhh authored
143 #
144 # It's even possible to use all the additional parameters to find_first and find_all. For example, the full interface for Payment.find_all_by_amount
145 # is actually Payment.find_all_by_amount(amount, orderings = nil, limit = nil, joins = nil). And the full interface to Person.find_by_user_name is
146 # actually Person.find_by_user_name(user_name, orderings = nil)
959f362 David Heinemeier Hansson Added find_all style to the new dynamic finders
dhh authored
147 #
098fa94 David Heinemeier Hansson Fixed documentation snafus #575, #576, #577, #585
dhh authored
148 # == Saving arrays, hashes, and other non-mappable objects in text columns
db045db David Heinemeier Hansson Initial
dhh authored
149 #
150 # 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+.
151 # This makes it possible to store arrays, hashes, and other non-mappeable objects without doing any additional work. Example:
152 #
153 # class User < ActiveRecord::Base
154 # serialize :preferences
155 # end
156 #
157 # user = User.create("preferences" => { "background" => "black", "display" => large })
158 # User.find(user.id).preferences # => { "background" => "black", "display" => large }
159 #
66f44e6 David Heinemeier Hansson Updated documentation for serialize
dhh authored
160 # You can also specify an class option as the second parameter that'll raise an exception if a serialized object is retrieved as a
db045db David Heinemeier Hansson Initial
dhh authored
161 # descendent of a class not in the hierarchy. Example:
162 #
163 # class User < ActiveRecord::Base
66f44e6 David Heinemeier Hansson Updated documentation for serialize
dhh authored
164 # serialize :preferences, Hash
db045db David Heinemeier Hansson Initial
dhh authored
165 # end
166 #
167 # user = User.create("preferences" => %w( one two three ))
168 # User.find(user.id).preferences # raises SerializationTypeMismatch
169 #
170 # == Single table inheritance
171 #
172 # Active Record allows inheritance by storing the name of the class in a column that by default is called "type" (can be changed
173 # by overwriting <tt>Base.inheritance_column</tt>). This means that an inheritance looking like this:
174 #
175 # class Company < ActiveRecord::Base; end
176 # class Firm < Company; end
177 # class Client < Company; end
178 # class PriorityClient < Client; end
179 #
e9426d2 David Heinemeier Hansson Optimized the SQL used to generate has_and_belongs_to_many queries by li...
dhh authored
180 # When you do Firm.create("name" => "37signals"), this record will be saved in the companies table with type = "Firm". You can then
db045db David Heinemeier Hansson Initial
dhh authored
181 # fetch this row again using Company.find_first "name = '37signals'" and it will return a Firm object.
182 #
f033833 David Heinemeier Hansson Improving documentation...
dhh authored
183 # 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
184 # like normal subclasses with no special magic for differentiating between them or reloading the right type with find.
185 #
db045db David Heinemeier Hansson Initial
dhh authored
186 # Note, all the attributes for all the cases are kept in the same table. Read more:
187 # http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
188 #
189 # == Connection to multiple databases in different models
190 #
191 # Connections are usually created through ActiveRecord::Base.establish_connection and retrieved by ActiveRecord::Base.connection.
192 # All classes inheriting from ActiveRecord::Base will use this connection. But you can also set a class-specific connection.
193 # For example, if Course is a ActiveRecord::Base, but resides in a different database you can just say Course.establish_connection
194 # and Course *and all its subclasses* will use this connection instead.
195 #
196 # This feature is implemented by keeping a connection pool in ActiveRecord::Base that is a Hash indexed by the class. If a connection is
197 # requested, the retrieve_connection method will go up the class-hierarchy until a connection is found in the connection pool.
198 #
199 # == Exceptions
200 #
201 # * +ActiveRecordError+ -- generic error class and superclass of all other errors raised by Active Record
202 # * +AdapterNotSpecified+ -- the configuration hash used in <tt>establish_connection</tt> didn't include a
203 # <tt>:adapter</tt> key.
098fa94 David Heinemeier Hansson Fixed documentation snafus #575, #576, #577, #585
dhh authored
204 # * +AdapterNotSpecified+ -- the <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified an non-existent adapter
db045db David Heinemeier Hansson Initial
dhh authored
205 # (or a bad spelling of an existing one).
206 # * +AssociationTypeMismatch+ -- the object assigned to the association wasn't of the type specified in the association definition.
66f44e6 David Heinemeier Hansson Updated documentation for serialize
dhh authored
207 # * +SerializationTypeMismatch+ -- the object serialized wasn't of the class specified as the second parameter.
db045db David Heinemeier Hansson Initial
dhh authored
208 # * +ConnectionNotEstablished+ -- no connection has been established. Use <tt>establish_connection</tt> before querying.
209 # * +RecordNotFound+ -- no record responded to the find* method.
210 # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
211 # * +StatementInvalid+ -- the database server rejected the SQL statement. The precise error is added in the message.
212 # Either the record with the given ID doesn't exist or the record didn't meet the additional restrictions.
d2fefbe David Heinemeier Hansson Added MultiparameterAssignmentErrors and AttributeAssignmentError except...
dhh authored
213 # * +MultiparameterAssignmentErrors+ -- collection of errors that occurred during a mass assignment using the
214 # +attributes=+ method. The +errors+ property of this exception contains an array of +AttributeAssignmentError+
215 # objects that should be inspected to determine which attributes triggered the errors.
216 # * +AttributeAssignmentError+ -- an error occurred while doing a mass assignment through the +attributes=+ method.
217 # You can inspect the +attribute+ property of the exception object to determine which attribute triggered the error.
db045db David Heinemeier Hansson Initial
dhh authored
218 # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
219 # So it's possible to assign a logger to the class through Base.logger= which will then be used by all
220 # instances in the current object space.
221 class Base
222 include ClassInheritableAttributes
223
224 # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
225 # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
226 cattr_accessor :logger
227
228 # Returns the connection currently associated with the class. This can
229 # also be used to "borrow" the connection to do database work unrelated
230 # to any of the specific Active Records.
231 def self.connection
232 retrieve_connection
233 end
234
235 # Returns the connection currently associated with the class. This can
236 # also be used to "borrow" the connection to do database work that isn't
237 # easily done without going straight to SQL.
238 def connection
239 self.class.connection
240 end
241
242 def self.inherited(child) #:nodoc:
243 @@subclasses[self] ||= []
244 @@subclasses[self] << child
245 super
246 end
247
248 @@subclasses = {}
8e1f1ee David Heinemeier Hansson Moved require_association to associations.rb and added methods for reset...
dhh authored
249
db045db David Heinemeier Hansson Initial
dhh authored
250 cattr_accessor :configurations
251 @@primary_key_prefix_type = {}
252
253 # Accessor for the prefix type that will be prepended to every primary key column name. The options are :table_name and
254 # :table_name_with_underscore. If the first is specified, the Product class will look for "productid" instead of "id" as
255 # the primary column. If the latter is specified, the Product class will look for "product_id" instead of "id". Remember
256 # that this is a global setting for all Active Records.
257 cattr_accessor :primary_key_prefix_type
258 @@primary_key_prefix_type = nil
259
260 # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
098fa94 David Heinemeier Hansson Fixed documentation snafus #575, #576, #577, #585
dhh authored
261 # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
db045db David Heinemeier Hansson Initial
dhh authored
262 # for tables in a shared database. By default, the prefix is the empty string.
263 cattr_accessor :table_name_prefix
264 @@table_name_prefix = ""
265
266 # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
267 # "people_basecamp"). By default, the suffix is the empty string.
268 cattr_accessor :table_name_suffix
269 @@table_name_suffix = ""
270
271 # Indicate whether or not table names should be the pluralized versions of the corresponding class names.
272 # If true, this the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
273 # See table_name for the full rules on table/class naming. This is true, by default.
274 cattr_accessor :pluralize_table_names
275 @@pluralize_table_names = true
276
911614d David Heinemeier Hansson Added ActiveRecord::Base.colorize_logging to control whether to use colo...
dhh authored
277 # Determines whether or not to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
278 # makes it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
279 # may complicate matters if you use software like syslog. This is true, by default.
280 cattr_accessor :colorize_logging
281 @@colorize_logging = true
282
60de8c1 David Heinemeier Hansson Added Base.default_timezone accessor that determines whether to use Time...
dhh authored
283 # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
284 # This is set to :local by default.
285 cattr_accessor :default_timezone
286 @@default_timezone = :local
287
db045db David Heinemeier Hansson Initial
dhh authored
288 class << self # Class methods
289 # Returns objects for the records responding to either a specific id (1), a list of ids (1, 5, 6) or an array of ids.
290 # If only one ID is specified, that object is returned directly. If more than one ID is specified, an array is returned.
291 # Examples:
292 # Person.find(1) # returns the object for ID = 1
293 # Person.find(1, 2, 6) # returns an array for objects with IDs in (1, 2, 6)
294 # Person.find([7, 17]) # returns an array for objects with IDs in (7, 17)
57ed93e David Heinemeier Hansson Fixed that Base#find will return an array if given an array -- regardles...
dhh authored
295 # Person.find([1]) # returns an array for objects the object with ID = 1
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
296 #
297 # The last argument may be a Hash of find options. Currently, +conditions+ is the only option, behaving the same as with +find_all+.
569f2ea David Heinemeier Hansson Added the possibility of specifying the remaining options for find_first...
dhh authored
298 # Person.find(1, :conditions => "associate_id = 5"
299 # Person.find(1, 2, 6, :conditions => "status = 'active'"
300 # Person.find([7, 17], :conditions => ["sanitize_me = ?", "bare'quote"]
5cd38ca David Heinemeier Hansson Added documentation about named bind variables
dhh authored
301 # Person.find(25, :conditions => ["name = :name AND age = :age", { :name => "Mary", :age => 22 }]
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
302 #
db045db David Heinemeier Hansson Initial
dhh authored
303 # +RecordNotFound+ is raised if no record can be found.
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
304 def find(*args)
305 options = extract_options_from_args!(args)
db045db David Heinemeier Hansson Initial
dhh authored
306
abc895b David Heinemeier Hansson Added new Base.find API and deprecated find_all, find_first. Added preli...
dhh authored
307 case args.first
308 when :first
309 find(:all, options.merge({ :limit => 1 })).first
310 when :all
311 options[:include] ? find_with_associations(options) : find_by_sql(construct_finder_sql(options))
db045db David Heinemeier Hansson Initial
dhh authored
312 else
abc895b David Heinemeier Hansson Added new Base.find API and deprecated find_all, find_first. Added preli...
dhh authored
313 expects_array = args.first.kind_of?(Array)
314 conditions = " AND #{sanitize_sql(options[:conditions])}" if options[:conditions]
315
316 ids = args.flatten.compact.uniq
317 case ids.size
318 when 0
319 raise RecordNotFound, "Couldn't find #{name} without an ID#{conditions}"
320 when 1
9e799e7 David Heinemeier Hansson Prefix primary key with table name so it works as part of a joined fetch
dhh authored
321 if result = find(:first, options.merge({ :conditions => "#{table_name}.#{primary_key} = #{sanitize(ids.first)}#{conditions}" }))
abc895b David Heinemeier Hansson Added new Base.find API and deprecated find_all, find_first. Added preli...
dhh authored
322 return expects_array ? [ result ] : result
323 else
324 raise RecordNotFound, "Couldn't find #{name} with ID=#{ids.first}#{conditions}"
325 end
326 else
327 # Find multiple ids
328 ids_list = ids.map { |id| sanitize(id) }.join(',')
9e799e7 David Heinemeier Hansson Prefix primary key with table name so it works as part of a joined fetch
dhh authored
329 result = find(:all, options.merge({ :conditions => "#{table_name}.#{primary_key} IN (#{ids_list})#{conditions}", :order => primary_key }))
abc895b David Heinemeier Hansson Added new Base.find API and deprecated find_all, find_first. Added preli...
dhh authored
330 if result.size == ids.size
331 return result
332 else
333 raise RecordNotFound, "Couldn't find all #{name.pluralize} with IDs (#{ids_list})#{conditions}"
334 end
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
335 end
db045db David Heinemeier Hansson Initial
dhh authored
336 end
337 end
338
a775cb1 David Heinemeier Hansson Added the option for sanitizing find_by_sql and the offset parts in regu...
dhh authored
339 # Works like find_all, but requires a complete SQL string. Examples:
db045db David Heinemeier Hansson Initial
dhh authored
340 # Post.find_by_sql "SELECT p.*, c.author FROM posts p, comments c WHERE p.id = c.post_id"
a775cb1 David Heinemeier Hansson Added the option for sanitizing find_by_sql and the offset parts in regu...
dhh authored
341 # Post.find_by_sql ["SELECT * FROM posts WHERE author = ? AND created > ?", author_id, start_date]
db045db David Heinemeier Hansson Initial
dhh authored
342 def find_by_sql(sql)
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
343 connection.select_all(sanitize_sql(sql), "#{name} Load").inject([]) { |objects, record| objects << instantiate(record) }
db045db David Heinemeier Hansson Initial
dhh authored
344 end
345
abc895b David Heinemeier Hansson Added new Base.find API and deprecated find_all, find_first. Added preli...
dhh authored
346 # Returns true if the given +id+ represents the primary key of a record in the database, false otherwise.
347 # Example:
348 # Person.exists?(5)
349 def exists?(id)
350 !find_first("#{primary_key} = #{sanitize(id)}").nil? rescue false
db045db David Heinemeier Hansson Initial
dhh authored
351 end
abc895b David Heinemeier Hansson Added new Base.find API and deprecated find_all, find_first. Added preli...
dhh authored
352
db045db David Heinemeier Hansson Initial
dhh authored
353 # Creates an object, instantly saves it as a record (if the validation permits it), and returns it. If the save
354 # fail under validations, the unsaved object is still returned.
355 def create(attributes = nil)
efa81da David Heinemeier Hansson Added the option of supplying an array of ids and attributes to Base#upd...
dhh authored
356 if attributes.is_a?(Array)
357 attributes.collect { |attr| create(attr) }
358 else
359 object = new(attributes)
360 object.save
361 object
362 end
db045db David Heinemeier Hansson Initial
dhh authored
363 end
364
365 # Finds the record from the passed +id+, instantly saves it with the passed +attributes+ (if the validation permits it),
366 # and returns it. If the save fail under validations, the unsaved object is still returned.
367 def update(id, attributes)
efa81da David Heinemeier Hansson Added the option of supplying an array of ids and attributes to Base#upd...
dhh authored
368 if id.is_a?(Array)
369 idx = -1
370 id.collect { |id| idx += 1; update(id, attributes[idx]) }
371 else
372 object = find(id)
373 object.update_attributes(attributes)
374 object
375 end
db045db David Heinemeier Hansson Initial
dhh authored
376 end
377
efa81da David Heinemeier Hansson Added the option of supplying an array of ids and attributes to Base#upd...
dhh authored
378 # Deletes the record with the given +id+ without instantiating an object first. If an array of ids is provided, all of them
379 # are deleted.
648b8fd David Heinemeier Hansson Added Base.destroy and Base.delete to remove records without holding a r...
dhh authored
380 def delete(id)
efa81da David Heinemeier Hansson Added the option of supplying an array of ids and attributes to Base#upd...
dhh authored
381 delete_all([ "#{primary_key} IN (?)", id ])
648b8fd David Heinemeier Hansson Added Base.destroy and Base.delete to remove records without holding a r...
dhh authored
382 end
383
384 # Destroys the record with the given +id+ by instantiating the object and calling #destroy (all the callbacks are the triggered).
efa81da David Heinemeier Hansson Added the option of supplying an array of ids and attributes to Base#upd...
dhh authored
385 # If an array of ids is provided, all of them are destroyed.
648b8fd David Heinemeier Hansson Added Base.destroy and Base.delete to remove records without holding a r...
dhh authored
386 def destroy(id)
efa81da David Heinemeier Hansson Added the option of supplying an array of ids and attributes to Base#upd...
dhh authored
387 id.is_a?(Array) ? id.each { |id| destroy(id) } : find(id).destroy
648b8fd David Heinemeier Hansson Added Base.destroy and Base.delete to remove records without holding a r...
dhh authored
388 end
389
69cb942 David Heinemeier Hansson Changed the interface on AbstractAdapter to require that adapters return...
dhh authored
390 # Updates all records with the SET-part of an SQL update statement in +updates+ and returns an integer with the number of rows updates.
391 # A subset of the records can be selected by specifying +conditions+. Example:
db045db David Heinemeier Hansson Initial
dhh authored
392 # Billing.update_all "category = 'authorized', approved = 1", "author = 'David'"
393 def update_all(updates, conditions = nil)
566a369 David Heinemeier Hansson Added that update_all calls sanitize_sql on its updates argument, so stu...
dhh authored
394 sql = "UPDATE #{table_name} SET #{sanitize_sql(updates)} "
db045db David Heinemeier Hansson Initial
dhh authored
395 add_conditions!(sql, conditions)
69cb942 David Heinemeier Hansson Changed the interface on AbstractAdapter to require that adapters return...
dhh authored
396 return connection.update(sql, "#{name} Update")
db045db David Heinemeier Hansson Initial
dhh authored
397 end
0d2db8a David Heinemeier Hansson Added Base.update_collection that can update an array of id/attribute pa...
dhh authored
398
db045db David Heinemeier Hansson Initial
dhh authored
399 # Destroys the objects for all the records that matches the +condition+ by instantiating each object and calling
400 # the destroy method. Example:
401 # Person.destroy_all "last_login < '2004-04-04'"
402 def destroy_all(conditions = nil)
403 find_all(conditions).each { |object| object.destroy }
404 end
405
406 # Deletes all the records that matches the +condition+ without instantiating the objects first (and hence not
407 # calling the destroy method). Example:
408 # Post.destroy_all "person_id = 5 AND (category = 'Something' OR category = 'Else')"
409 def delete_all(conditions = nil)
410 sql = "DELETE FROM #{table_name} "
411 add_conditions!(sql, conditions)
412 connection.delete(sql, "#{name} Delete all")
413 end
414
415 # Returns the number of records that meets the +conditions+. Zero is returned if no records match. Example:
416 # Product.count "sales > 1"
06a6133 David Heinemeier Hansson Added a join parameter as the third argument to Base.find_first and as t...
dhh authored
417 def count(conditions = nil, joins = nil)
418 tbl_var_name = joins ? table_name[0,1].downcase : ""
419 sql = "SELECT COUNT(*) FROM #{table_name} #{tbl_var_name} "
420 sql << ", #{joins} " if joins
db045db David Heinemeier Hansson Initial
dhh authored
421 add_conditions!(sql, conditions)
422 count_by_sql(sql)
423 end
424
425 # Returns the result of an SQL statement that should only include a COUNT(*) in the SELECT part.
426 # Product.count "SELECT COUNT(*) FROM sales s, customers c WHERE s.customer_id = c.id"
427 def count_by_sql(sql)
a775cb1 David Heinemeier Hansson Added the option for sanitizing find_by_sql and the offset parts in regu...
dhh authored
428 sql = sanitize_conditions(sql)
db045db David Heinemeier Hansson Initial
dhh authored
429 count = connection.select_one(sql, "#{name} Count").values.first
430 return count ? count.to_i : 0
431 end
432
433 # Increments the specified counter by one. So <tt>DiscussionBoard.increment_counter("post_count",
434 # discussion_board_id)</tt> would increment the "post_count" counter on the board responding to discussion_board_id.
435 # This is used for caching aggregate values, so that they doesn't need to be computed every time. Especially important
436 # for looping over a collection where each element require a number of aggregate values. Like the DiscussionBoard
437 # that needs to list both the number of posts and comments.
438 def increment_counter(counter_name, id)
4940383 David Heinemeier Hansson Fixed value quoting in all generated SQL statements, so that integers ar...
dhh authored
439 update_all "#{counter_name} = #{counter_name} + 1", "#{primary_key} = #{quote(id)}"
db045db David Heinemeier Hansson Initial
dhh authored
440 end
441
442 # Works like increment_counter, but decrements instead.
443 def decrement_counter(counter_name, id)
4940383 David Heinemeier Hansson Fixed value quoting in all generated SQL statements, so that integers ar...
dhh authored
444 update_all "#{counter_name} = #{counter_name} - 1", "#{primary_key} = #{quote(id)}"
db045db David Heinemeier Hansson Initial
dhh authored
445 end
446
447 # Attributes named in this macro are protected from mass-assignment, such as <tt>new(attributes)</tt> and
448 # <tt>attributes=(attributes)</tt>. Their assignment will simply be ignored. Instead, you can use the direct writer
449 # methods to do assignment. This is meant to protect sensitive attributes to be overwritten by URL/form hackers. Example:
450 #
451 # class Customer < ActiveRecord::Base
452 # attr_protected :credit_rating
453 # end
454 #
455 # customer = Customer.new("name" => David, "credit_rating" => "Excellent")
456 # customer.credit_rating # => nil
457 # customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
458 # customer.credit_rating # => nil
459 #
460 # customer.credit_rating = "Average"
461 # customer.credit_rating # => "Average"
462 def attr_protected(*attributes)
463 write_inheritable_array("attr_protected", attributes)
464 end
465
098fa94 David Heinemeier Hansson Fixed documentation snafus #575, #576, #577, #585
dhh authored
466 # Returns an array of all the attributes that have been protected from mass-assignment.
db045db David Heinemeier Hansson Initial
dhh authored
467 def protected_attributes # :nodoc:
468 read_inheritable_attribute("attr_protected")
469 end
470
471 # If this macro is used, only those attributed named in it will be accessible for mass-assignment, such as
472 # <tt>new(attributes)</tt> and <tt>attributes=(attributes)</tt>. This is the more conservative choice for mass-assignment
473 # protection. If you'd rather start from an all-open default and restrict attributes as needed, have a look at
474 # attr_protected.
475 def attr_accessible(*attributes)
476 write_inheritable_array("attr_accessible", attributes)
477 end
478
098fa94 David Heinemeier Hansson Fixed documentation snafus #575, #576, #577, #585
dhh authored
479 # Returns an array of all the attributes that have been made accessible to mass-assignment.
db045db David Heinemeier Hansson Initial
dhh authored
480 def accessible_attributes # :nodoc:
481 read_inheritable_attribute("attr_accessible")
482 end
483
484 # Specifies that the attribute by the name of +attr_name+ should be serialized before saving to the database and unserialized
485 # after loading from the database. The serialization is done through YAML. If +class_name+ is specified, the serialized
098fa94 David Heinemeier Hansson Fixed documentation snafus #575, #576, #577, #585
dhh authored
486 # object must be of that class on retrieval or +SerializationTypeMismatch+ will be raised.
db045db David Heinemeier Hansson Initial
dhh authored
487 def serialize(attr_name, class_name = Object)
488 write_inheritable_attribute("attr_serialized", serialized_attributes.update(attr_name.to_s => class_name))
489 end
490
491 # Returns a hash of all the attributes that have been specified for serialization as keys and their class restriction as values.
492 def serialized_attributes
493 read_inheritable_attribute("attr_serialized") || { }
494 end
495
496 # Guesses the table name (in forced lower-case) based on the name of the class in the inheritance hierarchy descending
497 # directly from ActiveRecord. So if the hierarchy looks like: Reply < Message < ActiveRecord, then Message is used
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
498 # to guess the table name from even when called on Reply. The rules used to do the guess are handled by the Inflector class
499 # in Active Support, which knows almost all common English inflections (report a bug if your inflection isn't covered).
db045db David Heinemeier Hansson Initial
dhh authored
500 #
501 # Additionally, the class-level table_name_prefix is prepended to the table_name and the table_name_suffix is appended.
502 # So if you have "myapp_" as a prefix, the table name guess for an Account class becomes "myapp_accounts".
503 #
504 # You can also overwrite this class method to allow for unguessable links, such as a Mouse class with a link to a
505 # "mice" table. Example:
506 #
507 # class Mouse < ActiveRecord::Base
9982578 David Heinemeier Hansson Doc fix #805
dhh authored
508 # set_table_name "mice"
db045db David Heinemeier Hansson Initial
dhh authored
509 # end
dcc4868 David Heinemeier Hansson Fixed that Base.table_name would expect a parameter when used in has_and...
dhh authored
510 def table_name
511 table_name_prefix + undecorated_table_name(class_name_of_active_record_descendant(self)) + table_name_suffix
db045db David Heinemeier Hansson Initial
dhh authored
512 end
513
098fa94 David Heinemeier Hansson Fixed documentation snafus #575, #576, #577, #585
dhh authored
514 # Defines the primary key field -- can be overridden in subclasses. Overwriting will negate any effect of the
db045db David Heinemeier Hansson Initial
dhh authored
515 # primary_key_prefix_type setting, though.
516 def primary_key
517 case primary_key_prefix_type
518 when :table_name
519 Inflector.foreign_key(class_name_of_active_record_descendant(self), false)
520 when :table_name_with_underscore
521 Inflector.foreign_key(class_name_of_active_record_descendant(self))
522 else
523 "id"
524 end
525 end
526
527 # Defines the column name for use with single table inheritance -- can be overridden in subclasses.
528 def inheritance_column
529 "type"
530 end
531
1aa82b3 David Heinemeier Hansson Added keyword-style approach to defining the custom relational bindings ...
dhh authored
532 # Sets the table name to use to the given value, or (if the value
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
533 # is nil or false) to the value returned by the given block.
1aa82b3 David Heinemeier Hansson Added keyword-style approach to defining the custom relational bindings ...
dhh authored
534 #
535 # Example:
536 #
537 # class Project < ActiveRecord::Base
538 # set_table_name "project"
539 # end
540 def set_table_name( value=nil, &block )
541 define_attr_method :table_name, value, &block
542 end
543 alias :table_name= :set_table_name
544
545 # Sets the name of the primary key column to use to the given value,
546 # or (if the value is nil or false) to the value returned by the given
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
547 # block.
1aa82b3 David Heinemeier Hansson Added keyword-style approach to defining the custom relational bindings ...
dhh authored
548 #
549 # Example:
550 #
551 # class Project < ActiveRecord::Base
552 # set_primary_key "sysid"
553 # end
554 def set_primary_key( value=nil, &block )
555 define_attr_method :primary_key, value, &block
556 end
557 alias :primary_key= :set_primary_key
558
559 # Sets the name of the inheritance column to use to the given value,
560 # or (if the value # is nil or false) to the value returned by the
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
561 # given block.
1aa82b3 David Heinemeier Hansson Added keyword-style approach to defining the custom relational bindings ...
dhh authored
562 #
563 # Example:
564 #
565 # class Project < ActiveRecord::Base
566 # set_inheritance_column do
567 # original_inheritance_column + "_id"
568 # end
569 # end
570 def set_inheritance_column( value=nil, &block )
571 define_attr_method :inheritance_column, value, &block
572 end
573 alias :inheritance_column= :set_inheritance_column
574
db045db David Heinemeier Hansson Initial
dhh authored
575 # Turns the +table_name+ back into a class name following the reverse rules of +table_name+.
576 def class_name(table_name = table_name) # :nodoc:
577 # remove any prefix and/or suffix from the table name
578 class_name = Inflector.camelize(table_name[table_name_prefix.length..-(table_name_suffix.length + 1)])
579 class_name = Inflector.singularize(class_name) if pluralize_table_names
580 return class_name
581 end
582
583 # Returns an array of column objects for the table associated with this class.
584 def columns
585 @columns ||= connection.columns(table_name, "#{name} Columns")
586 end
587
588 # Returns an array of column objects for the table associated with this class.
589 def columns_hash
590 @columns_hash ||= columns.inject({}) { |hash, column| hash[column.name] = column; hash }
591 end
592
593 # Returns an array of columns objects where the primary id, all columns ending in "_id" or "_count",
594 # and columns used for single table inheritance has been removed.
595 def content_columns
596 @content_columns ||= columns.reject { |c| c.name == primary_key || c.name =~ /(_id|_count)$/ || c.name == inheritance_column }
597 end
598
599 # 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
600 # 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
601 # is available.
602 def column_methods_hash
603 @dynamic_methods_hash ||= columns_hash.keys.inject(Hash.new(false)) do |methods, attr|
604 methods[attr.to_sym] = true
605 methods["#{attr}=".to_sym] = true
606 methods["#{attr}?".to_sym] = true
dad37cf David Heinemeier Hansson FormHelper should only use *_before_type_cast if they available on the m...
dhh authored
607 methods["#{attr}_before_type_cast".to_sym] = true
db045db David Heinemeier Hansson Initial
dhh authored
608 methods
609 end
610 end
1314f48 David Heinemeier Hansson Added methods for resetting the cached information on classes that you w...
dhh authored
611
612 # Resets all the cached information about columns, which will cause they to be reloaded on the next request.
613 def reset_column_information
614 @columns = @columns_hash = @content_columns = @dynamic_methods_hash = nil
615 end
616
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
617 def reset_column_information_and_inheritable_attributes_for_all_subclasses#:nodoc:
1314f48 David Heinemeier Hansson Added methods for resetting the cached information on classes that you w...
dhh authored
618 subclasses.each { |klass| klass.reset_inheritable_attributes; klass.reset_column_information }
619 end
db045db David Heinemeier Hansson Initial
dhh authored
620
621 # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
622 # Person.human_attribute_name("first_name") # => "First name"
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
623 # Deprecated in favor of just calling "first_name".humanize
624 def human_attribute_name(attribute_key_name) #:nodoc:
1f7e72f David Heinemeier Hansson Made human_attribute_name(attribute_key_name) use Inflector.humanize
dhh authored
625 attribute_key_name.humanize
db045db David Heinemeier Hansson Initial
dhh authored
626 end
627
628 def descends_from_active_record? # :nodoc:
605bc77 David Heinemeier Hansson Added a better exception for when a type column is used in a table witho...
dhh authored
629 superclass == Base || !columns_hash.has_key?(inheritance_column)
db045db David Heinemeier Hansson Initial
dhh authored
630 end
631
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
632 def quote(object) #:nodoc:
4940383 David Heinemeier Hansson Fixed value quoting in all generated SQL statements, so that integers ar...
dhh authored
633 connection.quote(object)
634 end
635
636 # Used to sanitize objects before they're used in an SELECT SQL-statement. Delegates to <tt>connection.quote</tt>.
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
637 def sanitize(object) #:nodoc:
4940383 David Heinemeier Hansson Fixed value quoting in all generated SQL statements, so that integers ar...
dhh authored
638 connection.quote(object)
db045db David Heinemeier Hansson Initial
dhh authored
639 end
640
641 # Used to aggregate logging and benchmark, so you can measure and represent multiple statements in a single block.
642 # Usage (hides all the SQL calls for the individual actions and calculates total runtime for them all):
643 #
644 # Project.benchmark("Creating project") do
645 # project = Project.create("name" => "stuff")
646 # project.create_manager("name" => "David")
647 # project.milestones << Milestone.find_all
648 # end
649 def benchmark(title)
650 result = nil
c00bf5f David Heinemeier Hansson Fixed the verbosity of using the AR store
dhh authored
651 bm = Benchmark.measure { result = silence { yield } }
aec31cd David Heinemeier Hansson Fixed Base.silence/benchmark to only log if a logger has been configured...
dhh authored
652 logger.info "#{title} (#{sprintf("%f", bm.real)})" if logger
c00bf5f David Heinemeier Hansson Fixed the verbosity of using the AR store
dhh authored
653 return result
654 end
655
656 # Silences the logger for the duration of the block.
657 def silence
658 result = nil
aec31cd David Heinemeier Hansson Fixed Base.silence/benchmark to only log if a logger has been configured...
dhh authored
659 logger.level = Logger::ERROR if logger
c00bf5f David Heinemeier Hansson Fixed the verbosity of using the AR store
dhh authored
660 result = yield
aec31cd David Heinemeier Hansson Fixed Base.silence/benchmark to only log if a logger has been configured...
dhh authored
661 logger.level = Logger::DEBUG if logger
db045db David Heinemeier Hansson Initial
dhh authored
662 return result
663 end
664
97849de David Heinemeier Hansson Fixed that association proxies would fail === tests like PremiumSubscrip...
dhh authored
665 # Overwrite the default class equality method to provide support for association proxies.
666 def ===(object)
667 object.is_a?(self)
668 end
669
db045db David Heinemeier Hansson Initial
dhh authored
670 private
671 # Finder methods must instantiate through this method to work with the single-table inheritance model
672 # that makes it possible to create objects of different types from the same table.
673 def instantiate(record)
605bc77 David Heinemeier Hansson Added a better exception for when a type column is used in a table witho...
dhh authored
674 require_association_class(record[inheritance_column])
675
676 begin
677 object = record_with_type?(record) ? compute_type(record[inheritance_column]).allocate : allocate
678 rescue NameError
679 raise(
680 SubclassNotFound,
681 "The single-table inheritance mechanism failed to locate the subclass: '#{record[inheritance_column]}'. " +
682 "This error is raised because the column '#{inheritance_column}' is reserved for storing the class in case of inheritance. " +
683 "Please rename this column if you didn't intend it to be used for storing the inheritance class " +
684 "or overwrite #{self.to_s}.inheritance_column to use another column for that information."
685 )
686 end
687
db045db David Heinemeier Hansson Initial
dhh authored
688 object.instance_variable_set("@attributes", record)
689 return object
690 end
691
692 # Returns true if the +record+ has a single table inheritance column and is using it.
693 def record_with_type?(record)
694 record.include?(inheritance_column) && !record[inheritance_column].nil? &&
695 !record[inheritance_column].empty?
696 end
697
698 # Returns the name of the type of the record using the current module as a prefix. So descendents of
699 # MyApp::Business::Account would be appear as "MyApp::Business::AccountSubclass".
700 def type_name_with_module(type_name)
701 self.name =~ /::/ ? self.name.scan(/(.*)::/).first.first + "::" + type_name : type_name
702 end
703
abc895b David Heinemeier Hansson Added new Base.find API and deprecated find_all, find_first. Added preli...
dhh authored
704 def construct_finder_sql(options)
705 sql = "SELECT * FROM #{table_name} "
706 sql << "#{options[:joins]} " if options[:joins]
707 add_conditions!(sql, options[:conditions])
708 sql << "ORDER BY #{options[:order]} " if options[:order]
efb55d1 David Heinemeier Hansson Allow order, conditions, and joins in finds that include associations
dhh authored
709 add_limit!(sql, options)
710
711 return sql
712 end
abc895b David Heinemeier Hansson Added new Base.find API and deprecated find_all, find_first. Added preli...
dhh authored
713
efb55d1 David Heinemeier Hansson Allow order, conditions, and joins in finds that include associations
dhh authored
714 def add_limit!(sql, options)
abc895b David Heinemeier Hansson Added new Base.find API and deprecated find_all, find_first. Added preli...
dhh authored
715 if options[:limit] && options[:offset]
716 connection.add_limit_with_offset!(sql, options[:limit].to_i, options[:offset].to_i)
717 elsif options[:limit]
718 connection.add_limit_without_offset!(sql, options[:limit].to_i)
719 end
720 end
721
db045db David Heinemeier Hansson Initial
dhh authored
722 # Adds a sanitized version of +conditions+ to the +sql+ string. Note that it's the passed +sql+ string is changed.
723 def add_conditions!(sql, conditions)
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
724 sql << "WHERE #{sanitize_sql(conditions)} " unless conditions.nil?
db045db David Heinemeier Hansson Initial
dhh authored
725 sql << (conditions.nil? ? "WHERE " : " AND ") + type_condition unless descends_from_active_record?
726 end
727
728 def type_condition
729 " (" + subclasses.inject("#{inheritance_column} = '#{Inflector.demodulize(name)}' ") do |condition, subclass|
4940383 David Heinemeier Hansson Fixed value quoting in all generated SQL statements, so that integers ar...
dhh authored
730 condition << "OR #{inheritance_column} = '#{Inflector.demodulize(subclass.name)}' "
db045db David Heinemeier Hansson Initial
dhh authored
731 end + ") "
732 end
733
734 # Guesses the table name, but does not decorate it with prefix and suffix information.
735 def undecorated_table_name(class_name = class_name_of_active_record_descendant(self))
736 table_name = Inflector.underscore(Inflector.demodulize(class_name))
737 table_name = Inflector.pluralize(table_name) if pluralize_table_names
738 return table_name
739 end
740
ac8fd7d David Heinemeier Hansson Added dynamic attribute-based finders as a cleaner way of getting object...
dhh authored
741 # Enables dynamic finders like find_by_user_name(user_name) and find_by_user_name_and_password(user_name, password) that are turned into
959f362 David Heinemeier Hansson Added find_all style to the new dynamic finders
dhh authored
742 # find_first(["user_name = ?", user_name]) and find_first(["user_name = ? AND password = ?", user_name, password]) respectively. Also works
743 # for find_all, but using find_all_by_amount(50) that are turned into find_all(["amount = ?", 50]).
569f2ea David Heinemeier Hansson Added the possibility of specifying the remaining options for find_first...
dhh authored
744 #
745 # It's even possible to use all the additional parameters to find_first and find_all. For example, the full interface for find_all_by_amount
746 # is actually find_all_by_amount(amount, orderings = nil, limit = nil, joins = nil).
ac8fd7d David Heinemeier Hansson Added dynamic attribute-based finders as a cleaner way of getting object...
dhh authored
747 def method_missing(method_id, *arguments)
748 method_name = method_id.id2name
749
06b8936 David Heinemeier Hansson Fixed that find_by_* would fail when column names had numbers #670 [deme...
dhh authored
750 if method_name =~ /find_(all_by|by)_([_a-z][_a-z\d]*)/
959f362 David Heinemeier Hansson Added find_all style to the new dynamic finders
dhh authored
751 finder, attributes = ($1 == "all_by" ? :find_all : :find_first), $2.split("_and_")
ac8fd7d David Heinemeier Hansson Added dynamic attribute-based finders as a cleaner way of getting object...
dhh authored
752 attributes.each { |attr_name| super unless column_methods_hash[attr_name.intern] }
93ec130 David Heinemeier Hansson Fixed that the dynamic finders didnt treat nil as a "IS NULL" but rather...
dhh authored
753
754 attr_index = -1
28edbca David Heinemeier Hansson Fixed that the dynamic finder like find_all_by_something_boolean(false) ...
dhh authored
755 conditions = attributes.collect { |attr_name| attr_index += 1; "#{attr_name} #{arguments[attr_index].nil? ? "IS" : "="} ? " }.join(" AND ")
569f2ea David Heinemeier Hansson Added the possibility of specifying the remaining options for find_first...
dhh authored
756 send(finder, [conditions, *arguments[0...attributes.length]], *arguments[attributes.length..-1])
ac8fd7d David Heinemeier Hansson Added dynamic attribute-based finders as a cleaner way of getting object...
dhh authored
757 else
758 super
759 end
760 end
db045db David Heinemeier Hansson Initial
dhh authored
761
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
762 # Defines an "attribute" method (like #inheritance_column or
763 # #table_name). A new (class) method will be created with the
764 # given name. If a value is specified, the new method will
765 # return that value (as a string). Otherwise, the given block
766 # will be used to compute the value of the method.
767 #
768 # The original method will be aliased, with the new name being
769 # prefixed with "original_". This allows the new method to
770 # access the original value.
771 #
772 # Example:
773 #
774 # class A < ActiveRecord::Base
775 # define_attr_method :primary_key, "sysid"
776 # define_attr_method( :inheritance_column ) do
777 # original_inheritance_column + "_id"
778 # end
779 # end
780 def define_attr_method(name, value=nil, &block)
781 sing = class << self; self; end
782 block = proc { value.to_s } if value
783 sing.send( :alias_method, "original_#{name}", name )
784 sing.send( :define_method, name, &block )
785 end
786
db045db David Heinemeier Hansson Initial
dhh authored
787 protected
788 def subclasses
789 @@subclasses[self] ||= []
790 @@subclasses[self] + extra = @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
791 end
792
793 # Returns the class type of the record using the current module as a prefix. So descendents of
794 # MyApp::Business::Account would be appear as MyApp::Business::AccountSubclass.
795 def compute_type(type_name)
796 type_name_with_module(type_name).split("::").inject(Object) do |final_type, part|
797 final_type = final_type.const_get(part)
798 end
799 end
800
801 # Returns the name of the class descending directly from ActiveRecord in the inheritance hierarchy.
802 def class_name_of_active_record_descendant(klass)
803 if klass.superclass == Base
804 return klass.name
805 elsif klass.superclass.nil?
806 raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord"
807 else
808 class_name_of_active_record_descendant(klass.superclass)
809 end
810 end
811
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
812 # Accepts an array or string. The string is returned untouched, but the array has each value
813 # sanitized and interpolated into the sql statement.
814 # ["name='%s' and group_id='%s'", "foo'bar", 4] returns "name='foo''bar' and group_id='4'"
815 def sanitize_sql(ary)
816 return ary unless ary.is_a?(Array)
3e7d191 David Heinemeier Hansson Added bind-style variable interpolation for the condition arrays that us...
dhh authored
817
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
818 statement, *values = ary
819 if values.first.is_a?(Hash) and statement =~ /:\w+/
820 replace_named_bind_variables(statement, values.first)
821 elsif statement.include?('?')
554597d David Heinemeier Hansson Added named bind-style variable interpolation #281 [Michael Koziarski]
dhh authored
822 replace_bind_variables(statement, values)
823 else
4940383 David Heinemeier Hansson Fixed value quoting in all generated SQL statements, so that integers ar...
dhh authored
824 statement % values.collect { |value| connection.quote_string(value.to_s) }
554597d David Heinemeier Hansson Added named bind-style variable interpolation #281 [Michael Koziarski]
dhh authored
825 end
3e7d191 David Heinemeier Hansson Added bind-style variable interpolation for the condition arrays that us...
dhh authored
826 end
827
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
828 alias_method :sanitize_conditions, :sanitize_sql
829
3e7d191 David Heinemeier Hansson Added bind-style variable interpolation for the condition arrays that us...
dhh authored
830 def replace_bind_variables(statement, values)
9322168 David Heinemeier Hansson Restored bind arity checking #412 [bitsweat]
dhh authored
831 raise_if_bind_arity_mismatch(statement, statement.count('?'), values.size)
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
832 bound = values.dup
872ddaf David Heinemeier Hansson Added bind-named arrays for interpolating a group of ids or strings in c...
dhh authored
833 statement.gsub('?') { quote_bound_value(bound.shift) }
554597d David Heinemeier Hansson Added named bind-style variable interpolation #281 [Michael Koziarski]
dhh authored
834 end
835
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
836 def replace_named_bind_variables(statement, bind_vars)
9322168 David Heinemeier Hansson Restored bind arity checking #412 [bitsweat]
dhh authored
837 raise_if_bind_arity_mismatch(statement, statement.scan(/:(\w+)/).uniq.size, bind_vars.size)
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
838 statement.gsub(/:(\w+)/) do
839 match = $1.to_sym
840 if bind_vars.has_key?(match)
872ddaf David Heinemeier Hansson Added bind-named arrays for interpolating a group of ids or strings in c...
dhh authored
841 quote_bound_value(bind_vars[match])
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
842 else
843 raise PreparedStatementInvalid, "missing value for :#{match} in #{statement}"
554597d David Heinemeier Hansson Added named bind-style variable interpolation #281 [Michael Koziarski]
dhh authored
844 end
845 end
9322168 David Heinemeier Hansson Restored bind arity checking #412 [bitsweat]
dhh authored
846 end
847
872ddaf David Heinemeier Hansson Added bind-named arrays for interpolating a group of ids or strings in c...
dhh authored
848 def quote_bound_value(value)
849 case value
850 when Array
851 value.map { |v| connection.quote(v) }.join(',')
852 else
853 connection.quote(value)
854 end
855 end
856
9322168 David Heinemeier Hansson Restored bind arity checking #412 [bitsweat]
dhh authored
857 def raise_if_bind_arity_mismatch(statement, expected, provided)
858 unless expected == provided
859 raise PreparedStatementInvalid, "wrong number of bind variables (#{provided} for #{expected}) in: #{statement}"
860 end
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
861 end
554597d David Heinemeier Hansson Added named bind-style variable interpolation #281 [Michael Koziarski]
dhh authored
862
6bd672e David Heinemeier Hansson Added that Base#find takes an optional options hash, including :conditio...
dhh authored
863 def extract_options_from_args!(args)
864 if args.last.is_a?(Hash) then args.pop else {} end
db045db David Heinemeier Hansson Initial
dhh authored
865 end
aaf9a45 David Heinemeier Hansson Added Base.validate_uniqueness thatv alidates whether the value of the s...
dhh authored
866
867 def encode_quoted_value(value)
868 quoted_value = connection.quote(value)
869 quoted_value = "'#{quoted_value[1..-2].gsub(/\'/, "\\\\'")}'" if quoted_value.include?("\\\'")
870 quoted_value
871 end
db045db David Heinemeier Hansson Initial
dhh authored
872 end
873
874 public
875 # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
876 # attributes but not yet saved (pass a hash with key names matching the associated table column names).
877 # In both instances, valid attribute keys are determined by the column names of the associated table --
878 # hence you can't have attributes that aren't part of the table columns.
879 def initialize(attributes = nil)
880 @attributes = attributes_from_column_definition
881 @new_record = true
882 ensure_proper_type
883 self.attributes = attributes unless attributes.nil?
884 yield self if block_given?
885 end
886
887 # Every Active Record class must use "id" as their primary ID. This getter overwrites the native
888 # id method, which isn't being used in this context.
889 def id
890 read_attribute(self.class.primary_key)
891 end
892
ce23862 David Heinemeier Hansson Added alias_method :to_param, :id to Base, such that Active Record objec...
dhh authored
893 # Enables Active Record objects to be used as URL parameters in Action Pack automatically.
894 alias_method :to_param, :id
895
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
896 def id_before_type_cast #:nodoc:
3e5a880 David Heinemeier Hansson Switched strategy on the id_before_type_cast problem and just did an exp...
dhh authored
897 read_attribute_before_type_cast(self.class.primary_key)
898 end
899
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
900 def quoted_id #:nodoc:
4940383 David Heinemeier Hansson Fixed value quoting in all generated SQL statements, so that integers ar...
dhh authored
901 quote(id, self.class.columns_hash[self.class.primary_key])
902 end
903
db045db David Heinemeier Hansson Initial
dhh authored
904 # Sets the primary ID.
905 def id=(value)
906 write_attribute(self.class.primary_key, value)
907 end
908
909 # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
910 def new_record?
911 @new_record
912 end
913
914 # * No record exists: Creates a new record with values matching those of the object attributes.
915 # * A record does exist: Updates the record with values matching those of the object attributes.
916 def save
917 create_or_update
918 end
919
920 # Deletes the record in the database and freezes this instance to reflect that no changes should
921 # be made (since they can't be persisted).
922 def destroy
923 unless new_record?
924 connection.delete(
925 "DELETE FROM #{self.class.table_name} " +
4940383 David Heinemeier Hansson Fixed value quoting in all generated SQL statements, so that integers ar...
dhh authored
926 "WHERE #{self.class.primary_key} = #{quote(id)}",
db045db David Heinemeier Hansson Initial
dhh authored
927 "#{self.class.name} Destroy"
928 )
929 end
930
931 freeze
932 end
933
934 # Returns a clone of the record that hasn't been assigned an id yet and is treated as a new record.
935 def clone
431e21c David Heinemeier Hansson Fixed Base#clone for use with PostgreSQL #565 [hanson@surgery.wisc.edu]
dhh authored
936 attrs = self.attributes
a3298e5 David Heinemeier Hansson Small indent
dhh authored
937 attrs.delete(self.class.primary_key)
431e21c David Heinemeier Hansson Fixed Base#clone for use with PostgreSQL #565 [hanson@surgery.wisc.edu]
dhh authored
938 cloned_record = self.class.new(attrs)
db045db David Heinemeier Hansson Initial
dhh authored
939 cloned_record
940 end
941
942 # Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
d678997 David Heinemeier Hansson Base#update_attribute isnt subject to validation
dhh authored
943 # Note: This method is overwritten by the Validation module that'll make sure that updates made with this method
944 # doesn't get subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
db045db David Heinemeier Hansson Initial
dhh authored
945 def update_attribute(name, value)
393e98a David Heinemeier Hansson Fixed Base#update_attribute to be indifferent to whether a string or sym...
dhh authored
946 self[name] = value
d1abe80 David Heinemeier Hansson Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it e...
dhh authored
947 save
604a094 David Heinemeier Hansson Added Base#update_attributes that'll accept a hash of attributes and sav...
dhh authored
948 end
949
d678997 David Heinemeier Hansson Base#update_attribute isnt subject to validation
dhh authored
950 # Updates all the attributes in from the passed hash and saves the record. If the object is invalid, the saving will
951 # fail and false will be returned.
604a094 David Heinemeier Hansson Added Base#update_attributes that'll accept a hash of attributes and sav...
dhh authored
952 def update_attributes(attributes)
3bef4c2 David Heinemeier Hansson Made Base#update_attributes actually work
dhh authored
953 self.attributes = attributes
604a094 David Heinemeier Hansson Added Base#update_attributes that'll accept a hash of attributes and sav...
dhh authored
954 return save
db045db David Heinemeier Hansson Initial
dhh authored
955 end
956
d1abe80 David Heinemeier Hansson Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it e...
dhh authored
957 # Initializes the +attribute+ to zero if nil and adds one. Only makes sense for number-based attributes. Returns self.
958 def increment(attribute)
959 self[attribute] ||= 0
960 self[attribute] += 1
961 self
962 end
963
964 # Increments the +attribute+ and saves the record.
965 def increment!(attribute)
966 increment(attribute).update_attribute(attribute, self[attribute])
967 end
968
969 # Initializes the +attribute+ to zero if nil and subtracts one. Only makes sense for number-based attributes. Returns self.
970 def decrement(attribute)
971 self[attribute] ||= 0
972 self[attribute] -= 1
973 self
974 end
975
976 # Decrements the +attribute+ and saves the record.
977 def decrement!(attribute)
978 decrement(attribute).update_attribute(attribute, self[attribute])
979 end
980
981 # Turns an +attribute+ that's currently true into false and vice versa. Returns self.
982 def toggle(attribute)
983 self[attribute] = quote(!send("#{attribute}?", column_for_attribute(attribute)))
984 self
985 end
986
987 # Toggles the +attribute+ and saves the record.
988 def toggle!(attribute)
989 toggle(attribute).update_attribute(attribute, self[attribute])
990 end
991
3ff5c58 David Heinemeier Hansson Added Base#reload that reloads the attributes of an object from the data...
dhh authored
992 # Reloads the attributes of this object from the database.
993 def reload
994 clear_association_cache
995 @attributes.update(self.class.find(self.id).instance_variable_get('@attributes'))
996 return self
997 end
998
db045db David Heinemeier Hansson Initial
dhh authored
999 # Returns the value of attribute identified by <tt>attr_name</tt> after it has been type cast (for example,
1000 # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
1001 # (Alias for the protected read_attribute method).
1002 def [](attr_name)
d1abe80 David Heinemeier Hansson Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it e...
dhh authored
1003 read_attribute(attr_name.to_s)
db045db David Heinemeier Hansson Initial
dhh authored
1004 end
1005
1006 # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
1007 # (Alias for the protected write_attribute method).
1008 def []= (attr_name, value)
d1abe80 David Heinemeier Hansson Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it e...
dhh authored
1009 write_attribute(attr_name.to_s, value)
db045db David Heinemeier Hansson Initial
dhh authored
1010 end
1011
1012 # Allows you to set all the attributes at once by passing in a hash with keys
1013 # matching the attribute names (which again matches the column names). Sensitive attributes can be protected
1014 # from this form of mass-assignment by using the +attr_protected+ macro. Or you can alternatively
1015 # specify which attributes *can* be accessed in with the +attr_accessible+ macro. Then all the
1016 # attributes not included in that won't be allowed to be mass-assigned.
1017 def attributes=(attributes)
1018 return if attributes.nil?
2bfaa05 David Heinemeier Hansson Fixed that symbols can be used on attribute assignment, like page.emails...
dhh authored
1019 attributes.stringify_keys!
db045db David Heinemeier Hansson Initial
dhh authored
1020
1021 multi_parameter_attributes = []
1022 remove_attributes_protected_from_mass_assignment(attributes).each do |k, v|
1023 k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
1024 end
1025 assign_multiparameter_attributes(multi_parameter_attributes)
1026 end
1027
b067bf7 David Heinemeier Hansson Added Base#attributes that returns a hash of all the attributes with the...
dhh authored
1028 # Returns a hash of all the attributes with their names as keys and clones of their objects as values.
1029 def attributes
1030 self.attribute_names.inject({}) do |attributes, name|
1031 begin
1032 attributes[name] = read_attribute(name).clone
55cb8c8 David Heinemeier Hansson Fixed that Active Record objects with float attribute could not be clone...
dhh authored
1033 rescue TypeError, NoMethodError
b067bf7 David Heinemeier Hansson Added Base#attributes that returns a hash of all the attributes with the...
dhh authored
1034 attributes[name] = read_attribute(name)
1035 end
1036 attributes
1037 end
1038 end
1039
db045db David Heinemeier Hansson Initial
dhh authored
1040 # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
1041 # nil nor empty? (the latter only applies to objects that responds to empty?, most notably Strings).
1042 def attribute_present?(attribute)
1043 is_empty = read_attribute(attribute).respond_to?("empty?") ? read_attribute(attribute).empty? : false
1044 @attributes.include?(attribute) && !@attributes[attribute].nil? && !is_empty
1045 end
1046
1047 # Returns an array of names for the attributes available on this object sorted alphabetically.
1048 def attribute_names
1049 @attributes.keys.sort
1050 end
1051
1052 # Returns the column object for the named attribute.
1053 def column_for_attribute(name)
d1abe80 David Heinemeier Hansson Added Base#toggle(attribute) and Base#toggle!(attribute) that makes it e...
dhh authored
1054 self.class.columns_hash[name.to_s]
db045db David Heinemeier Hansson Initial
dhh authored
1055 end
1056
823554e David Heinemeier Hansson Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
1057 # Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
db045db David Heinemeier Hansson Initial
dhh authored
1058 def ==(comparison_object)
823554e David Heinemeier Hansson Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
1059 comparison_object.equal?(self) or (comparison_object.instance_of?(self.class) and comparison_object.id == id)
db045db David Heinemeier Hansson Initial
dhh authored
1060 end
1061
1062 # Delegates to ==
1063 def eql?(comparison_object)
1064 self == (comparison_object)
1065 end
1066
1067 # Delegates to id in order to allow two records of the same type and id to work with something like:
1068 # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
1069 def hash
cfef86c David Heinemeier Hansson Fixed bug in Base#hash method that would treat records with the same str...
dhh authored
1070 id.hash
db045db David Heinemeier Hansson Initial
dhh authored
1071 end
1072
1073 # For checking respond_to? without searching the attributes (which is faster).
1074 alias_method :respond_to_without_attributes?, :respond_to?
1075
1076 # A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
1077 # person.respond_to?("name?") which will all return true.
846f0d5 David Heinemeier Hansson Fixed that the overwritten respond_to? method didn't take two parameters...
dhh authored
1078 def respond_to?(method, include_priv = false)
1079 self.class.column_methods_hash[method.to_sym] || respond_to_without_attributes?(method, include_priv)
db045db David Heinemeier Hansson Initial
dhh authored
1080 end
d82f73e David Heinemeier Hansson Abolished ActionController::Base.require_or_load in favor of require_dep...
dhh authored
1081
db045db David Heinemeier Hansson Initial
dhh authored
1082 private
1083 def create_or_update
1084 if new_record? then create else update end
823554e David Heinemeier Hansson Added support for associating unsaved objects #402 [Tim Bates]
dhh authored
1085 return true
db045db David Heinemeier Hansson Initial
dhh authored
1086 end
1087
1088 # Updates the associated record with values matching those of the instant attributes.
1089 def update
1090 connection.update(
1091 "UPDATE #{self.class.table_name} " +
9a248a8 David Heinemeier Hansson Dont include the primary key in updates -- its unneeded and SQL Server c...
dhh authored
1092 "SET #{quoted_comma_pair_list(connection, attributes_with_quotes(false))} " +
4940383 David Heinemeier Hansson Fixed value quoting in all generated SQL statements, so that integers ar...
dhh authored
1093 "WHERE #{self.class.primary_key} = #{quote(id)}",
db045db David Heinemeier Hansson Initial
dhh authored
1094 "#{self.class.name} Update"
1095 )
1096 end
1097
1098 # Creates a new record with values matching those of the instant attributes.
1099 def create
1100 self.id = connection.insert(
1101 "INSERT INTO #{self.class.table_name} " +
1102 "(#{quoted_column_names.join(', ')}) " +
1103 "VALUES(#{attributes_with_quotes.values.join(', ')})",
1104 "#{self.class.name} Create",
1105 self.class.primary_key, self.id
1106 )
1107
1108 @new_record = false
1109 end
1110
1111 # Sets the attribute used for single table inheritance to this class name if this is not the ActiveRecord descendant.
1112 # Considering the hierarchy Reply < Message < ActiveRecord, this makes it possible to do Reply.new without having to
1113 # set Reply[Reply.inheritance_column] = "Reply" yourself. No such attribute would be set for objects of the
1114 # Message class in that example.
1115 def ensure_proper_type
1116 unless self.class.descends_from_active_record?
1117 write_attribute(self.class.inheritance_column, Inflector.demodulize(self.class.name))
1118 end
1119 end
1120
1121 # Allows access to the object attributes, which are held in the @attributes hash, as were
1122 # they first-class methods. So a Person class with a name attribute can use Person#name and
1123 # Person#name= and never directly use the attributes hash -- except for multiple assigns with
1124 # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
1125 # the completed attribute is not nil or 0.
1126 #
1127 # It's also possible to instantiate related objects, so a Client class belonging to the clients
1128 # table with a master_id foreign key can instantiate master through Client#master.
1129 def method_missing(method_id, *arguments)
1130 method_name = method_id.id2name
daf3e92 David Heinemeier Hansson Added a db2 adapter that only depends on the Ruby/DB2 bindings (http://r...
dhh authored
1131
db045db David Heinemeier Hansson Initial
dhh authored
1132 if method_name =~ read_method? && @attributes.include?($1)
1133 return read_attribute($1)
7b5ed66 David Heinemeier Hansson Added respondence to *_before_type_cast for all attributes to return the...
dhh authored
1134 elsif method_name =~ read_untyped_method? && @attributes.include?($1)
1135 return read_attribute_before_type_cast($1)
db045db David Heinemeier Hansson Initial
dhh authored
1136 elsif method_name =~ write_method? && @attributes.include?($1)
1137 write_attribute($1, arguments[0])
1138 elsif method_name =~ query_method? && @attributes.include?($1)
1139 return query_attribute($1)
1140 else
1141 super
1142 end
1143 end
1144
7b5ed66 David Heinemeier Hansson Added respondence to *_before_type_cast for all attributes to return the...
dhh authored
1145 def read_method?() /^([a-zA-Z][-_\w]*)[^=?]*$/ end
1146 def read_untyped_method?() /^([a-zA-Z][-_\w]*)_before_type_cast$/ end
1147 def write_method?() /^([a-zA-Z][-_\w]*)=.*$/ end
1148 def query_method?() /^([a-zA-Z][-_\w]*)\?$/ end
db045db David Heinemeier Hansson Initial
dhh authored
1149
7b5ed66 David Heinemeier Hansson Added respondence to *_before_type_cast for all attributes to return the...
dhh authored
1150 # Returns the value of attribute identified by <tt>attr_name</tt> after it has been type cast (for example,
db045db David Heinemeier Hansson Initial
dhh authored
1151 # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
1152 def read_attribute(attr_name)
db045db David Heinemeier Hansson Initial
dhh authored
1153 if @attributes.keys.include? attr_name
1154 if column = column_for_attribute(attr_name)
7b5ed66 David Heinemeier Hansson Added respondence to *_before_type_cast for all attributes to return the...
dhh authored
1155 unserializable_attribute?(attr_name, column) ?
db045db David Heinemeier Hansson Initial
dhh authored
1156 unserialize_attribute(attr_name) : column.type_cast(@attributes[attr_name])
7b5ed66 David Heinemeier Hansson Added respondence to *_before_type_cast for all attributes to return the...
dhh authored
1157 else
1158 @attributes[attr_name]
db045db David Heinemeier Hansson Initial
dhh authored
1159 end
1160 else
1161 nil
1162 end
1163 end
1164
7b5ed66 David Heinemeier Hansson Added respondence to *_before_type_cast for all attributes to return the...
dhh authored
1165 def read_attribute_before_type_cast(attr_name)
1166 @attributes[attr_name]
1167 end
1168
db045db David Heinemeier Hansson Initial
dhh authored
1169 # Returns true if the attribute is of a text column and marked for serialization.
1170 def unserializable_attribute?(attr_name, column)
daf3e92 David Heinemeier Hansson Added a db2 adapter that only depends on the Ruby/DB2 bindings (http://r...
dhh authored
1171 @attributes[attr_name] && [:text, :string].include?(column.send(:type)) && @attributes[attr_name].is_a?(String) && self.class.serialized_attributes[attr_name]
db045db David Heinemeier Hansson Initial
dhh authored
1172 end
1173
1174 # Returns the unserialized object of the attribute.
1175 def unserialize_attribute(attr_name)
1176 unserialized_object = object_from_yaml(@attributes[attr_name])
1177
1178 if unserialized_object.is_a?(self.class.serialized_attributes[attr_name])
1179 @attributes[attr_name] = unserialized_object
1180 else
1181 raise(
1182 SerializationTypeMismatch,
1183 "#{attr_name} was supposed to be a #{self.class.serialized_attributes[attr_name]}, " +
1184 "but was a #{unserialized_object.class.to_s}"
1185 )
1186 end
1187 end
1188
1189 # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
1190 # columns are turned into nil.
4eab375 David Heinemeier Hansson Finished polishing API docs
dhh authored
1191 def write_attribute(attr_name, value)
db045db David Heinemeier Hansson Initial
dhh authored
1192 @attributes[attr_name] = empty_string_for_number_column?(attr_name, value) ? nil : value
1193 end
1194
1195 def empty_string_for_number_column?(attr_name, value)
1196 column = column_for_attribute(attr_name)
1197 column && (column.klass == Fixnum || column.klass == Float) && value == ""
1198 end
1199
1200 def query_attribute(attr_name)
1201 attribute = @attributes[attr_name]
1202 if attribute.kind_of?(Fixnum) && attribute == 0
1203 false
1204 elsif attribute.kind_of?(String) && attribute == "0"
1205 false
1206 elsif attribute.kind_of?(String) && attribute.empty?
1207 false
1208 elsif attribute.nil?
1209 false
1210 elsif attribute == false
1211 false
1212 elsif attribute == "f"
1213 false
1214 elsif attribute == "false"
1215 false
1216 else
1217 true
1218 end
1219 end
1220
1221 def remove_attributes_protected_from_mass_assignment(attributes)
1222 if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
95454bf David Heinemeier Hansson Added mass-assignment protection for the inheritance column -- regardles...
dhh authored
1223 attributes.reject { |key, value| attributes_protected_by_default.include?(key) }
db045db David Heinemeier Hansson Initial
dhh authored
1224 elsif self.class.protected_attributes.nil?
95454bf David Heinemeier Hansson Added mass-assignment protection for the inheritance column -- regardles...
dhh authored
1225 attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.intern) || attributes_protected_by_default.include?(key) }
db045db David Heinemeier Hansson Initial
dhh authored
1226 elsif self.class.accessible_attributes.nil?
95454bf David Heinemeier Hansson Added mass-assignment protection for the inheritance column -- regardles...
dhh authored
1227 attributes.reject { |key, value| self.class.protected_attributes.include?(key.intern) || attributes_protected_by_default.include?(key) }
db045db David Heinemeier Hansson Initial
dhh authored
1228 end
1229 end
1230
95454bf David Heinemeier Hansson Added mass-assignment protection for the inheritance column -- regardles...
dhh authored
1231 # The primary key and inheritance column can never be set by mass-assignment for security reasons.
1232 def attributes_protected_by_default
1233 [ self.class.primary_key, self.class.inheritance_column ]
1234 end
1235
db045db David Heinemeier Hansson Initial
dhh authored
1236 # Returns copy of the attributes hash where all the values have been safely quoted for use in
1237 # an SQL statement.
9a248a8 David Heinemeier Hansson Dont include the primary key in updates -- its unneeded and SQL Server c...
dhh authored
1238 def attributes_with_quotes(include_primary_key = true)
db045db David Heinemeier Hansson Initial
dhh authored
1239 columns_hash = self.class.columns_hash
b29c01e David Heinemeier Hansson Added that has_and_belongs_to_many associations with additional attribut...
dhh authored
1240
dabf906 David Heinemeier Hansson Added type conversion before saving a record, so string-based values lik...
dhh authored
1241 attrs_quoted = 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
1242 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
1243 attrs_quoted
1244 end
b29c01e David Heinemeier Hansson Added that has_and_belongs_to_many associations with additional attribut...
dhh authored
1245
1246 attrs_quoted.delete_if { |key, value| !self.class.columns_hash.keys.include?(key) }
db045db David Heinemeier Hansson Initial
dhh authored
1247 end
1248
1249 # Quote strings appropriately for SQL statements.
1250 def quote(value, column = nil)
1251 connection.quote(value, column)
1252 end
1253
1254 # Interpolate custom sql string in instance context.
1255 # Optional record argument is meant for custom insert_sql.
1256 def interpolate_sql(sql, record = nil)
1257 instance_eval("%(#{sql})")
1258 end
1259
1260 # Initializes the attributes array with keys matching the columns from the linked table and
1261 # the values matching the corresponding default value of that column, so
1262 # that a new instance, or one populated from a passed-in Hash, still has all the attributes
1263 # that instances loaded from the database would.
1264 def attributes_from_column_definition
1265 connection.columns(self.class.table_name, "#{self.class.name} Columns").inject({}) do |attributes, column|
3e5a880 David Heinemeier Hansson Switched strategy on the id_before_type_cast problem and just did an exp...
dhh authored
1266 attributes[column.name] = column.default unless column.name == self.class.primary_key
db045db David Heinemeier Hansson Initial
dhh authored
1267 attributes
1268 end
1269 end
1270
1271 # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
1272 # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
1273 # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
1274 # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
098fa94 David Heinemeier Hansson Fixed documentation snafus #575, #576, #577, #585
dhh authored
1275 # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
db045db David Heinemeier Hansson Initial
dhh authored
1276 # s for String, and a for Array. If all the values for a given attribute is empty, the attribute will be set to nil.
1277 def assign_multiparameter_attributes(pairs)
1278 execute_callstack_for_multiparameter_attributes(
1279 extract_callstack_for_multiparameter_attributes(pairs)
1280 )
1281 end
1282
1283 # Includes an ugly hack for Time.local instead of Time.new because the latter is reserved by Time itself.
1284 def execute_callstack_for_multiparameter_attributes(callstack)
d2fefbe David Heinemeier Hansson Added MultiparameterAssignmentErrors and AttributeAssignmentError except...
dhh authored
1285 errors = []
db045db David Heinemeier Hansson Initial
dhh authored
1286 callstack.each do |name, values|
1287 klass = (self.class.reflect_on_aggregation(name) || column_for_attribute(name)).klass
1288 if values.empty?
1289 send(name + "=", nil)
1290 else
d2fefbe David Heinemeier Hansson Added MultiparameterAssignmentErrors and AttributeAssignmentError except...
dhh authored
1291 begin
1292 send(name + "=", Time == klass ? klass.local(*values) : klass.new(*values))
1293 rescue => ex
1294 errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
1295 end
db045db David Heinemeier Hansson Initial
dhh authored
1296 end
1297 end
d2fefbe David Heinemeier Hansson Added MultiparameterAssignmentErrors and AttributeAssignmentError except...
dhh authored
1298 unless errors.empty?
1299 raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
1300 end
db045db David Heinemeier Hansson Initial
dhh authored
1301 end
1302
1303 def extract_callstack_for_multiparameter_attributes(pairs)
1304 attributes = { }
1305
1306 for pair in pairs
1307 multiparameter_name, value = pair
1308 attribute_name = multiparameter_name.split("(").first
1309 attributes[attribute_name] = [] unless attributes.include?(attribute_name)
1310
1311 unless value.empty?
1312 attributes[attribute_name] <<
b29c01e David Heinemeier Hansson Added that has_and_belongs_to_many associations with additional attribut...
dhh authored
1313 [ find_parameter_position(multiparameter_name), type_cast_attribute_value(multiparameter_name, value) ]
db045db David Heinemeier Hansson Initial
dhh authored
1314 end
1315 end
1316
1317 attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
1318 end
1319
1320 def type_cast_attribute_value(multiparameter_name, value)
1321 multiparameter_name =~ /\([0-9]*([a-z])\)/ ? value.send("to_" + $1) : value
1322 end
1323
1324 def find_parameter_position(multiparameter_name)
1325 multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
1326 end
1327
1328 # Returns a comma-separated pair list, like "key1 = val1, key2 = val2".
1329 def comma_pair_list(hash)
1330 hash.inject([]) { |list, pair| list << "#{pair.first} = #{pair.last}" }.join(", ")
1331 end
1332
1333 def quoted_column_names(attributes = attributes_with_quotes)
1334 attributes.keys.collect { |column_name| connection.quote_column_name(column_name) }
1335 end
1336
1337 def quote_columns(column_quoter, hash)
b29c01e David Heinemeier Hansson Added that has_and_belongs_to_many associations with additional attribut...
dhh authored
1338 hash.inject({}) do |list, pair|
db045db David Heinemeier Hansson Initial
dhh authored
1339 list[column_quoter.quote_column_name(pair.first)] = pair.last
1340 list
b29c01e David Heinemeier Hansson Added that has_and_belongs_to_many associations with additional attribut...
dhh authored
1341 end
db045db David Heinemeier Hansson Initial
dhh authored
1342 end
1343
1344 def quoted_comma_pair_list(column_quoter, hash)
1345 comma_pair_list(quote_columns(column_quoter, hash))
1346 end
1347
1348 def object_from_yaml(string)
b40d3c9 David Heinemeier Hansson Replaced === checks with is_a? checks #502, #82 [Marcel Molina]
dhh authored
1349 return string unless string.is_a?(String)
db045db David Heinemeier Hansson Initial
dhh authored
1350 if has_yaml_encoding_header?(string)
1351 begin
1352 YAML::load(string)
1353 rescue Object
1354 # Apparently wasn't YAML anyway
1355 string
1356 end
1357 else
1358 string
1359 end
1360 end
1361
1362 def has_yaml_encoding_header?(string)
1363 string[0..3] == "--- "
1364 end
1365 end
1aa82b3 David Heinemeier Hansson Added keyword-style approach to defining the custom relational bindings ...
dhh authored
1366 end
Something went wrong with that request. Please try again.