store_base_sti_class_for_3_0 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +35 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +65 -0
- data/Rakefile +76 -0
- data/VERSION +1 -0
- data/lib/store_base_sti_class_for_3_0.rb +478 -0
- data/store_base_sti_class_for_3_0.gemspec +77 -0
- data/test/connection.rb +16 -0
- data/test/helper.rb +27 -0
- data/test/models.rb +38 -0
- data/test/schema.rb +35 -0
- data/test/test_store_base_sti_class_for_3_0.rb +133 -0
- metadata +160 -0
data/.document
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.0.3)
|
5
|
+
activesupport (= 3.0.3)
|
6
|
+
builder (~> 2.1.2)
|
7
|
+
i18n (~> 0.4)
|
8
|
+
activerecord (3.0.3)
|
9
|
+
activemodel (= 3.0.3)
|
10
|
+
activesupport (= 3.0.3)
|
11
|
+
arel (~> 2.0.2)
|
12
|
+
tzinfo (~> 0.3.23)
|
13
|
+
activesupport (3.0.3)
|
14
|
+
arel (2.0.8)
|
15
|
+
builder (2.1.2)
|
16
|
+
git (1.2.5)
|
17
|
+
i18n (0.5.0)
|
18
|
+
jeweler (1.5.2)
|
19
|
+
bundler (~> 1.0.0)
|
20
|
+
git (>= 1.2.5)
|
21
|
+
rake
|
22
|
+
mysql2 (0.2.6)
|
23
|
+
rake (0.8.7)
|
24
|
+
rcov (0.9.9)
|
25
|
+
tzinfo (0.3.24)
|
26
|
+
|
27
|
+
PLATFORMS
|
28
|
+
ruby
|
29
|
+
|
30
|
+
DEPENDENCIES
|
31
|
+
activerecord (~> 3.0.3)
|
32
|
+
bundler (~> 1.0.0)
|
33
|
+
jeweler (~> 1.5.2)
|
34
|
+
mysql2
|
35
|
+
rcov
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Paul Kmiec
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
== Description
|
2
|
+
|
3
|
+
Given the following class definitions,
|
4
|
+
|
5
|
+
class Address
|
6
|
+
belongs_to :addressable, :polymorphic => true
|
7
|
+
end
|
8
|
+
|
9
|
+
class Person
|
10
|
+
has_many :addresses, :as => addressable
|
11
|
+
end
|
12
|
+
|
13
|
+
class Vendor < Person
|
14
|
+
end
|
15
|
+
|
16
|
+
and given the following code,
|
17
|
+
|
18
|
+
vendor = Vendor.create(...)
|
19
|
+
address = vendor.addresses.create(...)
|
20
|
+
|
21
|
+
p vendor
|
22
|
+
p address
|
23
|
+
|
24
|
+
will output,
|
25
|
+
|
26
|
+
#<Vendor id: 1, type: "Vendor" ...>
|
27
|
+
#<Address id: 1, addressable_id: 1, addressable_type: 'Person' ...>
|
28
|
+
|
29
|
+
Notice that addressable_type column is Person even though the actual class is Vendor.
|
30
|
+
|
31
|
+
Normally, this isn't a problem, however it can have negative performance characteristic in certain circumstances. The most obvious one is that
|
32
|
+
a join with persons or an extra query is required to find out the actual type of addressable.
|
33
|
+
|
34
|
+
This gem add ActiveRecord::Base.store_base_sti_class configuration option. It defaults to true for backwards compatibility. Setting it false will alter ActiveRecord's behavior to store the actual class in polymorphic _type columns when STI is used.
|
35
|
+
|
36
|
+
In the example above, if the ActiveRecord::Base.store_base_sti_class is false, the output will be,
|
37
|
+
|
38
|
+
#<Vendor id: 1, type: "Vendor" ...>
|
39
|
+
#<Address id: 1, addressable_id: 1, addressable_type: 'Vendor' ...>
|
40
|
+
|
41
|
+
== Usage
|
42
|
+
|
43
|
+
Add the following line to your Gemfile,
|
44
|
+
|
45
|
+
gem 'store_base_sti_class_for_3_0'
|
46
|
+
|
47
|
+
then bundle install. Once you have the gem installed, add the following to one of the initializers (or make a new one) in config/initializers,
|
48
|
+
|
49
|
+
ActiveRecord::Base.store_base_sti_class = false
|
50
|
+
|
51
|
+
When changing this behavior you will have write a migration to update all of your existing _type columns accordingly. You may also need to change your application if it explicitly relies on the _type columns.
|
52
|
+
|
53
|
+
== Notes
|
54
|
+
|
55
|
+
The gem has been extracted out of https://github.com/pkmiec/rails/tree/store_base_sti_class_for_3_0_4 patch. It allows the functionality to be used in applications that include Rails as a gem.
|
56
|
+
|
57
|
+
I've tested this gem with Rails 3.0.3 and Rails 3.0.4. Please let me know if it works with earlier version of Rails 3.0.
|
58
|
+
|
59
|
+
This gem will not work with Rails 3.1 as much of its ActiveRecord internals have been replaced with Arel. Similar but different changes need to be applied to Rails 3.1. Those changes will come.
|
60
|
+
|
61
|
+
== Copyright
|
62
|
+
|
63
|
+
Copyright (c) 2011 Paul Kmiec. See LICENSE.txt for
|
64
|
+
further details.
|
65
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "store_base_sti_class_for_3_0"
|
16
|
+
gem.homepage = "http://github.com/pkmiec/store_base_sti_class_for_3_0"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{
|
19
|
+
Modifies ActiveRecord 3.0.x with the ability to store the actual class (instead of the base class) in polymorhic _type columns when using STI
|
20
|
+
}
|
21
|
+
gem.description = %Q{
|
22
|
+
ActiveRecord has always stored the base class in polymorphic _type columns when using STI. This can have non-trivial
|
23
|
+
performance implications in certain cases. This gem adds 'store_base_sti_class' configuration options which controls
|
24
|
+
whether ActiveRecord will store the base class or the actual class. Default to true for backwards compatibility.
|
25
|
+
}
|
26
|
+
gem.email = "paul.kmiec@appfolio.com"
|
27
|
+
gem.authors = ["Paul Kmiec"]
|
28
|
+
|
29
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
30
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
31
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
32
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
33
|
+
end
|
34
|
+
Jeweler::RubygemsDotOrgTasks.new
|
35
|
+
|
36
|
+
require 'rake/testtask'
|
37
|
+
Rake::TestTask.new(:test) do |test|
|
38
|
+
test.libs << 'lib' << 'test'
|
39
|
+
test.pattern = 'test/**/test_*.rb'
|
40
|
+
test.verbose = true
|
41
|
+
end
|
42
|
+
|
43
|
+
require 'rcov/rcovtask'
|
44
|
+
Rcov::RcovTask.new do |test|
|
45
|
+
test.libs << 'test'
|
46
|
+
test.pattern = 'test/**/test_*.rb'
|
47
|
+
test.verbose = true
|
48
|
+
end
|
49
|
+
|
50
|
+
task :default => :test
|
51
|
+
|
52
|
+
require 'rake/rdoctask'
|
53
|
+
Rake::RDocTask.new do |rdoc|
|
54
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
55
|
+
|
56
|
+
rdoc.rdoc_dir = 'rdoc'
|
57
|
+
rdoc.title = "store_base_sti_class_for_3_0 #{version}"
|
58
|
+
rdoc.rdoc_files.include('README*')
|
59
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
60
|
+
end
|
61
|
+
|
62
|
+
namespace :mysql do
|
63
|
+
desc 'Build the MySQL test databases'
|
64
|
+
task :build_databases do
|
65
|
+
%x( echo "create DATABASE storebasestiname_unittest DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_unicode_ci " | mysql --user=root)
|
66
|
+
end
|
67
|
+
|
68
|
+
desc 'Drop the MySQL test databases'
|
69
|
+
task :drop_databases do
|
70
|
+
%x( mysqladmin --user=root -f drop storebasestiname_unittest )
|
71
|
+
end
|
72
|
+
|
73
|
+
desc 'Rebuild the MySQL test databases'
|
74
|
+
task :rebuild_databases => [:drop_databases, :build_databases]
|
75
|
+
end
|
76
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,478 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'active_record/associations'
|
3
|
+
require 'active_record/reflection'
|
4
|
+
require 'active_record/association_preload'
|
5
|
+
require 'active_record/associations/has_many_association'
|
6
|
+
require 'active_record/associations/has_one_association'
|
7
|
+
require 'active_record/associations/through_association_scope'
|
8
|
+
require 'active_record/associations/association_proxy'
|
9
|
+
require 'active_record/associations/belongs_to_polymorphic_association'
|
10
|
+
|
11
|
+
class ActiveRecord::Base
|
12
|
+
|
13
|
+
# Determine whether to store the base class or the actual class in polymorhic type columns when using STI
|
14
|
+
superclass_delegating_accessor :store_base_sti_class
|
15
|
+
self.store_base_sti_class = true
|
16
|
+
|
17
|
+
class << self
|
18
|
+
|
19
|
+
def polymorphic_sti_name
|
20
|
+
store_base_sti_class ? base_class.sti_name : sti_name
|
21
|
+
end
|
22
|
+
|
23
|
+
def in_or_equals_sti_names
|
24
|
+
if store_base_sti_class
|
25
|
+
"= #{quote_value(base_class.sti_name)}"
|
26
|
+
else
|
27
|
+
names = sti_names.map { |name| quote_value(name) }
|
28
|
+
names.length > 1 ? "IN (#{names.join(',')})" : "= #{names}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def sti_names
|
33
|
+
([self] + descendants).map { |model| model.sti_name }
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
module ActiveRecord
|
41
|
+
module Reflection
|
42
|
+
class AssociationReflection < MacroReflection #:nodoc:
|
43
|
+
|
44
|
+
def active_record_primary_key
|
45
|
+
@active_record_primary_key ||= options[:primary_key] || active_record.primary_key
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
module ActiveRecord
|
53
|
+
module AssociationPreload
|
54
|
+
module ClassMethods
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def preload_through_records(records, reflection, through_association)
|
59
|
+
# p 'preload_through_records'
|
60
|
+
through_reflection = reflections[through_association]
|
61
|
+
through_primary_key = through_reflection.primary_key_name
|
62
|
+
|
63
|
+
through_records = []
|
64
|
+
if reflection.options[:source_type]
|
65
|
+
interface = reflection.source_reflection.options[:foreign_type]
|
66
|
+
source_type = reflection.options[:source_type].to_s.constantize
|
67
|
+
preload_options = { :conditions => "#{connection.quote_column_name interface} #{source_type.in_or_equals_sti_names}" }
|
68
|
+
|
69
|
+
records.compact!
|
70
|
+
records.first.class.preload_associations(records, through_association, preload_options)
|
71
|
+
|
72
|
+
# Dont cache the association - we would only be caching a subset
|
73
|
+
records.each do |record|
|
74
|
+
proxy = record.send(through_association)
|
75
|
+
|
76
|
+
if proxy.respond_to?(:target)
|
77
|
+
through_records.concat Array.wrap(proxy.target)
|
78
|
+
proxy.reset
|
79
|
+
else # this is a has_one :through reflection
|
80
|
+
through_records << proxy if proxy
|
81
|
+
end
|
82
|
+
end
|
83
|
+
else
|
84
|
+
options = {}
|
85
|
+
options[:include] = reflection.options[:include] || reflection.options[:source] if reflection.options[:conditions] || reflection.options[:order]
|
86
|
+
options[:order] = reflection.options[:order]
|
87
|
+
options[:conditions] = reflection.options[:conditions]
|
88
|
+
records.first.class.preload_associations(records, through_association, options)
|
89
|
+
|
90
|
+
records.each do |record|
|
91
|
+
through_records.concat Array.wrap(record.send(through_association))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
through_records
|
95
|
+
end
|
96
|
+
|
97
|
+
def find_associated_records(ids, reflection, preload_options)
|
98
|
+
# p 'find_associated_records'
|
99
|
+
options = reflection.options
|
100
|
+
table_name = reflection.klass.quoted_table_name
|
101
|
+
|
102
|
+
if interface = reflection.options[:as]
|
103
|
+
conditions = "#{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_id"} #{in_or_equals_for_ids(ids)} and #{reflection.klass.quoted_table_name}.#{connection.quote_column_name "#{interface}_type"} #{self.in_or_equals_sti_names}"
|
104
|
+
else
|
105
|
+
foreign_key = reflection.primary_key_name
|
106
|
+
conditions = "#{reflection.klass.quoted_table_name}.#{foreign_key} #{in_or_equals_for_ids(ids)}"
|
107
|
+
end
|
108
|
+
|
109
|
+
# p 'append_conditions'
|
110
|
+
conditions << append_conditions(reflection, preload_options)
|
111
|
+
|
112
|
+
find_options = {
|
113
|
+
:select => preload_options[:select] || options[:select] || Arel::SqlLiteral.new("#{table_name}.*"),
|
114
|
+
:include => preload_options[:include] || options[:include],
|
115
|
+
:joins => options[:joins],
|
116
|
+
:group => preload_options[:group] || options[:group],
|
117
|
+
:order => preload_options[:order] || options[:order]
|
118
|
+
}
|
119
|
+
|
120
|
+
# p 'associated_records'
|
121
|
+
associated_records(ids) do |some_ids|
|
122
|
+
reflection.klass.scoped.apply_finder_options(find_options.merge(:conditions => [conditions, some_ids])).to_a
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# Some databases impose a limit on the number of ids in a list (in Oracle its 1000)
|
127
|
+
# Make several smaller queries if necessary or make one query if the adapter supports it
|
128
|
+
def associated_records(ids)
|
129
|
+
# rails 3.0.4 introduced ids_in_list_limit
|
130
|
+
max_ids_in_a_list = (connection.respond_to?(:ids_in_list_limit) ? connection.ids_in_list_limit : nil) || ids.size
|
131
|
+
records = []
|
132
|
+
ids.each_slice(max_ids_in_a_list) do |some_ids|
|
133
|
+
records += yield(some_ids)
|
134
|
+
end
|
135
|
+
records
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
module ActiveRecord
|
143
|
+
module Associations
|
144
|
+
class HasManyAssociation < AssociationCollection
|
145
|
+
|
146
|
+
protected
|
147
|
+
|
148
|
+
def construct_sql
|
149
|
+
# p 'construct_sql :has_many'
|
150
|
+
|
151
|
+
case
|
152
|
+
when @reflection.options[:finder_sql]
|
153
|
+
@finder_sql = interpolate_sql(@reflection.options[:finder_sql])
|
154
|
+
|
155
|
+
when @reflection.options[:as]
|
156
|
+
@finder_sql =
|
157
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
|
158
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type #{@owner.class.in_or_equals_sti_names}"
|
159
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
160
|
+
|
161
|
+
else
|
162
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
163
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
164
|
+
end
|
165
|
+
|
166
|
+
construct_counter_sql
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
module ActiveRecord
|
174
|
+
module Associations
|
175
|
+
class HasOneAssociation < AssociationProxy
|
176
|
+
|
177
|
+
protected
|
178
|
+
|
179
|
+
def construct_sql
|
180
|
+
# p 'construct_sql :has_one'
|
181
|
+
|
182
|
+
case
|
183
|
+
when @reflection.options[:as]
|
184
|
+
@finder_sql =
|
185
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_id = #{owner_quoted_id} AND " +
|
186
|
+
"#{@reflection.quoted_table_name}.#{@reflection.options[:as]}_type #{@owner.class.in_or_equals_sti_names}"
|
187
|
+
else
|
188
|
+
@finder_sql = "#{@reflection.quoted_table_name}.#{@reflection.primary_key_name} = #{owner_quoted_id}"
|
189
|
+
end
|
190
|
+
@finder_sql << " AND (#{conditions})" if conditions
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
module ActiveRecord
|
198
|
+
module Associations
|
199
|
+
module ThroughAssociationScope
|
200
|
+
|
201
|
+
protected
|
202
|
+
|
203
|
+
def construct_quoted_owner_attributes(reflection)
|
204
|
+
# p 'construct_quoted_owner_attributes'
|
205
|
+
|
206
|
+
if as = reflection.options[:as]
|
207
|
+
{
|
208
|
+
"#{as}_id" => owner_quoted_id,
|
209
|
+
"#{as}_type" => @owner.class.quote_value(@owner.class.polymorphic_sti_name)
|
210
|
+
}
|
211
|
+
elsif reflection.macro == :belongs_to
|
212
|
+
{ reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) }
|
213
|
+
else
|
214
|
+
{ reflection.primary_key_name => owner_quoted_id }
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def construct_joins(custom_joins = nil)
|
219
|
+
# p 'construct_joins'
|
220
|
+
|
221
|
+
polymorphic_join = nil
|
222
|
+
if @reflection.source_reflection.macro == :belongs_to
|
223
|
+
reflection_primary_key = @reflection.klass.primary_key
|
224
|
+
source_primary_key = @reflection.source_reflection.primary_key_name
|
225
|
+
if @reflection.options[:source_type]
|
226
|
+
source_type = @reflection.options[:source_type].to_s.constantize
|
227
|
+
polymorphic_join = "AND %s.%s #{source_type.in_or_equals_sti_names}" % [
|
228
|
+
@reflection.through_reflection.quoted_table_name,
|
229
|
+
"#{@reflection.source_reflection.options[:foreign_type]}"
|
230
|
+
]
|
231
|
+
end
|
232
|
+
else
|
233
|
+
reflection_primary_key = @reflection.source_reflection.primary_key_name
|
234
|
+
source_primary_key = @reflection.through_reflection.klass.primary_key
|
235
|
+
if @reflection.source_reflection.options[:as]
|
236
|
+
polymorphic_join = "AND %s.%s #{@reflection.through_reflection.klass.in_or_equals_sti_names}" % [
|
237
|
+
@reflection.quoted_table_name,
|
238
|
+
"#{@reflection.source_reflection.options[:as]}_type"
|
239
|
+
]
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
"INNER JOIN %s ON %s.%s = %s.%s %s #{@reflection.options[:joins]} #{custom_joins}" % [
|
244
|
+
@reflection.through_reflection.quoted_table_name,
|
245
|
+
@reflection.quoted_table_name, reflection_primary_key,
|
246
|
+
@reflection.through_reflection.quoted_table_name, source_primary_key,
|
247
|
+
polymorphic_join
|
248
|
+
]
|
249
|
+
end
|
250
|
+
|
251
|
+
def construct_owner_attributes(reflection)
|
252
|
+
# p 'construct_owner_attributes'
|
253
|
+
|
254
|
+
if as = reflection.options[:as]
|
255
|
+
{ "#{as}_id" => @owner.id,
|
256
|
+
"#{as}_type" => @owner.class.polymorphic_sti_name }
|
257
|
+
else
|
258
|
+
{ reflection.primary_key_name => @owner.id }
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
module ActiveRecord
|
267
|
+
module Associations
|
268
|
+
class AssociationProxy
|
269
|
+
|
270
|
+
protected
|
271
|
+
|
272
|
+
def set_belongs_to_association_for(record)
|
273
|
+
# p 'set_belongs_to_association_for'
|
274
|
+
|
275
|
+
if @reflection.options[:as]
|
276
|
+
record["#{@reflection.options[:as]}_id"] = @owner.id unless @owner.new_record?
|
277
|
+
record["#{@reflection.options[:as]}_type"] = @owner.class.polymorphic_sti_name
|
278
|
+
else
|
279
|
+
unless @owner.new_record?
|
280
|
+
primary_key = @reflection.options[:primary_key] || :id
|
281
|
+
record[@reflection.primary_key_name] = @owner.send(primary_key)
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
module ActiveRecord
|
291
|
+
module Associations
|
292
|
+
class BelongsToPolymorphicAssociation < AssociationProxy
|
293
|
+
|
294
|
+
def replace(record)
|
295
|
+
# p 'replace'
|
296
|
+
|
297
|
+
if record.nil?
|
298
|
+
@target = @owner[@reflection.primary_key_name] = @owner[@reflection.options[:foreign_type]] = nil
|
299
|
+
else
|
300
|
+
@target = (AssociationProxy === record ? record.target : record)
|
301
|
+
|
302
|
+
@owner[@reflection.primary_key_name] = record_id(record)
|
303
|
+
@owner[@reflection.options[:foreign_type]] = record.class.polymorphic_sti_name
|
304
|
+
|
305
|
+
@updated = true
|
306
|
+
end
|
307
|
+
|
308
|
+
set_inverse_instance(record, @owner)
|
309
|
+
loaded
|
310
|
+
record
|
311
|
+
end
|
312
|
+
|
313
|
+
end
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
module ActiveRecord
|
318
|
+
module Associations
|
319
|
+
module ThroughAssociationScope
|
320
|
+
|
321
|
+
protected
|
322
|
+
|
323
|
+
def construct_quoted_owner_attributes(reflection)
|
324
|
+
# p 'construct_quoted_owner_attributes'
|
325
|
+
if as = reflection.options[:as]
|
326
|
+
{
|
327
|
+
"#{as}_id" => owner_quoted_id,
|
328
|
+
"#{as}_type" => @owner.class.quote_value(@owner.class.polymorphic_sti_name)
|
329
|
+
}
|
330
|
+
elsif reflection.macro == :belongs_to
|
331
|
+
{ reflection.klass.primary_key => @owner.class.quote_value(@owner[reflection.primary_key_name]) }
|
332
|
+
else
|
333
|
+
{ reflection.primary_key_name => owner_quoted_id }
|
334
|
+
end
|
335
|
+
end
|
336
|
+
|
337
|
+
def construct_owner_attributes(reflection)
|
338
|
+
# p 'construct_owner_attributes'
|
339
|
+
if as = reflection.options[:as]
|
340
|
+
{ "#{as}_id" => @owner.id,
|
341
|
+
"#{as}_type" => @owner.class.polymorphic_sti_name }
|
342
|
+
else
|
343
|
+
{ reflection.primary_key_name => @owner.id }
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
def construct_join_attributes(associate)
|
348
|
+
# p 'construct_join_attributes'
|
349
|
+
# TODO: revisit this to allow it for deletion, supposing dependent option is supported
|
350
|
+
raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro)
|
351
|
+
|
352
|
+
join_attributes = construct_owner_attributes(@reflection.through_reflection).merge(@reflection.source_reflection.primary_key_name => associate.id)
|
353
|
+
|
354
|
+
if @reflection.options[:source_type]
|
355
|
+
join_attributes.merge!(@reflection.source_reflection.options[:foreign_type] => associate.class.polymorphic_sti_name)
|
356
|
+
end
|
357
|
+
|
358
|
+
if @reflection.through_reflection.options[:conditions].is_a?(Hash)
|
359
|
+
join_attributes.merge!(@reflection.through_reflection.options[:conditions])
|
360
|
+
end
|
361
|
+
|
362
|
+
join_attributes
|
363
|
+
end
|
364
|
+
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
module ActiveRecord
|
370
|
+
module Associations
|
371
|
+
module ClassMethods
|
372
|
+
class JoinDependency # :nodoc:
|
373
|
+
class JoinAssociation < JoinBase # :nodoc:
|
374
|
+
|
375
|
+
def association_join
|
376
|
+
# p 'association_join'
|
377
|
+
|
378
|
+
return @join if @join
|
379
|
+
|
380
|
+
aliased_table = Arel::Table.new(table_name, :as => @aliased_table_name,
|
381
|
+
:engine => arel_engine,
|
382
|
+
:columns => klass.columns)
|
383
|
+
|
384
|
+
parent_table = Arel::Table.new(parent.table_name, :as => parent.aliased_table_name,
|
385
|
+
:engine => arel_engine,
|
386
|
+
:columns => parent.active_record.columns)
|
387
|
+
|
388
|
+
@join = case reflection.macro
|
389
|
+
when :has_and_belongs_to_many
|
390
|
+
join_table = Arel::Table.new(options[:join_table], :as => aliased_join_table_name, :engine => arel_engine)
|
391
|
+
fk = options[:foreign_key] || reflection.active_record.to_s.foreign_key
|
392
|
+
klass_fk = options[:association_foreign_key] || klass.to_s.foreign_key
|
393
|
+
|
394
|
+
[
|
395
|
+
join_table[fk].eq(parent_table[reflection.active_record.primary_key]),
|
396
|
+
aliased_table[klass.primary_key].eq(join_table[klass_fk])
|
397
|
+
]
|
398
|
+
when :has_many, :has_one
|
399
|
+
if reflection.options[:through]
|
400
|
+
join_table = Arel::Table.new(through_reflection.klass.table_name, :as => aliased_join_table_name, :engine => arel_engine)
|
401
|
+
jt_as_extra = jt_source_extra = jt_sti_extra = nil
|
402
|
+
first_key = second_key = nil
|
403
|
+
|
404
|
+
if through_reflection.macro == :belongs_to
|
405
|
+
jt_primary_key = through_reflection.primary_key_name
|
406
|
+
jt_foreign_key = through_reflection.association_primary_key
|
407
|
+
else
|
408
|
+
jt_primary_key = through_reflection.active_record_primary_key
|
409
|
+
jt_foreign_key = through_reflection.primary_key_name
|
410
|
+
|
411
|
+
if through_reflection.options[:as] # has_many :through against a polymorphic join
|
412
|
+
jt_as_extra = join_table[through_reflection.options[:as].to_s + '_type'].in(parent.active_record.sti_names)
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
case source_reflection.macro
|
417
|
+
when :has_many
|
418
|
+
if source_reflection.options[:as]
|
419
|
+
first_key = "#{source_reflection.options[:as]}_id"
|
420
|
+
second_key = options[:foreign_key] || primary_key
|
421
|
+
else
|
422
|
+
first_key = through_reflection.klass.base_class.to_s.foreign_key
|
423
|
+
second_key = options[:foreign_key] || primary_key
|
424
|
+
end
|
425
|
+
|
426
|
+
unless through_reflection.klass.descends_from_active_record?
|
427
|
+
# there is no test for this condition
|
428
|
+
jt_sti_extra = join_table[through_reflection.active_record.inheritance_column].eq(through_reflection.klass.sti_name)
|
429
|
+
end
|
430
|
+
when :belongs_to
|
431
|
+
first_key = primary_key
|
432
|
+
if reflection.options[:source_type]
|
433
|
+
source_type = reflection.options[:source_type].to_s.constantize
|
434
|
+
second_key = source_reflection.association_foreign_key
|
435
|
+
jt_source_extra = join_table[reflection.source_reflection.options[:foreign_type]].in(source_type.sti_names)
|
436
|
+
else
|
437
|
+
second_key = source_reflection.primary_key_name
|
438
|
+
end
|
439
|
+
end
|
440
|
+
|
441
|
+
[
|
442
|
+
[parent_table[jt_primary_key].eq(join_table[jt_foreign_key]), jt_as_extra, jt_source_extra, jt_sti_extra].reject{|x| x.blank? },
|
443
|
+
aliased_table[first_key].eq(join_table[second_key])
|
444
|
+
]
|
445
|
+
elsif reflection.options[:as]
|
446
|
+
id_rel = aliased_table["#{reflection.options[:as]}_id"].eq(parent_table[parent.primary_key])
|
447
|
+
type_rel = aliased_table["#{reflection.options[:as]}_type"].in(parent.active_record.sti_names)
|
448
|
+
[id_rel, type_rel]
|
449
|
+
else
|
450
|
+
foreign_key = options[:foreign_key] || reflection.active_record.name.foreign_key
|
451
|
+
[aliased_table[foreign_key].eq(parent_table[reflection.options[:primary_key] || parent.primary_key])]
|
452
|
+
end
|
453
|
+
when :belongs_to
|
454
|
+
[aliased_table[options[:primary_key] || reflection.klass.primary_key].eq(parent_table[options[:foreign_key] || reflection.primary_key_name])]
|
455
|
+
end
|
456
|
+
|
457
|
+
unless klass.descends_from_active_record?
|
458
|
+
sti_column = aliased_table[klass.inheritance_column]
|
459
|
+
sti_condition = sti_column.eq(klass.sti_name)
|
460
|
+
klass.descendants.each {|subclass| sti_condition = sti_condition.or(sti_column.eq(subclass.sti_name)) }
|
461
|
+
|
462
|
+
@join << sti_condition
|
463
|
+
end
|
464
|
+
|
465
|
+
[through_reflection, reflection].each do |ref|
|
466
|
+
if ref && ref.options[:conditions]
|
467
|
+
@join << interpolate_sql(sanitize_sql(ref.options[:conditions], aliased_table_name))
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
@join
|
472
|
+
end
|
473
|
+
|
474
|
+
end
|
475
|
+
end
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{store_base_sti_class_for_3_0}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Paul Kmiec"]
|
12
|
+
s.date = %q{2011-02-15}
|
13
|
+
s.description = %q{
|
14
|
+
ActiveRecord has always stored the base class in polymorphic _type columns when using STI. This can have non-trivial
|
15
|
+
performance implications in certain cases. This gem adds 'store_base_sti_class' configuration options which controls
|
16
|
+
whether ActiveRecord will store the base class or the actual class. Default to true for backwards compatibility.
|
17
|
+
}
|
18
|
+
s.email = %q{paul.kmiec@appfolio.com}
|
19
|
+
s.extra_rdoc_files = [
|
20
|
+
"LICENSE.txt",
|
21
|
+
"README.rdoc"
|
22
|
+
]
|
23
|
+
s.files = [
|
24
|
+
".document",
|
25
|
+
"Gemfile",
|
26
|
+
"Gemfile.lock",
|
27
|
+
"LICENSE.txt",
|
28
|
+
"README.rdoc",
|
29
|
+
"Rakefile",
|
30
|
+
"VERSION",
|
31
|
+
"lib/store_base_sti_class_for_3_0.rb",
|
32
|
+
"store_base_sti_class_for_3_0.gemspec",
|
33
|
+
"test/connection.rb",
|
34
|
+
"test/helper.rb",
|
35
|
+
"test/models.rb",
|
36
|
+
"test/schema.rb",
|
37
|
+
"test/test_store_base_sti_class_for_3_0.rb"
|
38
|
+
]
|
39
|
+
s.homepage = %q{http://github.com/pkmiec/store_base_sti_class_for_3_0}
|
40
|
+
s.licenses = ["MIT"]
|
41
|
+
s.require_paths = ["lib"]
|
42
|
+
s.rubygems_version = %q{1.3.7}
|
43
|
+
s.summary = %q{Modifies ActiveRecord 3.0.x with the ability to store the actual class (instead of the base class) in polymorhic _type columns when using STI}
|
44
|
+
s.test_files = [
|
45
|
+
"test/connection.rb",
|
46
|
+
"test/helper.rb",
|
47
|
+
"test/models.rb",
|
48
|
+
"test/schema.rb",
|
49
|
+
"test/test_store_base_sti_class_for_3_0.rb"
|
50
|
+
]
|
51
|
+
|
52
|
+
if s.respond_to? :specification_version then
|
53
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
54
|
+
s.specification_version = 3
|
55
|
+
|
56
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
57
|
+
s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.3"])
|
58
|
+
s.add_runtime_dependency(%q<mysql2>, [">= 0"])
|
59
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
60
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
61
|
+
s.add_development_dependency(%q<rcov>, [">= 0"])
|
62
|
+
else
|
63
|
+
s.add_dependency(%q<activerecord>, ["~> 3.0.3"])
|
64
|
+
s.add_dependency(%q<mysql2>, [">= 0"])
|
65
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
66
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
67
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
68
|
+
end
|
69
|
+
else
|
70
|
+
s.add_dependency(%q<activerecord>, ["~> 3.0.3"])
|
71
|
+
s.add_dependency(%q<mysql2>, [">= 0"])
|
72
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
73
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
74
|
+
s.add_dependency(%q<rcov>, [">= 0"])
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
data/test/connection.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
ActiveRecord::Base.logger = Logger.new("debug.log")
|
4
|
+
|
5
|
+
# GRANT ALL PRIVILEGES ON storebasestiname_unittest.* to 'root'@'localhost';
|
6
|
+
|
7
|
+
ActiveRecord::Base.configurations = {
|
8
|
+
'unittest' => {
|
9
|
+
:adapter => 'mysql2',
|
10
|
+
:username => 'root',
|
11
|
+
:encoding => 'utf8',
|
12
|
+
:database => 'storebasestiname_unittest',
|
13
|
+
}
|
14
|
+
}
|
15
|
+
|
16
|
+
ActiveRecord::Base.establish_connection 'unittest'
|
data/test/helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'test/unit'
|
11
|
+
|
12
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
13
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
14
|
+
require 'store_base_sti_class_for_3_0'
|
15
|
+
|
16
|
+
require 'connection'
|
17
|
+
|
18
|
+
# silence verbose schema loading
|
19
|
+
original_stdout = $stdout
|
20
|
+
$stdout = StringIO.new
|
21
|
+
begin
|
22
|
+
require "schema.rb"
|
23
|
+
ensure
|
24
|
+
$stdout = original_stdout
|
25
|
+
end
|
26
|
+
|
27
|
+
require 'models'
|
data/test/models.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
class Author < ActiveRecord::Base
|
2
|
+
has_many :posts
|
3
|
+
|
4
|
+
has_many :tagging, :through => :posts # through polymorphic has_one
|
5
|
+
has_many :taggings, :through => :posts, :source => :taggings # through polymorphic has_many
|
6
|
+
has_many :tags, :through => :posts # through has_many :through
|
7
|
+
end
|
8
|
+
|
9
|
+
class Post < ActiveRecord::Base
|
10
|
+
belongs_to :author
|
11
|
+
|
12
|
+
has_one :tagging, :as => :taggable
|
13
|
+
has_many :taggings, :as => :taggable
|
14
|
+
has_many :tags, :through => :taggings
|
15
|
+
end
|
16
|
+
|
17
|
+
class SpecialPost < Post
|
18
|
+
end
|
19
|
+
|
20
|
+
class Tagging < ActiveRecord::Base
|
21
|
+
belongs_to :tag, :include => :tagging
|
22
|
+
belongs_to :polytag, :polymorphic => true
|
23
|
+
belongs_to :taggable, :polymorphic => true, :counter_cache => true
|
24
|
+
end
|
25
|
+
|
26
|
+
class Tag < ActiveRecord::Base
|
27
|
+
has_one :tagging
|
28
|
+
|
29
|
+
has_many :taggings
|
30
|
+
has_many :taggables, :through => :taggings
|
31
|
+
has_many :tagged_posts, :through => :taggings, :source => :taggable, :source_type => 'Post'
|
32
|
+
|
33
|
+
has_many :polytaggings, :as => :polytag, :class_name => 'Tagging'
|
34
|
+
has_many :polytagged_posts, :through => :polytaggings, :source => :taggable, :source_type => 'Post'
|
35
|
+
end
|
36
|
+
|
37
|
+
class SpecialTag < Tag
|
38
|
+
end
|
data/test/schema.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
|
3
|
+
# Please keep these create table statements in alphabetical order
|
4
|
+
# unless the ordering matters. In which case, define them below
|
5
|
+
|
6
|
+
create_table :authors, :force => true do |t|
|
7
|
+
t.string :name, :null => false
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table :posts, :force => true do |t|
|
11
|
+
t.string :type
|
12
|
+
|
13
|
+
t.integer :author_id
|
14
|
+
t.string :title, :null => false
|
15
|
+
t.text :body, :null => false
|
16
|
+
t.integer :taggings_count, :default => 0
|
17
|
+
end
|
18
|
+
|
19
|
+
create_table :taggings, :force => true do |t|
|
20
|
+
t.integer :tag_id
|
21
|
+
|
22
|
+
t.integer :polytag_id
|
23
|
+
t.string :polytag_type
|
24
|
+
|
25
|
+
t.string :taggable_type
|
26
|
+
t.integer :taggable_id
|
27
|
+
end
|
28
|
+
|
29
|
+
create_table :tags, :force => true do |t|
|
30
|
+
t.string :type
|
31
|
+
t.string :name
|
32
|
+
t.integer :taggings_count, :default => 0
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'active_record/test_case'
|
3
|
+
|
4
|
+
class TestStoreBaseStiNameFor30 < ActiveRecord::TestCase
|
5
|
+
|
6
|
+
def setup
|
7
|
+
@old_store_base_sti_class = ActiveRecord::Base.store_base_sti_class
|
8
|
+
ActiveRecord::Base.store_base_sti_class = false
|
9
|
+
|
10
|
+
@thinking_post = SpecialPost.create(:title => 'Thinking')
|
11
|
+
@misc_tag = Tag.create(:name => 'Misc')
|
12
|
+
end
|
13
|
+
|
14
|
+
def teardown
|
15
|
+
ActiveRecord::Base.store_base_sti_class = @old_store_base_sti_class
|
16
|
+
end
|
17
|
+
|
18
|
+
def test_polymorphic_belongs_to_assignment_with_inheritance
|
19
|
+
# should update when assigning a saved record
|
20
|
+
tagging = Tagging.new
|
21
|
+
post = SpecialPost.create(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
22
|
+
tagging.taggable = post
|
23
|
+
assert_equal post.id, tagging.taggable_id
|
24
|
+
assert_equal "SpecialPost", tagging.taggable_type
|
25
|
+
|
26
|
+
# should update when assigning a new record
|
27
|
+
tagging = Tagging.new
|
28
|
+
post = SpecialPost.new(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
29
|
+
tagging.taggable = post
|
30
|
+
assert_nil tagging.taggable_id
|
31
|
+
assert_equal "SpecialPost", tagging.taggable_type
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_polymorphic_has_many_create_model_with_inheritance
|
35
|
+
post = SpecialPost.new(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
36
|
+
|
37
|
+
tagging = @misc_tag.taggings.create(:taggable => post)
|
38
|
+
assert_equal "SpecialPost", tagging.taggable_type
|
39
|
+
|
40
|
+
post.reload
|
41
|
+
assert_equal [tagging], post.taggings
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_polymorphic_has_one_create_model_with_inheritance
|
45
|
+
post = SpecialPost.new(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
46
|
+
|
47
|
+
tagging = @misc_tag.create_tagging(:taggable => post)
|
48
|
+
assert_equal "SpecialPost", tagging.taggable_type
|
49
|
+
|
50
|
+
post.reload
|
51
|
+
assert_equal tagging, post.tagging
|
52
|
+
end
|
53
|
+
|
54
|
+
def test_include_polymorphic_has_one
|
55
|
+
post = SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
56
|
+
tagging = post.create_tagging(:tag => @misc_tag)
|
57
|
+
|
58
|
+
post = Post.find(post.id, :include => :tagging)
|
59
|
+
assert_equal tagging, assert_no_queries { post.tagging }
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_include_polymorphic_has_many
|
63
|
+
tag = SpecialTag.create!(:name => 'Special')
|
64
|
+
tag.polytagged_posts << SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
65
|
+
tag.polytagged_posts << @thinking_post
|
66
|
+
|
67
|
+
tag = Tag.find(tag.id, :include => :polytaggings)
|
68
|
+
assert_equal 2, assert_no_queries { tag.polytaggings.length }
|
69
|
+
end
|
70
|
+
|
71
|
+
def test_include_polymorphic_has_many_through
|
72
|
+
tag = SpecialTag.create!(:name => 'Special')
|
73
|
+
tag.polytagged_posts << SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
74
|
+
tag.polytagged_posts << @thinking_post
|
75
|
+
|
76
|
+
tag = Tag.find(tag.id, :include => :polytagged_posts)
|
77
|
+
assert_equal 2, assert_no_queries { tag.polytagged_posts.length }
|
78
|
+
end
|
79
|
+
|
80
|
+
def test_join_polymorhic_has_many
|
81
|
+
tag = SpecialTag.create!(:name => 'Special')
|
82
|
+
tag.polytagged_posts << SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
83
|
+
tag.polytagged_posts << @thinking_post
|
84
|
+
|
85
|
+
assert Tag.find_by_id(tag.id, :joins => :polytaggings, :conditions => [ 'taggings.id = ?', tag.polytaggings.first.id ])
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_join_polymorhic_has_many_through
|
89
|
+
tag = SpecialTag.create!(:name => 'Special')
|
90
|
+
tag.polytagged_posts << SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
91
|
+
tag.polytagged_posts << @thinking_post
|
92
|
+
|
93
|
+
assert Tag.find_by_id(tag.id, :joins => :polytagged_posts, :conditions => [ 'posts.id = ?', tag.polytaggings.first.taggable_id ])
|
94
|
+
end
|
95
|
+
|
96
|
+
def test_has_many_through_polymorphic_has_one
|
97
|
+
author = Author.create!(:name => 'Bob')
|
98
|
+
post = Post.create!(:title => 'Budget Forecasts Bigger 2011 Deficit', :author => author)
|
99
|
+
special_post = SpecialPost.create!(:title => 'IBM Watson''s Jeopardy play', :author => author)
|
100
|
+
special_tag = SpecialTag.create!(:name => 'SpecialGeneral')
|
101
|
+
|
102
|
+
taggings = [ post.taggings.create(:tag => special_tag), special_post.taggings.create(:tag => special_tag) ]
|
103
|
+
assert_equal taggings.sort_by(&:id), author.tagging.sort_by(&:id)
|
104
|
+
end
|
105
|
+
|
106
|
+
def test_has_many_polymorphic_with_source_type
|
107
|
+
tag = SpecialTag.create!(:name => 'Special')
|
108
|
+
tag.polytagged_posts << SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
109
|
+
tag.polytagged_posts << @thinking_post
|
110
|
+
|
111
|
+
tag = Tag.find(tag.id)
|
112
|
+
assert_equal 2, tag.polytagged_posts.length
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_polymorphic_has_many_through_with_double_sti_on_join_model
|
116
|
+
tag = SpecialTag.create!(:name => 'Special')
|
117
|
+
post = @thinking_post
|
118
|
+
|
119
|
+
tag.polytagged_posts << post
|
120
|
+
tag.reload
|
121
|
+
|
122
|
+
assert_equal 1, tag.polytaggings.length
|
123
|
+
|
124
|
+
tagging = tag.polytaggings.first
|
125
|
+
|
126
|
+
assert_equal 'SpecialTag', tagging.polytag_type
|
127
|
+
assert_equal 'SpecialPost', tagging.taggable_type
|
128
|
+
|
129
|
+
assert_equal tag, tagging.polytag
|
130
|
+
assert_equal post, tagging.taggable
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: store_base_sti_class_for_3_0
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Paul Kmiec
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-02-15 00:00:00 -08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
prerelease: false
|
23
|
+
type: :runtime
|
24
|
+
name: activerecord
|
25
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
hash: 1
|
31
|
+
segments:
|
32
|
+
- 3
|
33
|
+
- 0
|
34
|
+
- 3
|
35
|
+
version: 3.0.3
|
36
|
+
requirement: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
prerelease: false
|
39
|
+
type: :runtime
|
40
|
+
name: mysql2
|
41
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ">="
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
hash: 3
|
47
|
+
segments:
|
48
|
+
- 0
|
49
|
+
version: "0"
|
50
|
+
requirement: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
prerelease: false
|
53
|
+
type: :development
|
54
|
+
name: bundler
|
55
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ~>
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 23
|
61
|
+
segments:
|
62
|
+
- 1
|
63
|
+
- 0
|
64
|
+
- 0
|
65
|
+
version: 1.0.0
|
66
|
+
requirement: *id003
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
prerelease: false
|
69
|
+
type: :development
|
70
|
+
name: jeweler
|
71
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 7
|
77
|
+
segments:
|
78
|
+
- 1
|
79
|
+
- 5
|
80
|
+
- 2
|
81
|
+
version: 1.5.2
|
82
|
+
requirement: *id004
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
prerelease: false
|
85
|
+
type: :development
|
86
|
+
name: rcov
|
87
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
88
|
+
none: false
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
hash: 3
|
93
|
+
segments:
|
94
|
+
- 0
|
95
|
+
version: "0"
|
96
|
+
requirement: *id005
|
97
|
+
description: "\n ActiveRecord has always stored the base class in polymorphic _type columns when using STI. This can have non-trivial\n performance implications in certain cases. This gem adds 'store_base_sti_class' configuration options which controls\n whether ActiveRecord will store the base class or the actual class. Default to true for backwards compatibility.\n "
|
98
|
+
email: paul.kmiec@appfolio.com
|
99
|
+
executables: []
|
100
|
+
|
101
|
+
extensions: []
|
102
|
+
|
103
|
+
extra_rdoc_files:
|
104
|
+
- LICENSE.txt
|
105
|
+
- README.rdoc
|
106
|
+
files:
|
107
|
+
- .document
|
108
|
+
- Gemfile
|
109
|
+
- Gemfile.lock
|
110
|
+
- LICENSE.txt
|
111
|
+
- README.rdoc
|
112
|
+
- Rakefile
|
113
|
+
- VERSION
|
114
|
+
- lib/store_base_sti_class_for_3_0.rb
|
115
|
+
- store_base_sti_class_for_3_0.gemspec
|
116
|
+
- test/connection.rb
|
117
|
+
- test/helper.rb
|
118
|
+
- test/models.rb
|
119
|
+
- test/schema.rb
|
120
|
+
- test/test_store_base_sti_class_for_3_0.rb
|
121
|
+
has_rdoc: true
|
122
|
+
homepage: http://github.com/pkmiec/store_base_sti_class_for_3_0
|
123
|
+
licenses:
|
124
|
+
- MIT
|
125
|
+
post_install_message:
|
126
|
+
rdoc_options: []
|
127
|
+
|
128
|
+
require_paths:
|
129
|
+
- lib
|
130
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
131
|
+
none: false
|
132
|
+
requirements:
|
133
|
+
- - ">="
|
134
|
+
- !ruby/object:Gem::Version
|
135
|
+
hash: 3
|
136
|
+
segments:
|
137
|
+
- 0
|
138
|
+
version: "0"
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
none: false
|
141
|
+
requirements:
|
142
|
+
- - ">="
|
143
|
+
- !ruby/object:Gem::Version
|
144
|
+
hash: 3
|
145
|
+
segments:
|
146
|
+
- 0
|
147
|
+
version: "0"
|
148
|
+
requirements: []
|
149
|
+
|
150
|
+
rubyforge_project:
|
151
|
+
rubygems_version: 1.3.7
|
152
|
+
signing_key:
|
153
|
+
specification_version: 3
|
154
|
+
summary: Modifies ActiveRecord 3.0.x with the ability to store the actual class (instead of the base class) in polymorhic _type columns when using STI
|
155
|
+
test_files:
|
156
|
+
- test/connection.rb
|
157
|
+
- test/helper.rb
|
158
|
+
- test/models.rb
|
159
|
+
- test/schema.rb
|
160
|
+
- test/test_store_base_sti_class_for_3_0.rb
|