sskirby-activerecord 3.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +6749 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +222 -0
- data/examples/associations.png +0 -0
- data/examples/performance.rb +177 -0
- data/examples/simple.rb +14 -0
- data/lib/active_record.rb +147 -0
- data/lib/active_record/aggregations.rb +255 -0
- data/lib/active_record/associations.rb +1604 -0
- data/lib/active_record/associations/alias_tracker.rb +79 -0
- data/lib/active_record/associations/association.rb +239 -0
- data/lib/active_record/associations/association_scope.rb +119 -0
- data/lib/active_record/associations/belongs_to_association.rb +79 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +34 -0
- data/lib/active_record/associations/builder/association.rb +55 -0
- data/lib/active_record/associations/builder/belongs_to.rb +85 -0
- data/lib/active_record/associations/builder/collection_association.rb +75 -0
- data/lib/active_record/associations/builder/has_and_belongs_to_many.rb +57 -0
- data/lib/active_record/associations/builder/has_many.rb +71 -0
- data/lib/active_record/associations/builder/has_one.rb +62 -0
- data/lib/active_record/associations/builder/singular_association.rb +32 -0
- data/lib/active_record/associations/collection_association.rb +574 -0
- data/lib/active_record/associations/collection_proxy.rb +132 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +62 -0
- data/lib/active_record/associations/has_many_association.rb +108 -0
- data/lib/active_record/associations/has_many_through_association.rb +180 -0
- data/lib/active_record/associations/has_one_association.rb +73 -0
- data/lib/active_record/associations/has_one_through_association.rb +36 -0
- data/lib/active_record/associations/join_dependency.rb +214 -0
- data/lib/active_record/associations/join_dependency/join_association.rb +154 -0
- data/lib/active_record/associations/join_dependency/join_base.rb +24 -0
- data/lib/active_record/associations/join_dependency/join_part.rb +78 -0
- data/lib/active_record/associations/join_helper.rb +55 -0
- data/lib/active_record/associations/preloader.rb +177 -0
- data/lib/active_record/associations/preloader/association.rb +127 -0
- data/lib/active_record/associations/preloader/belongs_to.rb +17 -0
- data/lib/active_record/associations/preloader/collection_association.rb +24 -0
- data/lib/active_record/associations/preloader/has_and_belongs_to_many.rb +60 -0
- data/lib/active_record/associations/preloader/has_many.rb +17 -0
- data/lib/active_record/associations/preloader/has_many_through.rb +15 -0
- data/lib/active_record/associations/preloader/has_one.rb +23 -0
- data/lib/active_record/associations/preloader/has_one_through.rb +9 -0
- data/lib/active_record/associations/preloader/singular_association.rb +21 -0
- data/lib/active_record/associations/preloader/through_association.rb +67 -0
- data/lib/active_record/associations/singular_association.rb +64 -0
- data/lib/active_record/associations/through_association.rb +83 -0
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +272 -0
- data/lib/active_record/attribute_methods/before_type_cast.rb +31 -0
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +101 -0
- data/lib/active_record/attribute_methods/primary_key.rb +114 -0
- data/lib/active_record/attribute_methods/query.rb +39 -0
- data/lib/active_record/attribute_methods/read.rb +135 -0
- data/lib/active_record/attribute_methods/serialization.rb +93 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +62 -0
- data/lib/active_record/attribute_methods/write.rb +69 -0
- data/lib/active_record/autosave_association.rb +422 -0
- data/lib/active_record/base.rb +716 -0
- data/lib/active_record/callbacks.rb +275 -0
- data/lib/active_record/coders/yaml_column.rb +41 -0
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +452 -0
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +188 -0
- data/lib/active_record/connection_adapters/abstract/database_limits.rb +58 -0
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +388 -0
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +82 -0
- data/lib/active_record/connection_adapters/abstract/quoting.rb +115 -0
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +492 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +598 -0
- data/lib/active_record/connection_adapters/abstract_adapter.rb +296 -0
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/column.rb +270 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +288 -0
- data/lib/active_record/connection_adapters/mysql_adapter.rb +426 -0
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +1261 -0
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +55 -0
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +577 -0
- data/lib/active_record/connection_adapters/statement_pool.rb +40 -0
- data/lib/active_record/counter_cache.rb +119 -0
- data/lib/active_record/dynamic_finder_match.rb +56 -0
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/dynamic_scope_match.rb +23 -0
- data/lib/active_record/errors.rb +195 -0
- data/lib/active_record/explain.rb +85 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +906 -0
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +156 -0
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locale/en.yml +40 -0
- data/lib/active_record/locking/optimistic.rb +183 -0
- data/lib/active_record/locking/pessimistic.rb +77 -0
- data/lib/active_record/log_subscriber.rb +68 -0
- data/lib/active_record/migration.rb +765 -0
- data/lib/active_record/migration/command_recorder.rb +105 -0
- data/lib/active_record/model_schema.rb +366 -0
- data/lib/active_record/nested_attributes.rb +469 -0
- data/lib/active_record/observer.rb +121 -0
- data/lib/active_record/persistence.rb +372 -0
- data/lib/active_record/query_cache.rb +74 -0
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +119 -0
- data/lib/active_record/railties/console_sandbox.rb +6 -0
- data/lib/active_record/railties/controller_runtime.rb +49 -0
- data/lib/active_record/railties/databases.rake +620 -0
- data/lib/active_record/railties/jdbcmysql_error.rb +16 -0
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +534 -0
- data/lib/active_record/relation.rb +534 -0
- data/lib/active_record/relation/batches.rb +90 -0
- data/lib/active_record/relation/calculations.rb +354 -0
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +398 -0
- data/lib/active_record/relation/predicate_builder.rb +58 -0
- data/lib/active_record/relation/query_methods.rb +417 -0
- data/lib/active_record/relation/spawn_methods.rb +148 -0
- data/lib/active_record/result.rb +34 -0
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema.rb +58 -0
- data/lib/active_record/schema_dumper.rb +204 -0
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +142 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +18 -0
- data/lib/active_record/serializers/xml_serializer.rb +202 -0
- data/lib/active_record/session_store.rb +358 -0
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +73 -0
- data/lib/active_record/timestamp.rb +113 -0
- data/lib/active_record/transactions.rb +360 -0
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +83 -0
- data/lib/active_record/validations/associated.rb +43 -0
- data/lib/active_record/validations/uniqueness.rb +180 -0
- data/lib/active_record/version.rb +10 -0
- data/lib/rails/generators/active_record.rb +25 -0
- data/lib/rails/generators/active_record/migration.rb +15 -0
- data/lib/rails/generators/active_record/migration/migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/migration/templates/migration.rb +31 -0
- data/lib/rails/generators/active_record/model/model_generator.rb +43 -0
- data/lib/rails/generators/active_record/model/templates/migration.rb +15 -0
- data/lib/rails/generators/active_record/model/templates/model.rb +7 -0
- data/lib/rails/generators/active_record/model/templates/module.rb +7 -0
- data/lib/rails/generators/active_record/observer/observer_generator.rb +15 -0
- data/lib/rails/generators/active_record/observer/templates/observer.rb +4 -0
- data/lib/rails/generators/active_record/session_migration/session_migration_generator.rb +25 -0
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +12 -0
- metadata +242 -0
@@ -0,0 +1,716 @@
|
|
1
|
+
begin
|
2
|
+
require 'psych'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
|
6
|
+
require 'yaml'
|
7
|
+
require 'set'
|
8
|
+
require 'thread'
|
9
|
+
require 'active_support/benchmarkable'
|
10
|
+
require 'active_support/dependencies'
|
11
|
+
require 'active_support/descendants_tracker'
|
12
|
+
require 'active_support/time'
|
13
|
+
require 'active_support/core_ext/class/attribute'
|
14
|
+
require 'active_support/core_ext/class/attribute_accessors'
|
15
|
+
require 'active_support/core_ext/class/delegating_attributes'
|
16
|
+
require 'active_support/core_ext/class/attribute'
|
17
|
+
require 'active_support/core_ext/array/extract_options'
|
18
|
+
require 'active_support/core_ext/hash/deep_merge'
|
19
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
20
|
+
require 'active_support/core_ext/hash/slice'
|
21
|
+
require 'active_support/core_ext/string/behavior'
|
22
|
+
require 'active_support/core_ext/kernel/singleton_class'
|
23
|
+
require 'active_support/core_ext/module/delegation'
|
24
|
+
require 'active_support/core_ext/module/introspection'
|
25
|
+
require 'active_support/core_ext/object/duplicable'
|
26
|
+
require 'active_support/core_ext/object/blank'
|
27
|
+
require 'active_support/deprecation'
|
28
|
+
require 'arel'
|
29
|
+
require 'active_record/errors'
|
30
|
+
require 'active_record/log_subscriber'
|
31
|
+
require 'active_record/explain_subscriber'
|
32
|
+
|
33
|
+
module ActiveRecord #:nodoc:
|
34
|
+
# = Active Record
|
35
|
+
#
|
36
|
+
# Active Record objects don't specify their attributes directly, but rather infer them from
|
37
|
+
# the table definition with which they're linked. Adding, removing, and changing attributes
|
38
|
+
# and their type is done directly in the database. Any change is instantly reflected in the
|
39
|
+
# Active Record objects. The mapping that binds a given Active Record class to a certain
|
40
|
+
# database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
|
41
|
+
#
|
42
|
+
# See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight.
|
43
|
+
#
|
44
|
+
# == Creation
|
45
|
+
#
|
46
|
+
# Active Records accept constructor parameters either in a hash or as a block. The hash
|
47
|
+
# method is especially useful when you're receiving the data from somewhere else, like an
|
48
|
+
# HTTP request. It works like this:
|
49
|
+
#
|
50
|
+
# user = User.new(:name => "David", :occupation => "Code Artist")
|
51
|
+
# user.name # => "David"
|
52
|
+
#
|
53
|
+
# You can also use block initialization:
|
54
|
+
#
|
55
|
+
# user = User.new do |u|
|
56
|
+
# u.name = "David"
|
57
|
+
# u.occupation = "Code Artist"
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# And of course you can just create a bare object and specify the attributes after the fact:
|
61
|
+
#
|
62
|
+
# user = User.new
|
63
|
+
# user.name = "David"
|
64
|
+
# user.occupation = "Code Artist"
|
65
|
+
#
|
66
|
+
# == Conditions
|
67
|
+
#
|
68
|
+
# Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
|
69
|
+
# The array form is to be used when the condition input is tainted and requires sanitization. The string form can
|
70
|
+
# be used for statements that don't involve tainted data. The hash form works much like the array form, except
|
71
|
+
# only equality and range is possible. Examples:
|
72
|
+
#
|
73
|
+
# class User < ActiveRecord::Base
|
74
|
+
# def self.authenticate_unsafely(user_name, password)
|
75
|
+
# where("user_name = '#{user_name}' AND password = '#{password}'").first
|
76
|
+
# end
|
77
|
+
#
|
78
|
+
# def self.authenticate_safely(user_name, password)
|
79
|
+
# where("user_name = ? AND password = ?", user_name, password).first
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# def self.authenticate_safely_simply(user_name, password)
|
83
|
+
# where(:user_name => user_name, :password => password).first
|
84
|
+
# end
|
85
|
+
# end
|
86
|
+
#
|
87
|
+
# The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
|
88
|
+
# and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
|
89
|
+
# parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
|
90
|
+
# <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
|
91
|
+
# before inserting them in the query, which will ensure that an attacker can't escape the
|
92
|
+
# query and fake the login (or worse).
|
93
|
+
#
|
94
|
+
# When using multiple parameters in the conditions, it can easily become hard to read exactly
|
95
|
+
# what the fourth or fifth question mark is supposed to represent. In those cases, you can
|
96
|
+
# resort to named bind variables instead. That's done by replacing the question marks with
|
97
|
+
# symbols and supplying a hash with values for the matching symbol keys:
|
98
|
+
#
|
99
|
+
# Company.where(
|
100
|
+
# "id = :id AND name = :name AND division = :division AND created_at > :accounting_date",
|
101
|
+
# { :id => 3, :name => "37signals", :division => "First", :accounting_date => '2005-01-01' }
|
102
|
+
# ).first
|
103
|
+
#
|
104
|
+
# Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
|
105
|
+
# operator. For instance:
|
106
|
+
#
|
107
|
+
# Student.where(:first_name => "Harvey", :status => 1)
|
108
|
+
# Student.where(params[:student])
|
109
|
+
#
|
110
|
+
# A range may be used in the hash to use the SQL BETWEEN operator:
|
111
|
+
#
|
112
|
+
# Student.where(:grade => 9..12)
|
113
|
+
#
|
114
|
+
# An array may be used in the hash to use the SQL IN operator:
|
115
|
+
#
|
116
|
+
# Student.where(:grade => [9,11,12])
|
117
|
+
#
|
118
|
+
# When joining tables, nested hashes or keys written in the form 'table_name.column_name'
|
119
|
+
# can be used to qualify the table name of a particular condition. For instance:
|
120
|
+
#
|
121
|
+
# Student.joins(:schools).where(:schools => { :category => 'public' })
|
122
|
+
# Student.joins(:schools).where('schools.category' => 'public' )
|
123
|
+
#
|
124
|
+
# == Overwriting default accessors
|
125
|
+
#
|
126
|
+
# All column values are automatically available through basic accessors on the Active Record
|
127
|
+
# object, but sometimes you want to specialize this behavior. This can be done by overwriting
|
128
|
+
# the default accessors (using the same name as the attribute) and calling
|
129
|
+
# <tt>read_attribute(attr_name)</tt> and <tt>write_attribute(attr_name, value)</tt> to actually
|
130
|
+
# change things.
|
131
|
+
#
|
132
|
+
# class Song < ActiveRecord::Base
|
133
|
+
# # Uses an integer of seconds to hold the length of the song
|
134
|
+
#
|
135
|
+
# def length=(minutes)
|
136
|
+
# write_attribute(:length, minutes.to_i * 60)
|
137
|
+
# end
|
138
|
+
#
|
139
|
+
# def length
|
140
|
+
# read_attribute(:length) / 60
|
141
|
+
# end
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# You can alternatively use <tt>self[:attribute]=(value)</tt> and <tt>self[:attribute]</tt>
|
145
|
+
# instead of <tt>write_attribute(:attribute, value)</tt> and <tt>read_attribute(:attribute)</tt>.
|
146
|
+
#
|
147
|
+
# == Attribute query methods
|
148
|
+
#
|
149
|
+
# In addition to the basic accessors, query methods are also automatically available on the Active Record object.
|
150
|
+
# Query methods allow you to test whether an attribute value is present.
|
151
|
+
#
|
152
|
+
# For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
|
153
|
+
# to determine whether the user has a name:
|
154
|
+
#
|
155
|
+
# user = User.new(:name => "David")
|
156
|
+
# user.name? # => true
|
157
|
+
#
|
158
|
+
# anonymous = User.new(:name => "")
|
159
|
+
# anonymous.name? # => false
|
160
|
+
#
|
161
|
+
# == Accessing attributes before they have been typecasted
|
162
|
+
#
|
163
|
+
# Sometimes you want to be able to read the raw attribute data without having the column-determined
|
164
|
+
# typecast run its course first. That can be done by using the <tt><attribute>_before_type_cast</tt>
|
165
|
+
# accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
|
166
|
+
# you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
|
167
|
+
#
|
168
|
+
# This is especially useful in validation situations where the user might supply a string for an
|
169
|
+
# integer field and you want to display the original string back in an error message. Accessing the
|
170
|
+
# attribute normally would typecast the string to 0, which isn't what you want.
|
171
|
+
#
|
172
|
+
# == Dynamic attribute-based finders
|
173
|
+
#
|
174
|
+
# Dynamic attribute-based finders are a cleaner way of getting (and/or creating) objects
|
175
|
+
# by simple queries without turning to SQL. They work by appending the name of an attribute
|
176
|
+
# to <tt>find_by_</tt>, <tt>find_last_by_</tt>, or <tt>find_all_by_</tt> and thus produces finders
|
177
|
+
# like <tt>Person.find_by_user_name</tt>, <tt>Person.find_all_by_last_name</tt>, and
|
178
|
+
# <tt>Payment.find_by_transaction_id</tt>. Instead of writing
|
179
|
+
# <tt>Person.where(:user_name => user_name).first</tt>, you just do <tt>Person.find_by_user_name(user_name)</tt>.
|
180
|
+
# And instead of writing <tt>Person.where(:last_name => last_name).all</tt>, you just do
|
181
|
+
# <tt>Person.find_all_by_last_name(last_name)</tt>.
|
182
|
+
#
|
183
|
+
# It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
|
184
|
+
# <tt>ActiveRecord::RecordNotFound</tt> error if they do not return any records,
|
185
|
+
# like <tt>Person.find_by_last_name!</tt>.
|
186
|
+
#
|
187
|
+
# It's also possible to use multiple attributes in the same find by separating them with "_and_".
|
188
|
+
#
|
189
|
+
# Person.where(:user_name => user_name, :password => password).first
|
190
|
+
# Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
|
191
|
+
#
|
192
|
+
# It's even possible to call these dynamic finder methods on relations and named scopes.
|
193
|
+
#
|
194
|
+
# Payment.order("created_on").find_all_by_amount(50)
|
195
|
+
# Payment.pending.find_last_by_amount(100)
|
196
|
+
#
|
197
|
+
# The same dynamic finder style can be used to create the object if it doesn't already exist.
|
198
|
+
# This dynamic finder is called with <tt>find_or_create_by_</tt> and will return the object if
|
199
|
+
# it already exists and otherwise creates it, then returns it. Protected attributes won't be set
|
200
|
+
# unless they are given in a block.
|
201
|
+
#
|
202
|
+
# # No 'Summer' tag exists
|
203
|
+
# Tag.find_or_create_by_name("Summer") # equal to Tag.create(:name => "Summer")
|
204
|
+
#
|
205
|
+
# # Now the 'Summer' tag does exist
|
206
|
+
# Tag.find_or_create_by_name("Summer") # equal to Tag.find_by_name("Summer")
|
207
|
+
#
|
208
|
+
# # Now 'Bob' exist and is an 'admin'
|
209
|
+
# User.find_or_create_by_name('Bob', :age => 40) { |u| u.admin = true }
|
210
|
+
#
|
211
|
+
# Use the <tt>find_or_initialize_by_</tt> finder if you want to return a new record without
|
212
|
+
# saving it first. Protected attributes won't be set unless they are given in a block.
|
213
|
+
#
|
214
|
+
# # No 'Winter' tag exists
|
215
|
+
# winter = Tag.find_or_initialize_by_name("Winter")
|
216
|
+
# winter.persisted? # false
|
217
|
+
#
|
218
|
+
# To find by a subset of the attributes to be used for instantiating a new object, pass a hash instead of
|
219
|
+
# a list of parameters.
|
220
|
+
#
|
221
|
+
# Tag.find_or_create_by_name(:name => "rails", :creator => current_user)
|
222
|
+
#
|
223
|
+
# That will either find an existing tag named "rails", or create a new one while setting the
|
224
|
+
# user that created it.
|
225
|
+
#
|
226
|
+
# Just like <tt>find_by_*</tt>, you can also use <tt>scoped_by_*</tt> to retrieve data. The good thing about
|
227
|
+
# using this feature is that the very first time result is returned using <tt>method_missing</tt> technique
|
228
|
+
# but after that the method is declared on the class. Henceforth <tt>method_missing</tt> will not be hit.
|
229
|
+
#
|
230
|
+
# User.scoped_by_user_name('David')
|
231
|
+
#
|
232
|
+
# == Saving arrays, hashes, and other non-mappable objects in text columns
|
233
|
+
#
|
234
|
+
# Active Record can serialize any object in text columns using YAML. To do so, you must
|
235
|
+
# specify this with a call to the class method +serialize+.
|
236
|
+
# This makes it possible to store arrays, hashes, and other non-mappable objects without doing
|
237
|
+
# any additional work.
|
238
|
+
#
|
239
|
+
# class User < ActiveRecord::Base
|
240
|
+
# serialize :preferences
|
241
|
+
# end
|
242
|
+
#
|
243
|
+
# user = User.create(:preferences => { "background" => "black", "display" => large })
|
244
|
+
# User.find(user.id).preferences # => { "background" => "black", "display" => large }
|
245
|
+
#
|
246
|
+
# You can also specify a class option as the second parameter that'll raise an exception
|
247
|
+
# if a serialized object is retrieved as a descendant of a class not in the hierarchy.
|
248
|
+
#
|
249
|
+
# class User < ActiveRecord::Base
|
250
|
+
# serialize :preferences, Hash
|
251
|
+
# end
|
252
|
+
#
|
253
|
+
# user = User.create(:preferences => %w( one two three ))
|
254
|
+
# User.find(user.id).preferences # raises SerializationTypeMismatch
|
255
|
+
#
|
256
|
+
# When you specify a class option, the default value for that attribute will be a new
|
257
|
+
# instance of that class.
|
258
|
+
#
|
259
|
+
# class User < ActiveRecord::Base
|
260
|
+
# serialize :preferences, OpenStruct
|
261
|
+
# end
|
262
|
+
#
|
263
|
+
# user = User.new
|
264
|
+
# user.preferences.theme_color = "red"
|
265
|
+
#
|
266
|
+
#
|
267
|
+
# == Single table inheritance
|
268
|
+
#
|
269
|
+
# Active Record allows inheritance by storing the name of the class in a column that by
|
270
|
+
# default is named "type" (can be changed by overwriting <tt>Base.inheritance_column</tt>).
|
271
|
+
# This means that an inheritance looking like this:
|
272
|
+
#
|
273
|
+
# class Company < ActiveRecord::Base; end
|
274
|
+
# class Firm < Company; end
|
275
|
+
# class Client < Company; end
|
276
|
+
# class PriorityClient < Client; end
|
277
|
+
#
|
278
|
+
# When you do <tt>Firm.create(:name => "37signals")</tt>, this record will be saved in
|
279
|
+
# the companies table with type = "Firm". You can then fetch this row again using
|
280
|
+
# <tt>Company.where(:name => '37signals').first</tt> and it will return a Firm object.
|
281
|
+
#
|
282
|
+
# If you don't have a type column defined in your table, single-table inheritance won't
|
283
|
+
# be triggered. In that case, it'll work just like normal subclasses with no special magic
|
284
|
+
# for differentiating between them or reloading the right type with find.
|
285
|
+
#
|
286
|
+
# Note, all the attributes for all the cases are kept in the same table. Read more:
|
287
|
+
# http://www.martinfowler.com/eaaCatalog/singleTableInheritance.html
|
288
|
+
#
|
289
|
+
# == Connection to multiple databases in different models
|
290
|
+
#
|
291
|
+
# Connections are usually created through ActiveRecord::Base.establish_connection and retrieved
|
292
|
+
# by ActiveRecord::Base.connection. All classes inheriting from ActiveRecord::Base will use this
|
293
|
+
# connection. But you can also set a class-specific connection. For example, if Course is an
|
294
|
+
# ActiveRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
|
295
|
+
# and Course and all of its subclasses will use this connection instead.
|
296
|
+
#
|
297
|
+
# This feature is implemented by keeping a connection pool in ActiveRecord::Base that is
|
298
|
+
# a Hash indexed by the class. If a connection is requested, the retrieve_connection method
|
299
|
+
# will go up the class-hierarchy until a connection is found in the connection pool.
|
300
|
+
#
|
301
|
+
# == Exceptions
|
302
|
+
#
|
303
|
+
# * ActiveRecordError - Generic error class and superclass of all other errors raised by Active Record.
|
304
|
+
# * AdapterNotSpecified - The configuration hash used in <tt>establish_connection</tt> didn't include an
|
305
|
+
# <tt>:adapter</tt> key.
|
306
|
+
# * AdapterNotFound - The <tt>:adapter</tt> key used in <tt>establish_connection</tt> specified a
|
307
|
+
# non-existent adapter
|
308
|
+
# (or a bad spelling of an existing one).
|
309
|
+
# * AssociationTypeMismatch - The object assigned to the association wasn't of the type
|
310
|
+
# specified in the association definition.
|
311
|
+
# * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
|
312
|
+
# * ConnectionNotEstablished+ - No connection has been established. Use <tt>establish_connection</tt>
|
313
|
+
# before querying.
|
314
|
+
# * RecordNotFound - No record responded to the +find+ method. Either the row with the given ID doesn't exist
|
315
|
+
# or the row didn't meet the additional restrictions. Some +find+ calls do not raise this exception to signal
|
316
|
+
# nothing was found, please check its documentation for further details.
|
317
|
+
# * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
|
318
|
+
# * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
|
319
|
+
# <tt>attributes=</tt> method. The +errors+ property of this exception contains an array of
|
320
|
+
# AttributeAssignmentError
|
321
|
+
# objects that should be inspected to determine which attributes triggered the errors.
|
322
|
+
# * AttributeAssignmentError - An error occurred while doing a mass assignment through the
|
323
|
+
# <tt>attributes=</tt> method.
|
324
|
+
# You can inspect the +attribute+ property of the exception object to determine which attribute
|
325
|
+
# triggered the error.
|
326
|
+
#
|
327
|
+
# *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
|
328
|
+
# So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
|
329
|
+
# instances in the current object space.
|
330
|
+
class Base
|
331
|
+
##
|
332
|
+
# :singleton-method:
|
333
|
+
# Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class,
|
334
|
+
# which is then passed on to any new database connections made and which can be retrieved on both
|
335
|
+
# a class and instance level by calling +logger+.
|
336
|
+
cattr_accessor :logger, :instance_writer => false
|
337
|
+
|
338
|
+
##
|
339
|
+
# :singleton-method:
|
340
|
+
# Contains the database configuration - as is typically stored in config/database.yml -
|
341
|
+
# as a Hash.
|
342
|
+
#
|
343
|
+
# For example, the following database.yml...
|
344
|
+
#
|
345
|
+
# development:
|
346
|
+
# adapter: sqlite3
|
347
|
+
# database: db/development.sqlite3
|
348
|
+
#
|
349
|
+
# production:
|
350
|
+
# adapter: sqlite3
|
351
|
+
# database: db/production.sqlite3
|
352
|
+
#
|
353
|
+
# ...would result in ActiveRecord::Base.configurations to look like this:
|
354
|
+
#
|
355
|
+
# {
|
356
|
+
# 'development' => {
|
357
|
+
# 'adapter' => 'sqlite3',
|
358
|
+
# 'database' => 'db/development.sqlite3'
|
359
|
+
# },
|
360
|
+
# 'production' => {
|
361
|
+
# 'adapter' => 'sqlite3',
|
362
|
+
# 'database' => 'db/production.sqlite3'
|
363
|
+
# }
|
364
|
+
# }
|
365
|
+
cattr_accessor :configurations, :instance_writer => false
|
366
|
+
@@configurations = {}
|
367
|
+
|
368
|
+
##
|
369
|
+
# :singleton-method:
|
370
|
+
# Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling
|
371
|
+
# dates and times from the database. This is set to :local by default.
|
372
|
+
cattr_accessor :default_timezone, :instance_writer => false
|
373
|
+
@@default_timezone = :local
|
374
|
+
|
375
|
+
##
|
376
|
+
# :singleton-method:
|
377
|
+
# Specifies the format to use when dumping the database schema with Rails'
|
378
|
+
# Rakefile. If :sql, the schema is dumped as (potentially database-
|
379
|
+
# specific) SQL statements. If :ruby, the schema is dumped as an
|
380
|
+
# ActiveRecord::Schema file which can be loaded into any database that
|
381
|
+
# supports migrations. Use :ruby if you want to have different database
|
382
|
+
# adapters for, e.g., your development and test environments.
|
383
|
+
cattr_accessor :schema_format , :instance_writer => false
|
384
|
+
@@schema_format = :ruby
|
385
|
+
|
386
|
+
##
|
387
|
+
# :singleton-method:
|
388
|
+
# Specify whether or not to use timestamps for migration versions
|
389
|
+
cattr_accessor :timestamped_migrations , :instance_writer => false
|
390
|
+
@@timestamped_migrations = true
|
391
|
+
|
392
|
+
class << self # Class methods
|
393
|
+
def inherited(child_class) #:nodoc:
|
394
|
+
child_class.initialize_generated_modules
|
395
|
+
super
|
396
|
+
end
|
397
|
+
|
398
|
+
def initialize_generated_modules #:nodoc:
|
399
|
+
@attribute_methods_mutex = Mutex.new
|
400
|
+
|
401
|
+
# force attribute methods to be higher in inheritance hierarchy than other generated methods
|
402
|
+
generated_attribute_methods
|
403
|
+
generated_feature_methods
|
404
|
+
end
|
405
|
+
|
406
|
+
def generated_feature_methods
|
407
|
+
@generated_feature_methods ||= begin
|
408
|
+
mod = const_set(:GeneratedFeatureMethods, Module.new)
|
409
|
+
include mod
|
410
|
+
mod
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
# Returns a string like 'Post(id:integer, title:string, body:text)'
|
415
|
+
def inspect
|
416
|
+
if self == Base
|
417
|
+
super
|
418
|
+
elsif abstract_class?
|
419
|
+
"#{super}(abstract)"
|
420
|
+
elsif table_exists?
|
421
|
+
attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
|
422
|
+
"#{super}(#{attr_list})"
|
423
|
+
else
|
424
|
+
"#{super}(Table doesn't exist)"
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
# Overwrite the default class equality method to provide support for association proxies.
|
429
|
+
def ===(object)
|
430
|
+
object.is_a?(self)
|
431
|
+
end
|
432
|
+
|
433
|
+
def arel_table
|
434
|
+
@arel_table ||= Arel::Table.new(table_name, arel_engine)
|
435
|
+
end
|
436
|
+
|
437
|
+
def arel_engine
|
438
|
+
@arel_engine ||= begin
|
439
|
+
if self == ActiveRecord::Base
|
440
|
+
ActiveRecord::Base
|
441
|
+
else
|
442
|
+
connection_handler.connection_pools[name] ? self : superclass.arel_engine
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
private
|
448
|
+
|
449
|
+
def relation #:nodoc:
|
450
|
+
@relation ||= Relation.new(self, arel_table)
|
451
|
+
|
452
|
+
if finder_needs_type_condition?
|
453
|
+
@relation.where(type_condition).create_with(inheritance_column.to_sym => sti_name)
|
454
|
+
else
|
455
|
+
@relation
|
456
|
+
end
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
public
|
461
|
+
# New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
|
462
|
+
# attributes but not yet saved (pass a hash with key names matching the associated table column names).
|
463
|
+
# In both instances, valid attribute keys are determined by the column names of the associated table --
|
464
|
+
# hence you can't have attributes that aren't part of the table columns.
|
465
|
+
#
|
466
|
+
# +initialize+ respects mass-assignment security and accepts either +:as+ or +:without_protection+ options
|
467
|
+
# in the +options+ parameter.
|
468
|
+
#
|
469
|
+
# ==== Examples
|
470
|
+
# # Instantiates a single new object
|
471
|
+
# User.new(:first_name => 'Jamie')
|
472
|
+
#
|
473
|
+
# # Instantiates a single new object using the :admin mass-assignment security role
|
474
|
+
# User.new({ :first_name => 'Jamie', :is_admin => true }, :as => :admin)
|
475
|
+
#
|
476
|
+
# # Instantiates a single new object bypassing mass-assignment security
|
477
|
+
# User.new({ :first_name => 'Jamie', :is_admin => true }, :without_protection => true)
|
478
|
+
def initialize(attributes = nil, options = {})
|
479
|
+
@attributes = self.class.initialize_attributes(self.class.column_defaults.dup)
|
480
|
+
@association_cache = {}
|
481
|
+
@aggregation_cache = {}
|
482
|
+
@attributes_cache = {}
|
483
|
+
@new_record = true
|
484
|
+
@readonly = false
|
485
|
+
@destroyed = false
|
486
|
+
@marked_for_destruction = false
|
487
|
+
@previously_changed = {}
|
488
|
+
@changed_attributes = {}
|
489
|
+
@relation = nil
|
490
|
+
|
491
|
+
ensure_proper_type
|
492
|
+
|
493
|
+
populate_with_current_scope_attributes
|
494
|
+
|
495
|
+
assign_attributes(attributes, options) if attributes
|
496
|
+
|
497
|
+
yield self if block_given?
|
498
|
+
run_callbacks :initialize
|
499
|
+
end
|
500
|
+
|
501
|
+
# Initialize an empty model object from +coder+. +coder+ must contain
|
502
|
+
# the attributes necessary for initializing an empty model object. For
|
503
|
+
# example:
|
504
|
+
#
|
505
|
+
# class Post < ActiveRecord::Base
|
506
|
+
# end
|
507
|
+
#
|
508
|
+
# post = Post.allocate
|
509
|
+
# post.init_with('attributes' => { 'title' => 'hello world' })
|
510
|
+
# post.title # => 'hello world'
|
511
|
+
def init_with(coder)
|
512
|
+
@attributes = self.class.initialize_attributes(coder['attributes'])
|
513
|
+
@relation = nil
|
514
|
+
|
515
|
+
@attributes_cache, @previously_changed, @changed_attributes = {}, {}, {}
|
516
|
+
@association_cache = {}
|
517
|
+
@aggregation_cache = {}
|
518
|
+
@readonly = @destroyed = @marked_for_destruction = false
|
519
|
+
@new_record = false
|
520
|
+
run_callbacks :find
|
521
|
+
run_callbacks :initialize
|
522
|
+
|
523
|
+
self
|
524
|
+
end
|
525
|
+
|
526
|
+
# Duped objects have no id assigned and are treated as new records. Note
|
527
|
+
# that this is a "shallow" copy as it copies the object's attributes
|
528
|
+
# only, not its associations. The extent of a "deep" copy is application
|
529
|
+
# specific and is therefore left to the application to implement according
|
530
|
+
# to its need.
|
531
|
+
# The dup method does not preserve the timestamps (created|updated)_(at|on).
|
532
|
+
def initialize_dup(other)
|
533
|
+
cloned_attributes = other.clone_attributes(:read_attribute_before_type_cast)
|
534
|
+
cloned_attributes.delete(self.class.primary_key)
|
535
|
+
|
536
|
+
@attributes = cloned_attributes
|
537
|
+
|
538
|
+
_run_after_initialize_callbacks if respond_to?(:_run_after_initialize_callbacks)
|
539
|
+
|
540
|
+
@changed_attributes = {}
|
541
|
+
self.class.column_defaults.each do |attr, orig_value|
|
542
|
+
@changed_attributes[attr] = orig_value if field_changed?(attr, orig_value, @attributes[attr])
|
543
|
+
end
|
544
|
+
|
545
|
+
@aggregation_cache = {}
|
546
|
+
@association_cache = {}
|
547
|
+
@attributes_cache = {}
|
548
|
+
@new_record = true
|
549
|
+
|
550
|
+
ensure_proper_type
|
551
|
+
populate_with_current_scope_attributes
|
552
|
+
super
|
553
|
+
end
|
554
|
+
|
555
|
+
# Backport dup from 1.9 so that initialize_dup() gets called
|
556
|
+
unless Object.respond_to?(:initialize_dup)
|
557
|
+
def dup # :nodoc:
|
558
|
+
copy = super
|
559
|
+
copy.initialize_dup(self)
|
560
|
+
copy
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
# Populate +coder+ with attributes about this record that should be
|
565
|
+
# serialized. The structure of +coder+ defined in this method is
|
566
|
+
# guaranteed to match the structure of +coder+ passed to the +init_with+
|
567
|
+
# method.
|
568
|
+
#
|
569
|
+
# Example:
|
570
|
+
#
|
571
|
+
# class Post < ActiveRecord::Base
|
572
|
+
# end
|
573
|
+
# coder = {}
|
574
|
+
# Post.new.encode_with(coder)
|
575
|
+
# coder # => { 'id' => nil, ... }
|
576
|
+
def encode_with(coder)
|
577
|
+
coder['attributes'] = attributes
|
578
|
+
end
|
579
|
+
|
580
|
+
# Returns true if +comparison_object+ is the same exact object, or +comparison_object+
|
581
|
+
# is of the same type and +self+ has an ID and it is equal to +comparison_object.id+.
|
582
|
+
#
|
583
|
+
# Note that new records are different from any other record by definition, unless the
|
584
|
+
# other record is the receiver itself. Besides, if you fetch existing records with
|
585
|
+
# +select+ and leave the ID out, you're on your own, this predicate will return false.
|
586
|
+
#
|
587
|
+
# Note also that destroying a record preserves its ID in the model instance, so deleted
|
588
|
+
# models are still comparable.
|
589
|
+
def ==(comparison_object)
|
590
|
+
super ||
|
591
|
+
comparison_object.instance_of?(self.class) &&
|
592
|
+
id.present? &&
|
593
|
+
comparison_object.id == id
|
594
|
+
end
|
595
|
+
alias :eql? :==
|
596
|
+
|
597
|
+
# Delegates to id in order to allow two records of the same type and id to work with something like:
|
598
|
+
# [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
|
599
|
+
def hash
|
600
|
+
id.hash
|
601
|
+
end
|
602
|
+
|
603
|
+
# Freeze the attributes hash such that associations are still accessible, even on destroyed records.
|
604
|
+
def freeze
|
605
|
+
@attributes.freeze; self
|
606
|
+
end
|
607
|
+
|
608
|
+
# Returns +true+ if the attributes hash has been frozen.
|
609
|
+
def frozen?
|
610
|
+
@attributes.frozen?
|
611
|
+
end
|
612
|
+
|
613
|
+
# Allows sort on objects
|
614
|
+
def <=>(other_object)
|
615
|
+
if other_object.is_a?(self.class)
|
616
|
+
self.to_key <=> other_object.to_key
|
617
|
+
else
|
618
|
+
nil
|
619
|
+
end
|
620
|
+
end
|
621
|
+
|
622
|
+
# Returns +true+ if the record is read only. Records loaded through joins with piggy-back
|
623
|
+
# attributes will be marked as read only since they cannot be saved.
|
624
|
+
def readonly?
|
625
|
+
@readonly
|
626
|
+
end
|
627
|
+
|
628
|
+
# Marks this record as read only.
|
629
|
+
def readonly!
|
630
|
+
@readonly = true
|
631
|
+
end
|
632
|
+
|
633
|
+
# Returns the contents of the record as a nicely formatted string.
|
634
|
+
def inspect
|
635
|
+
inspection = if @attributes
|
636
|
+
self.class.column_names.collect { |name|
|
637
|
+
if has_attribute?(name)
|
638
|
+
"#{name}: #{attribute_for_inspect(name)}"
|
639
|
+
end
|
640
|
+
}.compact.join(", ")
|
641
|
+
else
|
642
|
+
"not initialized"
|
643
|
+
end
|
644
|
+
"#<#{self.class} #{inspection}>"
|
645
|
+
end
|
646
|
+
|
647
|
+
# Hackery to accomodate Syck. Remove for 4.0.
|
648
|
+
def to_yaml(opts = {}) #:nodoc:
|
649
|
+
if YAML.const_defined?(:ENGINE) && !YAML::ENGINE.syck?
|
650
|
+
super
|
651
|
+
else
|
652
|
+
coder = {}
|
653
|
+
encode_with(coder)
|
654
|
+
YAML.quick_emit(self, opts) do |out|
|
655
|
+
out.map(taguri, to_yaml_style) do |map|
|
656
|
+
coder.each { |k, v| map.add(k, v) }
|
657
|
+
end
|
658
|
+
end
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
# Hackery to accomodate Syck. Remove for 4.0.
|
663
|
+
def yaml_initialize(tag, coder) #:nodoc:
|
664
|
+
init_with(coder)
|
665
|
+
end
|
666
|
+
|
667
|
+
private
|
668
|
+
|
669
|
+
# Under Ruby 1.9, Array#flatten will call #to_ary (recursively) on each of the elements
|
670
|
+
# of the array, and then rescues from the possible NoMethodError. If those elements are
|
671
|
+
# ActiveRecord::Base's, then this triggers the various method_missing's that we have,
|
672
|
+
# which significantly impacts upon performance.
|
673
|
+
#
|
674
|
+
# So we can avoid the method_missing hit by explicitly defining #to_ary as nil here.
|
675
|
+
#
|
676
|
+
# See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary/
|
677
|
+
def to_ary # :nodoc:
|
678
|
+
nil
|
679
|
+
end
|
680
|
+
|
681
|
+
include ActiveRecord::Persistence
|
682
|
+
extend ActiveModel::Naming
|
683
|
+
extend QueryCache::ClassMethods
|
684
|
+
extend ActiveSupport::Benchmarkable
|
685
|
+
extend ActiveSupport::DescendantsTracker
|
686
|
+
|
687
|
+
extend Querying
|
688
|
+
include ReadonlyAttributes
|
689
|
+
include ModelSchema
|
690
|
+
extend Translation
|
691
|
+
include Inheritance
|
692
|
+
include Scoping
|
693
|
+
extend DynamicMatchers
|
694
|
+
include Sanitization
|
695
|
+
include Integration
|
696
|
+
include AttributeAssignment
|
697
|
+
include ActiveModel::Conversion
|
698
|
+
include Validations
|
699
|
+
extend CounterCache
|
700
|
+
include Locking::Optimistic, Locking::Pessimistic
|
701
|
+
include AttributeMethods
|
702
|
+
include Callbacks, ActiveModel::Observing, Timestamp
|
703
|
+
include Associations
|
704
|
+
include IdentityMap
|
705
|
+
include ActiveModel::SecurePassword
|
706
|
+
extend Explain
|
707
|
+
|
708
|
+
# AutosaveAssociation needs to be included before Transactions, because we want
|
709
|
+
# #save_with_autosave_associations to be wrapped inside a transaction.
|
710
|
+
include AutosaveAssociation, NestedAttributes
|
711
|
+
include Aggregations, Transactions, Reflection, Serialization, Store
|
712
|
+
end
|
713
|
+
end
|
714
|
+
|
715
|
+
require 'active_record/connection_adapters/abstract/connection_specification'
|
716
|
+
ActiveSupport.run_load_hooks(:active_record, ActiveRecord::Base)
|