Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 331 lines (268 sloc) 12.704 kb
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
1 module Indexer
2
fed8b19 @eladmeidar added #sortalize method, to sort 1st level arrays from AR::Base to ensur...
authored
3 def self.sortalize(array)
4 Marshal.load(Marshal.dump(array)).each do |element|
5 element.sort! if element.is_a?(Array)
6 end
7 end
8
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
9 def self.check_for_indexes(migration_format = false)
10 model_names = []
11 Dir.chdir(Rails.root) do
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
12 model_names = Dir["**/app/models/**/*.rb"].collect {|filename| File.basename(filename) }.uniq
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
13 end
14
15 model_classes = []
16 model_names.each do |model_name|
17 class_name = model_name.sub(/\.rb$/,'').camelize
18 begin
19 klass = class_name.split('::').inject(Object){ |klass,part| klass.const_get(part) }
20 if klass < ActiveRecord::Base && !klass.abstract_class?
21 model_classes << klass
22 end
23 rescue
24 # No-op
25 end
26 end
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
27
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
28 @index_migrations = Hash.new([])
29
30 model_classes.each do |class_name|
31
d4d85f5 @eladmeidar STI bugs fixed, now inheritance_column is taken from the child, not pare...
authored
32 # check if this is an STI child instance
33 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))
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
34
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
35 # add the inharitance column on the parent table
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
36 # index migration for STI should require both the primary key and the inheritance_column in a composite index.
37 @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)
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
38 end
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
39 #puts "class name: #{class_name}"
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
40 class_name.reflections.each_pair do |reflection_name, reflection_options|
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
41 #puts "reflection => #{reflection_name}"
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
42 case reflection_options.macro
43 when :belongs_to
44 # polymorphic?
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
45 @table_name = class_name.table_name.to_s
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
46 if reflection_options.options.has_key?(:polymorphic) && (reflection_options.options[:polymorphic] == true)
47 poly_type = "#{reflection_options.name.to_s}_type"
48 poly_id = "#{reflection_options.name.to_s}_id"
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
49
50 @index_migrations[@table_name.to_s] += [[poly_type, poly_id].sort] unless @index_migrations[@table_name.to_s].include?([poly_type, poly_id].sort)
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
51 else
52
53 foreign_key = reflection_options.options[:foreign_key] ||= reflection_options.primary_key_name
54 @index_migrations[@table_name.to_s] += [foreign_key] unless @index_migrations[@table_name.to_s].include?(foreign_key)
55 end
56 when :has_and_belongs_to_many
57 table_name = reflection_options.options[:join_table] ||= [class_name.table_name, reflection_name.to_s].sort.join('_')
58 association_foreign_key = reflection_options.options[:association_foreign_key] ||= "#{reflection_name.to_s.singularize}_id"
59f9014 @eladmeidar Bug in has_many_through_association and foreign key names
authored
59
60 # Guess foreign key?
61 if reflection_options.options[:foreign_key]
62 foreign_key = reflection_options.options[:foreign_key]
63 elsif reflection_options.options[:class_name]
64 foreign_key = reflection_options.options[:class_name].foreign_key
65 else
66 foreign_key = "#{class_name.name.tableize.singularize}_id"
67 end
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
68
69 composite_keys = [association_foreign_key, foreign_key]
70
71 @index_migrations[table_name.to_s] += [composite_keys] unless @index_migrations[table_name].include?(composite_keys)
72 @index_migrations[table_name.to_s] += [composite_keys.reverse] unless @index_migrations[table_name].include?(composite_keys.reverse)
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
73
74 else
75 #nothing
76 end
77 end
78 end
79
80 @missing_indexes = {}
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
81
82 @index_migrations.each do |table_name, foreign_keys|
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
83
84 unless foreign_keys.blank?
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
85 existing_indexes = ActiveRecord::Base.connection.indexes(table_name.to_sym).collect {|index| index.columns.size > 1 ? index.columns : index.columns.first}
86 keys_to_add = foreign_keys.uniq - existing_indexes #self.sortalize(foreign_keys.uniq) - self.sortalize(existing_indexes)
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
87 @missing_indexes[table_name] = keys_to_add unless keys_to_add.empty?
88 end
89 end
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
90
91 @missing_indexes
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
92 end
93
94 def self.scan_finds
89f9202 @eladmeidar Refactoring and fixing issues
authored
95
96
97 # Collect all files that can contain queries, in app/ directories (includes plugins and such)
98 # TODO: add lib too ?
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
99 file_names = []
d09a13b @eladmeidar primary key indexes are only added to valid, non-abstrat models.
authored
100
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
101 Dir.chdir(Rails.root) do
7f39051 @eladmeidar Excluding any occurrences of *.rb files that are under a test/ directory...
authored
102 file_names = Dir["**/app/**/*.rb"].uniq.reject {|file_with_path| file_with_path.include?('test')}
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
103 end
89f9202 @eladmeidar Refactoring and fixing issues
authored
104
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
105 @indexes_required = Hash.new([])
89f9202 @eladmeidar Refactoring and fixing issues
authored
106
107 # Scan each file
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
108 file_names.each do |file_name|
109 current_file = File.open(File.join(Rails.root, file_name), 'r')
110
89f9202 @eladmeidar Refactoring and fixing issues
authored
111 # Scan each line
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
112 current_file.each do |line|
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
113
114 # by default, try to add index on primary key, based on file name
115 # this will fail if the file isnot a model file
116
117 begin
d09a13b @eladmeidar primary key indexes are only added to valid, non-abstrat models.
authored
118 current_model_name = File.basename(file_name).sub(/\.rb$/,'').camelize
119 rescue
120 # NO-OP
121 end
122
89f9202 @eladmeidar Refactoring and fixing issues
authored
123 # Get the model class
d09a13b @eladmeidar primary key indexes are only added to valid, non-abstrat models.
authored
124 klass = current_model_name.split('::').inject(Object){ |klass,part| klass.const_get(part) } rescue nil
89f9202 @eladmeidar Refactoring and fixing issues
authored
125
126 # Only add primary key for active record dependent classes and non abstract ones too.
d09a13b @eladmeidar primary key indexes are only added to valid, non-abstrat models.
authored
127 if klass.present? && klass < ActiveRecord::Base && !klass.abstract_class?
128 current_model = current_model_name.constantize
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
129 primary_key = current_model.primary_key
130 table_name = current_model.table_name
131 @indexes_required[table_name] += [primary_key] unless @indexes_required[table_name].include?(primary_key)
132 end
133
1dcd9e2 @eladmeidar Fixed deprecation notice, RailsIndexes is now Rails3 compliant. wut wut
authored
134 check_line_for_find_indexes(file_name, line)
135
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
136 end
137 end
d09a13b @eladmeidar primary key indexes are only added to valid, non-abstrat models.
authored
138
fed8b19 @eladmeidar added #sortalize method, to sort 1st level arrays from AR::Base to ensur...
authored
139 @missing_indexes = {}
140 @indexes_required.each do |table_name, foreign_keys|
141
d09a13b @eladmeidar primary key indexes are only added to valid, non-abstrat models.
authored
142 unless foreign_keys.blank?
143 begin
144 if ActiveRecord::Base.connection.tables.include?(table_name.to_s)
145 existing_indexes = ActiveRecord::Base.connection.indexes(table_name.to_sym).collect {|index| index.columns.size > 1 ? index.columns : index.columns.first}
146 keys_to_add = self.sortalize(foreign_keys.uniq) - self.sortalize(existing_indexes)
147 @missing_indexes[table_name] = keys_to_add unless keys_to_add.empty?
148 else
149 puts "BUG: table '#{table_name.to_s}' does not exist, please report this bug."
150 end
151 rescue Exception => e
152 puts "ERROR: #{e}"
153 end
fed8b19 @eladmeidar added #sortalize method, to sort 1st level arrays from AR::Base to ensur...
authored
154 end
155 end
156
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
157 @indexes_required
158 end
159
89f9202 @eladmeidar Refactoring and fixing issues
authored
160 # Check line for find* methods (include find_all, find_by and just find)
1dcd9e2 @eladmeidar Fixed deprecation notice, RailsIndexes is now Rails3 compliant. wut wut
authored
161 def self.check_line_for_find_indexes(file_name, line)
89f9202 @eladmeidar Refactoring and fixing issues
authored
162
163 # TODO: Assumes that you have a called on #find. you can actually call #find without a caller in a model code. ex:
164 # def something
165 # find(self.id)
166 # end
167 #
168 # 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"\':=>. \[\]{},]*)\)/)
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 # If line matched a finder
1dcd9e2 @eladmeidar Fixed deprecation notice, RailsIndexes is now Rails3 compliant. wut wut
authored
173 if matches = find_regexp.match(line)
174
89f9202 @eladmeidar Refactoring and fixing issues
authored
175 model_name, column_names, options = matches[2], matches[7], matches[8]
176
177 # if the finder class is "self" or empty (can be a simple "find()" in a model)
178 if model_name == "self" || model_name.blank?
1dcd9e2 @eladmeidar Fixed deprecation notice, RailsIndexes is now Rails3 compliant. wut wut
authored
179 model_name = File.basename(file_name).sub(/\.rb$/,'').camelize
180 table_name = model_name.constantize.table_name
181 else
182 if model_name.respond_to?(:constantize)
183 if model_name.constantize.respond_to?(:table_name)
184 table_name = model_name.constantize.table_name
185 end
186 end
187 end
89f9202 @eladmeidar Refactoring and fixing issues
authored
188
189 # Check that all prerequisites are met
1bc8703 @eladmeidar Now only ARs ancestors are scanned
authored
190 if model_name.present? && table_name.present? && model_name.constantize.ancestors.include?(ActiveRecord::Base)
89f9202 @eladmeidar Refactoring and fixing issues
authored
191 primary_key = model_name.constantize.primary_key
192 @indexes_required[table_name] += [primary_key] unless @indexes_required[table_name].include?(primary_key)
1dcd9e2 @eladmeidar Fixed deprecation notice, RailsIndexes is now Rails3 compliant. wut wut
authored
193
89f9202 @eladmeidar Refactoring and fixing issues
authored
194 if column_names.present?
195 column_names = column_names.split('_and_')
1dcd9e2 @eladmeidar Fixed deprecation notice, RailsIndexes is now Rails3 compliant. wut wut
authored
196
89f9202 @eladmeidar Refactoring and fixing issues
authored
197 # remove find_by_sql references.
198 column_names.delete("sql")
1dcd9e2 @eladmeidar Fixed deprecation notice, RailsIndexes is now Rails3 compliant. wut wut
authored
199
89f9202 @eladmeidar Refactoring and fixing issues
authored
200 column_names = model_name.constantize.column_names & column_names
1dcd9e2 @eladmeidar Fixed deprecation notice, RailsIndexes is now Rails3 compliant. wut wut
authored
201
89f9202 @eladmeidar Refactoring and fixing issues
authored
202 # Check if there were more than 1 column
203 if column_names.size == 1
204 column_name = column_names.first
205 @indexes_required[table_name] += [column_name] unless @indexes_required[table_name].include?(column_name)
206 else
207 @indexes_required[table_name] += [column_names] unless @indexes_required[table_name].include?(column_names)
208 @indexes_required[table_name] += [column_names.reverse] unless @indexes_required[table_name].include?(column_names.reverse)
209 end
1dcd9e2 @eladmeidar Fixed deprecation notice, RailsIndexes is now Rails3 compliant. wut wut
authored
210 end
211 end
212 end
213 end
214
584ad9d remove existing keys
Michael Deimel authored
215 def self.key_exists?(table,key_columns)
216 result = (key_columns.to_a - ActiveRecord::Base.connection.indexes(table).map { |i| i.columns }.flatten)
217 result.empty?
218 end
219
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
220 def self.simple_migration
221 migration_format = true
222 missing_indexes = check_for_indexes(migration_format)
223
224 unless missing_indexes.keys.empty?
225 add = []
226 remove = []
227 missing_indexes.each do |table_name, keys_to_add|
228 keys_to_add.each do |key|
584ad9d remove existing keys
Michael Deimel authored
229 next if key_exists?(table_name,key)
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
230 next if key.blank?
231 if key.is_a?(Array)
232 keys = key.collect {|k| ":#{k}"}
233 add << "add_index :#{table_name}, [#{keys.join(', ')}]"
234 remove << "remove_index :#{table_name}, :column => [#{keys.join(', ')}]"
235 else
236 add << "add_index :#{table_name}, :#{key}"
237 remove << "remove_index :#{table_name}, :#{key}"
238 end
239
240 end
241 end
242
243 migration = <<EOM
244 class AddMissingIndexes < ActiveRecord::Migration
245 def self.up
246
247 # These indexes were found by searching for AR::Base finds on your application
248 # It is strongly recommanded that you will consult a professional DBA about your infrastucture and implemntation before
249 # changing your database in that matter.
250 # There is a possibility that some of the indexes offered below is not required and can be removed and not added, if you require
251 # further assistance with your rails application, database infrastructure or any other problem, visit:
252 #
253 # http://www.railsmentors.org
254 # http://www.railstutor.org
255 # http://guides.rubyonrails.org
256
257
3c0293e @andrew Ensure all indexes put into migration are unique.
andrew authored
258 #{add.uniq.join("\n ")}
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
259 end
260
261 def self.down
3c0293e @andrew Ensure all indexes put into migration are unique.
andrew authored
262 #{remove.uniq.join("\n ")}
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
263 end
264 end
265 EOM
266
267 puts "## Drop this into a file in db/migrate ##"
268 puts migration
269 end
270 end
271
272 def self.indexes_list
273 check_for_indexes.each do |table_name, keys_to_add|
274 puts "Table '#{table_name}' => #{keys_to_add.to_sentence}"
275 end
276 end
277
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
278 def self.ar_find_indexes(migration_mode=true)
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
279 find_indexes = self.scan_finds
280
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
281 if migration_mode
282 unless find_indexes.keys.empty?
283 add = []
284 remove = []
285 find_indexes.each do |table_name, keys_to_add|
286 keys_to_add.each do |key|
287 next if key_exists?(table_name,key)
288 next if key.blank?
289 if key.is_a?(Array)
290 keys = key.collect {|k| ":#{k}"}
291 add << "add_index :#{table_name}, [#{keys.join(', ')}]"
292 remove << "remove_index :#{table_name}, :column => [#{keys.join(', ')}]"
293 else
294 add << "add_index :#{table_name}, :#{key}"
295 remove << "remove_index :#{table_name}, :#{key}"
296 end
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
297
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
298 end
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
299 end
300
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
301 migration = <<EOM
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
302 class AddFindsMissingIndexes < ActiveRecord::Migration
303 def self.up
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
304
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
305 # These indexes were found by searching for AR::Base finds on your application
306 # It is strongly recommanded that you will consult a professional DBA about your infrastucture and implemntation before
307 # changing your database in that matter.
308 # There is a possibility that some of the indexes offered below is not required and can be removed and not added, if you require
309 # further assistance with your rails application, database infrastructure or any other problem, visit:
310 #
311 # http://www.railsmentors.org
312 # http://www.railstutor.org
313 # http://guides.rubyonrails.org
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
314
3c0293e @andrew Ensure all indexes put into migration are unique.
andrew authored
315 #{add.uniq.join("\n ")}
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
316 end
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
317
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
318 def self.down
3c0293e @andrew Ensure all indexes put into migration are unique.
andrew authored
319 #{remove.uniq.join("\n ")}
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
320 end
321 end
322 EOM
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
323
324 puts "## Drop this into a file in db/migrate ##"
325 puts migration
326 end
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
327 end
6627ee3 @eladmeidar Added more tests, cleaned some code
authored
328 else
329 find_indexes
24c7501 @eladmeidar Offloaded all the login to a ruby module.
authored
330 end
331 end
Something went wrong with that request. Please try again.