annotato 0.1.7 → 0.1.9

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: 40145b6423788c1e730c30b88fb7d0858f4e5103b5cf6848b888b58dbed9883b
4
- data.tar.gz: 84c1713c14000ad436410c8c2ae408d312a439f82158a6fc1a7cccfdf378e537
3
+ metadata.gz: '0488e086a169e36da60498c64e3f92d5ef351585e45ef8f7a27d53ea46eb5dc2'
4
+ data.tar.gz: ea52a7a819b2fb542731001b2161bdfaad4b360bf917dcc533618b7dc684599c
5
5
  SHA512:
6
- metadata.gz: 2b3103a08cccb5d2658e969eafddf36977b65e88f94cc72d35a1e485f841f67216071ee9bd37955a81d9c29844da11e4a3777d47dbf645e07ae861d4553f8d57
7
- data.tar.gz: f38edaf28cc9ca220f2825219ffc619d8638301719bebecbc60aae6d7bdadf01811fd9a84ce4e5d05b2d0432f908608d991c2e6be8e40fcaaef8f34bbebd8440
6
+ metadata.gz: e1fbed2a6e5f43ab7007fa0bdc6cce04143b7530d5cf3891ab050f8e8ead1aa7b9307ae74347b8f7fd34c315af2951c498b1b9a5aef4910981d283d2facdd6e1
7
+ data.tar.gz: 6a4b2a9d0213f41b050ce8ae077c095ee34602fe6b99904ee7cea345849d65901ed933d4e55aa40ad81eac4fcb5af7d26cca393c7a443dc2c2c8df7cd8e15c07
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- annotato (0.1.7)
4
+ annotato (0.1.9)
5
5
  rails (>= 6.0)
6
6
 
7
7
  GEM
data/README.md CHANGED
@@ -9,15 +9,15 @@
9
9
 
10
10
  ## Features
11
11
 
12
- - Annotates Rails models with:
13
- - Columns and types
14
- - Default values and constraints
15
- - Enums with values
16
- - Indexes (only if present)
17
- - Triggers (only if present)
18
- - Skips unchanged annotations
19
- - Replaces legacy `annotate`/`annotate_models` blocks
20
- - Smart formatting with aligned columns
12
+ * Annotates Rails models with:
13
+
14
+ * Columns and types
15
+ * Default values and constraints
16
+ * Enums with values
17
+ * Indexes (only if present)
18
+ * Triggers (only if present)
19
+ * Skips unchanged annotations
20
+ * Smart formatting with aligned columns
21
21
 
22
22
  Example:
23
23
 
@@ -57,23 +57,38 @@ bundle install
57
57
 
58
58
  ## Usage
59
59
 
60
- To annotate all models:
60
+ ### Annotate All Models
61
+
62
+ To annotate all models, run:
61
63
 
62
64
  ```bash
63
65
  bundle exec rake annotato:models
64
66
  ```
65
67
 
68
+ ### Annotate Specific Models
69
+
70
+ You can now pass one or multiple model names to the task.
71
+
72
+ ```bash
73
+ # Annotate only the User model
74
+ rake annotato:models[User]
75
+
76
+ # Annotate multiple models (User and Admin::Account)
77
+ rake annotato:models["User,Admin::Account"]
78
+ ```
79
+
66
80
  ---
67
81
 
68
82
  ## Rake Task
69
83
 
70
84
  ```bash
71
- rake annotato:models
85
+ rake annotato:models[MODEL]
72
86
  ```
73
87
 
74
- - Automatically loads all models via `Rails.application.eager_load!`
75
- - Modifies model files in place
76
- - Existing Annotato and Annotate blocks will be replaced
88
+ * Automatically loads all models via `Rails.application.eager_load!`
89
+ * Modifies model files in place
90
+ * Replace existing Annotato and legacy annotate blocks
91
+ * **Pass `MODEL` argument (single or comma-separated) to target specific models**
77
92
 
78
93
  ---
79
94
 
@@ -1,77 +1,97 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'json'
3
+ require "json"
4
4
 
5
5
  module Annotato
6
6
  class ColumnFormatter
7
+ # Main entry: returns an array of comment lines per column, flattened.
7
8
  def self.format(model, connection)
