annotate 2.7.0 → 2.7.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/AUTHORS.rdoc +1 -0
- data/CHANGELOG.rdoc +3 -0
- data/README.rdoc +9 -5
- data/annotate.gemspec +2 -2
- data/bin/annotate +47 -44
- data/lib/annotate.rb +59 -45
- data/lib/annotate/active_record_patch.rb +1 -1
- data/lib/annotate/annotate_models.rb +212 -156
- data/lib/annotate/annotate_routes.rb +94 -80
- data/lib/annotate/version.rb +1 -1
- data/lib/generators/annotate/install_generator.rb +2 -3
- data/lib/generators/annotate/templates/auto_annotate_models.rake +6 -5
- data/lib/tasks/annotate_models.rake +12 -10
- data/lib/tasks/annotate_routes.rake +1 -0
- metadata +11 -5
@@ -1,13 +1,14 @@
|
|
1
1
|
require 'bigdecimal'
|
2
2
|
|
3
3
|
module AnnotateModels
|
4
|
+
TRUE_RE = /^(true|t|yes|y|1)$/i
|
5
|
+
|
4
6
|
# Annotate Models plugin use this header
|
5
7
|
COMPAT_PREFIX = "== Schema Info"
|
6
8
|
COMPAT_PREFIX_MD = "## Schema Info"
|
7
9
|
PREFIX = "== Schema Information"
|
8
10
|
PREFIX_MD = "## Schema Information"
|
9
11
|
END_MARK = "== Schema Information End"
|
10
|
-
PATTERN = /^\r?\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\r?\n(#.*\r?\n)*(\r?\n)*/
|
11
12
|
|
12
13
|
MATCHED_TYPES = %w(test fixture factory serializer scaffold controller helper)
|
13
14
|
|
@@ -49,83 +50,111 @@ module AnnotateModels
|
|
49
50
|
# Controller files
|
50
51
|
CONTROLLER_DIR = File.join("app", "controllers")
|
51
52
|
|
53
|
+
# Active admin registry files
|
54
|
+
ACTIVEADMIN_DIR = File.join("app", "admin")
|
55
|
+
|
52
56
|
# Helper files
|
53
57
|
HELPER_DIR = File.join("app", "helpers")
|
54
58
|
|
55
59
|
# Don't show limit (#) on these column types
|
56
60
|
# Example: show "integer" instead of "integer(4)"
|
57
|
-
NO_LIMIT_COL_TYPES =
|
61
|
+
NO_LIMIT_COL_TYPES = %w(integer boolean)
|
62
|
+
|
63
|
+
# Don't show default value for these column types
|
64
|
+
NO_DEFAULT_COL_TYPES = %w(json jsonb)
|
58
65
|
|
59
66
|
class << self
|
60
|
-
def
|
61
|
-
|
67
|
+
def annotate_pattern(options = {})
|
68
|
+
if options[:wrapper_open]
|
69
|
+
return /(?:^\n?# (?:#{options[:wrapper_open]}).*\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\n(#.*\n)*\n*)|^\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\n(#.*\n)*\n*/
|
70
|
+
end
|
71
|
+
/^\n?# (?:#{COMPAT_PREFIX}|#{COMPAT_PREFIX_MD}).*?\n(#.*\n)*\n*/
|
62
72
|
end
|
63
73
|
|
64
|
-
def model_dir
|
65
|
-
@model_dir
|
74
|
+
def model_dir
|
75
|
+
@model_dir.is_a?(Array) ? @model_dir : [@model_dir || 'app/models']
|
66
76
|
end
|
67
77
|
|
78
|
+
attr_writer :model_dir
|
79
|
+
|
68
80
|
def root_dir
|
69
|
-
@root_dir.is_a?(Array) ? @root_dir : [@root_dir ||
|
81
|
+
@root_dir.is_a?(Array) ? @root_dir : [@root_dir || '']
|
82
|
+
end
|
83
|
+
|
84
|
+
attr_writer :root_dir
|
85
|
+
|
86
|
+
def test_files(root_directory)
|
87
|
+
[
|
88
|
+
File.join(root_directory, UNIT_TEST_DIR, "%MODEL_NAME%_test.rb"),
|
89
|
+
File.join(root_directory, MODEL_TEST_DIR, "%MODEL_NAME%_test.rb"),
|
90
|
+
File.join(root_directory, SPEC_MODEL_DIR, "%MODEL_NAME%_spec.rb")
|
91
|
+
]
|
92
|
+
end
|
93
|
+
|
94
|
+
def fixture_files(root_directory)
|
95
|
+
[
|
96
|
+
File.join(root_directory, FIXTURE_TEST_DIR, "%TABLE_NAME%.yml"),
|
97
|
+
File.join(root_directory, FIXTURE_SPEC_DIR, "%TABLE_NAME%.yml"),
|
98
|
+
File.join(root_directory, FIXTURE_TEST_DIR, "%PLURALIZED_MODEL_NAME%.yml"),
|
99
|
+
File.join(root_directory, FIXTURE_SPEC_DIR, "%PLURALIZED_MODEL_NAME%.yml")
|
100
|
+
]
|
101
|
+
end
|
102
|
+
|
103
|
+
def scaffold_files(root_directory)
|
104
|
+
[
|
105
|
+
File.join(root_directory, CONTROLLER_TEST_DIR, "%PLURALIZED_MODEL_NAME%_controller_test.rb"),
|
106
|
+
File.join(root_directory, CONTROLLER_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_controller_spec.rb"),
|
107
|
+
File.join(root_directory, REQUEST_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_spec.rb"),
|
108
|
+
File.join(root_directory, ROUTING_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_routing_spec.rb")
|
109
|
+
]
|
70
110
|
end
|
71
111
|
|
72
|
-
def
|
73
|
-
|
112
|
+
def factory_files(root_directory)
|
113
|
+
[
|
114
|
+
File.join(root_directory, EXEMPLARS_TEST_DIR, "%MODEL_NAME%_exemplar.rb"),
|
115
|
+
File.join(root_directory, EXEMPLARS_SPEC_DIR, "%MODEL_NAME%_exemplar.rb"),
|
116
|
+
File.join(root_directory, BLUEPRINTS_TEST_DIR, "%MODEL_NAME%_blueprint.rb"),
|
117
|
+
File.join(root_directory, BLUEPRINTS_SPEC_DIR, "%MODEL_NAME%_blueprint.rb"),
|
118
|
+
File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
|
119
|
+
File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
|
120
|
+
File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%TABLE_NAME%.rb"), # (new style)
|
121
|
+
File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%TABLE_NAME%.rb"), # (new style)
|
122
|
+
File.join(root_directory, FABRICATORS_TEST_DIR, "%MODEL_NAME%_fabricator.rb"),
|
123
|
+
File.join(root_directory, FABRICATORS_SPEC_DIR, "%MODEL_NAME%_fabricator.rb")
|
124
|
+
]
|
74
125
|
end
|
75
126
|
|
76
|
-
def
|
127
|
+
def serialize_files(root_directory)
|
128
|
+
[
|
129
|
+
File.join(root_directory, SERIALIZERS_DIR, "%MODEL_NAME%_serializer.rb"),
|
130
|
+
File.join(root_directory, SERIALIZERS_TEST_DIR, "%MODEL_NAME%_serializer_spec.rb"),
|
131
|
+
File.join(root_directory, SERIALIZERS_SPEC_DIR, "%MODEL_NAME%_serializer_spec.rb")
|
132
|
+
]
|
133
|
+
end
|
134
|
+
|
135
|
+
def files_by_pattern(root_directory, pattern_type)
|
136
|
+
case pattern_type
|
137
|
+
when 'test' then test_files(root_directory)
|
138
|
+
when 'fixture' then fixture_files(root_directory)
|
139
|
+
when 'scaffold' then scaffold_files(root_directory)
|
140
|
+
when 'factory' then factory_files(root_directory)
|
141
|
+
when 'serializer' then serialize_files(root_directory)
|
142
|
+
when 'controller'
|
143
|
+
[File.join(root_directory, CONTROLLER_DIR, "%PLURALIZED_MODEL_NAME%_controller.rb")]
|
144
|
+
when 'admin'
|
145
|
+
[File.join(root_directory, ACTIVEADMIN_DIR, "%MODEL_NAME%.rb")]
|
146
|
+
when 'helper'
|
147
|
+
[File.join(root_directory, HELPER_DIR, "%PLURALIZED_MODEL_NAME%_helper.rb")]
|
148
|
+
else
|
149
|
+
[]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def get_patterns(pattern_types=[])
|
77
154
|
current_patterns = []
|
78
155
|
root_dir.each do |root_directory|
|
79
156
|
Array(pattern_types).each do |pattern_type|
|
80
|
-
current_patterns +=
|
81
|
-
when 'test'
|
82
|
-
[
|
83
|
-
File.join(root_directory, UNIT_TEST_DIR, "%MODEL_NAME%_test.rb"),
|
84
|
-
File.join(root_directory, MODEL_TEST_DIR, "%MODEL_NAME%_test.rb"),
|
85
|
-
File.join(root_directory, SPEC_MODEL_DIR, "%MODEL_NAME%_spec.rb"),
|
86
|
-
]
|
87
|
-
when 'fixture'
|
88
|
-
[
|
89
|
-
File.join(root_directory, FIXTURE_TEST_DIR, "%TABLE_NAME%.yml"),
|
90
|
-
File.join(root_directory, FIXTURE_SPEC_DIR, "%TABLE_NAME%.yml"),
|
91
|
-
File.join(root_directory, FIXTURE_TEST_DIR, "%PLURALIZED_MODEL_NAME%.yml"),
|
92
|
-
File.join(root_directory, FIXTURE_SPEC_DIR, "%PLURALIZED_MODEL_NAME%.yml"),
|
93
|
-
]
|
94
|
-
when 'scaffold'
|
95
|
-
[
|
96
|
-
File.join(root_directory, CONTROLLER_TEST_DIR, "%PLURALIZED_MODEL_NAME%_controller_test.rb"),
|
97
|
-
File.join(root_directory, CONTROLLER_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_controller_spec.rb"),
|
98
|
-
File.join(root_directory, REQUEST_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_spec.rb"),
|
99
|
-
File.join(root_directory, ROUTING_SPEC_DIR, "%PLURALIZED_MODEL_NAME%_routing_spec.rb"),
|
100
|
-
]
|
101
|
-
when 'factory'
|
102
|
-
[
|
103
|
-
File.join(root_directory, EXEMPLARS_TEST_DIR, "%MODEL_NAME%_exemplar.rb"),
|
104
|
-
File.join(root_directory, EXEMPLARS_SPEC_DIR, "%MODEL_NAME%_exemplar.rb"),
|
105
|
-
File.join(root_directory, BLUEPRINTS_TEST_DIR, "%MODEL_NAME%_blueprint.rb"),
|
106
|
-
File.join(root_directory, BLUEPRINTS_SPEC_DIR, "%MODEL_NAME%_blueprint.rb"),
|
107
|
-
File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
|
108
|
-
File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%MODEL_NAME%_factory.rb"), # (old style)
|
109
|
-
File.join(root_directory, FACTORY_GIRL_TEST_DIR, "%TABLE_NAME%.rb"), # (new style)
|
110
|
-
File.join(root_directory, FACTORY_GIRL_SPEC_DIR, "%TABLE_NAME%.rb"), # (new style)
|
111
|
-
File.join(root_directory, FABRICATORS_TEST_DIR, "%MODEL_NAME%_fabricator.rb"),
|
112
|
-
File.join(root_directory, FABRICATORS_SPEC_DIR, "%MODEL_NAME%_fabricator.rb"),
|
113
|
-
]
|
114
|
-
when 'serializer'
|
115
|
-
[
|
116
|
-
File.join(root_directory, SERIALIZERS_DIR, "%MODEL_NAME%_serializer.rb"),
|
117
|
-
File.join(root_directory, SERIALIZERS_TEST_DIR, "%MODEL_NAME%_serializer_spec.rb"),
|
118
|
-
File.join(root_directory, SERIALIZERS_SPEC_DIR, "%MODEL_NAME%_serializer_spec.rb")
|
119
|
-
]
|
120
|
-
when 'controller'
|
121
|
-
[
|
122
|
-
File.join(root_directory, CONTROLLER_DIR, "%PLURALIZED_MODEL_NAME%_controller.rb")
|
123
|
-
]
|
124
|
-
when 'helper'
|
125
|
-
[
|
126
|
-
File.join(root_directory, HELPER_DIR, "%PLURALIZED_MODEL_NAME%_helper.rb")
|
127
|
-
]
|
128
|
-
end
|
157
|
+
current_patterns += files_by_pattern(root_directory, pattern_type)
|
129
158
|
end
|
130
159
|
end
|
131
160
|
current_patterns.map{ |p| p.sub(/^[\/]*/, '') }
|
@@ -134,9 +163,9 @@ module AnnotateModels
|
|
134
163
|
# Simple quoting for the default column value
|
135
164
|
def quote(value)
|
136
165
|
case value
|
137
|
-
when NilClass then
|
138
|
-
when TrueClass then
|
139
|
-
when FalseClass then
|
166
|
+
when NilClass then 'NULL'
|
167
|
+
when TrueClass then 'TRUE'
|
168
|
+
when FalseClass then 'FALSE'
|
140
169
|
when Float, Fixnum, Bignum then value.to_s
|
141
170
|
# BigDecimals need to be output in a non-normalized form and quoted.
|
142
171
|
when BigDecimal then value.to_s('F')
|
@@ -157,7 +186,7 @@ module AnnotateModels
|
|
157
186
|
def get_schema_info(klass, header, options = {})
|
158
187
|
info = "# #{header}\n"
|
159
188
|
info<< "#\n"
|
160
|
-
if
|
189
|
+
if options[:format_markdown]
|
161
190
|
info<< "# Table name: `#{klass.table_name}`\n"
|
162
191
|
info<< "#\n"
|
163
192
|
info<< "# ### Columns\n"
|
@@ -166,13 +195,13 @@ module AnnotateModels
|
|
166
195
|
end
|
167
196
|
info<< "#\n"
|
168
197
|
|
169
|
-
max_size = klass.column_names.map
|
198
|
+
max_size = klass.column_names.map(&:size).max || 0
|
170
199
|
max_size += options[:format_rdoc] ? 5 : 1
|
171
200
|
md_names_overhead = 6
|
172
201
|
md_type_allowance = 18
|
173
202
|
bare_type_allowance = 16
|
174
203
|
|
175
|
-
if
|
204
|
+
if options[:format_markdown]
|
176
205
|
info<< sprintf( "# %-#{max_size + md_names_overhead}.#{max_size + md_names_overhead}s | %-#{md_type_allowance}.#{md_type_allowance}s | %s\n", 'Name', 'Type', 'Attributes' )
|
177
206
|
info<< "# #{ '-' * ( max_size + md_names_overhead ) } | #{'-' * md_type_allowance} | #{ '-' * 27 }\n"
|
178
207
|
end
|
@@ -185,20 +214,20 @@ module AnnotateModels
|
|
185
214
|
klass.columns
|
186
215
|
end
|
187
216
|
|
188
|
-
cols = cols.sort_by(&:name) if
|
189
|
-
cols = classified_sort(cols) if
|
217
|
+
cols = cols.sort_by(&:name) if options[:sort]
|
218
|
+
cols = classified_sort(cols) if options[:classified_sort]
|
190
219
|
cols.each do |col|
|
191
220
|
col_type = (col.type || col.sql_type).to_s
|
192
221
|
|
193
222
|
attrs = []
|
194
|
-
attrs << "default(#{schema_default(klass, col)})" unless col.default.nil? || col_type
|
195
|
-
attrs <<
|
196
|
-
attrs <<
|
223
|
+
attrs << "default(#{schema_default(klass, col)})" unless col.default.nil? || NO_DEFAULT_COL_TYPES.include?(col_type)
|
224
|
+
attrs << 'not null' unless col.null
|
225
|
+
attrs << 'primary key' if klass.primary_key && (klass.primary_key.is_a?(Array) ? klass.primary_key.collect(&:to_sym).include?(col.name.to_sym) : col.name.to_sym == klass.primary_key.to_sym)
|
197
226
|
|
198
|
-
if col_type ==
|
227
|
+
if col_type == 'decimal'
|
199
228
|
col_type << "(#{col.precision}, #{col.scale})"
|
200
|
-
elsif col_type !=
|
201
|
-
if
|
229
|
+
elsif col_type != 'spatial'
|
230
|
+
if col.limit
|
202
231
|
if col.limit.is_a? Array
|
203
232
|
attrs << "(#{col.limit.join(', ')})"
|
204
233
|
else
|
@@ -208,15 +237,13 @@ module AnnotateModels
|
|
208
237
|
end
|
209
238
|
|
210
239
|
# Check out if we got an array column
|
211
|
-
if col.respond_to?(:array) && col.array
|
212
|
-
attrs << "is an Array"
|
213
|
-
end
|
240
|
+
attrs << 'is an Array' if col.respond_to?(:array) && col.array
|
214
241
|
|
215
242
|
# Check out if we got a geometric column
|
216
243
|
# and print the type and SRID
|
217
244
|
if col.respond_to?(:geometry_type)
|
218
245
|
attrs << "#{col.geometry_type}, #{col.srid}"
|
219
|
-
elsif col.respond_to?(:geometric_type)
|
246
|
+
elsif col.respond_to?(:geometric_type) && col.geometric_type.present?
|
220
247
|
attrs << "#{col.geometric_type.to_s.downcase}, #{col.srid}"
|
221
248
|
end
|
222
249
|
|
@@ -225,9 +252,9 @@ module AnnotateModels
|
|
225
252
|
if options[:simple_indexes] && klass.table_exists?# Check out if this column is indexed
|
226
253
|
indices = klass.connection.indexes(klass.table_name)
|
227
254
|
if indices = indices.select { |ind| ind.columns.include? col.name }
|
228
|
-
indices.sort_by
|
255
|
+
indices.sort_by(&:name).each do |ind|
|
229
256
|
ind = ind.columns.reject! { |i| i == col.name }
|
230
|
-
attrs << (ind.
|
257
|
+
attrs << (ind.empty? ? "indexed" : "indexed => [#{ind.join(", ")}]")
|
231
258
|
end
|
232
259
|
end
|
233
260
|
end
|
@@ -261,28 +288,29 @@ module AnnotateModels
|
|
261
288
|
end
|
262
289
|
|
263
290
|
def get_index_info(klass, options={})
|
264
|
-
if
|
291
|
+
if options[:format_markdown]
|
265
292
|
index_info = "#\n# ### Indexes\n#\n"
|
266
293
|
else
|
267
294
|
index_info = "#\n# Indexes\n#\n"
|
268
295
|
end
|
269
296
|
|
270
297
|
indexes = klass.connection.indexes(klass.table_name)
|
271
|
-
return
|
298
|
+
return '' if indexes.empty?
|
272
299
|
|
273
300
|
max_size = indexes.collect{|index| index.name.size}.max + 1
|
274
|
-
indexes.sort_by
|
275
|
-
if
|
301
|
+
indexes.sort_by(&:name).each do |index|
|
302
|
+
if options[:format_markdown]
|
276
303
|
index_info << sprintf("# * `%s`%s:\n# * **`%s`**\n", index.name, index.unique ? " (_unique_)" : "", index.columns.join("`**\n# * **`"))
|
277
304
|
else
|
278
305
|
index_info << sprintf("# %-#{max_size}.#{max_size}s %s %s", index.name, "(#{index.columns.join(",")})", index.unique ? "UNIQUE" : "").rstrip + "\n"
|
279
306
|
end
|
280
307
|
end
|
281
|
-
|
308
|
+
|
309
|
+
index_info
|
282
310
|
end
|
283
311
|
|
284
312
|
def hide_limit?(col_type, options)
|
285
|
-
excludes =
|
313
|
+
excludes =
|
286
314
|
if options[:hide_limit_column_types].blank?
|
287
315
|
NO_LIMIT_COL_TYPES
|
288
316
|
else
|
@@ -293,34 +321,42 @@ module AnnotateModels
|
|
293
321
|
end
|
294
322
|
|
295
323
|
def get_foreign_key_info(klass, options={})
|
296
|
-
if
|
324
|
+
if options[:format_markdown]
|
297
325
|
fk_info = "#\n# ### Foreign Keys\n#\n"
|
298
326
|
else
|
299
327
|
fk_info = "#\n# Foreign Keys\n#\n"
|
300
328
|
end
|
301
329
|
|
302
|
-
return
|
330
|
+
return '' unless klass.connection.respond_to?(:supports_foreign_keys?) &&
|
331
|
+
klass.connection.supports_foreign_keys? && klass.connection.respond_to?(:foreign_keys)
|
303
332
|
|
304
333
|
foreign_keys = klass.connection.foreign_keys(klass.table_name)
|
305
|
-
return
|
334
|
+
return '' if foreign_keys.empty?
|
306
335
|
|
307
336
|
max_size = foreign_keys.collect{|fk| fk.name.size}.max + 1
|
308
|
-
foreign_keys.sort_by
|
337
|
+
foreign_keys.sort_by(&:name).each do |fk|
|
309
338
|
ref_info = "#{fk.column} => #{fk.to_table}.#{fk.primary_key}"
|
310
|
-
|
311
|
-
|
339
|
+
constraints_info = ''
|
340
|
+
constraints_info += "ON DELETE => #{fk.on_delete} " if fk.on_delete
|
341
|
+
constraints_info += "ON UPDATE => #{fk.on_update} " if fk.on_update
|
342
|
+
constraints_info.strip!
|
343
|
+
if options[:format_markdown]
|
344
|
+
fk_info << sprintf("# * `%s`%s:\n# * **`%s`**\n", fk.name, constraints_info.blank? ? '' : " (_#{constraints_info}_)", ref_info)
|
312
345
|
else
|
313
|
-
fk_info << sprintf("# %-#{max_size}.#{max_size}s %s", fk.name, "(#{ref_info})").rstrip + "\n"
|
346
|
+
fk_info << sprintf("# %-#{max_size}.#{max_size}s %s %s", fk.name, "(#{ref_info})", constraints_info).rstrip + "\n"
|
314
347
|
end
|
315
348
|
end
|
316
|
-
|
349
|
+
|
350
|
+
fk_info
|
317
351
|
end
|
318
352
|
|
319
353
|
# Add a schema block to a file. If the file already contains
|
320
354
|
# a schema info block (a comment starting with "== Schema Information"), check if it
|
321
355
|
# matches the block that is already there. If so, leave it be. If not, remove the old
|
322
356
|
# info block and write a new one.
|
323
|
-
#
|
357
|
+
#
|
358
|
+
# == Returns:
|
359
|
+
# true or false depending on whether the file was modified.
|
324
360
|
#
|
325
361
|
# === Options (opts)
|
326
362
|
# :force<Symbol>:: whether to update the file even if it doesn't seem to need it.
|
@@ -330,7 +366,7 @@ module AnnotateModels
|
|
330
366
|
def annotate_one_file(file_name, info_block, position, options={})
|
331
367
|
if File.exist?(file_name)
|
332
368
|
old_content = File.read(file_name)
|
333
|
-
return false if
|
369
|
+
return false if old_content =~ /# -\*- SkipSchemaAnnotations.*\n/
|
334
370
|
|
335
371
|
# Ignore the Schema version line because it changes with each migration
|
336
372
|
header_pattern = /(^# Table name:.*?\n(#.*[\r]?\n)*[\r]?)/
|
@@ -348,10 +384,10 @@ module AnnotateModels
|
|
348
384
|
return false
|
349
385
|
else
|
350
386
|
# Replace inline the old schema info with the new schema info
|
351
|
-
new_content = old_content.sub(
|
387
|
+
new_content = old_content.sub(annotate_pattern(options), info_block + "\n")
|
352
388
|
|
353
389
|
if new_content.end_with?(info_block + "\n")
|
354
|
-
new_content = old_content.sub(
|
390
|
+
new_content = old_content.sub(annotate_pattern(options), "\n" + info_block)
|
355
391
|
end
|
356
392
|
|
357
393
|
wrapper_open = options[:wrapper_open] ? "# #{options[:wrapper_open]}\n" : ""
|
@@ -361,41 +397,48 @@ module AnnotateModels
|
|
361
397
|
# we simply need to insert it in correct position
|
362
398
|
if new_content == old_content || options[:force]
|
363
399
|
old_content.sub!(magic_comment_matcher, '')
|
364
|
-
old_content.sub!(
|
400
|
+
old_content.sub!(annotate_pattern(options), '')
|
365
401
|
|
366
|
-
|
367
|
-
|
368
|
-
|
402
|
+
if %w(after bottom).include?(options[position].to_s)
|
403
|
+
new_content = magic_comments.join + (old_content.rstrip + "\n\n" + wrapped_info_block)
|
404
|
+
else
|
405
|
+
new_content = magic_comments.join + wrapped_info_block + "\n" + old_content
|
406
|
+
end
|
369
407
|
end
|
370
408
|
|
371
|
-
File.open(file_name,
|
409
|
+
File.open(file_name, 'wb') { |f| f.puts new_content }
|
372
410
|
return true
|
373
411
|
end
|
374
412
|
else
|
375
|
-
|
413
|
+
false
|
376
414
|
end
|
377
415
|
end
|
378
416
|
|
379
|
-
def remove_annotation_of_file(file_name)
|
417
|
+
def remove_annotation_of_file(file_name, options={})
|
380
418
|
if File.exist?(file_name)
|
381
419
|
content = File.read(file_name)
|
420
|
+
wrapper_open = options[:wrapper_open] ? "# #{options[:wrapper_open]}\n" : ""
|
421
|
+
content.sub!(/(#{wrapper_open})?#{annotate_pattern(options)}/, '')
|
382
422
|
|
383
|
-
|
423
|
+
File.open(file_name, 'wb') { |f| f.puts content }
|
384
424
|
|
385
|
-
|
386
|
-
|
387
|
-
return true
|
425
|
+
true
|
388
426
|
else
|
389
|
-
|
427
|
+
false
|
390
428
|
end
|
391
429
|
end
|
392
430
|
|
431
|
+
def matched_types(options)
|
432
|
+
types = MATCHED_TYPES
|
433
|
+
types << 'admin' if options[:active_admin] =~ TRUE_RE && !types.include?('admin')
|
434
|
+
|
435
|
+
types
|
436
|
+
end
|
437
|
+
|
393
438
|
# Given the name of an ActiveRecord class, create a schema
|
394
439
|
# info block (basically a comment containing information
|
395
440
|
# on the columns and their types) and put it at the front
|
396
441
|
# of the model and fixture source files.
|
397
|
-
# Returns true or false depending on whether the source
|
398
|
-
# files were modified.
|
399
442
|
#
|
400
443
|
# === Options (opts)
|
401
444
|
# :position_in_class<Symbol>:: where to place the annotated section in model file
|
@@ -411,40 +454,53 @@ module AnnotateModels
|
|
411
454
|
# :exclude_controllers<Symbol>:: whether to skip modification of controller files
|
412
455
|
# :exclude_helpers<Symbol>:: whether to skip modification of helper files
|
413
456
|
#
|
457
|
+
# == Returns:
|
458
|
+
# an array of file names that were annotated.
|
459
|
+
#
|
414
460
|
def annotate(klass, file, header, options={})
|
415
461
|
begin
|
462
|
+
klass.reset_column_information
|
416
463
|
info = get_schema_info(klass, header, options)
|
417
|
-
did_annotate = false
|
418
464
|
model_name = klass.name.underscore
|
419
465
|
table_name = klass.table_name
|
420
466
|
model_file_name = File.join(file)
|
467
|
+
annotated = []
|
421
468
|
|
422
469
|
if annotate_one_file(model_file_name, info, :position_in_class, options_with_position(options, :position_in_class))
|
423
|
-
|
470
|
+
annotated << model_file_name
|
424
471
|
end
|
425
472
|
|
426
|
-
|
473
|
+
matched_types(options).each do |key|
|
427
474
|
exclusion_key = "exclude_#{key.pluralize}".to_sym
|
428
475
|
position_key = "position_in_#{key}".to_sym
|
429
476
|
|
477
|
+
# Same options for active_admin models
|
478
|
+
if key == 'admin'
|
479
|
+
exclusion_key = 'exclude_class'.to_sym
|
480
|
+
position_key = 'position_in_class'.to_sym
|
481
|
+
end
|
482
|
+
|
430
483
|
unless options[exclusion_key]
|
431
|
-
|
484
|
+
self.get_patterns(key).
|
432
485
|
map { |f| resolve_filename(f, model_name, table_name) }.
|
433
|
-
|
434
|
-
|
486
|
+
each { |f|
|
487
|
+
if annotate_one_file(f, info, position_key, options_with_position(options, position_key))
|
488
|
+
annotated << f
|
489
|
+
end
|
490
|
+
}
|
435
491
|
end
|
436
492
|
end
|
437
|
-
|
438
|
-
return did_annotate
|
439
493
|
rescue Exception => e
|
440
494
|
puts "Unable to annotate #{file}: #{e.message}"
|
441
495
|
puts "\t" + e.backtrace.join("\n\t") if options[:trace]
|
442
496
|
end
|
497
|
+
|
498
|
+
annotated
|
443
499
|
end
|
444
500
|
|
445
501
|
# position = :position_in_fixture or :position_in_class
|
446
502
|
def options_with_position(options, position_in)
|
447
|
-
options.merge(:
|
503
|
+
options.merge(position: (options[position_in] || options[:position]))
|
448
504
|
end
|
449
505
|
|
450
506
|
# Return a list of the model files to annotate.
|
@@ -453,7 +509,7 @@ module AnnotateModels
|
|
453
509
|
# in the model_dir directory.
|
454
510
|
def get_model_files(options)
|
455
511
|
models = []
|
456
|
-
if
|
512
|
+
if !options[:is_rake]
|
457
513
|
models = ARGV.dup.reject{|m| m.match(/^(.*)=/)}
|
458
514
|
end
|
459
515
|
|
@@ -488,13 +544,13 @@ module AnnotateModels
|
|
488
544
|
model_path = file.gsub(/\.rb$/, '')
|
489
545
|
model_dir.each { |dir| model_path = model_path.gsub(/^#{dir}/, '').gsub(/^\//, '') }
|
490
546
|
begin
|
491
|
-
get_loaded_model(model_path)
|
547
|
+
get_loaded_model(model_path) || raise(BadModelFileError.new)
|
492
548
|
rescue LoadError
|
493
549
|
# this is for non-rails projects, which don't get Rails auto-require magic
|
494
550
|
file_path = File.expand_path(file)
|
495
551
|
if File.file?(file_path) && silence_warnings { Kernel.require(file_path) }
|
496
552
|
retry
|
497
|
-
elsif model_path
|
553
|
+
elsif model_path =~ /\//
|
498
554
|
model_path = model_path.split('/')[1..-1].join('/').to_s
|
499
555
|
retry
|
500
556
|
else
|
@@ -511,37 +567,39 @@ module AnnotateModels
|
|
511
567
|
# Revert to the old way but it is not really robust
|
512
568
|
ObjectSpace.each_object(::Class).
|
513
569
|
select do |c|
|
514
|
-
Class === c
|
515
|
-
c.ancestors.respond_to?(:include?)
|
570
|
+
Class === c && # note: we use === to avoid a bug in activesupport 2.3.14 OptionMerger vs. is_a?
|
571
|
+
c.ancestors.respond_to?(:include?) && # to fix FactoryGirl bug, see https://github.com/ctran/annotate_models/pull/82
|
516
572
|
c.ancestors.include?(ActiveRecord::Base)
|
517
573
|
end.
|
518
574
|
detect { |c| ActiveSupport::Inflector.underscore(c.to_s) == model_path }
|
519
575
|
end
|
520
576
|
end
|
521
577
|
|
578
|
+
def parse_options(options={})
|
579
|
+
self.model_dir = options[:model_dir] if options[:model_dir]
|
580
|
+
self.root_dir = options[:root_dir] if options[:root_dir]
|
581
|
+
end
|
582
|
+
|
522
583
|
# We're passed a name of things that might be
|
523
584
|
# ActiveRecord models. If we can find the class, and
|
524
585
|
# if its a subclass of ActiveRecord::Base,
|
525
586
|
# then pass it to the associated block
|
526
587
|
def do_annotations(options={})
|
527
|
-
|
588
|
+
parse_options(options)
|
528
589
|
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
end
|
590
|
+
header = options[:format_markdown] ? PREFIX_MD.dup : PREFIX.dup
|
591
|
+
version = ActiveRecord::Migrator.current_version rescue 0
|
592
|
+
if options[:include_version] && version > 0
|
593
|
+
header << "\n# Schema version: #{version}"
|
534
594
|
end
|
535
595
|
|
536
|
-
self.model_dir = options[:model_dir] if options[:model_dir]
|
537
|
-
self.root_dir = options[:root_dir] if options[:root_dir]
|
538
|
-
|
539
596
|
annotated = []
|
540
|
-
get_model_files(options).each do |
|
541
|
-
annotate_model_file(annotated, File.join(
|
597
|
+
get_model_files(options).each do |path, filename|
|
598
|
+
annotate_model_file(annotated, File.join(path, filename), header, options)
|
542
599
|
end
|
600
|
+
|
543
601
|
if annotated.empty?
|
544
|
-
puts
|
602
|
+
puts 'Model files unchanged.'
|
545
603
|
else
|
546
604
|
puts "Annotated (#{annotated.length}): #{annotated.join(', ')}"
|
547
605
|
end
|
@@ -549,12 +607,10 @@ module AnnotateModels
|
|
549
607
|
|
550
608
|
def annotate_model_file(annotated, file, header, options)
|
551
609
|
begin
|
552
|
-
return false if
|
610
|
+
return false if /# -\*- SkipSchemaAnnotations.*/ =~ (File.exist?(file) ? File.read(file) : '')
|
553
611
|
klass = get_model_class(file)
|
554
612
|
if klass && klass < ActiveRecord::Base && !klass.abstract_class? && klass.table_exists?
|
555
|
-
|
556
|
-
annotated << file
|
557
|
-
end
|
613
|
+
annotated.concat(annotate(klass, file, header, options))
|
558
614
|
end
|
559
615
|
rescue BadModelFileError => e
|
560
616
|
unless options[:ignore_unknown_models]
|
@@ -568,8 +624,8 @@ module AnnotateModels
|
|
568
624
|
end
|
569
625
|
|
570
626
|
def remove_annotations(options={})
|
571
|
-
|
572
|
-
|
627
|
+
parse_options(options)
|
628
|
+
|
573
629
|
deannotated = []
|
574
630
|
deannotated_klass = false
|
575
631
|
get_model_files(options).each do |file|
|
@@ -580,18 +636,18 @@ module AnnotateModels
|
|
580
636
|
model_name = klass.name.underscore
|
581
637
|
table_name = klass.table_name
|
582
638
|
model_file_name = file
|
583
|
-
deannotated_klass = true if
|
639
|
+
deannotated_klass = true if remove_annotation_of_file(model_file_name, options)
|
584
640
|
|
585
|
-
get_patterns.
|
641
|
+
get_patterns(matched_types(options)).
|
586
642
|
map { |f| resolve_filename(f, model_name, table_name) }.
|
587
643
|
each do |f|
|
588
644
|
if File.exist?(f)
|
589
|
-
remove_annotation_of_file(f)
|
645
|
+
remove_annotation_of_file(f, options)
|
590
646
|
deannotated_klass = true
|
591
647
|
end
|
592
648
|
end
|
593
649
|
end
|
594
|
-
deannotated << klass if
|
650
|
+
deannotated << klass if deannotated_klass
|
595
651
|
rescue Exception => e
|
596
652
|
puts "Unable to deannotate #{File.join(file)}: #{e.message}"
|
597
653
|
puts "\t" + e.backtrace.join("\n\t") if options[:trace]
|
@@ -601,10 +657,10 @@ module AnnotateModels
|
|
601
657
|
end
|
602
658
|
|
603
659
|
def resolve_filename(filename_template, model_name, table_name)
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
660
|
+
filename_template.
|
661
|
+
gsub('%MODEL_NAME%', model_name).
|
662
|
+
gsub('%PLURALIZED_MODEL_NAME%', model_name.pluralize).
|
663
|
+
gsub('%TABLE_NAME%', table_name || model_name.pluralize)
|
608
664
|
end
|
609
665
|
|
610
666
|
def classified_sort(cols)
|
@@ -613,12 +669,12 @@ module AnnotateModels
|
|
613
669
|
associations = []
|
614
670
|
id = nil
|
615
671
|
|
616
|
-
cols
|
617
|
-
if c.name.eql?(
|
672
|
+
cols.each do |c|
|
673
|
+
if c.name.eql?('id')
|
618
674
|
id = c
|
619
|
-
elsif
|
675
|
+
elsif c.name.eql?('created_at') || c.name.eql?('updated_at')
|
620
676
|
timestamps << c
|
621
|
-
elsif c.name[-3,3].eql?(
|
677
|
+
elsif c.name[-3,3].eql?('_id')
|
622
678
|
associations << c
|
623
679
|
else
|
624
680
|
rest_cols << c
|
@@ -626,7 +682,7 @@ module AnnotateModels
|
|
626
682
|
end
|
627
683
|
[rest_cols, timestamps, associations].each {|a| a.sort_by!(&:name) }
|
628
684
|
|
629
|
-
return ([id] << rest_cols << timestamps << associations).flatten
|
685
|
+
return ([id] << rest_cols << timestamps << associations).flatten.compact
|
630
686
|
end
|
631
687
|
|
632
688
|
# Ignore warnings for the duration of the block ()
|