arfi 0.5.1 → 1.0.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.env.sample +2 -0
  3. data/.rspec +1 -0
  4. data/.rubocop.yml +8 -1
  5. data/.rubocop_todo.yml +12 -0
  6. data/CHANGELOG.md +94 -0
  7. data/CODE_OF_CONDUCT.md +5 -1
  8. data/CONTRIBUTING.md +26 -0
  9. data/README.md +327 -118
  10. data/SECURITY.md +17 -0
  11. data/Steepfile +1 -29
  12. data/compose.yml +33 -0
  13. data/docscribe.yml +92 -0
  14. data/gemfiles/rails_6_0.gemfile +11 -0
  15. data/gemfiles/rails_6_1.gemfile +11 -0
  16. data/gemfiles/rails_7_0.gemfile +9 -0
  17. data/gemfiles/rails_7_1.gemfile +10 -0
  18. data/gemfiles/rails_7_2.gemfile +10 -0
  19. data/gemfiles/rails_8_0.gemfile +10 -0
  20. data/gemfiles/rails_8_1.gemfile +10 -0
  21. data/lib/arfi/cli.rb +24 -9
  22. data/lib/arfi/commands/f_idx.rb +5 -230
  23. data/lib/arfi/commands/functions.rb +128 -0
  24. data/lib/arfi/commands/functions_candidates.rb +133 -0
  25. data/lib/arfi/commands/functions_creation.rb +157 -0
  26. data/lib/arfi/commands/functions_helpers.rb +181 -0
  27. data/lib/arfi/commands/functions_paths.rb +131 -0
  28. data/lib/arfi/commands/functions_rendering.rb +137 -0
  29. data/lib/arfi/commands/init.rb +88 -0
  30. data/lib/arfi/commands/project.rb +5 -51
  31. data/lib/arfi/errors.rb +15 -3
  32. data/lib/arfi/extensions/active_record/base.rb +33 -23
  33. data/lib/arfi/extensions/active_record/connection_adapters/postgresql/database_statements.rb +159 -24
  34. data/lib/arfi/sql_function_loader.rb +289 -90
  35. data/lib/arfi/tasks/db.rake +60 -18
  36. data/lib/arfi/version.rb +1 -1
  37. data/lib/arfi.rb +2 -0
  38. data/rbs_collection.lock.yaml +93 -61
  39. data/sig/compat/active_record_base_compat.rbs +17 -0
  40. data/sig/compat/thor_dsl.rbs +8 -0
  41. data/sig/lib/arfi/commands/f_idx.rbs +5 -103
  42. data/sig/lib/arfi/commands/functions.rbs +99 -0
  43. data/sig/lib/arfi/commands/functions_candidates.rbs +25 -0
  44. data/sig/lib/arfi/commands/functions_creation.rbs +29 -0
  45. data/sig/lib/arfi/commands/functions_helpers.rbs +41 -0
  46. data/sig/lib/arfi/commands/functions_paths.rbs +25 -0
  47. data/sig/lib/arfi/commands/functions_rendering.rbs +25 -0
  48. data/sig/lib/arfi/commands/init.rbs +22 -0
  49. data/sig/lib/arfi/commands/project.rbs +5 -24
  50. data/sig/lib/arfi/extensions/active_record/base.rbs +5 -10
  51. data/sig/lib/arfi/extensions/active_record/connection_adapters/postgresql/database_statements.rbs +28 -0
  52. data/sig/lib/arfi/sql_function_loader.rbs +45 -87
  53. data/sig/lib/arfi/tasks/db.rbs +3 -0
  54. data/sig/lib/arfi/version.rbs +1 -1
  55. metadata +125 -14