8
- table_name = model.table_name
9
- primary_key = model.primary_key
10
- enums = model.defined_enums
9
+ table_name = model.table_name
10
+ primary_key = model.primary_key
11
11
  unique_indexes = connection.indexes(table_name).select(&:unique)
12
- columns = connection.columns(table_name)
12
+ enums = model.defined_enums
13
+ columns = connection.columns(table_name)
13
14
 
14
- raw_data = columns.map do |col|
15
+ # Compute max widths for name and sql_type so that the table lines up.
16
+ name_width = columns.map(&:name).map(&:length).max
17
+ type_width = columns.map(&:sql_type).map(&:length).max
18
+
19
+ columns.flat_map do |col|
15
20
  name = col.name
16
21
  type = col.sql_type
17
- default = col.default
18
- opts = []
19
-
20
- opts << "default(#{default.inspect})" unless default.nil?
21
- opts << "not null" unless col.null
22
- opts << "primary key" if name == primary_key
23
- opts << "is an Array" if type.end_with?("[]")
24
- opts << "unique" if unique_indexes.any? { |idx| idx.columns == [name] }
25
- opts << "enum" if enums.key?(name)
26
-
27
- [name, type, default, opts]
28
- end
29
-
30
- name_width = raw_data.map { |name, *_| name.length }.max
31
- type_width = raw_data.map { |_, type, *_| type.length }.max
32
-
33
- raw_data.flat_map do |name, type, default, opts|
34
- base_line = "# %-#{name_width}s :%-#{type_width}s" % [name, type]
35
- indent = ' ' * (base_line.length + 1)
36
22
 
37
- if multiline_default?(default)
38
- formatted_defaults = format_multiline_default(default)
39
- remaining_opts = opts.reject { |o| o.start_with?("default(") }
23
+ # require "pry" if name == "allowed_statuses"
24
+ # binding.pry if name == "allowed_statuses"
25
+ # Build the left-hand side and calculate indent.
26
+ left = "# %-#{name_width}s :%-#{type_width}s" % [name, type]
27
+ # " default(" is 9 chars; +2 gives the extra gap.
28
+ indent_size = left.length + 1
29
+ indent_str = " " * indent_size
30
+ closing_indent_str = " " * (indent_size - 2)
40
31
 
41
- lines = []
42
- lines << "#{base_line} default(["
43
- formatted_defaults.each do |v|
44
- lines << "#{'#' + indent}#{v}"
45
- end
32
+ # Gather all options, pulling out default_lines if multiline.
33
+ opts = []
34
+ default_block = build_default_block(col.default)
35
+ if default_block
36
+ opts << "__MULTILINE__" # placeholder
37
+ elsif col.default
38
+ opts << "default(#{col.default.inspect})"
39
+ end
40
+ opts << "not null" unless col.null
41
+ opts << "primary key" if name == primary_key
42
+ opts << "is an Array" if type.end_with?("[]")
43
+ opts << "unique" if unique_indexes.any? { |idx| idx.columns == [name] }
44
+ opts << "enum" if enums.key?(name)
46
45
 
47
- closing = "#{'#' + ' ' * (indent.length - 1)}]),"
48
- closing += " #{remaining_opts.join(', ')}" unless remaining_opts.empty?
49
- lines << closing.rstrip
46
+ # Emit either a multiline block or a single line.
47
+ if default_block
48
+ # 1) opening line
49
+ lines = ["#{left} default(#{col.default[0]}"]
50
+ # 2) each interior line, prefixed by "# " + indent_str
51
+ lines += default_block.map { |l| "# #{indent_str}#{l}" }
52
+ # 3) closing line with trailing options
53
+ closing = "# #{closing_indent_str}#{col.default[-1]})"
54
+ trailing = opts.reject { |o| o == "__MULTILINE__" }
55
+ closing += ", #{trailing.join(', ')}" unless trailing.empty?
56
+ lines << closing
50
57
  lines
51
58
  else
52
- line = base_line
59
+ # single-line comment
60
+ line = left
53
61
  line += " #{opts.join(', ')}" unless opts.empty?
54
- line.rstrip
62
+ [line.rstrip]
55
63
  end
56
64
  end
57
65
  end
58
66
 
