activerecord 3.1.12 → 3.2.0.rc1
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.md +6263 -103
- data/README.rdoc +2 -2
- data/examples/performance.rb +55 -31
- data/lib/active_record.rb +28 -2
- data/lib/active_record/aggregations.rb +2 -2
- data/lib/active_record/associations.rb +82 -69
- data/lib/active_record/associations/association.rb +2 -37
- data/lib/active_record/associations/association_scope.rb +3 -30
- data/lib/active_record/associations/builder/association.rb +6 -4
- data/lib/active_record/associations/builder/belongs_to.rb +3 -3
- data/lib/active_record/associations/builder/collection_association.rb +2 -2
- data/lib/active_record/associations/builder/has_many.rb +4 -4
- data/lib/active_record/associations/builder/has_one.rb +5 -6
- data/lib/active_record/associations/builder/singular_association.rb +3 -16
- data/lib/active_record/associations/collection_association.rb +55 -28
- data/lib/active_record/associations/collection_proxy.rb +1 -35
- data/lib/active_record/associations/has_many_association.rb +5 -1
- data/lib/active_record/associations/has_many_through_association.rb +11 -8
- data/lib/active_record/associations/join_dependency.rb +1 -1
- data/lib/active_record/associations/preloader/association.rb +3 -1
- data/lib/active_record/attribute_assignment.rb +221 -0
- data/lib/active_record/attribute_methods.rb +212 -32
- data/lib/active_record/attribute_methods/deprecated_underscore_read.rb +32 -0
- data/lib/active_record/attribute_methods/dirty.rb +3 -3
- data/lib/active_record/attribute_methods/primary_key.rb +62 -25
- data/lib/active_record/attribute_methods/read.rb +69 -80
- data/lib/active_record/attribute_methods/serialization.rb +89 -0
- data/lib/active_record/attribute_methods/time_zone_conversion.rb +9 -14
- data/lib/active_record/attribute_methods/write.rb +27 -5
- data/lib/active_record/autosave_association.rb +23 -8
- data/lib/active_record/base.rb +223 -1712
- data/lib/active_record/connection_adapters/abstract/connection_pool.rb +98 -132
- data/lib/active_record/connection_adapters/abstract/connection_specification.rb +82 -29
- data/lib/active_record/connection_adapters/abstract/database_statements.rb +13 -42
- data/lib/active_record/connection_adapters/abstract/query_cache.rb +1 -1
- data/lib/active_record/connection_adapters/abstract/quoting.rb +7 -4
- data/lib/active_record/connection_adapters/abstract/schema_definitions.rb +36 -25
- data/lib/active_record/connection_adapters/abstract/schema_statements.rb +41 -13
- data/lib/active_record/connection_adapters/abstract_adapter.rb +78 -43
- data/lib/active_record/connection_adapters/abstract_mysql_adapter.rb +653 -0
- data/lib/active_record/connection_adapters/mysql2_adapter.rb +138 -578
- data/lib/active_record/connection_adapters/mysql_adapter.rb +86 -658
- data/lib/active_record/connection_adapters/postgresql_adapter.rb +144 -94
- data/lib/active_record/connection_adapters/schema_cache.rb +50 -0
- data/lib/active_record/connection_adapters/sqlite3_adapter.rb +2 -6
- data/lib/active_record/connection_adapters/sqlite_adapter.rb +43 -22
- data/lib/active_record/counter_cache.rb +1 -1
- data/lib/active_record/dynamic_matchers.rb +79 -0
- data/lib/active_record/errors.rb +11 -1
- data/lib/active_record/explain.rb +83 -0
- data/lib/active_record/explain_subscriber.rb +21 -0
- data/lib/active_record/fixtures.rb +31 -76
- data/lib/active_record/fixtures/file.rb +65 -0
- data/lib/active_record/identity_map.rb +1 -7
- data/lib/active_record/inheritance.rb +167 -0
- data/lib/active_record/integration.rb +49 -0
- data/lib/active_record/locking/optimistic.rb +19 -11
- data/lib/active_record/locking/pessimistic.rb +1 -1
- data/lib/active_record/log_subscriber.rb +3 -3
- data/lib/active_record/migration.rb +38 -29
- data/lib/active_record/migration/command_recorder.rb +7 -7
- data/lib/active_record/model_schema.rb +362 -0
- data/lib/active_record/nested_attributes.rb +3 -2
- data/lib/active_record/persistence.rb +51 -1
- data/lib/active_record/querying.rb +58 -0
- data/lib/active_record/railtie.rb +24 -28
- data/lib/active_record/railties/controller_runtime.rb +3 -1
- data/lib/active_record/railties/databases.rake +133 -77
- data/lib/active_record/readonly_attributes.rb +26 -0
- data/lib/active_record/reflection.rb +7 -15
- data/lib/active_record/relation.rb +78 -35
- data/lib/active_record/relation/batches.rb +5 -2
- data/lib/active_record/relation/calculations.rb +27 -6
- data/lib/active_record/relation/delegation.rb +49 -0
- data/lib/active_record/relation/finder_methods.rb +5 -4
- data/lib/active_record/relation/predicate_builder.rb +13 -16
- data/lib/active_record/relation/query_methods.rb +59 -4
- data/lib/active_record/result.rb +1 -1
- data/lib/active_record/sanitization.rb +194 -0
- data/lib/active_record/schema_dumper.rb +5 -2
- data/lib/active_record/scoping.rb +152 -0
- data/lib/active_record/scoping/default.rb +140 -0
- data/lib/active_record/scoping/named.rb +202 -0
- data/lib/active_record/serialization.rb +1 -43
- data/lib/active_record/serializers/xml_serializer.rb +2 -44
- data/lib/active_record/session_store.rb +11 -11
- data/lib/active_record/store.rb +50 -0
- data/lib/active_record/test_case.rb +11 -7
- data/lib/active_record/timestamp.rb +16 -3
- data/lib/active_record/transactions.rb +5 -5
- data/lib/active_record/translation.rb +22 -0
- data/lib/active_record/validations.rb +1 -1
- data/lib/active_record/validations/associated.rb +5 -4
- data/lib/active_record/validations/uniqueness.rb +4 -4
- data/lib/active_record/version.rb +3 -3
- data/lib/rails/generators/active_record/session_migration/templates/migration.rb +1 -5
- metadata +48 -38
- checksums.yaml +0 -7
- data/lib/active_record/named_scope.rb +0 -200
data/README.rdoc
CHANGED
@@ -197,7 +197,7 @@ Admit the Database:
|
|
197
197
|
|
198
198
|
== Download and installation
|
199
199
|
|
200
|
-
The latest version of Active Record can be installed with
|
200
|
+
The latest version of Active Record can be installed with RubyGems:
|
201
201
|
|
202
202
|
% [sudo] gem install activerecord
|
203
203
|
|
@@ -215,7 +215,7 @@ Active Record is released under the MIT license.
|
|
215
215
|
|
216
216
|
API documentation is at
|
217
217
|
|
218
|
-
* http://api.rubyonrails.
|
218
|
+
* http://api.rubyonrails.org
|
219
219
|
|
220
220
|
Bug reports and feature requests can be filed with the rest for the Ruby on Rails project here:
|
221
221
|
|
data/examples/performance.rb
CHANGED
@@ -1,9 +1,7 @@
|
|
1
|
+
TIMES = (ENV['N'] || 10000).to_i
|
2
|
+
|
1
3
|
require File.expand_path('../../../load_paths', __FILE__)
|
2
4
|
require "active_record"
|
3
|
-
require 'benchmark/ips'
|
4
|
-
|
5
|
-
TIME = (ENV['BENCHMARK_TIME'] || 20).to_i
|
6
|
-
RECORDS = (ENV['BENCHMARK_RECORDS'] || TIME*1000).to_i
|
7
5
|
|
8
6
|
conn = { :adapter => 'sqlite3', :database => ':memory:' }
|
9
7
|
|
@@ -31,6 +29,14 @@ class Exhibit < ActiveRecord::Base
|
|
31
29
|
def look; attributes end
|
32
30
|
def feel; look; user.name end
|
33
31
|
|
32
|
+
def self.with_name
|
33
|
+
where("name IS NOT NULL")
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.with_notes
|
37
|
+
where("notes IS NOT NULL")
|
38
|
+
end
|
39
|
+
|
34
40
|
def self.look(exhibits) exhibits.each { |e| e.look } end
|
35
41
|
def self.feel(exhibits) exhibits.each { |e| e.feel } end
|
36
42
|
end
|
@@ -39,7 +45,14 @@ puts 'Generating data...'
|
|
39
45
|
|
40
46
|
module ActiveRecord
|
41
47
|
class Faker
|
42
|
-
LOREM =
|
48
|
+
LOREM = %Q{Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse non aliquet diam. Curabitur vel urna metus, quis malesuada elit.
|
49
|
+
Integer consequat tincidunt felis. Etiam non erat dolor. Vivamus imperdiet nibh sit amet diam eleifend id posuere diam malesuada. Mauris at accumsan sem.
|
50
|
+
Donec id lorem neque. Fusce erat lorem, ornare eu congue vitae, malesuada quis neque. Maecenas vel urna a velit pretium fermentum. Donec tortor enim,
|
51
|
+
tempor venenatis egestas a, tempor sed ipsum. Ut arcu justo, faucibus non imperdiet ac, interdum at diam. Pellentesque ipsum enim, venenatis ut iaculis vitae,
|
52
|
+
varius vitae sem. Sed rutrum quam ac elit euismod bibendum. Donec ultricies ultricies magna, at lacinia libero mollis aliquam. Sed ac arcu in tortor elementum
|
53
|
+
tincidunt vel interdum sem. Curabitur eget erat arcu. Praesent eget eros leo. Nam magna enim, sollicitudin vehicula scelerisque in, vulputate ut libero.
|
54
|
+
Praesent varius tincidunt commodo}.split
|
55
|
+
|
43
56
|
def self.name
|
44
57
|
LOREM.grep(/^\w*$/).sort_by { rand }.first(2).join ' '
|
45
58
|
end
|
@@ -59,8 +72,8 @@ end
|
|
59
72
|
notes = ActiveRecord::Faker::LOREM.join ' '
|
60
73
|
today = Date.today
|
61
74
|
|
62
|
-
puts
|
63
|
-
|
75
|
+
puts 'Inserting 10,000 users and exhibits...'
|
76
|
+
10_000.times do
|
64
77
|
user = User.create(
|
65
78
|
:created_at => today,
|
66
79
|
:name => ActiveRecord::Faker.name,
|
@@ -75,7 +88,9 @@ RECORDS.times do
|
|
75
88
|
)
|
76
89
|
end
|
77
90
|
|
78
|
-
|
91
|
+
require 'benchmark'
|
92
|
+
|
93
|
+
Benchmark.bm(46) do |x|
|
79
94
|
ar_obj = Exhibit.find(1)
|
80
95
|
attrs = { :name => 'sam' }
|
81
96
|
attrs_first = { :name => 'sam' }
|
@@ -86,68 +101,77 @@ Benchmark.ips(TIME) do |x|
|
|
86
101
|
:created_at => Date.today
|
87
102
|
}
|
88
103
|
|
89
|
-
x.report("Model#id") do
|
90
|
-
ar_obj.id
|
104
|
+
x.report("Model#id (x#{(TIMES * 100).ceil})") do
|
105
|
+
(TIMES * 100).ceil.times { ar_obj.id }
|
91
106
|
end
|
92
107
|
|
93
108
|
x.report 'Model.new (instantiation)' do
|
94
|
-
Exhibit.new
|
109
|
+
TIMES.times { Exhibit.new }
|
95
110
|
end
|
96
111
|
|
97
112
|
x.report 'Model.new (setting attributes)' do
|
98
|
-
Exhibit.new(attrs)
|
113
|
+
TIMES.times { Exhibit.new(attrs) }
|
99
114
|
end
|
100
115
|
|
101
116
|
x.report 'Model.first' do
|
102
|
-
Exhibit.first.look
|
117
|
+
TIMES.times { Exhibit.first.look }
|
118
|
+
end
|
119
|
+
|
120
|
+
x.report 'Model.named_scope' do
|
121
|
+
TIMES.times { Exhibit.limit(10).with_name.with_notes }
|
103
122
|
end
|
104
123
|
|
105
|
-
x.report("Model.all limit(100)") do
|
106
|
-
Exhibit.look Exhibit.limit(100)
|
124
|
+
x.report("Model.all limit(100) (x#{(TIMES / 10).ceil})") do
|
125
|
+
(TIMES / 10).ceil.times { Exhibit.look Exhibit.limit(100) }
|
107
126
|
end
|
108
127
|
|
109
|
-
x.report "Model.all limit(100) with relationship" do
|
110
|
-
Exhibit.feel Exhibit.limit(100).includes(:user)
|
128
|
+
x.report "Model.all limit(100) with relationship (x#{(TIMES / 10).ceil})" do
|
129
|
+
(TIMES / 10).ceil.times { Exhibit.feel Exhibit.limit(100).includes(:user) }
|
111
130
|
end
|
112
131
|
|
113
|
-
x.report "Model.all limit(10,000)" do
|
114
|
-
Exhibit.look Exhibit.limit(10000)
|
132
|
+
x.report "Model.all limit(10,000) x(#{(TIMES / 1000).ceil})" do
|
133
|
+
(TIMES / 1000).ceil.times { Exhibit.look Exhibit.limit(10000) }
|
115
134
|
end
|
116
135
|
|
117
136
|
x.report 'Model.create' do
|
118
|
-
Exhibit.create(exhibit)
|
137
|
+
TIMES.times { Exhibit.create(exhibit) }
|
119
138
|
end
|
120
139
|
|
121
140
|
x.report 'Resource#attributes=' do
|
122
|
-
|
123
|
-
|
141
|
+
TIMES.times {
|
142
|
+
exhibit = Exhibit.new(attrs_first)
|
143
|
+
exhibit.attributes = attrs_second
|
144
|
+
}
|
124
145
|
end
|
125
146
|
|
126
147
|
x.report 'Resource#update' do
|
127
|
-
Exhibit.first.update_attributes(:name => 'bob')
|
148
|
+
TIMES.times { Exhibit.first.update_attributes(:name => 'bob') }
|
128
149
|
end
|
129
150
|
|
130
151
|
x.report 'Resource#destroy' do
|
131
|
-
Exhibit.first.destroy
|
152
|
+
TIMES.times { Exhibit.first.destroy }
|
132
153
|
end
|
133
154
|
|
134
155
|
x.report 'Model.transaction' do
|
135
|
-
Exhibit.transaction { Exhibit.new }
|
156
|
+
TIMES.times { Exhibit.transaction { Exhibit.new } }
|
136
157
|
end
|
137
158
|
|
138
159
|
x.report 'Model.find(id)' do
|
139
|
-
|
160
|
+
id = Exhibit.first.id
|
161
|
+
TIMES.times { Exhibit.find(id) }
|
140
162
|
end
|
141
163
|
|
142
164
|
x.report 'Model.find_by_sql' do
|
143
|
-
|
165
|
+
TIMES.times {
|
166
|
+
Exhibit.find_by_sql("SELECT * FROM exhibits WHERE id = #{(rand * 1000 + 1).to_i}").first
|
167
|
+
}
|
144
168
|
end
|
145
169
|
|
146
|
-
x.report "Model.log" do
|
147
|
-
Exhibit.connection.send(:log, "hello", "world") {}
|
170
|
+
x.report "Model.log x(#{TIMES * 10})" do
|
171
|
+
(TIMES * 10).times { Exhibit.connection.send(:log, "hello", "world") {} }
|
148
172
|
end
|
149
173
|
|
150
|
-
x.report "AR.execute(query)" do
|
151
|
-
ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}")
|
174
|
+
x.report "AR.execute(query) (#{TIMES / 2})" do
|
175
|
+
(TIMES / 2).times { ActiveRecord::Base.connection.execute("Select * from exhibits where id = #{(rand * 1000 + 1).to_i}") }
|
152
176
|
end
|
153
177
|
end
|
data/lib/active_record.rb
CHANGED
@@ -34,10 +34,12 @@ module ActiveRecord
|
|
34
34
|
eager_autoload do
|
35
35
|
autoload :ActiveRecordError, 'active_record/errors'
|
36
36
|
autoload :ConnectionNotEstablished, 'active_record/errors'
|
37
|
+
autoload :ConnectionAdapters, 'active_record/connection_adapters/abstract_adapter'
|
37
38
|
|
38
39
|
autoload :Aggregations
|
39
40
|
autoload :Associations
|
40
41
|
autoload :AttributeMethods
|
42
|
+
autoload :AttributeAssignment
|
41
43
|
autoload :AutosaveAssociation
|
42
44
|
|
43
45
|
autoload :Relation
|
@@ -49,29 +51,42 @@ module ActiveRecord
|
|
49
51
|
autoload :PredicateBuilder
|
50
52
|
autoload :SpawnMethods
|
51
53
|
autoload :Batches
|
54
|
+
autoload :Explain
|
55
|
+
autoload :Delegation
|
52
56
|
end
|
53
57
|
|
54
58
|
autoload :Base
|
55
59
|
autoload :Callbacks
|
56
60
|
autoload :CounterCache
|
61
|
+
autoload :DynamicMatchers
|
57
62
|
autoload :DynamicFinderMatch
|
58
63
|
autoload :DynamicScopeMatch
|
64
|
+
autoload :Explain
|
65
|
+
autoload :IdentityMap
|
66
|
+
autoload :Inheritance
|
67
|
+
autoload :Integration
|
59
68
|
autoload :Migration
|
60
69
|
autoload :Migrator, 'active_record/migration'
|
61
|
-
autoload :
|
70
|
+
autoload :ModelSchema
|
62
71
|
autoload :NestedAttributes
|
63
72
|
autoload :Observer
|
64
73
|
autoload :Persistence
|
65
74
|
autoload :QueryCache
|
75
|
+
autoload :Querying
|
76
|
+
autoload :ReadonlyAttributes
|
66
77
|
autoload :Reflection
|
78
|
+
autoload :Result
|
79
|
+
autoload :Sanitization
|
67
80
|
autoload :Schema
|
68
81
|
autoload :SchemaDumper
|
82
|
+
autoload :Scoping
|
69
83
|
autoload :Serialization
|
70
84
|
autoload :SessionStore
|
85
|
+
autoload :Store
|
71
86
|
autoload :Timestamp
|
72
87
|
autoload :Transactions
|
88
|
+
autoload :Translation
|
73
89
|
autoload :Validations
|
74
|
-
autoload :IdentityMap
|
75
90
|
end
|
76
91
|
|
77
92
|
module Coders
|
@@ -89,6 +104,8 @@ module ActiveRecord
|
|
89
104
|
autoload :Read
|
90
105
|
autoload :TimeZoneConversion
|
91
106
|
autoload :Write
|
107
|
+
autoload :Serialization
|
108
|
+
autoload :DeprecatedUnderscoreRead
|
92
109
|
end
|
93
110
|
end
|
94
111
|
|
@@ -110,6 +127,15 @@ module ActiveRecord
|
|
110
127
|
end
|
111
128
|
end
|
112
129
|
|
130
|
+
module Scoping
|
131
|
+
extend ActiveSupport::Autoload
|
132
|
+
|
133
|
+
eager_autoload do
|
134
|
+
autoload :Named
|
135
|
+
autoload :Default
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
113
139
|
autoload :TestCase
|
114
140
|
autoload :TestFixtures, 'active_record/fixtures'
|
115
141
|
end
|
@@ -46,7 +46,7 @@ module ActiveRecord
|
|
46
46
|
#
|
47
47
|
# def <=>(other_money)
|
48
48
|
# if currency == other_money.currency
|
49
|
-
# amount <=>
|
49
|
+
# amount <=> amount
|
50
50
|
# else
|
51
51
|
# amount <=> other_money.exchange_to(currency).amount
|
52
52
|
# end
|
@@ -176,7 +176,7 @@ module ActiveRecord
|
|
176
176
|
# order in which mappings are defined determines the order in which attributes are sent to the
|
177
177
|
# value class constructor.
|
178
178
|
# * <tt>:allow_nil</tt> - Specifies that the value object will not be instantiated when all mapped
|
179
|
-
# attributes are +nil+.
|
179
|
+
# attributes are +nil+. Setting the value object to +nil+ has the effect of writing +nil+ to all
|
180
180
|
# mapped attributes.
|
181
181
|
# This defaults to +false+.
|
182
182
|
# * <tt>:constructor</tt> - A symbol specifying the name of the constructor method or a Proc that
|
@@ -5,7 +5,6 @@ require 'active_support/core_ext/object/blank'
|
|
5
5
|
require 'active_support/core_ext/string/conversions'
|
6
6
|
require 'active_support/core_ext/module/remove_method'
|
7
7
|
require 'active_support/core_ext/class/attribute'
|
8
|
-
require 'active_support/deprecation'
|
9
8
|
|
10
9
|
module ActiveRecord
|
11
10
|
class InverseOfAssociationNotFoundError < ActiveRecordError #:nodoc:
|
@@ -71,7 +70,7 @@ module ActiveRecord
|
|
71
70
|
end
|
72
71
|
end
|
73
72
|
|
74
|
-
class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc
|
73
|
+
class HasManyThroughNestedAssociationsAreReadonly < ActiveRecordError #:nodoc:
|
75
74
|
def initialize(owner, reflection)
|
76
75
|
super("Cannot modify association '#{owner.class.name}##{reflection.name}' because it goes through more than one other association.")
|
77
76
|
end
|
@@ -114,7 +113,6 @@ module ActiveRecord
|
|
114
113
|
autoload :SingularAssociation, 'active_record/associations/singular_association'
|
115
114
|
autoload :CollectionAssociation, 'active_record/associations/collection_association'
|
116
115
|
autoload :CollectionProxy, 'active_record/associations/collection_proxy'
|
117
|
-
autoload :AssociationCollection, 'active_record/associations/collection_proxy'
|
118
116
|
|
119
117
|
autoload :BelongsToAssociation, 'active_record/associations/belongs_to_association'
|
120
118
|
autoload :BelongsToPolymorphicAssociation, 'active_record/associations/belongs_to_polymorphic_association'
|
@@ -193,11 +191,31 @@ module ActiveRecord
|
|
193
191
|
# * <tt>Project#portfolio, Project#portfolio=(portfolio), Project#portfolio.nil?</tt>
|
194
192
|
# * <tt>Project#project_manager, Project#project_manager=(project_manager), Project#project_manager.nil?,</tt>
|
195
193
|
# * <tt>Project#milestones.empty?, Project#milestones.size, Project#milestones, Project#milestones<<(milestone),</tt>
|
196
|
-
# <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.
|
194
|
+
# <tt>Project#milestones.delete(milestone), Project#milestones.find(milestone_id), Project#milestones.all(options),</tt>
|
197
195
|
# <tt>Project#milestones.build, Project#milestones.create</tt>
|
198
196
|
# * <tt>Project#categories.empty?, Project#categories.size, Project#categories, Project#categories<<(category1),</tt>
|
199
197
|
# <tt>Project#categories.delete(category1)</tt>
|
200
198
|
#
|
199
|
+
# === Overriding generated methods
|
200
|
+
#
|
201
|
+
# Association methods are generated in a module that is included into the model class,
|
202
|
+
# which allows you to easily override with your own methods and call the original
|
203
|
+
# generated method with +super+. For example:
|
204
|
+
#
|
205
|
+
# class Car < ActiveRecord::Base
|
206
|
+
# belongs_to :owner
|
207
|
+
# belongs_to :old_owner
|
208
|
+
# def owner=(new_owner)
|
209
|
+
# self.old_owner = self.owner
|
210
|
+
# super
|
211
|
+
# end
|
212
|
+
# end
|
213
|
+
#
|
214
|
+
# If your model class is <tt>Project</tt>, the module is
|
215
|
+
# named <tt>Project::GeneratedFeatureMethods</tt>. The GeneratedFeatureMethods module is
|
216
|
+
# included in the model class immediately after the (anonymous) generated attributes methods
|
217
|
+
# module, meaning an association will override the methods for an attribute with the same name.
|
218
|
+
#
|
201
219
|
# === A word of warning
|
202
220
|
#
|
203
221
|
# Don't create associations that have the same name as instance methods of
|
@@ -471,9 +489,9 @@ module ActiveRecord
|
|
471
489
|
# === Association Join Models
|
472
490
|
#
|
473
491
|
# Has Many associations can be configured with the <tt>:through</tt> option to use an
|
474
|
-
# explicit join model to retrieve the data.
|
475
|
-
# +has_and_belongs_to_many+ association.
|
476
|
-
# callbacks, and extra attributes on the join model.
|
492
|
+
# explicit join model to retrieve the data. This operates similarly to a
|
493
|
+
# +has_and_belongs_to_many+ association. The advantage is that you're able to add validations,
|
494
|
+
# callbacks, and extra attributes on the join model. Consider the following schema:
|
477
495
|
#
|
478
496
|
# class Author < ActiveRecord::Base
|
479
497
|
# has_many :authorships
|
@@ -530,7 +548,7 @@ module ActiveRecord
|
|
530
548
|
# @group.avatars # selects all avatars by going through the User join model.
|
531
549
|
#
|
532
550
|
# An important caveat with going through +has_one+ or +has_many+ associations on the
|
533
|
-
# join model is that these associations are *read-only*.
|
551
|
+
# join model is that these associations are *read-only*. For example, the following
|
534
552
|
# would not work following the previous example:
|
535
553
|
#
|
536
554
|
# @group.avatars << Avatar.new # this would work if User belonged_to Avatar rather than the other way around
|
@@ -598,7 +616,7 @@ module ActiveRecord
|
|
598
616
|
# === Polymorphic Associations
|
599
617
|
#
|
600
618
|
# Polymorphic associations on models are not restricted on what types of models they
|
601
|
-
# can be associated with.
|
619
|
+
# can be associated with. Rather, they specify an interface that a +has_many+ association
|
602
620
|
# must adhere to.
|
603
621
|
#
|
604
622
|
# class Asset < ActiveRecord::Base
|
@@ -612,7 +630,7 @@ module ActiveRecord
|
|
612
630
|
# @asset.attachable = @post
|
613
631
|
#
|
614
632
|
# This works by using a type column in addition to a foreign key to specify the associated
|
615
|
-
# record.
|
633
|
+
# record. In the Asset example, you'd need an +attachable_id+ integer column and an
|
616
634
|
# +attachable_type+ string column.
|
617
635
|
#
|
618
636
|
# Using polymorphic associations in combination with single table inheritance (STI) is
|
@@ -668,7 +686,7 @@ module ActiveRecord
|
|
668
686
|
#
|
669
687
|
# Consider the following loop using the class above:
|
670
688
|
#
|
671
|
-
#
|
689
|
+
# Post.all.each do |post|
|
672
690
|
# puts "Post: " + post.title
|
673
691
|
# puts "Written by: " + post.author.name
|
674
692
|
# puts "Last comment on: " + post.comments.first.created_on
|
@@ -677,7 +695,7 @@ module ActiveRecord
|
|
677
695
|
# To iterate over these one hundred posts, we'll generate 201 database queries. Let's
|
678
696
|
# first just optimize it for retrieving the author:
|
679
697
|
#
|
680
|
-
#
|
698
|
+
# Post.includes(:author).each do |post|
|
681
699
|
#
|
682
700
|
# This references the name of the +belongs_to+ association that also used the <tt>:author</tt>
|
683
701
|
# symbol. After loading the posts, find will collect the +author_id+ from each one and load
|
@@ -686,7 +704,7 @@ module ActiveRecord
|
|
686
704
|
#
|
687
705
|
# We can improve upon the situation further by referencing both associations in the finder with:
|
688
706
|
#
|
689
|
-
#
|
707
|
+
# Post.includes(:author, :comments).each do |post|
|
690
708
|
#
|
691
709
|
# This will load all comments with a single query. This reduces the total number of queries
|
692
710
|
# to 3. More generally the number of queries will be 1 plus the number of associations
|
@@ -694,7 +712,7 @@ module ActiveRecord
|
|
694
712
|
#
|
695
713
|
# To include a deep hierarchy of associations, use a hash:
|
696
714
|
#
|
697
|
-
#
|
715
|
+
# Post.includes(:author, {:comments => {:author => :gravatar}}).each do |post|
|
698
716
|
#
|
699
717
|
# That'll grab not only all the comments but all their authors and gravatar pictures.
|
700
718
|
# You can mix and match symbols, arrays and hashes in any combination to describe the
|
@@ -722,13 +740,13 @@ module ActiveRecord
|
|
722
740
|
# <tt>:order => "author.name DESC"</tt> will work but <tt>:order => "name DESC"</tt> will not.
|
723
741
|
#
|
724
742
|
# If you do want eager load only some members of an association it is usually more natural
|
725
|
-
# to
|
743
|
+
# to include an association which has conditions defined on it:
|
726
744
|
#
|
727
745
|
# class Post < ActiveRecord::Base
|
728
746
|
# has_many :approved_comments, :class_name => 'Comment', :conditions => ['approved = ?', true]
|
729
747
|
# end
|
730
748
|
#
|
731
|
-
# Post.
|
749
|
+
# Post.includes(:approved_comments)
|
732
750
|
#
|
733
751
|
# This will load posts and eager load the +approved_comments+ association, which contains
|
734
752
|
# only those comments that have been approved.
|
@@ -740,10 +758,10 @@ module ActiveRecord
|
|
740
758
|
# has_many :most_recent_comments, :class_name => 'Comment', :order => 'id DESC', :limit => 10
|
741
759
|
# end
|
742
760
|
#
|
743
|
-
# Picture.
|
761
|
+
# Picture.includes(:most_recent_comments).first.most_recent_comments # => returns all associated comments.
|
744
762
|
#
|
745
763
|
# When eager loaded, conditions are interpolated in the context of the model class, not
|
746
|
-
# the model instance.
|
764
|
+
# the model instance. Conditions are lazily interpolated before the actual model exists.
|
747
765
|
#
|
748
766
|
# Eager loading is supported with polymorphic associations.
|
749
767
|
#
|
@@ -753,7 +771,7 @@ module ActiveRecord
|
|
753
771
|
#
|
754
772
|
# A call that tries to eager load the addressable model
|
755
773
|
#
|
756
|
-
# Address.
|
774
|
+
# Address.includes(:addressable)
|
757
775
|
#
|
758
776
|
# This will execute one query to load the addresses and load the addressables with one
|
759
777
|
# query per addressable type.
|
@@ -767,47 +785,47 @@ module ActiveRecord
|
|
767
785
|
# == Table Aliasing
|
768
786
|
#
|
769
787
|
# Active Record uses table aliasing in the case that a table is referenced multiple times
|
770
|
-
# in a join.
|
788
|
+
# in a join. If a table is referenced only once, the standard table name is used. The
|
771
789
|
# second time, the table is aliased as <tt>#{reflection_name}_#{parent_table_name}</tt>.
|
772
790
|
# Indexes are appended for any more successive uses of the table name.
|
773
791
|
#
|
774
|
-
# Post.
|
792
|
+
# Post.joins(:comments)
|
775
793
|
# # => SELECT ... FROM posts INNER JOIN comments ON ...
|
776
|
-
# Post.
|
794
|
+
# Post.joins(:special_comments) # STI
|
777
795
|
# # => SELECT ... FROM posts INNER JOIN comments ON ... AND comments.type = 'SpecialComment'
|
778
|
-
# Post.
|
796
|
+
# Post.joins(:comments, :special_comments) # special_comments is the reflection name, posts is the parent table name
|
779
797
|
# # => SELECT ... FROM posts INNER JOIN comments ON ... INNER JOIN comments special_comments_posts
|
780
798
|
#
|
781
799
|
# Acts as tree example:
|
782
800
|
#
|
783
|
-
# TreeMixin.
|
801
|
+
# TreeMixin.joins(:children)
|
784
802
|
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
|
785
|
-
# TreeMixin.
|
803
|
+
# TreeMixin.joins(:children => :parent)
|
786
804
|
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
|
787
805
|
# INNER JOIN parents_mixins ...
|
788
|
-
# TreeMixin.
|
806
|
+
# TreeMixin.joins(:children => {:parent => :children})
|
789
807
|
# # => SELECT ... FROM mixins INNER JOIN mixins childrens_mixins ...
|
790
808
|
# INNER JOIN parents_mixins ...
|
791
809
|
# INNER JOIN mixins childrens_mixins_2
|
792
810
|
#
|
793
811
|
# Has and Belongs to Many join tables use the same idea, but add a <tt>_join</tt> suffix:
|
794
812
|
#
|
795
|
-
# Post.
|
813
|
+
# Post.joins(:categories)
|
796
814
|
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
|
797
|
-
# Post.
|
815
|
+
# Post.joins(:categories => :posts)
|
798
816
|
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
|
799
817
|
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
|
800
|
-
# Post.
|
818
|
+
# Post.joins(:categories => {:posts => :categories})
|
801
819
|
# # => SELECT ... FROM posts INNER JOIN categories_posts ... INNER JOIN categories ...
|
802
820
|
# INNER JOIN categories_posts posts_categories_join INNER JOIN posts posts_categories
|
803
821
|
# INNER JOIN categories_posts categories_posts_join INNER JOIN categories categories_posts_2
|
804
822
|
#
|
805
|
-
# If you wish to specify your own custom joins using
|
823
|
+
# If you wish to specify your own custom joins using <tt>joins</tt> method, those table
|
806
824
|
# names will take precedence over the eager associations:
|
807
825
|
#
|
808
|
-
# Post.
|
826
|
+
# Post.joins(:comments).joins("inner join comments ...")
|
809
827
|
# # => SELECT ... FROM posts INNER JOIN comments_posts ON ... INNER JOIN comments ...
|
810
|
-
# Post.
|
828
|
+
# Post.joins(:comments, :special_comments).joins("inner join comments ...")
|
811
829
|
# # => SELECT ... FROM posts INNER JOIN comments comments_posts ON ...
|
812
830
|
# INNER JOIN comments special_comments_posts ...
|
813
831
|
# INNER JOIN comments ...
|
@@ -849,7 +867,7 @@ module ActiveRecord
|
|
849
867
|
# == Bi-directional associations
|
850
868
|
#
|
851
869
|
# When you specify an association there is usually an association on the associated model
|
852
|
-
# that specifies the same relationship in reverse.
|
870
|
+
# that specifies the same relationship in reverse. For example, with the following models:
|
853
871
|
#
|
854
872
|
# class Dungeon < ActiveRecord::Base
|
855
873
|
# has_many :traps
|
@@ -866,9 +884,9 @@ module ActiveRecord
|
|
866
884
|
#
|
867
885
|
# The +traps+ association on +Dungeon+ and the +dungeon+ association on +Trap+ are
|
868
886
|
# the inverse of each other and the inverse of the +dungeon+ association on +EvilWizard+
|
869
|
-
# is the +evil_wizard+ association on +Dungeon+ (and vice-versa).
|
887
|
+
# is the +evil_wizard+ association on +Dungeon+ (and vice-versa). By default,
|
870
888
|
# Active Record doesn't know anything about these inverse relationships and so no object
|
871
|
-
# loading
|
889
|
+
# loading optimization is possible. For example:
|
872
890
|
#
|
873
891
|
# d = Dungeon.first
|
874
892
|
# t = d.traps.first
|
@@ -878,8 +896,8 @@ module ActiveRecord
|
|
878
896
|
#
|
879
897
|
# The +Dungeon+ instances +d+ and <tt>t.dungeon</tt> in the above example refer to
|
880
898
|
# the same object data from the database, but are actually different in-memory copies
|
881
|
-
# of that data.
|
882
|
-
# Active Record about inverse relationships and it will optimise object loading.
|
899
|
+
# of that data. Specifying the <tt>:inverse_of</tt> option on associations lets you tell
|
900
|
+
# Active Record about inverse relationships and it will optimise object loading. For
|
883
901
|
# example, if we changed our model definitions to:
|
884
902
|
#
|
885
903
|
# class Dungeon < ActiveRecord::Base
|
@@ -1039,7 +1057,7 @@ module ActiveRecord
|
|
1039
1057
|
# === Example
|
1040
1058
|
#
|
1041
1059
|
# Example: A Firm class declares <tt>has_many :clients</tt>, which will add:
|
1042
|
-
# * <tt>Firm#clients</tt> (similar to <tt>Clients.
|
1060
|
+
# * <tt>Firm#clients</tt> (similar to <tt>Clients.all :conditions => ["firm_id = ?", id]</tt>)
|
1043
1061
|
# * <tt>Firm#clients<<</tt>
|
1044
1062
|
# * <tt>Firm#clients.delete</tt>
|
1045
1063
|
# * <tt>Firm#clients=</tt>
|
@@ -1062,7 +1080,7 @@ module ActiveRecord
|
|
1062
1080
|
# specify it with this option.
|
1063
1081
|
# [:conditions]
|
1064
1082
|
# Specify the conditions that the associated objects must meet in order to be included as a +WHERE+
|
1065
|
-
# SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>.
|
1083
|
+
# SQL fragment, such as <tt>price > 5 AND name LIKE 'B%'</tt>. Record creations from
|
1066
1084
|
# the association are scoped if a hash is used.
|
1067
1085
|
# <tt>has_many :posts, :conditions => {:published => true}</tt> will create published
|
1068
1086
|
# posts with <tt>@blog.posts.create</tt> or <tt>@blog.posts.build</tt>.
|
@@ -1077,10 +1095,11 @@ module ActiveRecord
|
|
1077
1095
|
# Specify the method that returns the primary key used for the association. By default this is +id+.
|
1078
1096
|
# [:dependent]
|
1079
1097
|
# If set to <tt>:destroy</tt> all the associated objects are destroyed
|
1080
|
-
# alongside this object by calling their +destroy+ method.
|
1081
|
-
# objects are deleted *without* calling their +destroy+ method.
|
1098
|
+
# alongside this object by calling their +destroy+ method. If set to <tt>:delete_all</tt> all associated
|
1099
|
+
# objects are deleted *without* calling their +destroy+ method. If set to <tt>:nullify</tt> all associated
|
1082
1100
|
# objects' foreign keys are set to +NULL+ *without* calling their +save+ callbacks. If set to
|
1083
|
-
# <tt>:restrict</tt> this object
|
1101
|
+
# <tt>:restrict</tt> this object raises an <tt>ActiveRecord::DeleteRestrictionError</tt> exception and
|
1102
|
+
# cannot be deleted if it has any associated objects.
|
1084
1103
|
#
|
1085
1104
|
# If using with the <tt>:through</tt> option, the association on the join model must be
|
1086
1105
|
# a +belongs_to+, and the records which get deleted are the join records, rather than
|
@@ -1088,7 +1107,8 @@ module ActiveRecord
|
|
1088
1107
|
#
|
1089
1108
|
# [:finder_sql]
|
1090
1109
|
# Specify a complete SQL statement to fetch the association. This is a good way to go for complex
|
1091
|
-
# associations that depend on multiple tables.
|
1110
|
+
# associations that depend on multiple tables. May be supplied as a string or a proc where interpolation is
|
1111
|
+
# required. Note: When this option is used, +find_in_collection+
|
1092
1112
|
# is _not_ added.
|
1093
1113
|
# [:counter_sql]
|
1094
1114
|
# Specify a complete SQL statement to fetch the size of the association. If <tt>:finder_sql</tt> is
|
@@ -1163,11 +1183,14 @@ module ActiveRecord
|
|
1163
1183
|
# has_many :tags, :as => :taggable
|
1164
1184
|
# has_many :reports, :readonly => true
|
1165
1185
|
# has_many :subscribers, :through => :subscriptions, :source => :user
|
1166
|
-
# has_many :subscribers, :class_name => "Person", :finder_sql =>
|
1167
|
-
#
|
1168
|
-
#
|
1169
|
-
#
|
1170
|
-
#
|
1186
|
+
# has_many :subscribers, :class_name => "Person", :finder_sql => Proc.new {
|
1187
|
+
# %Q{
|
1188
|
+
# SELECT DISTINCT *
|
1189
|
+
# FROM people p, post_subscriptions ps
|
1190
|
+
# WHERE ps.post_id = #{id} AND ps.person_id = p.id
|
1191
|
+
# ORDER BY p.first_name
|
1192
|
+
# }
|
1193
|
+
# }
|
1171
1194
|
def has_many(name, options = {}, &extension)
|
1172
1195
|
Builder::HasMany.build(self, name, options, &extension)
|
1173
1196
|
end
|
@@ -1229,7 +1252,8 @@ module ActiveRecord
|
|
1229
1252
|
# If set to <tt>:destroy</tt>, the associated object is destroyed when this object is. If set to
|
1230
1253
|
# <tt>:delete</tt>, the associated object is deleted *without* calling its destroy method.
|
1231
1254
|
# If set to <tt>:nullify</tt>, the associated object's foreign key is set to +NULL+.
|
1232
|
-
# Also, association is assigned.
|
1255
|
+
# Also, association is assigned. If set to <tt>:restrict</tt> this object raises an
|
1256
|
+
# <tt>ActiveRecord::DeleteRestrictionError</tt> exception and cannot be deleted if it has any associated object.
|
1233
1257
|
# [:foreign_key]
|
1234
1258
|
# Specify the foreign key used for the association. By default this is guessed to be the name
|
1235
1259
|
# of this class in lower-case and "_id" suffixed. So a Person class that makes a +has_one+ association
|
@@ -1245,7 +1269,7 @@ module ActiveRecord
|
|
1245
1269
|
# you want to do a join but not include the joined columns. Do not forget to include the
|
1246
1270
|
# primary and foreign keys, otherwise it will raise an error.
|
1247
1271
|
# [:through]
|
1248
|
-
# Specifies a Join Model through which to perform the query.
|
1272
|
+
# Specifies a Join Model through which to perform the query. Options for <tt>:class_name</tt>,
|
1249
1273
|
# <tt>:primary_key</tt>, and <tt>:foreign_key</tt> are ignored, as the association uses the
|
1250
1274
|
# source reflection. You can only use a <tt>:through</tt> query through a <tt>has_one</tt>
|
1251
1275
|
# or <tt>belongs_to</tt> association on the join model.
|
@@ -1267,7 +1291,7 @@ module ActiveRecord
|
|
1267
1291
|
# By default, only save the associated object if it's a new record.
|
1268
1292
|
# [:inverse_of]
|
1269
1293
|
# Specifies the name of the <tt>belongs_to</tt> association on the associated object
|
1270
|
-
# that is the inverse of this <tt>has_one</tt> association.
|
1294
|
+
# that is the inverse of this <tt>has_one</tt> association. Does not work in combination
|
1271
1295
|
# with <tt>:through</tt> or <tt>:as</tt> options.
|
1272
1296
|
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
|
1273
1297
|
#
|
@@ -1325,7 +1349,7 @@ module ActiveRecord
|
|
1325
1349
|
#
|
1326
1350
|
# [:class_name]
|
1327
1351
|
# Specify the class name of the association. Use it only if that name can't be inferred
|
1328
|
-
# from the association name. So <tt>
|
1352
|
+
# from the association name. So <tt>belongs_to :author</tt> will by default be linked to the Author class, but
|
1329
1353
|
# if the real class name is Person, you'll have to specify it with this option.
|
1330
1354
|
# [:conditions]
|
1331
1355
|
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
|
@@ -1385,7 +1409,7 @@ module ActiveRecord
|
|
1385
1409
|
# will be updated with the current time in addition to the updated_at/on attribute.
|
1386
1410
|
# [:inverse_of]
|
1387
1411
|
# Specifies the name of the <tt>has_one</tt> or <tt>has_many</tt> association on the associated
|
1388
|
-
# object that is the inverse of this <tt>belongs_to</tt> association.
|
1412
|
+
# object that is the inverse of this <tt>belongs_to</tt> association. Does not work in
|
1389
1413
|
# combination with the <tt>:polymorphic</tt> options.
|
1390
1414
|
# See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
|
1391
1415
|
#
|
@@ -1405,15 +1429,15 @@ module ActiveRecord
|
|
1405
1429
|
end
|
1406
1430
|
|
1407
1431
|
# Specifies a many-to-many relationship with another class. This associates two classes via an
|
1408
|
-
# intermediate join table.
|
1432
|
+
# intermediate join table. Unless the join table is explicitly specified as an option, it is
|
1409
1433
|
# guessed using the lexical order of the class names. So a join between Developer and Project
|
1410
1434
|
# will give the default join table name of "developers_projects" because "D" outranks "P".
|
1411
|
-
# Note that this precedence is calculated using the <tt><</tt> operator for String.
|
1435
|
+
# Note that this precedence is calculated using the <tt><</tt> operator for String. This
|
1412
1436
|
# means that if the strings are of different lengths, and the strings are equal when compared
|
1413
1437
|
# up to the shortest length, then the longer string is considered of higher
|
1414
|
-
# lexical precedence than the shorter one.
|
1438
|
+
# lexical precedence than the shorter one. For example, one would expect the tables "paper_boxes" and "papers"
|
1415
1439
|
# to generate a join table name of "papers_paper_boxes" because of the length of the name "paper_boxes",
|
1416
|
-
# but it in fact generates a join table name of "paper_boxes_papers".
|
1440
|
+
# but it in fact generates a join table name of "paper_boxes_papers". Be aware of this caveat, and use the
|
1417
1441
|
# custom <tt>:join_table</tt> option if you need to.
|
1418
1442
|
#
|
1419
1443
|
# The join table should not have a primary key or a model associated with it. You must manually generate the
|
@@ -1515,7 +1539,7 @@ module ActiveRecord
|
|
1515
1539
|
# the association will use "project_id" as the default <tt>:association_foreign_key</tt>.
|
1516
1540
|
# [:conditions]
|
1517
1541
|
# Specify the conditions that the associated object must meet in order to be included as a +WHERE+
|
1518
|
-
# SQL fragment, such as <tt>authorized = 1</tt>.
|
1542
|
+
# SQL fragment, such as <tt>authorized = 1</tt>. Record creations from the association are
|
1519
1543
|
# scoped if a hash is used.
|
1520
1544
|
# <tt>has_many :posts, :conditions => {:published => true}</tt> will create published posts with <tt>@blog.posts.create</tt>
|
1521
1545
|
# or <tt>@blog.posts.build</tt>.
|
@@ -1575,17 +1599,6 @@ module ActiveRecord
|
|
1575
1599
|
def has_and_belongs_to_many(name, options = {}, &extension)
|
1576
1600
|
Builder::HasAndBelongsToMany.build(self, name, options, &extension)
|
1577
1601
|
end
|
1578
|
-
|
1579
|
-
protected
|
1580
|
-
|
1581
|
-
def preload_associations(records, associations, options = {}) #:nodoc:
|
1582
|
-
ActiveSupport::Deprecation.warn(
|
1583
|
-
"preload_associations(records, associations, options = {}) is deprecated. Use " \
|
1584
|
-
"ActiveRecord::Associations::Preloader.new(records, associations, options = {}).run " \
|
1585
|
-
"instead."
|
1586
|
-
)
|
1587
|
-
Preloader.new(records, associations, options).run
|
1588
|
-
end
|
1589
1602
|
end
|
1590
1603
|
end
|
1591
1604
|
end
|