active-query-explorer 0.1.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.
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveQueryExplorer
4
+ class QueryDiscovery
5
+ def grouped_queries
6
+ query_classes = ActiveQuery::Base.registry.select { |k| k.is_a?(Class) }
7
+ query_classes.group_by { |klass| namespace_for(klass) }.map do |namespace, klasses|
8
+ {
9
+ namespace: namespace,
10
+ query_objects: klasses.map { |k| query_object_payload(k) }
11
+ }
12
+ end
13
+ end
14
+
15
+ def find_query_class!(name)
16
+ ActiveQuery::Base.registry.find { |k| k.name == name } or
17
+ raise NameError, "Unknown query class: #{name}"
18
+ end
19
+
20
+ def find_query_def!(klass, query_name)
21
+ (klass.queries || []).find { |q| q[:name] == query_name } or
22
+ raise NameError, "Unknown query :#{query_name} on #{klass.name}"
23
+ end
24
+
25
+ private
26
+
27
+ def query_object_payload(klass)
28
+ {
29
+ class_name: klass.name,
30
+ source_location: source_location_for(klass),
31
+ queries: (klass.queries || []).map do |q|
32
+ {
33
+ name: q[:name],
34
+ description: q[:description],
35
+ params: (q[:args_def] || {}).map do |name, config|
36
+ {
37
+ name: name,
38
+ type: config[:type]&.name,
39
+ optional: config[:optional] || false,
40
+ default: config[:default]
41
+ }.compact
42
+ end
43
+ }
44
+ end
45
+ }
46
+ end
47
+
48
+ def source_location_for(klass)
49
+ file, line = Object.const_source_location(klass.name)
50
+ return nil unless file
51
+ { file: file, line: line }
52
+ end
53
+
54
+ def namespace_for(klass)
55
+ name = klass.name.to_s
56
+ last_separator = name.rindex("::")
57
+ last_separator ? name[0...last_separator] : ""
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveQueryExplorer
4
+ class QueryExecutor
5
+ def execute(klass, query_name, raw_args, args_def)
6
+ args = coerce_args(raw_args, args_def)
7
+
8
+ if args.empty?
9
+ klass.public_send(query_name)
10
+ else
11
+ klass.public_send(query_name, args)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def coerce_args(args_hash, args_def)
18
+ return {} if args_hash.blank?
19
+
20
+ permitted_keys = args_def.keys.map(&:to_s)
21
+ safe_args = args_hash.permit(*permitted_keys).to_h.symbolize_keys
22
+
23
+ safe_args.each_with_object({}) do |(key, value), result|
24
+ next if value.blank? && (args_def.dig(key, :optional) == true)
25
+
26
+ type = args_def.dig(key, :type)
27
+ result[key] = coerce_value(value, type)
28
+ end
29
+ end
30
+
31
+ def coerce_value(value, type)
32
+ return value.to_s unless type && ActiveQuery::TypeRegistry.coercer?(type)
33
+
34
+ ActiveQuery::TypeRegistry.coerce(type, value)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveQueryExplorer
4
+ class QueryTextFormatter
5
+ def format(grouped_queries)
6
+ grouped_queries.flat_map { |group| format_group(group) }.join("\n")
7
+ end
8
+
9
+ private
10
+
11
+ def format_group(group)
12
+ group[:query_objects].map { |qo| format_query_object(qo, group[:namespace]) }
13
+ end
14
+
15
+ def format_query_object(query_object, namespace)
16
+ query_object[:queries].map do |query|
17
+ format_query(query, query_object, namespace)
18
+ end.join("\n")
19
+ end
20
+
21
+ def format_query(query, query_object, namespace)
22
+ lines = []
23
+ lines << "=== QUERY START ==="
24
+ lines << "name: #{query_object[:class_name]}##{query[:name]}"
25
+ lines << "namespace: #{namespace.presence || "root"}"
26
+ lines << "description: #{query[:description] || "unknown"}"
27
+ lines << "returns: unknown"
28
+ lines << "side_effects: unknown"
29
+ lines << "idempotent: unknown"
30
+ lines << "safety: unknown"
31
+ lines << ""
32
+ lines << format_inputs(query[:params])
33
+ lines << "=== QUERY END ==="
34
+ lines.join("\n")
35
+ end
36
+
37
+ def format_inputs(params)
38
+ return "inputs: none" if params.nil? || params.empty?
39
+
40
+ lines = ["inputs:"]
41
+ params.each do |param|
42
+ lines << "- name: #{param[:name]}"
43
+ lines << " type: #{param[:type] || "unknown"}"
44
+ lines << " required: #{!param[:optional]}"
45
+ lines << " default: #{param.key?(:default) ? param[:default].inspect : "none"}"
46
+ end
47
+ lines.join("\n")
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveQueryExplorer
4
+ class ResultSerializer
5
+ def initialize(limit: ActiveQueryExplorer.result_limit)
6
+ @limit = limit
7
+ end
8
+
9
+ def serialize(result)
10
+ case result
11
+ when ActiveRecord::Relation then result.limit(@limit).as_json
12
+ when Integer, Float, String, NilClass, TrueClass, FalseClass then result
13
+ when ActiveRecord::Base then result.as_json
14
+ when Enumerable then result.first(@limit).as_json
15
+ else result.as_json
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveQueryExplorer
4
+ VERSION = "0.1.0"
5
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_query"
4
+ require_relative "active_query_explorer/version"
5
+ require_relative "active_query_explorer/query_discovery"
6
+ require_relative "active_query_explorer/query_executor"
7
+ require_relative "active_query_explorer/result_serializer"
8
+ require_relative "active_query_explorer/query_text_formatter"
9
+ require_relative "active_query_explorer/engine" if defined?(Rails::Engine)
10
+
11
+ module ActiveQueryExplorer
12
+ mattr_accessor :result_limit, default: 100
13
+ mattr_accessor :query_paths, default: %w[queries query_objects]
14
+ mattr_accessor :discovery_class, default: QueryDiscovery
15
+ mattr_accessor :executor_class, default: QueryExecutor
16
+ mattr_accessor :serializer_class, default: ResultSerializer
17
+ end
metadata ADDED
@@ -0,0 +1,195 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active-query-explorer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Matias Asis
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-03-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: active-query
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.1.3
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '0.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 0.1.3
33
+ - !ruby/object:Gem::Dependency
34
+ name: actionpack
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '6.1'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '9.0'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '6.1'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '9.0'
53
+ - !ruby/object:Gem::Dependency
54
+ name: railties
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '6.1'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '9.0'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '6.1'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '9.0'
73
+ - !ruby/object:Gem::Dependency
74
+ name: rake
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '13.0'
80
+ type: :development
81
+ prerelease: false
82
+ version_requirements: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '13.0'
87
+ - !ruby/object:Gem::Dependency
88
+ name: rspec
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '3.9'
94
+ type: :development
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '3.9'
101
+ - !ruby/object:Gem::Dependency
102
+ name: activerecord
103
+ requirement: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '6.1'
108
+ - - "<"
109
+ - !ruby/object:Gem::Version
110
+ version: '9.0'
111
+ type: :development
112
+ prerelease: false
113
+ version_requirements: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '6.1'
118
+ - - "<"
119
+ - !ruby/object:Gem::Version
120
+ version: '9.0'
121
+ - !ruby/object:Gem::Dependency
122
+ name: sqlite3
123
+ requirement: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: 1.5.1
128
+ - - "<"
129
+ - !ruby/object:Gem::Version
130
+ version: '3.0'
131
+ type: :development
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: 1.5.1
138
+ - - "<"
139
+ - !ruby/object:Gem::Version
140
+ version: '3.0'
141
+ description: ActiveQuery Explorer provides a web GUI (similar to GraphiQL) that discovers
142
+ all registered ActiveQuery objects, displays their metadata, and allows executing
143
+ them with parameters.
144
+ email:
145
+ - matiasis.90@gmail.com
146
+ executables: []
147
+ extensions: []
148
+ extra_rdoc_files: []
149
+ files:
150
+ - CHANGELOG.md
151
+ - LICENSE.txt
152
+ - README.md
153
+ - Rakefile
154
+ - active-query-explorer.gemspec
155
+ - app/assets/javascripts/active_query_explorer/application.js
156
+ - app/assets/stylesheets/active_query_explorer/application.css
157
+ - app/controllers/active_query_explorer/queries_controller.rb
158
+ - app/views/active_query_explorer/queries/index.html.erb
159
+ - config/routes.rb
160
+ - lib/active_query_explorer.rb
161
+ - lib/active_query_explorer/engine.rb
162
+ - lib/active_query_explorer/query_discovery.rb
163
+ - lib/active_query_explorer/query_executor.rb
164
+ - lib/active_query_explorer/query_text_formatter.rb
165
+ - lib/active_query_explorer/result_serializer.rb
166
+ - lib/active_query_explorer/version.rb
167
+ homepage: https://github.com/matiasasis/active-query-explorer
168
+ licenses:
169
+ - MIT
170
+ metadata:
171
+ homepage_uri: https://github.com/matiasasis/active-query-explorer
172
+ source_code_uri: https://github.com/matiasasis/active-query-explorer
173
+ bug_tracker_uri: https://github.com/matiasasis/active-query-explorer/issues
174
+ changelog_uri: https://github.com/matiasasis/active-query-explorer/blob/main/CHANGELOG.md
175
+ rubygems_mfa_required: 'true'
176
+ post_install_message:
177
+ rdoc_options: []
178
+ require_paths:
179
+ - lib
180
+ required_ruby_version: !ruby/object:Gem::Requirement
181
+ requirements:
182
+ - - ">="
183
+ - !ruby/object:Gem::Version
184
+ version: 3.2.0
185
+ required_rubygems_version: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ requirements: []
191
+ rubygems_version: 3.5.23
192
+ signing_key:
193
+ specification_version: 4
194
+ summary: A mountable Rails engine for browsing and executing ActiveQuery query objects.
195
+ test_files: []