activerecord 3.0.0.beta4 → 3.0.0.rc
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activerecord might be problematic. Click here for more details.
- data/CHANGELOG +267 -254
- data/README.rdoc +222 -0
- data/examples/performance.rb +9 -9
- data/lib/active_record/aggregations.rb +3 -4
- data/lib/active_record/association_preload.rb +15 -10
- data/lib/active_record/associations.rb +54 -37
- data/lib/active_record/associations/association_collection.rb +43 -17
- data/lib/active_record/associations/association_proxy.rb +2 -0
- data/lib/active_record/associations/belongs_to_association.rb +1 -0
- data/lib/active_record/associations/belongs_to_polymorphic_association.rb +1 -0
- data/lib/active_record/associations/has_and_belongs_to_many_association.rb +22 -7
- data/lib/active_record/associations/has_many_association.rb +6 -1
- data/lib/active_record/associations/has_many_through_association.rb +1 -0
- data/lib/active_record/associations/has_one_association.rb +1 -0
- data/lib/active_record/associations/has_one_through_association.rb +1 -0
- data/lib/active_record/associations/through_association_scope.rb +3 -2
- data/lib/active_record/attribute_methods.rb +1 -0
- data/lib/active_record/autosave_association.rb +4 -6
- data/lib/active_record/base.rb +106 -240
- data/lib/active_record/callbacks.rb +4 -25
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +22 -29
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +2 -8
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +2 -2
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +10 -0
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +56 -7
- data/lib/active_record/connection_adapters/abstract_adapter.rb +10 -18
- data/lib/active_record/connection_adapters/mysql_adapter.rb +2 -2
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +65 -69
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +19 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +20 -46
- data/lib/active_record/counter_cache.rb +14 -4
- data/lib/active_record/dynamic_finder_match.rb +9 -0
- data/lib/active_record/dynamic_scope_match.rb +7 -0
- data/lib/active_record/errors.rb +3 -0
- data/lib/active_record/fixtures.rb +5 -6
- data/lib/active_record/locale/en.yml +1 -1
- data/lib/active_record/locking/optimistic.rb +1 -0
- data/lib/active_record/log_subscriber.rb +48 -0
- data/lib/active_record/migration.rb +64 -37
- data/lib/active_record/named_scope.rb +33 -19
- data/lib/active_record/nested_attributes.rb +17 -13
- data/lib/active_record/observer.rb +13 -6
- data/lib/active_record/persistence.rb +55 -22
- data/lib/active_record/query_cache.rb +1 -0
- data/lib/active_record/railtie.rb +14 -8
- data/lib/active_record/railties/controller_runtime.rb +2 -2
- data/lib/active_record/railties/databases.rake +63 -33
- data/lib/active_record/reflection.rb +46 -28
- data/lib/active_record/relation.rb +38 -24
- data/lib/active_record/relation/finder_methods.rb +5 -5
- data/lib/active_record/relation/predicate_builder.rb +2 -4
- data/lib/active_record/relation/query_methods.rb +134 -115
- data/lib/active_record/relation/spawn_methods.rb +1 -1
- data/lib/active_record/schema.rb +2 -0
- data/lib/active_record/schema_dumper.rb +15 -12
- data/lib/active_record/serialization.rb +2 -0
- data/lib/active_record/session_store.rb +93 -79
- data/lib/active_record/test_case.rb +3 -0
- data/lib/active_record/timestamp.rb +49 -29
- data/lib/active_record/transactions.rb +5 -2
- data/lib/active_record/validations.rb +5 -2
- data/lib/active_record/validations/associated.rb +1 -1
- data/lib/active_record/validations/uniqueness.rb +1 -1
- data/lib/active_record/version.rb +1 -1
- data/lib/rails/generators/active_record/migration/templates/migration.rb +12 -6
- data/lib/rails/generators/active_record/model/model_generator.rb +1 -1
- metadata +27 -14
- data/README +0 -351
- data/lib/active_record/railties/log_subscriber.rb +0 -32
data/README.rdoc
ADDED
@@ -0,0 +1,222 @@
|
|
1
|
+
= Active Record -- Object-relational mapping put on rails
|
2
|
+
|
3
|
+
Active Record connects classes to relational database tables to establish an
|
4
|
+
almost zero-configuration persistence layer for applications. The library
|
5
|
+
provides a base class that, when subclassed, sets up a mapping between the new
|
6
|
+
class and an existing table in the database. In context of an application,
|
7
|
+
these classes are commonly referred to as *models*. Models can also be
|
8
|
+
connected to other models; this is done by defining *associations*.
|
9
|
+
|
10
|
+
Active Record relies heavily on naming in that it uses class and association
|
11
|
+
names to establish mappings between respective database tables and foreign key
|
12
|
+
columns. Although these mappings can be defined explicitly, it's recommended
|
13
|
+
to follow naming conventions, especially when getting started with the
|
14
|
+
library.
|
15
|
+
|
16
|
+
A short rundown of some of the major features:
|
17
|
+
|
18
|
+
* Automated mapping between classes and tables, attributes and columns.
|
19
|
+
|
20
|
+
class Product < ActiveRecord::Base
|
21
|
+
end
|
22
|
+
|
23
|
+
The Product class is automatically mapped to the table named "products",
|
24
|
+
which might look like this:
|
25
|
+
|
26
|
+
CREATE TABLE products (
|
27
|
+
id int(11) NOT NULL auto_increment,
|
28
|
+
name varchar(255),
|
29
|
+
PRIMARY KEY (id)
|
30
|
+
);
|
31
|
+
|
32
|
+
This would also define the following accessors: `Product#name` and
|
33
|
+
`Product#name=(new_name)`
|
34
|
+
|
35
|
+
{Learn more}[link:classes/ActiveRecord/Base.html]
|
36
|
+
|
37
|
+
|
38
|
+
* Associations between objects defined by simple class methods.
|
39
|
+
|
40
|
+
class Firm < ActiveRecord::Base
|
41
|
+
has_many :clients
|
42
|
+
has_one :account
|
43
|
+
belongs_to :conglomerate
|
44
|
+
end
|
45
|
+
|
46
|
+
{Learn more}[link:classes/ActiveRecord/Associations/ClassMethods.html]
|
47
|
+
|
48
|
+
|
49
|
+
* Aggregations of value objects.
|
50
|
+
|
51
|
+
class Account < ActiveRecord::Base
|
52
|
+
composed_of :balance, :class_name => "Money",
|
53
|
+
:mapping => %w(balance amount)
|
54
|
+
composed_of :address,
|
55
|
+
:mapping => [%w(address_street street), %w(address_city city)]
|
56
|
+
end
|
57
|
+
|
58
|
+
{Learn more}[link:classes/ActiveRecord/Aggregations/ClassMethods.html]
|
59
|
+
|
60
|
+
|
61
|
+
* Validation rules that can differ for new or existing objects.
|
62
|
+
|
63
|
+
class Account < ActiveRecord::Base
|
64
|
+
validates_presence_of :subdomain, :name, :email_address, :password
|
65
|
+
validates_uniqueness_of :subdomain
|
66
|
+
validates_acceptance_of :terms_of_service, :on => :create
|
67
|
+
validates_confirmation_of :password, :email_address, :on => :create
|
68
|
+
end
|
69
|
+
|
70
|
+
{Learn more}[link:classes/ActiveRecord/Validations.html]
|
71
|
+
|
72
|
+
|
73
|
+
* Callbacks available for the entire lifecycle (instantiation, saving, destroying, validating, etc.)
|
74
|
+
|
75
|
+
class Person < ActiveRecord::Base
|
76
|
+
before_destroy :invalidate_payment_plan
|
77
|
+
# the `invalidate_payment_plan` method gets called just before Person#destroy
|
78
|
+
end
|
79
|
+
|
80
|
+
{Learn more}[link:classes/ActiveRecord/Callbacks.html]
|
81
|
+
|
82
|
+
|
83
|
+
* Observers that react to changes in a model
|
84
|
+
|
85
|
+
class CommentObserver < ActiveRecord::Observer
|
86
|
+
def after_create(comment) # is called just after Comment#save
|
87
|
+
Notifications.deliver_new_comment("david@loudthinking.com", comment)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
{Learn more}[link:classes/ActiveRecord/Observer.html]
|
92
|
+
|
93
|
+
|
94
|
+
* Inheritance hierarchies
|
95
|
+
|
96
|
+
class Company < ActiveRecord::Base; end
|
97
|
+
class Firm < Company; end
|
98
|
+
class Client < Company; end
|
99
|
+
class PriorityClient < Client; end
|
100
|
+
|
101
|
+
{Learn more}[link:classes/ActiveRecord/Base.html]
|
102
|
+
|
103
|
+
|
104
|
+
* Transactions
|
105
|
+
|
106
|
+
# Database transaction
|
107
|
+
Account.transaction do
|
108
|
+
david.withdrawal(100)
|
109
|
+
mary.deposit(100)
|
110
|
+
end
|
111
|
+
|
112
|
+
{Learn more}[link:classes/ActiveRecord/Transactions/ClassMethods.html]
|
113
|
+
|
114
|
+
|
115
|
+
* Reflections on columns, associations, and aggregations
|
116
|
+
|
117
|
+
reflection = Firm.reflect_on_association(:clients)
|
118
|
+
reflection.klass # => Client (class)
|
119
|
+
Firm.columns # Returns an array of column descriptors for the firms table
|
120
|
+
|
121
|
+
{Learn more}[link:classes/ActiveRecord/Reflection/ClassMethods.html]
|
122
|
+
|
123
|
+
|
124
|
+
* Database abstraction through simple adapters
|
125
|
+
|
126
|
+
# connect to SQLite3
|
127
|
+
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => "dbfile.sqlite3")
|
128
|
+
|
129
|
+
# connect to MySQL with authentication
|
130
|
+
ActiveRecord::Base.establish_connection(
|
131
|
+
:adapter => "mysql",
|
132
|
+
:host => "localhost",
|
133
|
+
:username => "me",
|
134
|
+
:password => "secret",
|
135
|
+
:database => "activerecord"
|
136
|
+
)
|
137
|
+
|
138
|
+
{Learn more}[link:classes/ActiveRecord/Base.html] and read about the built-in support for
|
139
|
+
MySQL[link:classes/ActiveRecord/ConnectionAdapters/MysqlAdapter.html],
|
140
|
+
PostgreSQL[link:classes/ActiveRecord/ConnectionAdapters/PostgreSQLAdapter.html], and
|
141
|
+
SQLite3[link:classes/ActiveRecord/ConnectionAdapters/SQLite3Adapter.html].
|
142
|
+
|
143
|
+
|
144
|
+
* Logging support for Log4r[http://log4r.sourceforge.net] and Logger[http://www.ruby-doc.org/stdlib/libdoc/logger/rdoc]
|
145
|
+
|
146
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT)
|
147
|
+
ActiveRecord::Base.logger = Log4r::Logger.new("Application Log")
|
148
|
+
|
149
|
+
|
150
|
+
* Database agnostic schema management with Migrations
|
151
|
+
|
152
|
+
class AddSystemSettings < ActiveRecord::Migration
|
153
|
+
def self.up
|
154
|
+
create_table :system_settings do |t|
|
155
|
+
t.string :name
|
156
|
+
t.string :label
|
157
|
+
t.text :value
|
158
|
+
t.string :type
|
159
|
+
t.integer :position
|
160
|
+
end
|
161
|
+
|
162
|
+
SystemSetting.create :name => "notice", :label => "Use notice?", :value => 1
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.down
|
166
|
+
drop_table :system_settings
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
{Learn more}[link:classes/ActiveRecord/Migration.html]
|
171
|
+
|
172
|
+
|
173
|
+
== Philosophy
|
174
|
+
|
175
|
+
Active Record is an implementation of the object-relational mapping (ORM)
|
176
|
+
pattern[http://www.martinfowler.com/eaaCatalog/activeRecord.html] by the same
|
177
|
+
name described by Martin Fowler:
|
178
|
+
|
179
|
+
"An object that wraps a row in a database table or view,
|
180
|
+
encapsulates the database access, and adds domain logic on that data."
|
181
|
+
|
182
|
+
Active Record attempts to provide a coherent wrapper as a solution for the inconvenience that is
|
183
|
+
object-relational mapping. The prime directive for this mapping has been to minimize
|
184
|
+
the amount of code needed to build a real-world domain model. This is made possible
|
185
|
+
by relying on a number of conventions that make it easy for Active Record to infer
|
186
|
+
complex relations and structures from a minimal amount of explicit direction.
|
187
|
+
|
188
|
+
Convention over Configuration:
|
189
|
+
* No XML-files!
|
190
|
+
* Lots of reflection and run-time extension
|
191
|
+
* Magic is not inherently a bad word
|
192
|
+
|
193
|
+
Admit the Database:
|
194
|
+
* Lets you drop down to SQL for odd cases and performance
|
195
|
+
* Doesn't attempt to duplicate or replace data definitions
|
196
|
+
|
197
|
+
|
198
|
+
== Download and installation
|
199
|
+
|
200
|
+
The latest version of Active Record can be installed with Rubygems:
|
201
|
+
|
202
|
+
% [sudo] gem install activerecord
|
203
|
+
|
204
|
+
Source code can be downloaded as part of the Rails project on GitHub
|
205
|
+
|
206
|
+
* http://github.com/rails/rails/tree/master/activerecord/
|
207
|
+
|
208
|
+
|
209
|
+
== License
|
210
|
+
|
211
|
+
Active Record is released under the MIT license.
|
212
|
+
|
213
|
+
|
214
|
+
== Support
|
215
|
+
|
216
|
+
API documentation is at
|
217
|
+
|
218
|
+
* http://api.rubyonrails.com
|
219
|
+
|
220
|
+
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
|
221
|
+
|
222
|
+
* https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets
|
data/examples/performance.rb
CHANGED
@@ -1,18 +1,18 @@
|
|
1
1
|
#!/usr/bin/env ruby -KU
|
2
2
|
|
3
3
|
TIMES = (ENV['N'] || 10000).to_i
|
4
|
-
|
5
4
|
require 'rubygems'
|
5
|
+
|
6
6
|
gem 'addressable', '~>2.0'
|
7
7
|
gem 'faker', '~>0.3.1'
|
8
8
|
gem 'rbench', '~>0.2.3'
|
9
|
+
|
9
10
|
require 'addressable/uri'
|
10
11
|
require 'faker'
|
11
12
|
require 'rbench'
|
12
13
|
|
13
|
-
|
14
|
-
|
15
|
-
require 'active_record'
|
14
|
+
require File.expand_path("../../../load_paths", __FILE__)
|
15
|
+
require "active_record"
|
16
16
|
|
17
17
|
conn = { :adapter => 'mysql',
|
18
18
|
:database => 'activerecord_unittest',
|
@@ -55,10 +55,10 @@ class Exhibit < ActiveRecord::Base
|
|
55
55
|
def self.feel(exhibits) exhibits.each { |e| e.feel } end
|
56
56
|
end
|
57
57
|
|
58
|
-
sqlfile = "
|
58
|
+
sqlfile = File.expand_path("../performance.sql", __FILE__)
|
59
59
|
|
60
60
|
if File.exists?(sqlfile)
|
61
|
-
mysql_bin = %w[mysql mysql5].
|
61
|
+
mysql_bin = %w[mysql mysql5].detect { |bin| `which #{bin}`.length > 0 }
|
62
62
|
`#{mysql_bin} -u #{conn[:username]} #{"-p#{conn[:password]}" unless conn[:password].blank?} #{conn[:database]} < #{sqlfile}`
|
63
63
|
else
|
64
64
|
puts 'Generating data...'
|
@@ -116,15 +116,15 @@ RBench.run(TIMES) do
|
|
116
116
|
end
|
117
117
|
|
118
118
|
report 'Model.all limit(100)', (TIMES / 10).ceil do
|
119
|
-
ar { Exhibit.look Exhibit.
|
119
|
+
ar { Exhibit.look Exhibit.limit(100) }
|
120
120
|
end
|
121
121
|
|
122
122
|
report 'Model.all limit(100) with relationship', (TIMES / 10).ceil do
|
123
|
-
ar { Exhibit.feel Exhibit.
|
123
|
+
ar { Exhibit.feel Exhibit.limit(100).includes(:user) }
|
124
124
|
end
|
125
125
|
|
126
126
|
report 'Model.all limit(10,000)', (TIMES / 1000).ceil do
|
127
|
-
ar { Exhibit.look Exhibit.
|
127
|
+
ar { Exhibit.look Exhibit.limit(10000) }
|
128
128
|
end
|
129
129
|
|
130
130
|
exhibit = {
|
@@ -1,4 +1,5 @@
|
|
1
1
|
module ActiveRecord
|
2
|
+
# = Active Record Aggregations
|
2
3
|
module Aggregations # :nodoc:
|
3
4
|
extend ActiveSupport::Concern
|
4
5
|
|
@@ -189,7 +190,7 @@ module ActiveRecord
|
|
189
190
|
# :constructor => Proc.new { |ip| IPAddr.new(ip, Socket::AF_INET) },
|
190
191
|
# :converter => Proc.new { |ip| ip.is_a?(Integer) ? IPAddr.new(ip, Socket::AF_INET) : IPAddr.new(ip.to_s) }
|
191
192
|
#
|
192
|
-
def composed_of(part_id, options = {}
|
193
|
+
def composed_of(part_id, options = {})
|
193
194
|
options.assert_valid_keys(:class_name, :mapping, :allow_nil, :constructor, :converter)
|
194
195
|
|
195
196
|
name = part_id.id2name
|
@@ -198,9 +199,7 @@ module ActiveRecord
|
|
198
199
|
mapping = [ mapping ] unless mapping.first.is_a?(Array)
|
199
200
|
allow_nil = options[:allow_nil] || false
|
200
201
|
constructor = options[:constructor] || :new
|
201
|
-
converter = options[:converter]
|
202
|
-
|
203
|
-
ActiveSupport::Deprecation.warn('The conversion block has been deprecated, use the :converter option instead.', caller) if block_given?
|
202
|
+
converter = options[:converter]
|
204
203
|
|
205
204
|
reader_method(name, class_name, mapping, allow_nil, constructor)
|
206
205
|
writer_method(name, class_name, mapping, allow_nil, converter)
|
@@ -6,7 +6,7 @@ module ActiveRecord
|
|
6
6
|
module AssociationPreload #:nodoc:
|
7
7
|
extend ActiveSupport::Concern
|
8
8
|
|
9
|
-
# Implements the details of eager loading of
|
9
|
+
# Implements the details of eager loading of Active Record associations.
|
10
10
|
# Application developers should not use this module directly.
|
11
11
|
#
|
12
12
|
# ActiveRecord::Base is extended with this module. The source code in
|
@@ -18,7 +18,7 @@ module ActiveRecord
|
|
18
18
|
# The first one is by using table joins. This was only strategy available
|
19
19
|
# prior to Rails 2.1. Suppose that you have an Author model with columns
|
20
20
|
# 'name' and 'age', and a Book model with columns 'name' and 'sales'. Using
|
21
|
-
# this strategy,
|
21
|
+
# this strategy, Active Record would try to retrieve all data for an author
|
22
22
|
# and all of its books via a single query:
|
23
23
|
#
|
24
24
|
# SELECT * FROM authors
|
@@ -31,7 +31,7 @@ module ActiveRecord
|
|
31
31
|
# 'books' table is useful; the joined 'authors' data is just redundant, and
|
32
32
|
# processing this redundant data takes memory and CPU time. The problem
|
33
33
|
# quickly becomes worse and worse as the level of eager loading increases
|
34
|
-
# (i.e. if
|
34
|
+
# (i.e. if Active Record is to eager load the associations' associations as
|
35
35
|
# well).
|
36
36
|
#
|
37
37
|
# The second strategy is to use multiple database queries, one for each
|
@@ -45,7 +45,7 @@ module ActiveRecord
|
|
45
45
|
module ClassMethods
|
46
46
|
protected
|
47
47
|
|
48
|
-
# Eager loads the named associations for the given
|
48
|
+
# Eager loads the named associations for the given Active Record record(s).
|
49
49
|
#
|
50
50
|
# In this description, 'association name' shall refer to the name passed
|
51
51
|
# to an association creation method. For example, a model that specifies
|
@@ -80,7 +80,7 @@ module ActiveRecord
|
|
80
80
|
# { :author => :avatar }
|
81
81
|
# [ :books, { :author => :avatar } ]
|
82
82
|
#
|
83
|
-
# +preload_options+ contains options that will be passed to ActiveRecord#find
|
83
|
+
# +preload_options+ contains options that will be passed to ActiveRecord::Base#find
|
84
84
|
# (which is called under the hood for preloading records). But it is passed
|
85
85
|
# only one level deep in the +associations+ argument, i.e. it's not passed
|
86
86
|
# to the child associations when +associations+ is a Hash.
|
@@ -112,13 +112,13 @@ module ActiveRecord
|
|
112
112
|
# Not all records have the same class, so group then preload
|
113
113
|
# group on the reflection itself so that if various subclass share the same association then we do not split them
|
114
114
|
# unnecessarily
|
115
|
-
records.group_by {|record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection,
|
115
|
+
records.group_by { |record| class_to_reflection[record.class] ||= record.class.reflections[association]}.each do |reflection, _records|
|
116
116
|
raise ConfigurationError, "Association named '#{ association }' was not found; perhaps you misspelled it?" unless reflection
|
117
117
|
|
118
118
|
# 'reflection.macro' can return 'belongs_to', 'has_many', etc. Thus,
|
119
119
|
# the following could call 'preload_belongs_to_association',
|
120
120
|
# 'preload_has_many_association', etc.
|
121
|
-
send("preload_#{reflection.macro}_association",
|
121
|
+
send("preload_#{reflection.macro}_association", _records, reflection, preload_options)
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|
@@ -166,7 +166,7 @@ module ActiveRecord
|
|
166
166
|
end
|
167
167
|
end
|
168
168
|
|
169
|
-
# Given a collection of
|
169
|
+
# Given a collection of Active Record objects, constructs a Hash which maps
|
170
170
|
# the objects' IDs to the relevant objects. Returns a 2-tuple
|
171
171
|
# <tt>(id_to_record_map, ids)</tt> where +id_to_record_map+ is the Hash,
|
172
172
|
# and +ids+ is an Array of record IDs.
|
@@ -282,7 +282,12 @@ module ActiveRecord
|
|
282
282
|
end
|
283
283
|
end
|
284
284
|
else
|
285
|
-
|
285
|
+
options = {}
|
286
|
+
options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions]
|
287
|
+
options[:order] = reflection.options[:order]
|
288
|
+
options[:conditions] = reflection.options[:conditions]
|
289
|
+
records.first.class.preload_associations(records, through_association, options)
|
290
|
+
|
286
291
|
records.each do |record|
|
287
292
|
through_records.concat Array.wrap(record.send(through_association))
|
288
293
|
end
|
@@ -373,7 +378,7 @@ module ActiveRecord
|
|
373
378
|
:order => preload_options[:order] || options[:order]
|
374
379
|
}
|
375
380
|
|
376
|
-
reflection.klass.
|
381
|
+
reflection.klass.scoped.apply_finder_options(find_options).to_a
|
377
382
|
end
|
378
383
|
|
379
384
|
|
@@ -3,6 +3,7 @@ require 'active_support/core_ext/enumerable'
|
|
3
3
|
require 'active_support/core_ext/module/delegation'
|
4
4
|
require 'active_support/core_ext/object/blank'
|
5
5
|
require 'active_support/core_ext/string/conversions'
|
6
|
+
require 'active_support/core_ext/module/remove_method'
|
6
7
|
|
7
8
|
module ActiveRecord
|
8
9
|
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
|
@@ -88,8 +89,8 @@ module ActiveRecord
|
|
88
89
|
end
|
89
90
|
end
|
90
91
|
|
91
|
-
# This error is raised when trying to destroy a parent instance in
|
92
|
-
# (has_many, has_one) when there is at least 1 child
|
92
|
+
# This error is raised when trying to destroy a parent instance in N:1 or 1:1 associations
|
93
|
+
# (has_many, has_one) when there is at least 1 child associated instance.
|
93
94
|
# ex: if @project.tasks.size > 0, DeleteRestrictionError will be raised when trying to destroy @project
|
94
95
|
class DeleteRestrictionError < ActiveRecordError #:nodoc:
|
95
96
|
def initialize(reflection)
|
@@ -711,7 +712,7 @@ module ActiveRecord
|
|
711
712
|
#
|
712
713
|
# The +traps+ association on +Dungeon+ and the the +dungeon+ association on +Trap+ are the inverse of each other and the
|
713
714
|
# inverse of the +dungeon+ association on +EvilWizard+ is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
|
714
|
-
#
|
715
|
+
# Active Record doesn't know anything about these inverse relationships and so no object loading optimisation is possible. For example:
|
715
716
|
#
|
716
717
|
# d = Dungeon.first
|
717
718
|
# t = d.traps.first
|
@@ -721,7 +722,7 @@ module ActiveRecord
|
|
721
722
|
#
|
722
723
|
# The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to the same object data from the database, but are
|
723
724
|
# actually different in-memory copies of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
|
724
|
-
#
|
725
|
+
# Active Record about inverse relationships and it will optimise object loading. For example, if we changed our model definitions to:
|
725
726
|
#
|
726
727
|
# class Dungeon < ActiveRecord::Base
|
727
728
|
# has_many :traps, :inverse_of => :dungeon
|
@@ -763,20 +764,27 @@ module ActiveRecord
|
|
763
764
|
# An empty array is returned if none are found.
|
764
765
|
# [collection<<(object, ...)]
|
765
766
|
# Adds one or more objects to the collection by setting their foreign keys to the collection's primary key.
|
767
|
+
# Note that this operation instantly fires update sql without waiting for the save or update call on the
|
768
|
+
# parent object.
|
766
769
|
# [collection.delete(object, ...)]
|
767
770
|
# Removes one or more objects from the collection by setting their foreign keys to +NULL+.
|
768
771
|
# Objects will be in addition destroyed if they're associated with <tt>:dependent => :destroy</tt>,
|
769
772
|
# and deleted if they're associated with <tt>:dependent => :delete_all</tt>.
|
770
773
|
# [collection=objects]
|
771
|
-
# Replaces the collections content by deleting and adding objects as appropriate.
|
774
|
+
# Replaces the collections content by deleting and adding objects as appropriate. If the <tt>:through</tt>
|
775
|
+
# option is true callbacks in the join models are triggered except destroy callbacks, since deletion is
|
776
|
+
# direct.
|
772
777
|
# [collection_singular_ids]
|
773
778
|
# Returns an array of the associated objects' ids
|
774
779
|
# [collection_singular_ids=ids]
|
775
|
-
# Replace the collection with the objects identified by the primary keys in +ids
|
780
|
+
# Replace the collection with the objects identified by the primary keys in +ids+. This
|
781
|
+
# method loads the models and calls <tt>collection=</tt>. See above.
|
776
782
|
# [collection.clear]
|
777
783
|
# Removes every object from the collection. This destroys the associated objects if they
|
778
784
|
# are associated with <tt>:dependent => :destroy</tt>, deletes them directly from the
|
779
785
|
# database if <tt>:dependent => :delete_all</tt>, otherwise sets their foreign keys to +NULL+.
|
786
|
+
# If the <tt>:through</tt> option is true no destroy callbacks are invoked on the join models.
|
787
|
+
# Join models are directly deleted.
|
780
788
|
# [collection.empty?]
|
781
789
|
# Returns +true+ if there are no associated objects.
|
782
790
|
# [collection.size]
|
@@ -869,9 +877,11 @@ module ActiveRecord
|
|
869
877
|
# [:as]
|
870
878
|
# Specifies a polymorphic interface (See <tt>belongs_to</tt>).
|
871
879
|
# [:through]
|
872
|
-
# Specifies a
|
880
|
+
# Specifies a join model through which to perform the query. Options for <tt>:class_name</tt> and <tt>:foreign_key</tt>
|
873
881
|
# are ignored, as the association uses the source reflection. You can only use a <tt>:through</tt> query through a <tt>belongs_to</tt>
|
874
|
-
# <tt>has_one</tt> or <tt>has_many</tt> association on the join model.
|
882
|
+
# <tt>has_one</tt> or <tt>has_many</tt> association on the join model. The collection of join models can be managed via the collection
|
883
|
+
# API. For example, new join models are created for newly associated objects, and if some are gone their rows are deleted (directly,
|
884
|
+
# no destroy callbacks are triggered).
|
875
885
|
# [:source]
|
876
886
|
# Specifies the source association name used by <tt>has_many :through</tt> queries. Only use it if the name cannot be
|
877
887
|
# inferred from the association. <tt>has_many :subscribers, :through => :subscriptions</tt> will look for either <tt>:subscribers</tt> or
|
@@ -886,11 +896,13 @@ module ActiveRecord
|
|
886
896
|
# [:validate]
|
887
897
|
# If false, don't validate the associated objects when saving the parent object. true by default.
|
888
898
|
# [:autosave]
|
889
|
-
# If true, always save
|
899
|
+
# If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object.
|
900
|
+
# If false, never save or destroy the associated objects.
|
901
|
+
# By default, only save associated objects that are new records.
|
890
902
|
# [:inverse_of]
|
891
903
|
# Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_many</tt>
|
892
904
|
# association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
|
893
|
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional
|
905
|
+
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
|
894
906
|
#
|
895
907
|
# Option examples:
|
896
908
|
# has_many :comments, :order => "posted_on"
|
@@ -1001,11 +1013,13 @@ module ActiveRecord
|
|
1001
1013
|
# [:validate]
|
1002
1014
|
# If false, don't validate the associated object when saving the parent object. +false+ by default.
|
1003
1015
|
# [:autosave]
|
1004
|
-
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
|
1016
|
+
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
|
1017
|
+
# If false, never save or destroy the associated object.
|
1018
|
+
# By default, only save the associated object if it's a new record.
|
1005
1019
|
# [:inverse_of]
|
1006
1020
|
# Specifies the name of the <tt>belongs_to</tt> association on the associated object that is the inverse of this <tt>has_one</tt>
|
1007
1021
|
# association. Does not work in combination with <tt>:through</tt> or <tt>:as</tt> options.
|
1008
|
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional
|
1022
|
+
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
|
1009
1023
|
#
|
1010
1024
|
# Option examples:
|
1011
1025
|
# has_one :credit_card, :dependent => :destroy # destroys the associated credit card
|
@@ -1103,14 +1117,16 @@ module ActiveRecord
|
|
1103
1117
|
# [:validate]
|
1104
1118
|
# If false, don't validate the associated objects when saving the parent object. +false+ by default.
|
1105
1119
|
# [:autosave]
|
1106
|
-
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
|
1120
|
+
# If true, always save the associated object or destroy it if marked for destruction, when saving the parent object.
|
1121
|
+
# If false, never save or destroy the associated object.
|
1122
|
+
# By default, only save the associated object if it's a new record.
|
1107
1123
|
# [:touch]
|
1108
1124
|
# If true, the associated object will be touched (the updated_at/on attributes set to now) when this record is either saved or
|
1109
1125
|
# destroyed. If you specify a symbol, that attribute will be updated with the current time instead of the updated_at/on attribute.
|
1110
1126
|
# [:inverse_of]
|
1111
1127
|
# Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated object that is the inverse of this <tt>belongs_to</tt>
|
1112
1128
|
# association. Does not work in combination with the <tt>:polymorphic</tt> options.
|
1113
|
-
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional
|
1129
|
+
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
|
1114
1130
|
#
|
1115
1131
|
# Option examples:
|
1116
1132
|
# belongs_to :firm, :foreign_key => "client_of"
|
@@ -1180,6 +1196,8 @@ module ActiveRecord
|
|
1180
1196
|
# [collection<<(object, ...)]
|
1181
1197
|
# Adds one or more objects to the collection by creating associations in the join table
|
1182
1198
|
# (<tt>collection.push</tt> and <tt>collection.concat</tt> are aliases to this method).
|
1199
|
+
# Note that this operation instantly fires update sql without waiting for the save or update call on the
|
1200
|
+
# parent object.
|
1183
1201
|
# [collection.delete(object, ...)]
|
1184
1202
|
# Removes one or more objects from the collection by removing their associations from the join table.
|
1185
1203
|
# This does not destroy the objects.
|
@@ -1290,7 +1308,9 @@ module ActiveRecord
|
|
1290
1308
|
# [:validate]
|
1291
1309
|
# If false, don't validate the associated objects when saving the parent object. +true+ by default.
|
1292
1310
|
# [:autosave]
|
1293
|
-
# If true, always save
|
1311
|
+
# If true, always save the associated objects or destroy them if marked for destruction, when saving the parent object.
|
1312
|
+
# If false, never save or destroy the associated objects.
|
1313
|
+
# By default, only save associated objects that are new records.
|
1294
1314
|
#
|
1295
1315
|
# Option examples:
|
1296
1316
|
# has_and_belongs_to_many :projects
|
@@ -1335,7 +1355,7 @@ module ActiveRecord
|
|
1335
1355
|
end
|
1336
1356
|
|
1337
1357
|
def association_accessor_methods(reflection, association_proxy_class)
|
1338
|
-
|
1358
|
+
redefine_method(reflection.name) do |*params|
|
1339
1359
|
force_reload = params.first unless params.empty?
|
1340
1360
|
association = association_instance_get(reflection.name)
|
1341
1361
|
|
@@ -1352,12 +1372,12 @@ module ActiveRecord
|
|
1352
1372
|
association.target.nil? ? nil : association
|
1353
1373
|
end
|
1354
1374
|
|
1355
|
-
|
1375
|
+
redefine_method("loaded_#{reflection.name}?") do
|
1356
1376
|
association = association_instance_get(reflection.name)
|
1357
1377
|
association && association.loaded?
|
1358
1378
|
end
|
1359
|
-
|
1360
|
-
|
1379
|
+
|
1380
|
+
redefine_method("#{reflection.name}=") do |new_value|
|
1361
1381
|
association = association_instance_get(reflection.name)
|
1362
1382
|
|
1363
1383
|
if association.nil? || association.target != new_value
|
@@ -1367,8 +1387,8 @@ module ActiveRecord
|
|
1367
1387
|
association.replace(new_value)
|
1368
1388
|
association_instance_set(reflection.name, new_value.nil? ? nil : association)
|
1369
1389
|
end
|
1370
|
-
|
1371
|
-
|
1390
|
+
|
1391
|
+
redefine_method("set_#{reflection.name}_target") do |target|
|
1372
1392
|
return if target.nil? and association_proxy_class == BelongsToAssociation
|
1373
1393
|
association = association_proxy_class.new(self, reflection)
|
1374
1394
|
association.target = target
|
@@ -1377,7 +1397,7 @@ module ActiveRecord
|
|
1377
1397
|
end
|
1378
1398
|
|
1379
1399
|
def collection_reader_method(reflection, association_proxy_class)
|
1380
|
-
|
1400
|
+
redefine_method(reflection.name) do |*params|
|
1381
1401
|
force_reload = params.first unless params.empty?
|
1382
1402
|
association = association_instance_get(reflection.name)
|
1383
1403
|
|
@@ -1390,8 +1410,8 @@ module ActiveRecord
|
|
1390
1410
|
|
1391
1411
|
association
|
1392
1412
|
end
|
1393
|
-
|
1394
|
-
|
1413
|
+
|
1414
|
+
redefine_method("#{reflection.name.to_s.singularize}_ids") do
|
1395
1415
|
if send(reflection.name).loaded? || reflection.options[:finder_sql]
|
1396
1416
|
send(reflection.name).map(&:id)
|
1397
1417
|
else
|
@@ -1411,14 +1431,14 @@ module ActiveRecord
|
|
1411
1431
|
collection_reader_method(reflection, association_proxy_class)
|
1412
1432
|
|
1413
1433
|
if writer
|
1414
|
-
|
1434
|
+
redefine_method("#{reflection.name}=") do |new_value|
|
1415
1435
|
# Loads proxy class instance (defined in collection_reader_method) if not already loaded
|
1416
1436
|
association = send(reflection.name)
|
1417
1437
|
association.replace(new_value)
|
1418
1438
|
association
|
1419
1439
|
end
|
1420
|
-
|
1421
|
-
|
1440
|
+
|
1441
|
+
redefine_method("#{reflection.name.to_s.singularize}_ids=") do |new_value|
|
1422
1442
|
ids = (new_value || []).reject { |nid| nid.blank? }.map(&:to_i)
|
1423
1443
|
send("#{reflection.name}=", reflection.klass.find(ids).index_by(&:id).values_at(*ids))
|
1424
1444
|
end
|
@@ -1426,7 +1446,7 @@ module ActiveRecord
|
|
1426
1446
|
end
|
1427
1447
|
|
1428
1448
|
def association_constructor_method(constructor, reflection, association_proxy_class)
|
1429
|
-
|
1449
|
+
redefine_method("#{constructor}_#{reflection.name}") do |*params|
|
1430
1450
|
attributees = params.first unless params.empty?
|
1431
1451
|
replace_existing = params[1].nil? ? true : params[1]
|
1432
1452
|
association = association_instance_get(reflection.name)
|
@@ -1467,8 +1487,8 @@ module ActiveRecord
|
|
1467
1487
|
end
|
1468
1488
|
|
1469
1489
|
def add_touch_callbacks(reflection, touch_attribute)
|
1470
|
-
method_name = "belongs_to_touch_after_save_or_destroy_for_#{reflection.name}"
|
1471
|
-
|
1490
|
+
method_name = :"belongs_to_touch_after_save_or_destroy_for_#{reflection.name}"
|
1491
|
+
redefine_method(method_name) do
|
1472
1492
|
association = send(reflection.name)
|
1473
1493
|
|
1474
1494
|
if touch_attribute == true
|
@@ -1742,7 +1762,7 @@ module ActiveRecord
|
|
1742
1762
|
def graft(*associations)
|
1743
1763
|
associations.each do |association|
|
1744
1764
|
join_associations.detect {|a| association == a} ||
|
1745
|
-
build(association.reflection.name, association.find_parent_in(self), association.join_class)
|
1765
|
+
build(association.reflection.name, association.find_parent_in(self) || join_base, association.join_class)
|
1746
1766
|
end
|
1747
1767
|
self
|
1748
1768
|
end
|
@@ -1946,7 +1966,7 @@ module ActiveRecord
|
|
1946
1966
|
end
|
1947
1967
|
|
1948
1968
|
class JoinAssociation < JoinBase # :nodoc:
|
1949
|
-
attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name
|
1969
|
+
attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix, :aliased_join_table_name, :parent_table_name, :join_class
|
1950
1970
|
delegate :options, :klass, :through_reflection, :source_reflection, :to => :reflection
|
1951
1971
|
|
1952
1972
|
def initialize(reflection, join_dependency, parent = nil)
|
@@ -1963,6 +1983,7 @@ module ActiveRecord
|
|
1963
1983
|
@parent_table_name = parent.active_record.table_name
|
1964
1984
|
@aliased_table_name = aliased_table_name_for(table_name)
|
1965
1985
|
@join = nil
|
1986
|
+
@join_class = Arel::InnerJoin
|
1966
1987
|
|
1967
1988
|
if reflection.macro == :has_and_belongs_to_many
|
1968
1989
|
@aliased_join_table_name = aliased_table_name_for(reflection.options[:join_table], "_join")
|
@@ -1985,10 +2006,6 @@ module ActiveRecord
|
|
1985
2006
|
end
|
1986
2007
|
end
|
1987
2008
|
|
1988
|
-
def join_class
|
1989
|
-
@join_class ||= Arel::InnerJoin
|
1990
|
-
end
|
1991
|
-
|
1992
2009
|
def with_join_class(join_class)
|
1993
2010
|
@join_class = join_class
|
1994
2011
|
self
|
@@ -2066,7 +2083,7 @@ module ActiveRecord
|
|
2066
2083
|
unless klass.descends_from_active_record?
|
2067
2084
|
sti_column = aliased_table[klass.inheritance_column]
|
2068
2085
|
sti_condition = sti_column.eq(klass.sti_name)
|
2069
|
-
klass.
|
2086
|
+
klass.descendants.each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
|
2070
2087
|
|
2071
2088
|
@join << sti_condition
|
2072
2089
|
end
|