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