data/docscribe.yml ADDED
@@ -0,0 +1,92 @@
1
+ ---
2
+ # Docscribe configuration file
3
+ #
4
+ # Docscribe works without this file — create it only for customization.
5
+ #
6
+ # Quick start:
7
+ # bundle exec docscribe lib # check what would change
8
+ # bundle exec docscribe -a lib # apply safe updates
9
+ # bundle exec docscribe -A lib # rebuild all doc blocks
10
+
11
+ emit:
12
+ # What to include in generated documentation
13
+ header: false # +MyClass#foo+ -> ReturnType
14
+ param_tags: true # @param tags
15
+ return_tag: true # @return tag
16
+ visibility_tags: true # @private / @protected
17
+ raise_tags: true # @raise tags
18
+ rescue_conditional_returns: true # @return [Type] if Error
19
+ attributes: false # @!attribute for attr_*
20
+
21
+ # Placeholder text for generated docs
22
+ include_default_message: true # "Method documentation."
23
+ include_param_documentation: true # "Param documentation."
24
+
25
+ doc:
26
+ # Default text and formatting
27
+ default_message: "Method documentation."
28
+ param_documentation: "Param documentation."
29
+ param_tag_style: "type_name" # "type_name" or "name_type"
30
+ sort_tags: true
31
+ tag_order: ["todo", "note", "api", "private", "protected", "param", "option", "yieldparam", "raise", "return"]
32
+
33
+ inference:
34
+ # Type inference behavior
35
+ fallback_type: "Object" # when uncertain
36
+ nil_as_optional: true # String | nil => String?
37
+ treat_options_keyword_as_hash: true # options: keyword => Hash
38
+
39
+ filter:
40
+ # Which methods and files to process
41
+ # Method format: "Container#method" (instance) or "Container.method" (class)
42
+ # Supports globs ("*#initialize") and regex ("/^MyApp::.*$/")
43
+ include: []
44
+ exclude: []
45
+ visibilities: ["public", "protected", "private"]
46
+ scopes: ["instance", "class"]
47
+
48
+ files:
49
+ # File paths relative to project root (globs or /regex/)
50
+ include: []
51
+ exclude: ["spec"]
52
+
53
+ methods:
54
+ # Override defaults per scope and visibility.
55
+ # Empty {} means "use values from `doc` section".
56
+ #
57
+ # Example:
58
+ # instance:
59
+ # public:
60
+ # default_message: "Public API."
61
+ # private:
62
+ # return_tag: false
63
+ instance:
64
+ public: {}
65
+ protected: {}
66
+ private: {}
67
+ class:
68
+ public: {}
69
+ protected: {}
70
+ private: {}
71
+
72
+ rbs:
73
+ # Use RBS signatures for better types (requires `gem "rbs"`)
74
+ enabled: true
75
+ sig_dirs: ["sig"]
76
+ collection_dirs: [] # auto-discovered from --rbs-collection
77
+ collapse_generics: false # Hash<Symbol, String> => Hash
78
+ collection: true # auto-discover from rbs_collection.lock.yaml
79
+
80
+ sorbet:
81
+ # Use Sorbet inline sigs and RBI files for better types
82
+ enabled: false
83
+ rbi_dirs: ["sorbet/rbi", "rbi"]
84
+ collapse_generics: false
85
+
86
+ plugins:
87
+ # Load custom plugins
88
+ # Example:
89
+ # require:
90
+ # - ./docscribe_plugins
91
+ # - docscribe-rails-associations
92
+ require: []
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec path: '..'
6
+
7
+ gem 'benchmark'
8
+ gem 'bigdecimal'
9
+ gem 'mysql2'
10
+ gem 'pg'
11
+ gem 'rails', '~> 6.0'
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec path: '..'
6
+
7
+ gem 'benchmark'
8
+ gem 'bigdecimal'
9
+ gem 'mysql2'
10
+ gem 'pg'
11
+ gem 'rails', '~> 6.1'
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec path: '..'
6
+
7
+ gem 'mysql2'
8
+ gem 'pg'
9
+ gem 'rails', '~> 7.0.0'
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec path: '..'
6
+
7
+ gem 'mysql2'
8
+ gem 'pg'
9
+ gem 'rails', '~> 7.1.0'
10
+ gem 'trilogy'
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec path: '..'
6
+
7
+ gem 'mysql2'
8
+ gem 'pg'
9
+ gem 'rails', '~> 7.2.0'
10
+ gem 'trilogy'
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec path: '..'
6
+
7
+ gem 'mysql2'
8
+ gem 'pg'
9
+ gem 'rails', '~> 8.0'
10
+ gem 'trilogy'
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec path: '..'
6
+
7
+ gem 'mysql2'
8
+ gem 'pg'
9
+ gem 'rails', '~> 8.1'
10
+ gem 'trilogy'
data/lib/arfi/cli.rb CHANGED
@@ -1,23 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'thor'
4
- require_relative 'commands/project'
5
- require_relative 'commands/f_idx'
4
+ require_relative 'commands/init'
5
+ require_relative 'commands/functions'
6
6
 
