rails-annotate-solargraph 0.2.2 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d62415c07dd7235a4151593d2def72a55a7f8b7f7785f71eb66fe6996ce657d
4
- data.tar.gz: a4737b994cb8ece0516da3d2e57ba66e05a5243cfc29ad1149f99bb03e1f8d1b
3
+ metadata.gz: 80d2fcb8fc2090d6a1eabc8f510b3b9fec1608da1870c7d88b301ce64907920a
4
+ data.tar.gz: 9553090d359f8f829e955349cac6c527c43335433e38d91152862780cce3ab63
5
5
  SHA512:
6
- metadata.gz: b67bd482cf97936d6b55a9c28b5845a45a53650683cc05a89823427573aee2d66ac9500147b53c8b18ab74c6883f3f3306db864341aa03344ca5c376a6173892
7
- data.tar.gz: 96d4d8aff31ee3a530635959539483572c268164c0214701e7b286cbcde37069419752ea3fd5b0bf466d5480dcdb4637f97952f764b932ea2824b8a14ec025c5
6
+ metadata.gz: 8b2415d929d939454a4f65277d85baa03935219226c6ad5b35c35264cd2de94bb1b1d55a55a55ee8cc737523210e3653c648bea50798dd53b580767fbb6a2eec
7
+ data.tar.gz: 9436e870a6cbc28e636151b76717cde5bf4186f44bc8af4293ada785f1c50e039895fcf5cb38dd27868c5e88027df9f52be8ee070591e0635de30fdba7437aef
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "cSpell.words": [
3
3
  "activerecord",
4
+ "klass",
4
5
  "rakefile",
5
6
  "solargraph",
6
7
  "yieldparam"
data/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.0] - 2022-04-17
4
+
5
+ - Annotations get saved to a schema file by default `app/models/annotate_solargraph_schema.rb`
6
+
7
+ ## [0.3.0] - 2022-04-17
8
+
9
+ - `has_many :through` and `has_one :through` relations get documented
10
+
11
+ ## [0.2.3] - 2022-04-16
12
+
13
+ - A nicer fix for the previous problem
14
+
3
15
  ## [0.2.2] - 2022-04-16
4
16
 
5
17
  - Inexistent class loading error has been resolved
@@ -11,6 +23,7 @@
11
23
  ## [0.2.0] - 2022-04-16
12
24
 
13
25
  - Associations get fully documented
26
+ - `has_many`, `has_one` and `belongs_to` relations get documented
14
27
 
15
28
  ## [0.1.1] - 2022-04-15
16
29
 
@@ -20,4 +33,5 @@
20
33
 
21
34
  - Initial release
22
35
  - Automatic generation of annotations after migrations
36
+ - Database fields get documented
23
37
  - Manual rake tasks `annotate:solargraph:generate`, `annotate:solargraph:remove`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails-annotate-solargraph (0.2.2)
