rails-indexes 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/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.textile +37 -0
- data/Rakefile +1 -0
- data/lib/rails-indexes.rb +7 -0
- data/lib/rails-indexes/indexer.rb +335 -0
- data/lib/rails-indexes/version.rb +5 -0
- data/lib/tasks/indexer.rake +12 -0
- data/rails-indexes.gemspec +20 -0
- data/test/fixtures/app/controllers/cats_controller.rb +14 -0
- data/test/fixtures/app/controllers/users_controller.rb +8 -0
- data/test/fixtures/app/models/address.rb +5 -0
- data/test/fixtures/app/models/company.rb +11 -0
- data/test/fixtures/app/models/country.rb +4 -0
- data/test/fixtures/app/models/freelancer.rb +3 -0
- data/test/fixtures/app/models/gift.rb +10 -0
- data/test/fixtures/app/models/god.rb +3 -0
- data/test/fixtures/app/models/user.rb +17 -0
- data/test/fixtures/app/sweepers/user_sweeper.rb +9 -0
- data/test/fixtures/schema.rb +46 -0
- data/test/rails_indexes_test.rb +83 -0
- data/test/test_helper.rb +30 -0
- metadata +86 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.textile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
h1. Rails Indexes
|
2
|
+
|
3
|
+
Rails indexes is a small package of 2 rake tasks that scan your application models and displays a list of columns that _probably_ should be indexed.
|
4
|
+
|
5
|
+
note: there should be mode fields depending on your application design and custom queries.
|
6
|
+
|
7
|
+
h2. Installation
|
8
|
+
|
9
|
+
as a rails plugin:
|
10
|
+
<pre>script/plugin install git://github.com/eladmeidar/rails_indexes.git</pre>
|
11
|
+
|
12
|
+
h2. Usage
|
13
|
+
|
14
|
+
Display a migration for adding/removing all necessary indexes based on associations:
|
15
|
+
<pre>rake db:index_migration</pre>
|
16
|
+
|
17
|
+
Display a migration for adding/removing all necessary indexes based on AR::Base#find calls (including: find, find_by, find_all_by, find_by_x_and_y, find_all_by_x_and_y):
|
18
|
+
<pre>rake db:find_query_indexes</pre>
|
19
|
+
|
20
|
+
Note that it would probably make more sense running those tasks on production, where you *actually* need those indexes to be added.
|
21
|
+
|
22
|
+
h2. Tests
|
23
|
+
|
24
|
+
Requires SQLite3 installed, then just:
|
25
|
+
<pre>rake</pre>
|
26
|
+
to run the tests
|
27
|
+
|
28
|
+
h4. Author:
|
29
|
+
|
30
|
+
Elad Meidar - "http://blog.eizesus.com":http://blog.eizesus.com
|
31
|
+
|
32
|
+
Thanks:
|
33
|
+
Eric Davis - "http://littlestreamsoftware.com":http://littlestreamsoftware.com
|
34
|
+
|
35
|
+
Released under the same license as Ruby. No Support. No Warranty, no Pain.
|
36
|
+
|
37
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
@@ -0,0 +1,335 @@
|
|
1
|
+
module TechnoGate
|
2
|
+
module RailsIndexes
|
3
|
+
module Indexer
|
4
|
+
|
5
|
+
def self.sortalize(array)
|
6
|
+
Marshal.load(Marshal.dump(array)).each do |element|
|
7
|
+
element.sort! if element.is_a?(Array)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.check_for_indexes(migration_format = false)
|
12
|
+
model_names = []
|
13
|
+
Dir.chdir(Rails.root) do
|
14
|
+
model_names = Dir["**/app/models/**/*.rb"].collect {|filename| File.basename(filename) }.uniq
|
15
|
+
end
|
16
|
+
|
17
|
+
model_classes = []
|
18
|
+
model_names.each do |model_name|
|
19
|
+
class_name = model_name.sub(/\.rb$/,'').camelize
|
20
|
+
begin
|
21
|
+
klass = class_name.split('::').inject(Object){ |klass,part| klass.const_get(part) }
|
22
|
+
if klass < ActiveRecord::Base && !klass.abstract_class?
|
23
|
+
model_classes << klass
|
24
|
+
end
|
25
|
+
rescue
|
26
|
+
# No-op
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
@index_migrations = Hash.new([])
|
31
|
+
|
32
|
+
model_classes.each do |class_name|
|
33
|
+
|
34
|
+
# check if this is an STI child instance
|
35
|
+
if class_name.base_class.name != class_name.name && (class_name.column_names.include?(class_name.base_class.inheritance_column) || class_name.column_names.include?(class_name.inheritance_column))
|
36
|
+
|
37
|
+
# add the inharitance column on the parent table
|
38
|
+
# index migration for STI should require both the primary key and the inheritance_column in a composite index.
|
39
|
+
@index_migrations[class_name.base_class.table_name] += [[class_name.inheritance_column, class_name.base_class.primary_key].sort] unless @index_migrations[class_name.base_class.table_name].include?([class_name.base_class.inheritance_column].sort)
|
40
|
+
end
|
41
|
+
#puts "class name: #{class_name}"
|
42
|
+
class_name.reflections.each_pair do |reflection_name, reflection_options|
|
43
|
+
#puts "reflection => #{reflection_name}"
|
44
|
+
case reflection_options.macro
|
45
|
+
when :belongs_to
|
46
|
+
# polymorphic?
|
47
|
+
@table_name = class_name.table_name.to_s
|
48
|
+
if reflection_options.options.has_key?(:polymorphic) && (reflection_options.options[:polymorphic] == true)
|
49
|
+
poly_type = "#{reflection_options.name.to_s}_type"
|
50
|
+
poly_id = "#{reflection_options.name.to_s}_id"
|
51
|
+
|
52
|
+
@index_migrations[@table_name.to_s] += [[poly_type, poly_id].sort] unless @index_migrations[@table_name.to_s].include?([poly_type, poly_id].sort)
|
53
|
+
else
|
54
|
+
|
55
|
+
foreign_key = reflection_options.options[:foreign_key] ||= reflection_options.primary_key_name
|
56
|
+
@index_migrations[@table_name.to_s] += [foreign_key] unless @index_migrations[@table_name.to_s].include?(foreign_key)
|
57
|
+
end
|
58
|
+
when :has_and_belongs_to_many
|
59
|
+
table_name = reflection_options.options[:join_table] ||= [class_name.table_name, reflection_name.to_s].sort.join('_')
|
60
|
+
association_foreign_key = reflection_options.options[:association_foreign_key] ||= "#{reflection_name.to_s.singularize}_id"
|
61
|
+
|
62
|
+
# Guess foreign key?
|
63
|
+
if reflection_options.options[:foreign_key]
|
64
|
+
foreign_key = reflection_options.options[:foreign_key]
|
65
|
+
elsif reflection_options.options[:class_name]
|
66
|
+
foreign_key = reflection_options.options[:class_name].foreign_key
|
67
|
+
else
|
68
|
+
foreign_key = "#{class_name.name.tableize.singularize}_id"
|
69
|
+
end
|
70
|
+
|
71
|
+
composite_keys = [association_foreign_key, foreign_key]
|
72
|
+
|
73
|
+
@index_migrations[table_name.to_s] += [composite_keys] unless @index_migrations[table_name].include?(composite_keys)
|
74
|
+
@index_migrations[table_name.to_s] += [composite_keys.reverse] unless @index_migrations[table_name].include?(composite_keys.reverse)
|
75
|
+
|
76
|
+
else
|
77
|
+
#nothing
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
@missing_indexes = {}
|
83
|
+
|
84
|
+
@index_migrations.each do |table_name, foreign_keys|
|
85
|
+
|
86
|
+
unless foreign_keys.blank?
|
87
|
+
existing_indexes = ActiveRecord::Base.connection.indexes(table_name.to_sym).collect {|index| index.columns.size > 1 ? index.columns : index.columns.first}
|
88
|
+
keys_to_add = foreign_keys.uniq - existing_indexes #self.sortalize(foreign_keys.uniq) - self.sortalize(existing_indexes)
|
89
|
+
@missing_indexes[table_name] = keys_to_add unless keys_to_add.empty?
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
@missing_indexes
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.scan_finds
|
97
|
+
|
98
|
+
|
99
|
+
# Collect all files that can contain queries, in app/ directories (includes plugins and such)
|
100
|
+
# TODO: add lib too ?
|
101
|
+
file_names = []
|
102
|
+
|
103
|
+
Dir.chdir(Rails.root) do
|
104
|
+
file_names = Dir["**/app/**/*.rb"].uniq.reject {|file_with_path| file_with_path.include?('test')}
|
105
|
+
end
|
106
|
+
|
107
|
+
@indexes_required = Hash.new([])
|
108
|
+
|
109
|
+
# Scan each file
|
110
|
+
file_names.each do |file_name|
|
111
|
+
current_file = File.open(File.join(Rails.root, file_name), 'r')
|
112
|
+
|
113
|
+
# Scan each line
|
114
|
+
current_file.each do |line|
|
115
|
+
|
116
|
+
# by default, try to add index on primary key, based on file name
|
117
|
+
# this will fail if the file isnot a model file
|
118
|
+
|
119
|
+
begin
|
120
|
+
current_model_name = File.basename(file_name).sub(/\.rb$/,'').camelize
|
121
|
+
rescue
|
122
|
+
# NO-OP
|
123
|
+
end
|
124
|
+
|
125
|
+
# Get the model class
|
126
|
+
klass = current_model_name.split('::').inject(Object){ |klass,part| klass.const_get(part) } rescue nil
|
127
|
+
|
128
|
+
# Only add primary key for active record dependent classes and non abstract ones too.
|
129
|
+
if klass.present? && klass < ActiveRecord::Base && !klass.abstract_class?
|
130
|
+
current_model = current_model_name.constantize
|
131
|
+
primary_key = current_model.primary_key
|
132
|
+
table_name = current_model.table_name
|
133
|
+
@indexes_required[table_name] += [primary_key] unless @indexes_required[table_name].include?(primary_key)
|
134
|
+
end
|
135
|
+
|
136
|
+
check_line_for_find_indexes(file_name, line)
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
@missing_indexes = {}
|
142
|
+
@indexes_required.each do |table_name, foreign_keys|
|
143
|
+
|
144
|
+
unless foreign_keys.blank?
|
145
|
+
begin
|
146
|
+
if ActiveRecord::Base.connection.tables.include?(table_name.to_s)
|
147
|
+
existing_indexes = ActiveRecord::Base.connection.indexes(table_name.to_sym).collect {|index| index.columns.size > 1 ? index.columns : index.columns.first}
|
148
|
+
keys_to_add = self.sortalize(foreign_keys.uniq) - self.sortalize(existing_indexes)
|
149
|
+
@missing_indexes[table_name] = keys_to_add unless keys_to_add.empty?
|
150
|
+
else
|
151
|
+
puts "BUG: table '#{table_name.to_s}' does not exist, please report this bug."
|
152
|
+
end
|
153
|
+
rescue Exception => e
|
154
|
+
puts "ERROR: #{e}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
@indexes_required
|
160
|
+
end
|
161
|
+
|
162
|
+
# Check line for find* methods (include find_all, find_by and just find)
|
163
|
+
def self.check_line_for_find_indexes(file_name, line)
|
164
|
+
|
165
|
+
# TODO: Assumes that you have a called on #find. you can actually call #find without a caller in a model code. ex:
|
166
|
+
# def something
|
167
|
+
# find(self.id)
|
168
|
+
# end
|
169
|
+
#
|
170
|
+
# find_regexp = Regexp.new(/([A-Z]{1}[A-Za-z]+|self).(find){1}((_all){0,1}(_by_){0,1}([A-Za-z_]+))?\(([0-9A-Za-z"\':=>. \[\]{},]*)\)/)
|
171
|
+
|
172
|
+
find_regexp = Regexp.new(/(([A-Z]{1}[A-Za-z]+|self).)?(find){1}((_all){0,1}(_by_){0,1}([A-Za-z_]+))?\(([0-9A-Za-z"\':=>. \[\]{},]*)\)/)
|
173
|
+
|
174
|
+
# If line matched a finder
|
175
|
+
if matches = find_regexp.match(line)
|
176
|
+
|
177
|
+
model_name, column_names, options = matches[2], matches[7], matches[8]
|
178
|
+
|
179
|
+
# if the finder class is "self" or empty (can be a simple "find()" in a model)
|
180
|
+
if model_name == "self" || model_name.blank?
|
181
|
+
model_name = File.basename(file_name).sub(/\.rb$/,'').camelize
|
182
|
+
table_name = model_name.constantize.table_name
|
183
|
+
else
|
184
|
+
if model_name.respond_to?(:constantize)
|
185
|
+
if model_name.constantize.respond_to?(:table_name)
|
186
|
+
table_name = model_name.constantize.table_name
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
# Check that all prerequisites are met
|
192
|
+
if model_name.present? && table_name.present? && model_name.constantize.ancestors.include?(ActiveRecord::Base)
|
193
|
+
primary_key = model_name.constantize.primary_key
|
194
|
+
@indexes_required[table_name] += [primary_key] unless @indexes_required[table_name].include?(primary_key)
|
195
|
+
|
196
|
+
if column_names.present?
|
197
|
+
column_names = column_names.split('_and_')
|
198
|
+
|
199
|
+
# remove find_by_sql references.
|
200
|
+
column_names.delete("sql")
|
201
|
+
|
202
|
+
column_names = model_name.constantize.column_names & column_names
|
203
|
+
|
204
|
+
# Check if there were more than 1 column
|
205
|
+
if column_names.size == 1
|
206
|
+
column_name = column_names.first
|
207
|
+
@indexes_required[table_name] += [column_name] unless @indexes_required[table_name].include?(column_name)
|
208
|
+
else
|
209
|
+
@indexes_required[table_name] += [column_names] unless @indexes_required[table_name].include?(column_names)
|
210
|
+
@indexes_required[table_name] += [column_names.reverse] unless @indexes_required[table_name].include?(column_names.reverse)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def self.key_exists?(table,key_columns)
|
218
|
+
result = (key_columns.to_a - ActiveRecord::Base.connection.indexes(table).map { |i| i.columns }.flatten)
|
219
|
+
result.empty?
|
220
|
+
end
|
221
|
+
|
222
|
+
def self.simple_migration
|
223
|
+
migration_format = true
|
224
|
+
missing_indexes = check_for_indexes(migration_format)
|
225
|
+
|
226
|
+
unless missing_indexes.keys.empty?
|
227
|
+
add = []
|
228
|
+
remove = []
|
229
|
+
missing_indexes.each do |table_name, keys_to_add|
|
230
|
+
keys_to_add.each do |key|
|
231
|
+
next if key_exists?(table_name,key)
|
232
|
+
next if key.blank?
|
233
|
+
if key.is_a?(Array)
|
234
|
+
keys = key.collect {|k| ":#{k}"}
|
235
|
+
add << "add_index :#{table_name}, [#{keys.join(', ')}]"
|
236
|
+
remove << "remove_index :#{table_name}, :column => [#{keys.join(', ')}]"
|
237
|
+
else
|
238
|
+
add << "add_index :#{table_name}, :#{key}"
|
239
|
+
remove << "remove_index :#{table_name}, :#{key}"
|
240
|
+
end
|
241
|
+
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
migration = <<-EOM
|
246
|
+
class AddMissingIndexes < ActiveRecord::Migration
|
247
|
+
def self.up
|
248
|
+
|
249
|
+
# These indexes were found by searching for AR::Base finds on your application
|
250
|
+
# It is strongly recommanded that you will consult a professional DBA about your infrastucture and implemntation before
|
251
|
+
# changing your database in that matter.
|
252
|
+
# There is a possibility that some of the indexes offered below is not required and can be removed and not added, if you require
|
253
|
+
# further assistance with your rails application, database infrastructure or any other problem, visit:
|
254
|
+
#
|
255
|
+
# http://www.railsmentors.org
|
256
|
+
# http://www.railstutor.org
|
257
|
+
# http://guides.rubyonrails.org
|
258
|
+
|
259
|
+
|
260
|
+
#{add.uniq.join("\n ")}
|
261
|
+
end
|
262
|
+
|
263
|
+
def self.down
|
264
|
+
#{remove.uniq.join("\n ")}
|
265
|
+
end
|
266
|
+
end
|
267
|
+
EOM
|
268
|
+
|
269
|
+
puts "## Drop this into a file in db/migrate ##"
|
270
|
+
puts migration
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def self.indexes_list
|
275
|
+
check_for_indexes.each do |table_name, keys_to_add|
|
276
|
+
puts "Table '#{table_name}' => #{keys_to_add.to_sentence}"
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def self.ar_find_indexes(migration_mode=true)
|
281
|
+
find_indexes = self.scan_finds
|
282
|
+
|
283
|
+
if migration_mode
|
284
|
+
unless find_indexes.keys.empty?
|
285
|
+
add = []
|
286
|
+
remove = []
|
287
|
+
find_indexes.each do |table_name, keys_to_add|
|
288
|
+
keys_to_add.each do |key|
|
289
|
+
next if key_exists?(table_name,[key].flatten)
|
290
|
+
next if key.blank?
|
291
|
+
if key.is_a?(Array)
|
292
|
+
keys = key.collect {|k| ":#{k}"}
|
293
|
+
add << "add_index :#{table_name}, [#{keys.join(', ')}]"
|
294
|
+
remove << "remove_index :#{table_name}, :column => [#{keys.join(', ')}]"
|
295
|
+
else
|
296
|
+
add << "add_index :#{table_name}, :#{key}"
|
297
|
+
remove << "remove_index :#{table_name}, :#{key}"
|
298
|
+
end
|
299
|
+
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
migration = <<-EOM
|
304
|
+
class AddFindsMissingIndexes < ActiveRecord::Migration
|
305
|
+
def self.up
|
306
|
+
|
307
|
+
# These indexes were found by searching for AR::Base finds on your application
|
308
|
+
# It is strongly recommanded that you will consult a professional DBA about your infrastucture and implemntation before
|
309
|
+
# changing your database in that matter.
|
310
|
+
# There is a possibility that some of the indexes offered below is not required and can be removed and not added, if you require
|
311
|
+
# further assistance with your rails application, database infrastructure or any other problem, visit:
|
312
|
+
#
|
313
|
+
# http://www.railsmentors.org
|
314
|
+
# http://www.railstutor.org
|
315
|
+
# http://guides.rubyonrails.org
|
316
|
+
|
317
|
+
#{add.uniq.join("\n ")}
|
318
|
+
end
|
319
|
+
|
320
|
+
def self.down
|
321
|
+
#{remove.uniq.join("\n ")}
|
322
|
+
end
|
323
|
+
end
|
324
|
+
EOM
|
325
|
+
|
326
|
+
puts "## Drop this into a file in db/migrate ##"
|
327
|
+
puts migration
|
328
|
+
end
|
329
|
+
end
|
330
|
+
else
|
331
|
+
find_indexes
|
332
|
+
end
|
333
|
+
end
|
334
|
+
end
|
335
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rails-indexes'
|
2
|
+
|
3
|
+
namespace :db do
|
4
|
+
desc "collect indexes based on AR::Base.find calls."
|
5
|
+
task :find_query_indexes => :environment do
|
6
|
+
TechnoGate::RailsIndexes::Indexer.ar_find_indexes
|
7
|
+
end
|
8
|
+
|
9
|
+
task :index_migration => :environment do
|
10
|
+
TechnoGate::RailsIndexes::Indexer.simple_migration
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "rails-indexes/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rails-indexes"
|
7
|
+
s.version = TechnoGate::RailsIndexes::VERSION
|
8
|
+
s.authors = ["Elad Meidar", "Wael Nasreddine"]
|
9
|
+
s.email = ["elad@eizesus.com","wael.nasreddine@gmail.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{A rake task to track down missing database indexes. does not assume that all foreign keys end with the convention of _id.}
|
12
|
+
s.description = %q{A rake task to track down missing database indexes. does not assume that all foreign keys end with the convention of _id.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "rails-indexes"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class Gift < ActiveRecord::Base
|
2
|
+
|
3
|
+
set_primary_key :custom_primary_key
|
4
|
+
has_and_belongs_to_many :users, :join_table => "purchases", :association_foreign_key => 'buyer_id', :foreign_key => 'present_id'
|
5
|
+
|
6
|
+
def search_all(name, price)
|
7
|
+
Gift.find_all_by_name_and_price(name, price)
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class User < ActiveRecord::Base
|
2
|
+
|
3
|
+
has_one :company, :foreign_key => 'owner_id'
|
4
|
+
has_one :address, :as => :addressable
|
5
|
+
|
6
|
+
has_and_belongs_to_many :users, :join_table => "purchases", :association_foreign_key => 'present_id', :foreign_key => 'buyer_id'
|
7
|
+
|
8
|
+
validates_uniqueness_of :name
|
9
|
+
|
10
|
+
def search_via_email(email = "user@domain.com")
|
11
|
+
self.find_by_email(email)
|
12
|
+
end
|
13
|
+
|
14
|
+
def search_via_email_and_name(email, name)
|
15
|
+
self.find_by_email_and_name(email, name)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
create_table "users", :force => true do |t|
|
3
|
+
t.column "name", :text
|
4
|
+
t.column "email", :text
|
5
|
+
end
|
6
|
+
|
7
|
+
create_table "companies", :force => true do |t|
|
8
|
+
t.column "name", :text
|
9
|
+
t.column "owned_id", :integer
|
10
|
+
t.column "country_id", :integer
|
11
|
+
end
|
12
|
+
|
13
|
+
add_index :companies, :country_id
|
14
|
+
|
15
|
+
create_table "addresses", :force => true do |t|
|
16
|
+
t.column "addressable_type", :string
|
17
|
+
t.column "addressable_id", :integer
|
18
|
+
t.column "address", :text
|
19
|
+
t.column "country_id", :integer
|
20
|
+
end
|
21
|
+
|
22
|
+
create_table "freelancers", :force => true do |t|
|
23
|
+
t.column "name", :string
|
24
|
+
t.column "price_per_hour", :integer
|
25
|
+
end
|
26
|
+
|
27
|
+
create_table "companies_freelancers", :force => true do |t|
|
28
|
+
t.column "freelancer_id", :integer
|
29
|
+
t.column "company_id", :integer
|
30
|
+
end
|
31
|
+
|
32
|
+
create_table "gifts", :force => true do |t|
|
33
|
+
t.column "custom_primary_key", :integer
|
34
|
+
t.column "name", :string
|
35
|
+
t.column "price", :integer
|
36
|
+
end
|
37
|
+
|
38
|
+
create_table "purchases", :force => true do |t|
|
39
|
+
t.column "present_id", :integer
|
40
|
+
t.column "buyer_id", :integer
|
41
|
+
end
|
42
|
+
|
43
|
+
create_table "countries", :force => true do |t|
|
44
|
+
t.column "name", :string
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class RailsIndexesTest < ActiveSupport::TestCase
|
4
|
+
|
5
|
+
test "relationship indexes are found" do
|
6
|
+
@relationship_indexes = Indexer.check_for_indexes
|
7
|
+
|
8
|
+
assert @relationship_indexes.size > 0
|
9
|
+
|
10
|
+
assert @relationship_indexes.has_key?("companies")
|
11
|
+
assert @relationship_indexes.has_key?("companies_freelancers")
|
12
|
+
assert @relationship_indexes.has_key?("addresses")
|
13
|
+
assert @relationship_indexes.has_key?("purchases")
|
14
|
+
end
|
15
|
+
|
16
|
+
# should add 2 composite indexes or each pair
|
17
|
+
test "has_and_belongs_to_many" do
|
18
|
+
@relationship_indexes = Indexer.check_for_indexes(true)
|
19
|
+
|
20
|
+
assert @relationship_indexes["companies_freelancers"].include?(["company_id", "freelancer_id"])
|
21
|
+
assert @relationship_indexes["companies_freelancers"].include?(["freelancer_id", "company_id"])
|
22
|
+
end
|
23
|
+
|
24
|
+
test "has_and_belongs_to_many with custom columns" do
|
25
|
+
@relationship_indexes = Indexer.check_for_indexes(true)
|
26
|
+
|
27
|
+
assert @relationship_indexes["purchases"].include?(["present_id", "buyer_id"])
|
28
|
+
assert @relationship_indexes["purchases"].include?(["buyer_id", "present_id"])
|
29
|
+
end
|
30
|
+
|
31
|
+
test "belongs_to" do
|
32
|
+
@relationship_indexes = Indexer.check_for_indexes(true)
|
33
|
+
assert @relationship_indexes["addresses"].include?("country_id")
|
34
|
+
end
|
35
|
+
|
36
|
+
test "belongs_to with a custom foreign key" do
|
37
|
+
@relationship_indexes = Indexer.check_for_indexes(true)
|
38
|
+
assert @relationship_indexes["companies"].include?("owner_id")
|
39
|
+
end
|
40
|
+
|
41
|
+
test "should not add an already existing index" do
|
42
|
+
@relationship_indexes = Indexer.check_for_indexes(true)
|
43
|
+
assert !(@relationship_indexes["companies"].include?("country_id"))
|
44
|
+
end
|
45
|
+
|
46
|
+
test "default find_by indexes for primary keys" do
|
47
|
+
@find_by_indexes = Indexer.ar_find_indexes(false)
|
48
|
+
|
49
|
+
# Default added the primary key for each table
|
50
|
+
assert @find_by_indexes.has_key?("users")
|
51
|
+
assert @find_by_indexes.has_key?("companies")
|
52
|
+
assert @find_by_indexes.has_key?("gifts")
|
53
|
+
assert @find_by_indexes.has_key?("freelancers")
|
54
|
+
assert @find_by_indexes.has_key?("countries")
|
55
|
+
end
|
56
|
+
|
57
|
+
test "default find_by indexes for custom primary keys" do
|
58
|
+
@find_by_indexes = Indexer.ar_find_indexes(false)
|
59
|
+
|
60
|
+
assert @find_by_indexes["gifts"].include?("custom_primary_key")
|
61
|
+
end
|
62
|
+
|
63
|
+
test "find_by indexes for self.find_by_email_and_name" do
|
64
|
+
@find_by_indexes = Indexer.ar_find_indexes(false)
|
65
|
+
|
66
|
+
assert @find_by_indexes["users"].include?(["name", "email"])
|
67
|
+
assert @find_by_indexes["users"].include?(["email", "name"])
|
68
|
+
end
|
69
|
+
|
70
|
+
test "find_by indexes for Gift.find_all_by_name_and_price" do
|
71
|
+
@find_by_indexes = Indexer.ar_find_indexes(false)
|
72
|
+
|
73
|
+
assert @find_by_indexes["gifts"].include?(["name", "price"])
|
74
|
+
assert @find_by_indexes["gifts"].include?(["price", "name"])
|
75
|
+
end
|
76
|
+
|
77
|
+
test "find_by indexes from UsersController" do
|
78
|
+
@find_by_indexes = Indexer.ar_find_indexes(false)
|
79
|
+
|
80
|
+
assert @find_by_indexes["freelancers"].include?("name")
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
|
5
|
+
require 'active_record'
|
6
|
+
require 'active_record/fixtures'
|
7
|
+
require 'active_support'
|
8
|
+
require 'active_support/test_case'
|
9
|
+
require 'action_controller'
|
10
|
+
|
11
|
+
require 'indexer'
|
12
|
+
|
13
|
+
ActiveRecord::Base.establish_connection(
|
14
|
+
:adapter => "sqlite3",
|
15
|
+
:database => ":memory:"
|
16
|
+
)
|
17
|
+
|
18
|
+
class Rails
|
19
|
+
def self.root
|
20
|
+
"test/fixtures/"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
load 'test/fixtures/schema.rb'
|
25
|
+
|
26
|
+
# Load models
|
27
|
+
Dir['test/fixtures/app/models/**/*.rb'].each { |f| require f }
|
28
|
+
|
29
|
+
# load controllers
|
30
|
+
Dir['test/fixtures/app/controllers/**/*.rb'].each { |f| require f }
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rails-indexes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Elad Meidar
|
9
|
+
- Wael Nasreddine
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2011-07-03 00:00:00.000000000 +02:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
description: A rake task to track down missing database indexes. does not assume that
|
17
|
+
all foreign keys end with the convention of _id.
|
18
|
+
email:
|
19
|
+
- elad@eizesus.com
|
20
|
+
- wael.nasreddine@gmail.com
|
21
|
+
executables: []
|
22
|
+
extensions: []
|
23
|
+
extra_rdoc_files: []
|
24
|
+
files:
|
25
|
+
- .gitignore
|
26
|
+
- Gemfile
|
27
|
+
- README.textile
|
28
|
+
- Rakefile
|
29
|
+
- lib/rails-indexes.rb
|
30
|
+
- lib/rails-indexes/indexer.rb
|
31
|
+
- lib/rails-indexes/version.rb
|
32
|
+
- lib/tasks/indexer.rake
|
33
|
+
- rails-indexes.gemspec
|
34
|
+
- test/fixtures/app/controllers/cats_controller.rb
|
35
|
+
- test/fixtures/app/controllers/users_controller.rb
|
36
|
+
- test/fixtures/app/models/address.rb
|
37
|
+
- test/fixtures/app/models/company.rb
|
38
|
+
- test/fixtures/app/models/country.rb
|
39
|
+
- test/fixtures/app/models/freelancer.rb
|
40
|
+
- test/fixtures/app/models/gift.rb
|
41
|
+
- test/fixtures/app/models/god.rb
|
42
|
+
- test/fixtures/app/models/user.rb
|
43
|
+
- test/fixtures/app/sweepers/user_sweeper.rb
|
44
|
+
- test/fixtures/schema.rb
|
45
|
+
- test/rails_indexes_test.rb
|
46
|
+
- test/test_helper.rb
|
47
|
+
has_rdoc: true
|
48
|
+
homepage: ''
|
49
|
+
licenses: []
|
50
|
+
post_install_message:
|
51
|
+
rdoc_options: []
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project: rails-indexes
|
68
|
+
rubygems_version: 1.6.2
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: A rake task to track down missing database indexes. does not assume that
|
72
|
+
all foreign keys end with the convention of _id.
|
73
|
+
test_files:
|
74
|
+
- test/fixtures/app/controllers/cats_controller.rb
|
75
|
+
- test/fixtures/app/controllers/users_controller.rb
|
76
|
+
- test/fixtures/app/models/address.rb
|
77
|
+
- test/fixtures/app/models/company.rb
|
78
|
+
- test/fixtures/app/models/country.rb
|
79
|
+
- test/fixtures/app/models/freelancer.rb
|
80
|
+
- test/fixtures/app/models/gift.rb
|
81
|
+
- test/fixtures/app/models/god.rb
|
82
|
+
- test/fixtures/app/models/user.rb
|
83
|
+
- test/fixtures/app/sweepers/user_sweeper.rb
|
84
|
+
- test/fixtures/schema.rb
|
85
|
+
- test/rails_indexes_test.rb
|
86
|
+
- test/test_helper.rb
|