store_base_sti_class_for_3_1 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +2 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +37 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +61 -0
- data/Rakefile +76 -0
- data/VERSION +1 -0
- data/lib/store_base_sti_class_for_3_1.rb +304 -0
- data/polymorphic_and_sti_fix_for_rails_3_1.diff +111 -0
- data/store_base_sti_class_for_3_1.gemspec +77 -0
- data/test/connection.rb +16 -0
- data/test/helper.rb +27 -0
- data/test/models.rb +48 -0
- data/test/schema.rb +35 -0
- data/test/test_store_base_sti_class_for_3_1.rb +161 -0
- metadata +161 -0
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activemodel (3.1.3)
|
5
|
+
activesupport (= 3.1.3)
|
6
|
+
builder (~> 3.0.0)
|
7
|
+
i18n (~> 0.6)
|
8
|
+
activerecord (3.1.3)
|
9
|
+
activemodel (= 3.1.3)
|
10
|
+
activesupport (= 3.1.3)
|
11
|
+
arel (~> 2.2.1)
|
12
|
+
tzinfo (~> 0.3.29)
|
13
|
+
activesupport (3.1.3)
|
14
|
+
multi_json (~> 1.0)
|
15
|
+
arel (2.2.1)
|
16
|
+
builder (3.0.0)
|
17
|
+
git (1.2.5)
|
18
|
+
i18n (0.6.0)
|
19
|
+
jeweler (1.5.2)
|
20
|
+
bundler (~> 1.0.0)
|
21
|
+
git (>= 1.2.5)
|
22
|
+
rake
|
23
|
+
multi_json (1.0.4)
|
24
|
+
mysql2 (0.3.7)
|
25
|
+
rake (0.9.1)
|
26
|
+
rcov (0.9.9)
|
27
|
+
tzinfo (0.3.31)
|
28
|
+
|
29
|
+
PLATFORMS
|
30
|
+
ruby
|
31
|
+
|
32
|
+
DEPENDENCIES
|
33
|
+
activerecord (>= 3.1.0)
|
34
|
+
bundler (~> 1.0.0)
|
35
|
+
jeweler (~> 1.5.2)
|
36
|
+
mysql2 (= 0.3.7)
|
37
|
+
rcov
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 AppFolio, inc.
|
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,61 @@
|
|
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_1'
|
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 used the test cases from https://github.com/pkmiec/store_base_sti_class_for_3_0, but has been completely rewritten for 3.1. It currently works with ActiveRecord 3.1.0 through 3.1.3.
|
56
|
+
|
57
|
+
== Copyright
|
58
|
+
|
59
|
+
Copyright (c) 2011 AppFolio, inc. See LICENSE.txt for
|
60
|
+
further details.
|
61
|
+
|
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_1"
|
16
|
+
gem.homepage = "http://github.com/appfolio/store_base_sti_class_for_3_1"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{
|
19
|
+
Modifies ActiveRecord 3.1.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 = "andrew.mutz@appfolio.com"
|
27
|
+
gem.authors = ["Andrew Mutz"]
|
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_1 #{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,304 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
if ActiveRecord::VERSION::STRING =~ /^3\.1/
|
4
|
+
module ActiveRecord
|
5
|
+
|
6
|
+
class Base
|
7
|
+
class_attribute :store_base_sti_class
|
8
|
+
self.store_base_sti_class = true
|
9
|
+
end
|
10
|
+
|
11
|
+
module Associations
|
12
|
+
class Association
|
13
|
+
|
14
|
+
def creation_attributes
|
15
|
+
attributes = {}
|
16
|
+
|
17
|
+
if reflection.macro.in?([:has_one, :has_many]) && !options[:through]
|
18
|
+
attributes[reflection.foreign_key] = owner[reflection.active_record_primary_key]
|
19
|
+
|
20
|
+
if reflection.options[:as]
|
21
|
+
# START PATCH
|
22
|
+
# original:
|
23
|
+
# attributes[reflection.type] = owner.class.base_class.name
|
24
|
+
|
25
|
+
attributes[reflection.type] = ActiveRecord::Base.store_base_sti_class ? owner.class.base_class.name : owner.class.name
|
26
|
+
|
27
|
+
# END PATCH
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
attributes
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class JoinDependency # :nodoc:
|
37
|
+
class JoinAssociation < JoinPart # :nodoc:
|
38
|
+
def join_to(relation)
|
39
|
+
|
40
|
+
tables = @tables.dup
|
41
|
+
foreign_table = parent_table
|
42
|
+
foreign_klass = parent.active_record
|
43
|
+
|
44
|
+
# The chain starts with the target table, but we want to end with it here (makes
|
45
|
+
# more sense in this context), so we reverse
|
46
|
+
chain.reverse.each_with_index do |reflection, i|
|
47
|
+
table = tables.shift
|
48
|
+
|
49
|
+
case reflection.source_macro
|
50
|
+
when :belongs_to
|
51
|
+
key = reflection.association_primary_key
|
52
|
+
foreign_key = reflection.foreign_key
|
53
|
+
when :has_and_belongs_to_many
|
54
|
+
# Join the join table first...
|
55
|
+
relation.from(join(
|
56
|
+
table,
|
57
|
+
table[reflection.foreign_key].
|
58
|
+
eq(foreign_table[reflection.active_record_primary_key])
|
59
|
+
))
|
60
|
+
|
61
|
+
foreign_table, table = table, tables.shift
|
62
|
+
|
63
|
+
key = reflection.association_primary_key
|
64
|
+
foreign_key = reflection.association_foreign_key
|
65
|
+
else
|
66
|
+
key = reflection.foreign_key
|
67
|
+
foreign_key = reflection.active_record_primary_key
|
68
|
+
end
|
69
|
+
|
70
|
+
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
|
71
|
+
|
72
|
+
conditions = self.conditions[i].dup
|
73
|
+
|
74
|
+
# START PATCH
|
75
|
+
# original:
|
76
|
+
# conditions << { reflection.type => foreign_klass.base_class.name } if reflection.type
|
77
|
+
|
78
|
+
if ActiveRecord::Base.store_base_sti_class
|
79
|
+
conditions << { reflection.type => foreign_klass.base_class.name } if reflection.type
|
80
|
+
else
|
81
|
+
conditions << { reflection.type => ([foreign_klass] + foreign_klass.descendants).map(&:name) } if reflection.type
|
82
|
+
end
|
83
|
+
|
84
|
+
# END PATCH
|
85
|
+
|
86
|
+
unless conditions.empty?
|
87
|
+
constraint = constraint.and(sanitize(conditions, table))
|
88
|
+
end
|
89
|
+
|
90
|
+
relation.from(join(table, constraint))
|
91
|
+
|
92
|
+
# The current table in this iteration becomes the foreign table in the next
|
93
|
+
foreign_table, foreign_klass = table, reflection.klass
|
94
|
+
end
|
95
|
+
|
96
|
+
relation
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
class BelongsToPolymorphicAssociation < BelongsToAssociation #:nodoc:
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def replace_keys(record)
|
107
|
+
super
|
108
|
+
# START PATCH
|
109
|
+
# original: owner[reflection.foreign_type] = record && record.class.base_class.name
|
110
|
+
unless ActiveRecord::Base.store_base_sti_class
|
111
|
+
owner[reflection.foreign_type] = record && record.class.sti_name
|
112
|
+
else
|
113
|
+
owner[reflection.foreign_type] = record && record.class.base_class.name
|
114
|
+
end
|
115
|
+
#END PATCH
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
module Associations
|
120
|
+
class Preloader
|
121
|
+
class Association
|
122
|
+
private
|
123
|
+
def build_scope
|
124
|
+
|
125
|
+
scope = klass.scoped
|
126
|
+
|
127
|
+
scope = scope.where(process_conditions(options[:conditions]))
|
128
|
+
scope = scope.where(process_conditions(preload_options[:conditions]))
|
129
|
+
|
130
|
+
scope = scope.select(preload_options[:select] || options[:select] || table[Arel.star])
|
131
|
+
scope = scope.includes(preload_options[:include] || options[:include])
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
if options[:as]
|
136
|
+
scope = scope.where(
|
137
|
+
klass.table_name => {
|
138
|
+
#START PATCH
|
139
|
+
#original: reflection.type => model.base_class.sti_name
|
140
|
+
reflection.type => ActiveRecord::Base.store_base_sti_class ? model.base_class.sti_name : model.sti_name
|
141
|
+
#END PATCH
|
142
|
+
|
143
|
+
}
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
scope
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
module ThroughAssociation
|
152
|
+
def through_options
|
153
|
+
through_options = {}
|
154
|
+
if options[:source_type]
|
155
|
+
#START PATCH
|
156
|
+
#original: through_options[:conditions] = { reflection.foreign_type => options[:source_type] }
|
157
|
+
through_options[:conditions] = { reflection.foreign_type => ([options[:source_type].constantize] + options[:source_type].constantize.descendants).map(&:to_s) }
|
158
|
+
#END PATCH
|
159
|
+
else
|
160
|
+
if options[:conditions]
|
161
|
+
through_options[:include] = options[:include] || options[:source]
|
162
|
+
through_options[:conditions] = options[:conditions]
|
163
|
+
end
|
164
|
+
|
165
|
+
through_options[:order] = options[:order]
|
166
|
+
end
|
167
|
+
through_options
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
class AssociationScope
|
173
|
+
def add_constraints(scope)
|
174
|
+
|
175
|
+
tables = construct_tables
|
176
|
+
|
177
|
+
chain.each_with_index do |reflection, i|
|
178
|
+
table, foreign_table = tables.shift, tables.first
|
179
|
+
|
180
|
+
if reflection.source_macro == :has_and_belongs_to_many
|
181
|
+
join_table = tables.shift
|
182
|
+
|
183
|
+
scope = scope.joins(join(
|
184
|
+
join_table,
|
185
|
+
table[reflection.association_primary_key].
|
186
|
+
eq(join_table[reflection.association_foreign_key])
|
187
|
+
))
|
188
|
+
|
189
|
+
table, foreign_table = join_table, tables.first
|
190
|
+
end
|
191
|
+
|
192
|
+
if reflection.source_macro == :belongs_to
|
193
|
+
if reflection.options[:polymorphic]
|
194
|
+
# START PATCH
|
195
|
+
# This line exists to support multiple versions of AR 3.1
|
196
|
+
# original in 3.1.3: key = reflection.association_primary_key
|
197
|
+
|
198
|
+
key = (reflection.method(:association_primary_key).arity == 0) ? reflection.association_primary_key : reflection.association_primary_key(klass)
|
199
|
+
# END PATCH
|
200
|
+
else
|
201
|
+
key = reflection.association_primary_key
|
202
|
+
end
|
203
|
+
|
204
|
+
foreign_key = reflection.foreign_key
|
205
|
+
else
|
206
|
+
key = reflection.foreign_key
|
207
|
+
foreign_key = reflection.active_record_primary_key
|
208
|
+
end
|
209
|
+
|
210
|
+
conditions = self.conditions[i]
|
211
|
+
|
212
|
+
if reflection == chain.last
|
213
|
+
scope = scope.where(table[key].eq(owner[foreign_key]))
|
214
|
+
|
215
|
+
if reflection.type
|
216
|
+
# START PATCH
|
217
|
+
# original: scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
|
218
|
+
|
219
|
+
unless ActiveRecord::Base.store_base_sti_class
|
220
|
+
scope = scope.where(table[reflection.type].eq(owner.class.name))
|
221
|
+
else
|
222
|
+
scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
|
223
|
+
end
|
224
|
+
|
225
|
+
# END PATCH
|
226
|
+
end
|
227
|
+
|
228
|
+
conditions.each do |condition|
|
229
|
+
if options[:through] && condition.is_a?(Hash)
|
230
|
+
condition = { table.name => condition }
|
231
|
+
end
|
232
|
+
|
233
|
+
scope = scope.where(interpolate(condition))
|
234
|
+
end
|
235
|
+
else
|
236
|
+
constraint = table[key].eq(foreign_table[foreign_key])
|
237
|
+
|
238
|
+
if reflection.type
|
239
|
+
# START PATCH
|
240
|
+
# original: type = chain[i + 1].klass.base_class.name
|
241
|
+
# constraint = constraint.and(table[reflection.type].eq(type))
|
242
|
+
|
243
|
+
if ActiveRecord::Base.store_base_sti_class
|
244
|
+
type = chain[i + 1].klass.base_class.name
|
245
|
+
constraint = constraint.and(table[reflection.type].eq(type))
|
246
|
+
else
|
247
|
+
klass = chain[i + 1].klass
|
248
|
+
constraint = constraint.and(table[reflection.type].in(([klass] + klass.descendants).map(&:name)))
|
249
|
+
end
|
250
|
+
|
251
|
+
# END PATCH
|
252
|
+
end
|
253
|
+
|
254
|
+
scope = scope.joins(join(foreign_table, constraint))
|
255
|
+
|
256
|
+
unless conditions.empty?
|
257
|
+
scope = scope.where(sanitize(conditions, table))
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
scope
|
263
|
+
end
|
264
|
+
|
265
|
+
end
|
266
|
+
end
|
267
|
+
module Reflection
|
268
|
+
class ThroughReflection < AssociationReflection
|
269
|
+
|
270
|
+
def conditions
|
271
|
+
@conditions ||= begin
|
272
|
+
conditions = source_reflection.conditions.map { |c| c.dup }
|
273
|
+
|
274
|
+
# Add to it the conditions from this reflection if necessary.
|
275
|
+
conditions.first << options[:conditions] if options[:conditions]
|
276
|
+
|
277
|
+
through_conditions = through_reflection.conditions
|
278
|
+
|
279
|
+
if options[:source_type]
|
280
|
+
# START PATCH
|
281
|
+
# original: through_conditions.first << { foreign_type => options[:source_type] }
|
282
|
+
|
283
|
+
unless ActiveRecord::Base.store_base_sti_class
|
284
|
+
through_conditions.first << { foreign_type => ([options[:source_type].constantize] + options[:source_type].constantize.descendants).map(&:to_s) }
|
285
|
+
else
|
286
|
+
through_conditions.first << { foreign_type => options[:source_type] }
|
287
|
+
end
|
288
|
+
|
289
|
+
# END PATCH
|
290
|
+
end
|
291
|
+
|
292
|
+
# Recursively fill out the rest of the array from the through reflection
|
293
|
+
conditions += through_conditions
|
294
|
+
|
295
|
+
# And return
|
296
|
+
conditions
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
end
|
303
|
+
|
304
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
diff --git a/activerecord/lib/active_record/associations/association_scope.rb b/activerecord/lib/active_record/associations/association_scope.rb
|
2
|
+
index 9e6d9e7..cbf176e 100644
|
3
|
+
--- a/activerecord/lib/active_record/associations/association_scope.rb
|
4
|
+
+++ b/activerecord/lib/active_record/associations/association_scope.rb
|
5
|
+
@@ -81,7 +81,11 @@ module ActiveRecord
|
6
|
+
scope = scope.where(table[key].eq(owner[foreign_key]))
|
7
|
+
|
8
|
+
if reflection.type
|
9
|
+
- scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
|
10
|
+
+ unless ActiveRecord::Base.store_base_sti_class
|
11
|
+
+ scope = scope.where(table[reflection.type].eq(owner.class.name))
|
12
|
+
+ else
|
13
|
+
+ scope = scope.where(table[reflection.type].eq(owner.class.base_class.name))
|
14
|
+
+ end
|
15
|
+
end
|
16
|
+
|
17
|
+
conditions.each do |condition|
|
18
|
+
diff --git a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
|
19
|
+
index 2ee5dbb..837abfb 100644
|
20
|
+
--- a/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
|
21
|
+
+++ b/activerecord/lib/active_record/associations/belongs_to_polymorphic_association.rb
|
22
|
+
@@ -11,7 +11,11 @@ module ActiveRecord
|
23
|
+
|
24
|
+
def replace_keys(record)
|
25
|
+
super
|
26
|
+
- owner[reflection.foreign_type] = record && record.class.base_class.name
|
27
|
+
+ unless ActiveRecord::Base.store_base_sti_class
|
28
|
+
+ owner[reflection.foreign_type] = record && record.class.sti_name
|
29
|
+
+ else
|
30
|
+
+ owner[reflection.foreign_type] = record && record.class.base_class.name
|
31
|
+
+ end
|
32
|
+
end
|
33
|
+
|
34
|
+
def different_target?(record)
|
35
|
+
diff --git a/activerecord/lib/active_record/associations/join_dependency/join_association.rb b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
|
36
|
+
index 03963ab..3ad1aeb 100644
|
37
|
+
--- a/activerecord/lib/active_record/associations/join_dependency/join_association.rb
|
38
|
+
+++ b/activerecord/lib/active_record/associations/join_dependency/join_association.rb
|
39
|
+
@@ -93,7 +93,12 @@ module ActiveRecord
|
40
|
+
constraint = build_constraint(reflection, table, key, foreign_table, foreign_key)
|
41
|
+
|
42
|
+
conditions = self.conditions[i].dup
|
43
|
+
- conditions << { reflection.type => foreign_klass.base_class.name } if reflection.type
|
44
|
+
+
|
45
|
+
+ if ActiveRecord::Base.store_base_sti_class
|
46
|
+
+ conditions << { reflection.type => foreign_klass.base_class.name } if reflection.type
|
47
|
+
+ else
|
48
|
+
+ conditions << { reflection.type => ([foreign_klass.base_class] + foreign_klass.base_class.descendants).map(&:name) } if reflection.type
|
49
|
+
+ end
|
50
|
+
|
51
|
+
unless conditions.empty?
|
52
|
+
constraint = constraint.and(sanitize(conditions, table))
|
53
|
+
diff --git a/activerecord/lib/active_record/associations/preloader/association.rb b/activerecord/lib/active_record/associations/preloader/association.rb
|
54
|
+
index 779f816..e99cfcb 100644
|
55
|
+
--- a/activerecord/lib/active_record/associations/preloader/association.rb
|
56
|
+
+++ b/activerecord/lib/active_record/associations/preloader/association.rb
|
57
|
+
@@ -104,7 +104,7 @@ module ActiveRecord
|
58
|
+
if options[:as]
|
59
|
+
scope = scope.where(
|
60
|
+
klass.table_name => {
|
61
|
+
- reflection.type => model.base_class.sti_name
|
62
|
+
+ reflection.type => ActiveRecord::Base.store_base_sti_class ? model.base_class.sti_name : model.sti_name
|
63
|
+
}
|
64
|
+
)
|
65
|
+
end
|
66
|
+
diff --git a/activerecord/lib/active_record/associations/preloader/through_association.rb b/activerecord/lib/active_record/associations/preloader/through_association.rb
|
67
|
+
index ad6374d..7e14fa0 100644
|
68
|
+
--- a/activerecord/lib/active_record/associations/preloader/through_association.rb
|
69
|
+
+++ b/activerecord/lib/active_record/associations/preloader/through_association.rb
|
70
|
+
@@ -49,7 +49,7 @@ module ActiveRecord
|
71
|
+
through_options = {}
|
72
|
+
|
73
|
+
if options[:source_type]
|
74
|
+
- through_options[:conditions] = { reflection.foreign_type => options[:source_type] }
|
75
|
+
+ through_options[:conditions] = { reflection.foreign_type => ([options[:source_type].constantize] + options[:source_type].constantize.descendants).map(&:to_s) }
|
76
|
+
else
|
77
|
+
if options[:conditions]
|
78
|
+
through_options[:include] = options[:include] || options[:source]
|
79
|
+
diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb
|
80
|
+
index c866736..3673294 100644
|
81
|
+
--- a/activerecord/lib/active_record/base.rb
|
82
|
+
+++ b/activerecord/lib/active_record/base.rb
|
83
|
+
@@ -424,6 +424,11 @@ module ActiveRecord #:nodoc:
|
84
|
+
# Determine whether to store the full constant name including namespace when using STI
|
85
|
+
class_attribute :store_full_sti_class
|
86
|
+
self.store_full_sti_class = true
|
87
|
+
+
|
88
|
+
+ # Store the actual class (instead of the base class) in polymorhic _type columns when using STI
|
89
|
+
+ class_attribute :store_base_sti_class
|
90
|
+
+ self.store_base_sti_class = true
|
91
|
+
+
|
92
|
+
|
93
|
+
# Stores the default scope for the class
|
94
|
+
class_attribute :default_scopes, :instance_writer => false
|
95
|
+
diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb
|
96
|
+
index 6ddf76e..6affef3 100644
|
97
|
+
--- a/activerecord/lib/active_record/reflection.rb
|
98
|
+
+++ b/activerecord/lib/active_record/reflection.rb
|
99
|
+
@@ -451,7 +451,11 @@ module ActiveRecord
|
100
|
+
through_conditions = through_reflection.conditions
|
101
|
+
|
102
|
+
if options[:source_type]
|
103
|
+
- through_conditions.first << { foreign_type => options[:source_type] }
|
104
|
+
+ unless ActiveRecord::Base.store_base_sti_class
|
105
|
+
+ through_conditions.first << { foreign_type => ([options[:source_type].constantize] + options[:source_type].constantize.descendants).map(&:to_s) }
|
106
|
+
+ else
|
107
|
+
+ through_conditions.first << { foreign_type => options[:source_type] }
|
108
|
+
+ end
|
109
|
+
end
|
110
|
+
|
111
|
+
# Recursively fill out the rest of the array from the through reflection
|
@@ -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_1}
|
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 = [%q{Andrew Mutz}]
|
12
|
+
s.date = %q{2011-12-23}
|
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{andrew.mutz@appfolio.com}
|
19
|
+
s.extra_rdoc_files = [
|
20
|
+
"LICENSE.txt",
|
21
|
+
"README.rdoc"
|
22
|
+
]
|
23
|
+
s.files = [
|
24
|
+
"CHANGELOG",
|
25
|
+
"Gemfile",
|
26
|
+
"Gemfile.lock",
|
27
|
+
"LICENSE.txt",
|
28
|
+
"README.rdoc",
|
29
|
+
"Rakefile",
|
30
|
+
"VERSION",
|
31
|
+
"lib/store_base_sti_class_for_3_1.rb",
|
32
|
+
"polymorphic_and_sti_fix_for_rails_3_1.diff",
|
33
|
+
"store_base_sti_class_for_3_1.gemspec",
|
34
|
+
"test/connection.rb",
|
35
|
+
"test/helper.rb",
|
36
|
+
"test/models.rb",
|
37
|
+
"test/schema.rb",
|
38
|
+
"test/test_store_base_sti_class_for_3_1.rb"
|
39
|
+
]
|
40
|
+
s.homepage = %q{http://github.com/appfolio/store_base_sti_class_for_3_1}
|
41
|
+
s.licenses = [%q{MIT}]
|
42
|
+
s.require_paths = [%q{lib}]
|
43
|
+
s.rubygems_version = %q{1.8.5}
|
44
|
+
s.summary = %q{Modifies ActiveRecord 3.1.x with the ability to store the actual class (instead of the base class) in polymorhic _type columns when using STI}
|
45
|
+
s.test_files = [
|
46
|
+
"test/connection.rb",
|
47
|
+
"test/helper.rb",
|
48
|
+
"test/models.rb",
|
49
|
+
"test/schema.rb",
|
50
|
+
"test/test_store_base_sti_class_for_3_1.rb"
|
51
|
+
]
|
52
|
+
|
53
|
+
if s.respond_to? :specification_version then
|
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.1.0"])
|
58
|
+
s.add_development_dependency(%q<mysql2>, ["= 0.3.7"])
|
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.1.0"])
|
64
|
+
s.add_dependency(%q<mysql2>, ["= 0.3.7"])
|
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.1.0"])
|
71
|
+
s.add_dependency(%q<mysql2>, ["= 0.3.7"])
|
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_1'
|
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,48 @@
|
|
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
|
+
|
36
|
+
has_many :authors, :class_name => "Author", :finder_sql => proc {
|
37
|
+
<<-SQL
|
38
|
+
SELECT authors.* FROM authors
|
39
|
+
INNER JOIN posts p ON authors.id = p.author_id
|
40
|
+
INNER JOIN taggings tgs ON tgs.taggable_id = p.id AND tgs.taggable_type = "Post"
|
41
|
+
WHERE tgs.tag_id = #{self.id}
|
42
|
+
SQL
|
43
|
+
}
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
class SpecialTag < Tag
|
48
|
+
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,161 @@
|
|
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_polymorphic_has_many_create_via_association
|
55
|
+
tag = SpecialTag.create!(:name => 'Special')
|
56
|
+
tagging = tag.polytaggings.create!
|
57
|
+
|
58
|
+
assert_equal "SpecialTag", tagging.polytag_type
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_polymorphic_has_many_through_create_via_association
|
62
|
+
tag = SpecialTag.create!(:name => 'Special')
|
63
|
+
post = tag.polytagged_posts.create!(:title => 'To Be or Not To Be?')
|
64
|
+
|
65
|
+
assert_equal "SpecialTag", tag.polytaggings.first.polytag_type
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_include_polymorphic_has_one
|
69
|
+
post = SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
70
|
+
tagging = post.create_tagging(:tag => @misc_tag)
|
71
|
+
|
72
|
+
post = Post.find(post.id, :include => :tagging)
|
73
|
+
assert_equal tagging, assert_no_queries { post.tagging }
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_include_polymorphic_has_many
|
77
|
+
tag = SpecialTag.create!(:name => 'Special')
|
78
|
+
tag.polytagged_posts << SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
79
|
+
tag.polytagged_posts << @thinking_post
|
80
|
+
|
81
|
+
tag = Tag.find(tag.id, :include => :polytaggings)
|
82
|
+
assert_equal 2, assert_no_queries { tag.polytaggings.length }
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_include_polymorphic_has_many_through
|
86
|
+
tag = SpecialTag.create!(:name => 'Special')
|
87
|
+
tag.polytagged_posts << SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
88
|
+
tag.polytagged_posts << @thinking_post
|
89
|
+
|
90
|
+
tag = Tag.find(tag.id, :include => :polytagged_posts)
|
91
|
+
assert_equal 2, assert_no_queries { tag.polytagged_posts.length }
|
92
|
+
end
|
93
|
+
|
94
|
+
def test_join_polymorhic_has_many
|
95
|
+
tag = SpecialTag.create!(:name => 'Special')
|
96
|
+
tag.polytagged_posts << SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
97
|
+
tag.polytagged_posts << @thinking_post
|
98
|
+
|
99
|
+
assert Tag.find_by_id(tag.id, :joins => :polytaggings, :conditions => [ 'taggings.id = ?', tag.polytaggings.first.id ])
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_join_polymorhic_has_many_through
|
103
|
+
tag = SpecialTag.create!(:name => 'Special')
|
104
|
+
tag.polytagged_posts << SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
105
|
+
tag.polytagged_posts << @thinking_post
|
106
|
+
|
107
|
+
assert Tag.find_by_id(tag.id, :joins => :polytagged_posts, :conditions => [ 'posts.id = ?', tag.polytaggings.first.taggable_id ])
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_has_many_through_polymorphic_has_one
|
111
|
+
author = Author.create!(:name => 'Bob')
|
112
|
+
post = Post.create!(:title => 'Budget Forecasts Bigger 2011 Deficit', :author => author)
|
113
|
+
special_post = SpecialPost.create!(:title => 'IBM Watson''s Jeopardy play', :author => author)
|
114
|
+
special_tag = SpecialTag.create!(:name => 'SpecialGeneral')
|
115
|
+
|
116
|
+
taggings = [ post.taggings.create(:tag => special_tag), special_post.taggings.create(:tag => special_tag) ]
|
117
|
+
assert_equal taggings.sort_by(&:id), author.tagging.sort_by(&:id)
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_has_many_polymorphic_with_source_type
|
121
|
+
tag = SpecialTag.create!(:name => 'Special')
|
122
|
+
tag.polytagged_posts << SpecialPost.create!(:title => 'Budget Forecasts Bigger 2011 Deficit')
|
123
|
+
tag.polytagged_posts << @thinking_post
|
124
|
+
|
125
|
+
tag.save!
|
126
|
+
tag.reload
|
127
|
+
|
128
|
+
tag = Tag.find(tag.id)
|
129
|
+
assert_equal 2, tag.polytagged_posts.length
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_polymorphic_has_many_through_with_double_sti_on_join_model
|
133
|
+
tag = SpecialTag.create!(:name => 'Special')
|
134
|
+
post = @thinking_post
|
135
|
+
|
136
|
+
tag.polytagged_posts << post
|
137
|
+
|
138
|
+
|
139
|
+
tag.reload
|
140
|
+
|
141
|
+
assert_equal 1, tag.polytaggings.length
|
142
|
+
|
143
|
+
tagging = tag.polytaggings.first
|
144
|
+
|
145
|
+
assert_equal 'SpecialTag', tagging.polytag_type
|
146
|
+
assert_equal 'SpecialPost', tagging.taggable_type
|
147
|
+
|
148
|
+
assert_equal tag, tagging.polytag
|
149
|
+
assert_equal post, tagging.taggable
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_finder_sql_is_supported
|
153
|
+
author = Author.create!(:name => 'Bob')
|
154
|
+
post = Post.create!(:title => 'Budget Forecasts Bigger 2011 Deficit', :author => author)
|
155
|
+
special_tag = Tag.create!(:name => 'SpecialGeneral')
|
156
|
+
post.taggings.create(:tag => special_tag)
|
157
|
+
|
158
|
+
assert_equal [author], special_tag.authors
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: store_base_sti_class_for_3_1
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Andrew Mutz
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2012-01-14 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
prerelease: false
|
22
|
+
type: :runtime
|
23
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 3
|
31
|
+
- 1
|
32
|
+
- 0
|
33
|
+
version: 3.1.0
|
34
|
+
requirement: *id001
|
35
|
+
name: activerecord
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
prerelease: false
|
38
|
+
type: :development
|
39
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - "="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
hash: 29
|
45
|
+
segments:
|
46
|
+
- 0
|
47
|
+
- 3
|
48
|
+
- 7
|
49
|
+
version: 0.3.7
|
50
|
+
requirement: *id002
|
51
|
+
name: mysql2
|
52
|
+
- !ruby/object:Gem::Dependency
|
53
|
+
prerelease: false
|
54
|
+
type: :development
|
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
|
+
name: bundler
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
prerelease: false
|
70
|
+
type: :development
|
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
|
+
name: jeweler
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
prerelease: false
|
86
|
+
type: :development
|
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
|
+
name: rcov
|
98
|
+
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 "
|
99
|
+
email: andrew.mutz@appfolio.com
|
100
|
+
executables: []
|
101
|
+
|
102
|
+
extensions: []
|
103
|
+
|
104
|
+
extra_rdoc_files:
|
105
|
+
- LICENSE.txt
|
106
|
+
- README.rdoc
|
107
|
+
files:
|
108
|
+
- CHANGELOG
|
109
|
+
- Gemfile
|
110
|
+
- Gemfile.lock
|
111
|
+
- LICENSE.txt
|
112
|
+
- README.rdoc
|
113
|
+
- Rakefile
|
114
|
+
- VERSION
|
115
|
+
- lib/store_base_sti_class_for_3_1.rb
|
116
|
+
- polymorphic_and_sti_fix_for_rails_3_1.diff
|
117
|
+
- store_base_sti_class_for_3_1.gemspec
|
118
|
+
- test/connection.rb
|
119
|
+
- test/helper.rb
|
120
|
+
- test/models.rb
|
121
|
+
- test/schema.rb
|
122
|
+
- test/test_store_base_sti_class_for_3_1.rb
|
123
|
+
homepage: http://github.com/appfolio/store_base_sti_class_for_3_1
|
124
|
+
licenses:
|
125
|
+
- MIT
|
126
|
+
post_install_message:
|
127
|
+
rdoc_options: []
|
128
|
+
|
129
|
+
require_paths:
|
130
|
+
- lib
|
131
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
132
|
+
none: false
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
hash: 3
|
137
|
+
segments:
|
138
|
+
- 0
|
139
|
+
version: "0"
|
140
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
141
|
+
none: false
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
hash: 3
|
146
|
+
segments:
|
147
|
+
- 0
|
148
|
+
version: "0"
|
149
|
+
requirements: []
|
150
|
+
|
151
|
+
rubyforge_project:
|
152
|
+
rubygems_version: 1.8.5
|
153
|
+
signing_key:
|
154
|
+
specification_version: 3
|
155
|
+
summary: Modifies ActiveRecord 3.1.x with the ability to store the actual class (instead of the base class) in polymorhic _type columns when using STI
|
156
|
+
test_files:
|
157
|
+
- test/connection.rb
|
158
|
+
- test/helper.rb
|
159
|
+
- test/models.rb
|
160
|
+
- test/schema.rb
|
161
|
+
- test/test_store_base_sti_class_for_3_1.rb
|