4
+ rails-annotate-solargraph (0.4.0)
5
5
  rails (>= 5.0, < 8.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -63,11 +63,31 @@ You can change the gem's default configuration like so:
63
63
  ```ruby
64
64
  # config/initializers/rails_annotate_solargraph.rb
65
65
 
66
- Rails::Annotate::Solargraph.configure do |conf|
67
- conf.annotation_position = :top # `:bottom` by default
66
+ if ::Rails.env.development?
67
+ ::Rails::Annotate::Solargraph.configure do |conf|
68
+ conf.annotation_position = :top # `:schema_file` by default
69
+ end
68
70
  end
69
71
  ```
70
72
 
73
+ #### annotation_position
74
+
75
+ There are a few values for this option:
76
+
77
+ - `:schema_file` -- default value, annotations get saved to a special file `app/models/annotate_solargraph_schema.rb`
78
+ - `:bottom` -- annotations are appended to the model files
79
+ - `:top` -- annotations are prepended to the model files
80
+
81
+ ### Update
82
+
83
+ To update this gem you should generate the rakefiles once again. Overwrite them.
84
+
85
+ ```sh
86
+ $ rails g annotate:solargraph:install
87
+ $ rake annotate:solargraph:remove
88
+ $ rake annotate:solargraph:generate
89
+ ```
90
+
71
91
  ## Development
72
92
 
73
93
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -9,10 +9,10 @@ module Rails
9
9
  # @return [Symbol]
10
10
  attr_reader :annotation_position
11
11
 
12
- ANNOTATION_POSITIONS = ::Set[:bottom, :top].freeze
12
+ ANNOTATION_POSITIONS = ::Set[:bottom, :top, :schema_file].freeze
13
13
 
14
14
  def initialize
15
- @annotation_position = :bottom
15
+ @annotation_position = :schema_file
16
16
  end
17
17
 
18
18
  # @param val [Symbol]
@@ -22,6 +22,10 @@ module Rails
22
22
 
23
23
  @annotation_position = val
24
24
  end
25
+
26
+ def schema_file?
27
+ @annotation_position == :schema_file
28
+ end
25
29
  end
26
30
  end
27
31
  end
@@ -1,32 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'fileutils'
4
+
3
5
  module Rails
4
6
  module Annotate
5
7
  module Solargraph
6
8
  class Model
7
9
  using TerminalColors::Refinement
8
10
 
9
- # @return [String]
10
- ANNOTATION_START = "\n# %%<RailsAnnotateSolargraph:Start>%%"
11
- # @return [String]
12
- ANNOTATION_END = "%%<RailsAnnotateSolargraph:End>%%\n\n"
13
- # @return [Regexp]
14
- ANNOTATION_REGEXP = /#{ANNOTATION_START}.*#{ANNOTATION_END}/m.freeze
15
11
  # @return [Regexp]
16
12
  MAGIC_COMMENT_REGEXP = /(^#\s*encoding:.*(?:\n|r\n))|(^# coding:.*(?:\n|\r\n))|(^# -\*- coding:.*(?:\n|\r\n))|(^# -\*- encoding\s?:.*(?:\n|\r\n))|(^#\s*frozen_string_literal:.+(?:\n|\r\n))|(^# -\*- frozen_string_literal\s*:.+-\*-(?:\n|\r\n))/.freeze
17
13
 
18
- class << (FAKE_MODEL_CLASS = Object.new)
19
- def to_s
20
- ::Object.to_s
21
- end
22
-
23
- alias name to_s
24
-
25
- def table_name
26
- '<unknown>'
27
- end
28
- end
29
-
30
14
  class << self
31
15
  # @param type [Symbol, String, nil]
32
16
  # @return [String]
@@ -52,6 +36,26 @@ module Rails
52
36
  ::Object.to_s
53
37
  end
54
38
  end
39
+
40
+ # @param klass [Class]
41
+ # @return [String]
42
+ def annotation_start(klass = nil)
43
+ table_name = klass && CONFIG.schema_file? ? ":#{klass.table_name}" : ''
44
+ "\n# %%<RailsAnnotateSolargraph:Start#{table_name}>%%"
45
+ end
46
+
47
+ # @param klass [Class]
48
+ # @return [String]
49
+ def annotation_end(klass = nil)
50
+ table_name = klass && CONFIG.schema_file? ? ":#{klass.table_name}" : ''
51
+ "%%<RailsAnnotateSolargraph:End#{table_name}>%%\n\n"
52
+ end
53
+
54
+ # @param klass [Class]
55
+ # @return [Regexp]
56
+ def annotation_regexp(klass = nil)
57
+ /#{annotation_start(klass)}.*#{annotation_end(klass)}/m
58
+ end
55
59
  end
56
60
 
57
61
  # @return [String]
@@ -63,7 +67,23 @@ module Rails
63
67
  # @param klass [Class]
64
68
  def initialize(klass)
65
69
  @klass = klass
66
- @file_name = ::File.join(::Rails.root, MODEL_DIR, "#{klass.to_s.underscore}.rb")
70
+ base_file_name = CONFIG.schema_file? ? SCHEMA_FILE_NAME : "#{klass.to_s.underscore}.rb"
71
+ @file_name = ::File.join(::Rails.root, MODEL_DIR, base_file_name)
72
+ end
73
+
74
+ # @return [String]
75
+ def annotation_start
76
+ self.class.annotation_start(@klass)
77
+ end
78
+
79
+ # @return [String]
80
+ def annotation_end
81
+ self.class.annotation_end(@klass)
82
+ end
83
+
84
+ # @return [Regexp]
85
+ def annotation_regexp
86
+ self.class.annotation_regexp(@klass)
67
87
  end
68
88
 
69
89
  # @param :write [Boolean]
@@ -91,8 +111,10 @@ module Rails
91
111
  # @param :write [Boolean]
92
112
  # @return [Array<String>] Old file content followed by new content.
93
113
  def remove_annotation(write: true)
114
+ return ['', ''] unless ::File.exist?(@file_name)
115
+
94
116
  file_content = ::File.read(@file_name)
95
- new_file_content = file_content.sub(ANNOTATION_REGEXP, '')
117
+ new_file_content = file_content.sub(annotation_regexp, '')
96
118
  result = [file_content, new_file_content]
97
119
  return result unless write
98
120
  return result if file_content == new_file_content
@@ -105,7 +127,7 @@ module Rails
105
127
  def annotation
106
128
  doc_string = ::String.new
107
129
  doc_string << <<~DOC
108
- #{ANNOTATION_START}
130
+ #{annotation_start}
109
131
  # @!parse
110
132
  # class #{@klass} < #{@klass.superclass}
111
133
  DOC
@@ -129,7 +151,7 @@ module Rails
129
151
 
130
152
  doc_string << <<~DOC.chomp
131
153
  # end
132
- # #{ANNOTATION_END}
154
+ # #{annotation_end}
133
155
  DOC
134
156
  end
135
157
 
@@ -145,6 +167,7 @@ module Rails
145
167
  # @param content [String]
146
168
  # @return [void]
147
169
  def write_file(file_name, content)
170
+ ::FileUtils.touch(file_name) unless ::File.exists?(file_name)
148
171
  ::File.write(file_name, content)
149
172
  puts "modify".rjust(12).with_styles(:bold, :green) + " #{relative_file_name(file_name)}"
150
173
  end
@@ -154,35 +177,57 @@ module Rails
154
177
  @klass.table_name[..-2]
155
178
  end
156
179
 
180
+ # @param reflection [ActiveRecord::Reflection::AbstractReflection]
181
+ # @return [Class]
182
+ def reflection_class(reflection)
183
+ reflection.klass
184
+ rescue ::NameError
185
+ Object
186
+ end
187
+
188
+ # @param reflection [ActiveRecord::Reflection::AbstractReflection]
189
+ # @return [String]
190
+ def reflection_foreign_key(reflection)
191
+ reflection.try(:foreign_key) || '<unknown>'
192
+ end
193
+
194
+ # @param klass [Class]
195
+ # @return [String]
196
+ def class_table_name(klass)
197
+ klass.try(:table_name) || '<unknown>'
198
+ end
199
+
157
200
  # @param doc_string [String]
158
201
  # @param attr_name [String]
159
202
  # @param reflection [ActiveRecord::Reflection::AbstractReflection]
160
203
  # @return [void]
161
204
  def document_relation(doc_string, attr_name, reflection)
162
- reflection_class = begin
163
- reflection.klass
164
- rescue ::NameError
165
- FAKE_MODEL_CLASS
166
- end
167
-
168
- db_description = \
205
+ reflection_klass = reflection_class(reflection)
206
+ type_docstring, db_description = \
169
207
  case reflection
170
208
  when ::ActiveRecord::Reflection::BelongsToReflection
171
- type_docstring = reflection_class
172
- "`belongs_to` relation with `#{reflection_class}`. Database column `#{@klass.table_name}.#{reflection.foreign_key}`."
209
+ belongs_to_description(reflection_klass,
210
+ class_table_name(@klass),
211
+ reflection_foreign_key(reflection))
173
212
  when ::ActiveRecord::Reflection::HasOneReflection
174
- type_docstring = reflection_class
175
- "`has_one` relation with `#{reflection_class}`. Database column `#{reflection_class.table_name}.#{reflection.foreign_key}`."
213
+ has_one_description(reflection_klass,
214
+ class_table_name(reflection_klass),
215
+ reflection_foreign_key(reflection))
176
216
  when ::ActiveRecord::Reflection::HasManyReflection
177
- type_docstring = "Array<#{reflection_class}>"
178
- "`has_many` relation with `#{reflection_class}`. Database column `#{reflection_class.table_name}.#{reflection.foreign_key}`."
217
+ has_many_description(reflection_klass,
218
+ class_table_name(reflection_klass),
219
+ reflection_foreign_key(reflection))
220
+ when ::ActiveRecord::Reflection::ThroughReflection
221
+ through_description(reflection)
222
+ else
223
+ [::Object.to_s, '']
179
224
  end
180
225
 
181
226
  doc_string << <<~DOC
182
- # # #{db_description}
227
+ # ##{db_description}
183
228
  # # @param val [#{type_docstring}, nil]
184
229
  # def #{attr_name}=(val); end
185
- # # #{db_description}
230
+ # ##{db_description}
186
231
  # # @return [#{type_docstring}, nil]
187
232
  # def #{attr_name}; end
188
233
  DOC
@@ -197,7 +242,7 @@ module Rails
197
242
  model_class.reflections[klass_relation_name]&.options&.[](:as)&.to_sym == attr_name.to_sym
198
243
  end
199
244
 
200
- classes_string = classes.join(', ')
245
+ classes_string = classes.empty? ? ::Object.to_s : classes.join(', ')
201
246
  doc_string << <<~DOC
202
247
  # # Polymorphic relation. Database columns `#{@klass.table_name}.#{attr_name}_id` and `#{@klass.table_name}.#{attr_name}_type`.
203
248
  # # @param val [#{classes_string}, nil]
@@ -208,6 +253,91 @@ module Rails
208
253
  DOC
209
254
  end
210
255
 
256
+ # @param reflection [ActiveRecord::Reflection::AbstractReflection]
257
+ # @return [Array<String>]
258
+ def through_description(reflection)
259
+ through_klass = reflection_class(reflection.through_reflection)
260
+
261
+ case (reflection.__send__(:delegate_reflection) rescue nil)
262
+ when ::ActiveRecord::Reflection::HasOneReflection
263
+ has_one_description(reflection_class(reflection.source_reflection),
264
+ class_table_name(through_klass),
265
+ reflection_foreign_key(reflection.source_reflection),
266
+ through: through_klass)
267
+ when ::ActiveRecord::Reflection::HasManyReflection
268
+ has_many_description(reflection_class(reflection.source_reflection),
269
+ class_table_name(through_klass),
270
+ reflection_foreign_key(reflection.source_reflection),
271
+ through: through_klass)
272
+ else
273
+ [::Object.to_s, '']
274
+ end
275
+ end
276
+
277
+ # @param through [Class, nil]
278
+ # @return [String]
279
+ def through_sentence(through = nil)
280
+ return '' unless through
281
+
282
+ " through `#{through}`"
283
+ end
284
+
285
+ # @param table_name [String]
286
+ # @param foreign_key [String]
287
+ # @param through [Class, nil]
288
+ # @return [String]
289
+ def column_description(table_name, foreign_key, through = nil)
290
+ return '' if through
291
+
292
+ " Database column `#{table_name}.#{foreign_key}`."
293
+ end
294
+
295
+ # @param relation [Symbol, String]
296
+ # @param klass [Class]
297
+ # @param table_name [String]
298
+ # @param foreign_key [String]
299
+ # @param through [Class, nil]
300
+ # @return [String]
301
+ def relation_description(relation, klass, table_name, foreign_key, through = nil)
302
+ " `#{relation}` relation with `#{klass}`#{through_sentence(through)}.#{column_description(table_name, foreign_key, through)}"
303
+ end
304
+
305
+
306
+ # @param klass [Class]
307
+ # @param table_name [String]
308
+ # @param foreign_key [String]
309
+ # @param :through [Class, nil]
310
+ # @return [Array<String>] Type docstring followed by the description of the method.
311
+ def has_many_description(klass, table_name, foreign_key, through: nil)
312
+ type_docstring = "Array<#{klass}>"
313
+ desc = relation_description(:has_many, klass, table_name, foreign_key, through)
314
+
315
+ [type_docstring, desc]
316
+ end
317
+
318
+ # @param klass [Class]
319
+ # @param table_name [String]
320
+ # @param foreign_key [String]
321
+ # @param :through [Class, nil]
322
+ # @return [Array<String>] Type docstring followed by the description of the method.
323
+ def has_one_description(klass, table_name, foreign_key, through: nil)
324
+ type_docstring = klass
325
+ desc = relation_description(:has_one, klass, table_name, foreign_key, through)
326
+
327
+ [type_docstring, desc]
328
+ end
329
+
330
+ # @param klass [Class]
331
+ # @param table_name [String]
332
+ # @param foreign_key [String]
333
+ # @return [Array<String>] Type docstring followed by the description of the method.
334
+ def belongs_to_description(klass, table_name, foreign_key)
335
+ type_docstring = klass
336
+ desc = relation_description(:belongs_to, klass, table_name, foreign_key)
337
+
338
+ [type_docstring, desc]
339
+ end
340
+
211
341
  # @param attr_type [ActiveModel::Type::Value]
212
342
  # @return [String]
213
343
  def yard_type(attr_type)
@@ -3,7 +3,7 @@
3
3
  module Rails
4
4
  module Annotate
5
5
  module Solargraph
6
- VERSION = '0.2.2'
6
+ VERSION = '0.4.0'
7
7
  end
8
8
  end
9
9
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
+ require 'fileutils'
4
5
 
5
6
  require_relative "solargraph/version"
6
7
  require_relative "solargraph/configuration"
@@ -19,11 +20,16 @@ module Rails
19
20
  CONFIG = Configuration.new
20
21
  # @return [Set<Symbol>]
21
22
  VALID_MODIFICATION_METHODS = ::Set[:annotate, :remove_annotation].freeze
23
+ # @return [String]
24
+ SCHEMA_CLASS_NAME = 'AnnotateSolargraphSchema'
25
+ # @return [String]
26
+ SCHEMA_FILE_NAME = "annotate_solargraph_schema.rb"
22
27
 
23
28
  class << self
24
29
  # @return [Array<String>] Array of changed files.
25
30
  def generate
26
31
  title 'Generating model schema annotations'
32
+ create_schema_file
27
33
  modify_models :annotate
28
34
  end
29
35
 
@@ -49,6 +55,16 @@ module Rails
49
55
 
50
56
  include TerminalColors
51
57
 
58
+ def create_schema_file
59
+ schema_file = ::File.join(::Rails.root, MODEL_DIR, SCHEMA_FILE_NAME)
60
+ return unless CONFIG.schema_file? && !::File.exist?(schema_file)
61
+
62
+ ::FileUtils.touch(schema_file)
63
+ ::File.write schema_file, <<~SCHEMA
64
+ module AnnotateSolargraphSchema; end
65
+ SCHEMA
66
+ end
67
+
52
68
  # @param method [Symbol] Name of the method that will be called on every loaded Model
53
69
  # @return [Array<String>] Array of changed files.
54
70
  def modify_models(method)
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
 
17
17
  spec.metadata["homepage_uri"] = spec.homepage
18
18
  spec.metadata["source_code_uri"] = "https://gitlab.com/mateuszdrewniak/rails-annotate-solargraph"
19
- # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
19
+ spec.metadata["changelog_uri"] = "https://gitlab.com/mateuszdrewniak/rails-annotate-solargraph/-/raw/main/CHANGELOG.md"
20
20
 
21
21
  # Specify which files should be added to the gem when it is released.
22
22
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-annotate-solargraph
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mateusz Drewniak
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-04-16 00:00:00.000000000 Z
11
+ date: 2022-04-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -67,6 +67,7 @@ licenses:
67
67
  metadata:
68
68
  homepage_uri: https://gitlab.com/mateuszdrewniak/rails-annotate-solargraph
69
69
  source_code_uri: https://gitlab.com/mateuszdrewniak/rails-annotate-solargraph
70
+ changelog_uri: https://gitlab.com/mateuszdrewniak/rails-annotate-solargraph/-/raw/main/CHANGELOG.md
70
71
  post_install_message:
71
72
  rdoc_options: []
72
73
  require_paths: