store_base_sti_class_for_3_1 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|