annotate 2.7.0 → 2.7.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.
- 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 ()
|