7
- # steep:ignore:start
8
7
  module Arfi
9
- # Top level CLI class
8
+ # Top-level CLI entrypoint for the `arfi` executable.
9
+ #
10
+ # It wires new command groups and keeps backward-compatible aliases:
11
+ # - `arfi init` (preferred)
12
+ # - `arfi functions` (preferred)
13
+ # - `arfi project` (alias for init)
14
+ # - `arfi f_idx` (alias for functions)
15
+ #
16
+ # @api public
10
17
  class CLI < Thor
11
- desc 'project [COMMAND]', 'Project specific commands.'
12
- subcommand 'project', Commands::Project
18
+ desc 'init [COMMAND]', 'Initialize ARFI directories (new command). Default: create'
19
+ subcommand 'init', Arfi::Commands::Init
13
20
 
14
- desc 'f_idx [COMMAND]', 'Command to handle functions.'
15
- subcommand 'f_idx', Commands::FIdx
21
+ desc 'project [COMMAND]', 'Alias for `arfi init` (backward compatible).'
22
+ subcommand 'project', Arfi::Commands::Init
23
+
24
+ desc 'functions [COMMAND]', 'Manage SQL function files (new command). Default: list'
25
+ subcommand 'functions', Arfi::Commands::Functions
26
+
27
+ desc 'f_idx [COMMAND]', 'Alias for `arfi functions` (backward compatible).'
28
+ subcommand 'f_idx', Arfi::Commands::Functions
16
29
 
17
30
  desc 'version', 'Print the version'
31
+ # Print the gem version to stdout.
32
+ #
33
+ # @return [Integer]
18
34
  def version
19
35
  $stdout.write(Arfi::VERSION, "\n")
20
36
  end
21
37
  end
22
38
  end
23
- # steep:ignore:end
@@ -1,238 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'thor'
4
- require 'rails'
5
- require File.expand_path('config/environment', Dir.pwd)
3
+ require_relative 'functions'
6
4
 
7
5
  module Arfi
8
6
  module Commands
9
7
  # +Arfi::Commands::FIdx+ module contains commands for manipulating functional index in Rails project.