59
- def self.multiline_default?(value)
60
- value.is_a?(String) && value.strip.start_with?("[") && value.strip.end_with?("]") && value.include?(",")
61
- end
67
+ # Returns nil (no default) or an Array of un-indented lines:
68
+ # ["[", "\"A\",", "\"B\"", "]"] or ["{", "\"k\":v,", ... , "}"]
69
+ # If the value is empty array or hash, returns ["[]"] or ["{}"].
70
+ def self.build_default_block(value)
71
+ return nil if value.nil?
72
+ s = value.strip
73
+ return nil unless s.start_with?("[") || s.start_with?("{")
62
74
 
63
- def self.format_multiline_default(value)
64
- parsed = JSON.parse(value)
65
- return [value] unless parsed.is_a?(Array)
75
+ parsed = JSON.parse(s) rescue nil
76
+ return nil unless parsed.is_a?(Array) || parsed.is_a?(Hash)
66
77
 
67
- items = parsed.is_a?(Array) && parsed[0].is_a?(Array) ? parsed[0] : parsed
78
+ if parsed.is_a?(Array)
79
+ return if parsed.empty? # empty array → ["[]"]
68
80
 
69
- items.map.with_index do |item, idx|
70
- comma = idx == items.size - 1 ? "" : ","
71
- "#{item.to_json}#{comma}"
81
+ # Only a JSON array of strings?
82
+ parsed.map.with_index do |e, i|
83
+ comma = i == parsed.size - 1 ? "" : ","
84
+ %Q{"#{e}"#{comma}}
85
+ end
86
+ else
87
+ return if parsed.empty? # empty hash → ["{}"]
88
+
89
+ # JSON hash → key/value pairs
90
+ parsed.map.with_index do |(k, v), i|
91
+ comma = i == parsed.size - 1 ? "" : ","
92
+ %Q{"#{k}": #{v.inspect}#{comma}}
93
+ end
72
94
  end
73
- rescue JSON::ParserError
74
- [value]
75
95
  end
76
96
  end
77
97
  end
@@ -10,8 +10,10 @@ module Annotato
10
10
  @output = output
11
11
  end
12
12
 
13
- def run
14
- models.each do |model|
13
+ def run(one_model = nil)
14
+ mtd = one_model ? [one_model] : models
15
+
16
+ mtd.each do |model|
15
17
  next unless model.table_exists?
16
18
 
17
19
  annotation = AnnotationBuilder.build(model)
@@ -24,7 +26,10 @@ module Annotato
24
26
 
25
27
  def models
26
28
  Rails.application.eager_load!
27
- ActiveRecord::Base.descendants.reject(&:abstract_class?)
29
+ # All AR models, but skip STI subclasses (only keep base classes)
30
+ ActiveRecord::Base.descendants
31
+ .reject(&:abstract_class?)
32
+ .select { |m| m.base_class == m }
28
33
  end
29
34
 
30
35
  def model_file(model)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Annotato
4
- VERSION = "0.1.7"
4
+ VERSION = "0.1.9"
5
5
  end
@@ -1,8 +1,23 @@
1
+ # lib/tasks/annotato.rake
2
+
1
3
  require "annotato/model_annotator"
2
4
 
3
5
  namespace :annotato do
4
- desc "Annotate models with schema info"
5
- task models: :environment do
6
- Annotato::ModelAnnotator.new.run
6
+ desc "Annotate models with schema info. Pass MODEL=name or comma-separated list"
7
+ task :models, [:MODEL] => :environment do |t, args|
8
+ # Parse MODEL argument into an array of model names, if given
9
+ model_list = args[:MODEL]&.split(",")&.map(&:strip)
10
+
11
+ annotator = Annotato::ModelAnnotator.new
12
+
13
+ if model_list && model_list.any?
14
+ # Constantize each name and annotate only those classes
15
+ model_list.map(&:constantize).each do |klass|
16
+ annotator.run(klass)
17
+ end
18
+ else
19
+ # No MODEL passed → annotate all models
20
+ annotator.run
21
+ end
7
22
  end
8
23
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: annotato
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Serhii Bodnaruk
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 2025-05-16 00:00:00.000000000 Z
10
+ date: 2025-05-17 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: rails