10
- class FIdx < Thor
11
- ADAPTERS = %i[postgresql mysql].freeze
12
-
13
- # steep:ignore:start
14
- desc 'create FUNCTION_NAME [--template=template_file --adapter=adapter]', 'Initialize the functional index'
15
- option :template, type: :string, banner: 'template_file',
16
- desc: 'Path to the template file. See `README.md` for details.'
17
- option :adapter, type: :string,
18
- desc: 'Specify database adapter, used for projects with multiple database architecture. ' \
19
- "Available adapters: #{ADAPTERS.join(', ')}",
20
- banner: 'adapter'
21
- # steep:ignore:end
22
-
23
- # +Arfi::Commands::FIdx#create+ -> void
24
- #
25
- # This command is used to create the functional index.
26
- #
27
- # @example
28
- # bundle exec arfi f_idx create some_function
29
- #
30
- # ARFI also supports the use of custom templates for SQL functions, but now there are some restrictions and rules
31
- # according to which it is necessary to describe the function. First, the function must be written in a
32
- # Ruby-compatible syntax: the file name is not so important, but the name for the function name must be
33
- # interpolated with the +index_name+ variable name, and the function itself must be placed in the HEREDOC
34
- # statement. Below is an example file.
35
- #
36
- # @example
37
- # # ./template/my_custom_template
38
- # <<~SQL
39
- # CREATE OR REPLACE FUNCTION #{index_name}() RETURNS TEXT[]
40
- # LANGUAGE SQL
41
- # IMMUTABLE AS
42
- # $$
43
- # -- Function body here
44
- # $$
45
- # SQL
46
- #
47
- # To use a custom template, add the --template flag.
48
- #
49
- # @example
50
- # bundle exec arfi f_idx create some_function --template ./template/my_custom_template
51
- #
52
- # @param index_name [String] Name of the index.
53
- # @return [void]
54
- # @raise [Arfi::Errors::InvalidSchemaFormat] if ActiveRecord.schema_format is not :ruby
55
- # @raise [Arfi::Errors::NoFunctionsDir] if there is no `db/functions` directory
56
- # @see Arfi::Commands::FIdx#validate_schema_format!
57
- def create(index_name)
58
- validate_schema_format!
59
- content = build_sql_function(index_name)
60
- create_function_file(index_name, content)
61
- end
62
-
63
- # steep:ignore:start
64
- desc 'destroy INDEX_NAME [--revision=revision --adapter=adapter]', 'Delete the functional index.'
65
- option :revision, type: :string, banner: 'revision', desc: 'Revision of the function.'
66
- option :adapter, type: :string,
67
- desc: 'Specify database adapter, used for projects with multiple database architecture. ' \
68
- "Available adapters: #{ADAPTERS.join(', ')}",
69
- banner: 'adapter'
70
- # steep:ignore:end
71
-
72
- # +Arfi::Commands::FIdx#destroy+ -> void
73
- #
74
- # This command is used to delete the functional index.
75
- #
76
- # @example
77
- # bundle exec arfi f_idx destroy some_function [revision index (just an integer, 1 is by default)]
78
- # @param index_name [String] Name of the index.
79
- # @return [void]
80
- # @raise [Arfi::Errors::InvalidSchemaFormat] if ActiveRecord.schema_format is not :ruby
81
- def destroy(index_name)
82
- validate_schema_format!
83
-
84
- revision = Integer(options[:revision] || '01') # steep:ignore NoMethod
85
- revision = "0#{revision}"
86
- FileUtils.rm("#{functions_dir}/#{index_name}_v#{revision}.sql")
87
- puts "Deleted: #{functions_dir}/#{index_name}_v#{revision}.sql"
88
- end
89
-
90
- private
91
-
92
- # +Arfi::Commands::FIdx#validate_schema_format!+ -> void
93
- #
94
- # Helper method to validate the schema format.
95
- #
96
- # @!visibility private
97
- # @private
98
- # @raise [Arfi::Errors::InvalidSchemaFormat] if ActiveRecord.schema_format is not :ruby.
99
- # @return [nil] if the schema format is valid.
100
- def validate_schema_format!
101
- raise Arfi::Errors::InvalidSchemaFormat unless ActiveRecord.schema_format == :ruby # steep:ignore NoMethod
102
- end
103
-
104
- # +Arfi::Commands::FIdx#build_sql_function+ -> String
105
- #
106
- # Helper method to build the SQL function.
107
- #
108
- # @!visibility private
109
- # @private
110
- # @param index_name [String] Name of the index.
111
- # @return [String] SQL function body.
112
- def build_sql_function(index_name) # rubocop:disable Metrics/MethodLength
113
- return build_from_file(index_name) if options[:template] # steep:ignore NoMethod
114
-
115
- unless options[:adapter] # steep:ignore NoMethod
116
- return <<~SQL
117
- CREATE OR REPLACE FUNCTION #{index_name}() RETURNS TEXT[]
118
- LANGUAGE SQL
119
- IMMUTABLE AS
120
- $$
121
- -- Function body here
122
- $$
123
- SQL
124
- end
125
-
126
- case options[:adapter] # steep:ignore NoMethod
127
- when 'postgresql'
128
- <<~SQL
129
- CREATE OR REPLACE FUNCTION #{index_name}() RETURNS TEXT[]
130
- LANGUAGE SQL
131
- IMMUTABLE AS
132
- $$
133
- -- Function body here
134
- $$
135
- SQL
136
- when 'mysql'
137
- <<~SQL
138
- CREATE FUNCTION #{index_name} ()
139
- RETURNS return_type
140
- BEGIN
141
- -- Function body here
142
- END;
143
- SQL
144
- else
145
- # steep:ignore:start
146
- raise "Unknown adapter: #{options[:adapter]}. Supported adapters: #{ADAPTERS.join(', ')}"
147
- # steep:ignore:end
148
- end
149
- end
150
-
151
- # +Arfi::Commands::FIdx#build_from_file+ -> String
152
- #
153
- # Helper method to build the SQL function. Used with flag `--template`.
154
- #
155
- # @!visibility private
156
- # @private
157
- # @param index_name [String] Name of the index.
158
- # @return [String] SQL function body.
159
- # @see Arfi::Commands::FIdx#create
160
- # @see Arfi::Commands::FIdx#build_sql_function
161
- def build_from_file(index_name)
162
- # steep:ignore:start
163
- RubyVM::InstructionSequence.compile("index_name = '#{index_name}'; #{File.read(options[:template])}").eval
164
- # steep:ignore:end
165
- end
166
-
167
- # +Arfi::Commands::FIdx#create_function_file+ -> void
168
- #
169
- # Helper method to create the index file.
170
- #
171
- # @!visibility private
172
- # @private
173
- # @param index_name [String] Name of the index.
174
- # @param content [String] SQL function body.
175
- # @return [void]
176
- def create_function_file(index_name, content)
177
- existing_files = Dir.glob("#{functions_dir}/#{index_name}*.sql")
178
-
179
- return write_file(index_name, content, 1) if existing_files.empty?
180
-
181
- latest_version = extract_latest_version(existing_files)
182
- write_file(index_name, content, latest_version.succ)
183
- end
184
-
185
- # +Arfi::Commands::FIdx#extract_latest_version+ -> Integer
186
- #
187
- # Helper method to extract the latest version of the index.
188
- #
189
- # @!visibility private
190
- # @private
191
- # @param files [Array<String>] List of files.
192
- # @return [String] Latest version of the index.
193
- def extract_latest_version(files)
194
- version_numbers = files.map do |file|
195
- File.basename(file)[/\w+_v(\d+)\.sql/, 1]
196
- end.compact
197
-
198
- version_numbers.max
199
- end
200
-
201
- # +Arfi::Commands::FIdx#write_file+ -> void
202
- #
203
- # Helper method to write the index file.
204
- #
205
- # @!visibility private
206
- # @private
207
- # @param index_name [String] Name of the index.
208
- # @param content [String] SQL function body.
209
- # @param version [String|Integer] Version of the index.
210
- # @return [void]
211
- def write_file(index_name, content, version)
212
- version_str = format('%02d', version)
213
- path = "#{functions_dir}/#{index_name}_v#{version_str}.sql"
214
- File.write(path, content.to_s)
215
- puts "Created: #{path}"
216
- end
217
-
218
- # +Arfi::Commands::FIdx#functions_dir+ -> Pathname
219
- #
220
- # Helper method to get path to `db/functions` directory.
221
- #
222
- # @!visibility private
223
- # @private
224
- # @return [Pathname] Path to `db/functions` directory
225
- def functions_dir
226
- # steep:ignore:start
227
- if options[:adapter]
228
- raise Arfi::Errors::AdapterNotSupported unless ADAPTERS.include?(options[:adapter].to_sym)
229
-
230
- Rails.root.join("db/functions/#{options[:adapter]}")
231
- # steep:ignore:end
232
- else
233
- Rails.root.join('db/functions')
234
- end
235
- end
236
- end
8
+ #
9
+ # Backward-compatible constant for code that references Arfi::Commands::FIdx.
10
+ # @deprecated
11
+ FIdx = Functions
237
12
  end
238
13
  end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require 'rails'
5
+ require 'fileutils'
6
+ require 'json'
7
+
8
+ require_relative 'functions_helpers'
9
+ require_relative 'functions_creation'
10
+ require_relative 'functions_paths'
11
+ require_relative 'functions_rendering'
12
+ require_relative 'functions_candidates'
13
+
14
+ module Arfi
15
+ module Commands
16
+ ADAPTERS = %i[postgresql mysql trilogy].freeze
17
+ ROOT_DIR = 'db/functions'
18
+ DEFAULT_SCHEMA = 'public'
19
+ IDENT = /\A[a-zA-Z_][a-zA-Z0-9_]*\z/.freeze
20
+
21
+ # Thor CLI for managing SQL function files.
22
+ class Functions < Thor
23
+ include FunctionsHelpers
24
+ include FunctionsCreation
25
+ include FunctionsPaths
26
+ include FunctionsRendering
27
+ include FunctionsCandidates
28
+
29
+ default_task :list
30
+
31
+ # UX aliases
32
+ map %w[ls] => :list
33
+ map %w[rm delete del] => :destroy
34
+ map %w[new add] => :create
35
+
36
+ # steep:ignore:start
37
+ desc(
38
+ 'create FUNCTION_NAME [--schema=schema --template=template_file --adapter=adapter --force]',
39
+ "Create (or overwrite with --force) a SQL function file.\n " \
40
+ "Generic public: db/functions/public/<function>.sql\n " \
41
+ "PostgreSQL public: db/functions/postgresql/public/<function>.sql\n " \
42
+ "PostgreSQL schema: db/functions/postgresql/<schema>/<function>.sql\n" \
43
+ "Schema can be passed as 'schema.function' or via --schema (PostgreSQL only)."
44
+ )
45
+ option :schema, type: :string, banner: 'schema',
46
+ desc: "PostgreSQL schema name (alternative to 'schema.function')."
47
+ option :template, type: :string, banner: 'template_file',
48
+ desc: 'Path to the template file. See README.md for details.'
49
+ option :adapter, type: :string,
50
+ desc: "Specify database adapter. Available adapters: #{ADAPTERS.join(', ')}",
51
+ banner: 'adapter'
52
+ option :force, type: :boolean, default: false,
53
+ desc: 'Overwrite existing function file if it already exists.'
54
+ # steep:ignore:end
55
+ # Create (or overwrite with --force) a SQL function file in the appropriate directory.
56
+ #
57
+ # @param [String] function_ref Function reference string (e.g. 'my_func' or 'schema.my_func')
58
+ # @return [void]
59
+ def create(function_ref)
60
+ validate_schema_format!
61
+ validate_adapter_option!
62
+
63
+ schema, function_name = parse_function_ref(function_ref)
64
+ validate_identifiers!(schema, function_name)
65
+
66
+ ensure_dirs!(adapter: adapter_opt, schema: schema)
67
+
68
+ content = build_sql_function(schema, function_name, original_ref: function_ref)
69
+ write_file(schema, function_name, content)
70
+ end
71
+
72
+ # steep:ignore:start
73
+ desc(
74
+ 'destroy FUNCTION_NAME [--schema=schema --adapter=adapter]',
75
+ 'Delete a SQL function file (supports both new and legacy locations).'
76
+ )
77
+ option :schema, type: :string, banner: 'schema',
78
+ desc: "PostgreSQL schema name (alternative to 'schema.function')."
79
+ option :adapter, type: :string,
80
+ desc: "Specify database adapter. Available adapters: #{ADAPTERS.join(', ')}",
81
+ banner: 'adapter'
82
+ # steep:ignore:end
83
+ # Delete a SQL function file from disk (supports both new and legacy locations).
84
+ #
85
+ # @param [String] function_ref Function reference string (e.g. 'my_func' or 'schema.my_func')
86
+ # @return [void]
87
+ def destroy(function_ref)
88
+ validate_schema_format!
89
+ validate_adapter_option!
90
+
91
+ schema, function_name = parse_function_ref(function_ref)
92
+ validate_identifiers!(schema, function_name)
93
+
94
+ ensure_dirs!(adapter: adapter_opt, schema: schema)
95
+
96
+ remove_function_file(schema, function_name)
97
+ end
98
+
99
+ # steep:ignore:start
100
+ desc(
101
+ 'list [--adapter=adapter] [--format=table|paths|json] [--all]',
102
+ "List SQL function files ARFI would load for the chosen adapter.\n" \
103
+ "Default: inferred adapter from Rails config. Default output: table.\n" \
104
+ '--all shows shadowed/overridden candidates too.'
105
+ )
106
+ option :adapter, type: :string,
107
+ desc: "Specify database adapter. Available adapters: #{ADAPTERS.join(', ')}",
108
+ banner: 'adapter'
109
+ option :format, type: :string, default: 'table',
110
+ desc: 'Output format: table, paths, json'
111
+ option :all, type: :boolean, default: false,
112
+ desc: 'Show all candidates (including overridden ones), not just the effective set.'
113
+ # steep:ignore:end
114
+ # List SQL function files ARFI would load for the chosen adapter.
115
+ #
116
+ # @return [void]
117
+ def list
118
+ validate_schema_format!
119
+ validate_adapter_option!
120
+
121
+ adapter = resolve_adapter
122
+ rows = resolve_functions_for(adapter: adapter)
123
+
124
+ render_list(rows)
125
+ end
126
+ end
127
+ end
128
+ end