inquery 1.0.10 → 1.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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rubocop.yml +1 -1
  3. data/.github/workflows/ruby.yml +24 -8
  4. data/.gitignore +2 -2
  5. data/.rubocop.yml +4 -0
  6. data/Appraisals +24 -4
  7. data/CHANGELOG.md +47 -1
  8. data/Gemfile +17 -1
  9. data/Gemfile.lock +125 -0
  10. data/LICENSE +1 -1
  11. data/MIGRATION.md +260 -0
  12. data/README.md +31 -9
  13. data/RUBY_VERSION +1 -1
  14. data/Rakefile +4 -11
  15. data/VERSION +1 -1
  16. data/doc/Inquery/Exceptions/Base.html +4 -4
  17. data/doc/Inquery/Exceptions/InvalidRelation.html +4 -4
  18. data/doc/Inquery/Exceptions/UnknownCallSignature.html +4 -4
  19. data/doc/Inquery/Exceptions.html +4 -4
  20. data/doc/Inquery/MethodAccessibleHash.html +431 -0
  21. data/doc/Inquery/Mixins/RawSqlUtils.html +17 -4
  22. data/doc/Inquery/Mixins/RelationValidation/ClassMethods.html +8 -7
  23. data/doc/Inquery/Mixins/RelationValidation.html +9 -8
  24. data/doc/Inquery/Mixins/SchemaValidation/ClassMethods.html +20 -6
  25. data/doc/Inquery/Mixins/SchemaValidation.html +4 -4
  26. data/doc/Inquery/Mixins.html +4 -4
  27. data/doc/Inquery/Query/Chainable.html +207 -90
  28. data/doc/Inquery/Query.html +401 -73
  29. data/doc/Inquery.html +121 -6
  30. data/doc/_index.html +12 -5
  31. data/doc/class_list.html +6 -3
  32. data/doc/css/full_list.css +3 -3
  33. data/doc/css/style.css +6 -0
  34. data/doc/file.README.html +118 -17
  35. data/doc/file_list.html +5 -2
  36. data/doc/frames.html +10 -5
  37. data/doc/index.html +118 -17
  38. data/doc/js/app.js +294 -264
  39. data/doc/js/full_list.js +30 -4
  40. data/doc/method_list.html +52 -17
  41. data/doc/top-level-namespace.html +4 -4
  42. data/gemfiles/rails_5.2.gemfile +1 -0
  43. data/gemfiles/rails_6.0.gemfile +1 -0
  44. data/gemfiles/rails_6.1.gemfile +1 -0
  45. data/gemfiles/rails_7.0.gemfile +1 -0
  46. data/gemfiles/{rails_5.1.gemfile → rails_7.1.gemfile} +2 -1
  47. data/gemfiles/rails_7.2.gemfile +8 -0
  48. data/gemfiles/rails_8.0.gemfile +8 -0
  49. data/gemfiles/rails_8.1.gemfile +8 -0
  50. data/inquery.gemspec +11 -34
  51. data/lib/inquery/method_accessible_hash.rb +45 -0
  52. data/lib/inquery/mixins/raw_sql_utils.rb +32 -6
  53. data/lib/inquery/mixins/relation_validation.rb +1 -1
  54. data/lib/inquery/mixins/schema_validation.rb +8 -1
  55. data/lib/inquery/query/chainable.rb +69 -27
  56. data/lib/inquery/query.rb +67 -14
  57. data/lib/inquery.rb +9 -0
  58. data/test/inquery/error_handling_test.rb +117 -0
  59. data/test/inquery/method_accessible_hash_test.rb +85 -0
  60. data/test/inquery/mixins/raw_sql_utils_test.rb +67 -0
  61. data/test/inquery/query/chainable_test.rb +78 -0
  62. data/test/inquery/query_test.rb +86 -0
  63. data/test/test_helper.rb +11 -0
  64. metadata +30 -129
  65. data/.yardopts +0 -1
data/doc/js/full_list.js CHANGED
@@ -62,8 +62,25 @@ function enableToggles() {
62
62
  evt.stopPropagation();
63
63
  evt.preventDefault();
64
64
  $(this).parent().parent().toggleClass('collapsed');
65
+ $(this).attr('aria-expanded', function (i, attr) {
66
+ return attr == 'true' ? 'false' : 'true'
67
+ });
65
68
  highlight();
66
69
  });
70
+
71
+ // navigation of nested classes using keyboard
72
+ $('#full_list a.toggle').on('keypress',function(evt) {
73
+ // enter key is pressed
74
+ if (evt.which == 13) {
75
+ evt.stopPropagation();
76
+ evt.preventDefault();
77
+ $(this).parent().parent().toggleClass('collapsed');
78
+ $(this).attr('aria-expanded', function (i, attr) {
79
+ return attr == 'true' ? 'false' : 'true'
80
+ });
81
+ highlight();
82
+ }
83
+ });
67
84
  }
68
85
 
69
86
  function populateSearchCache() {
@@ -91,7 +108,7 @@ function enableSearch() {
91
108
  }
92
109
  });
93
110
 
94
- $('#full_list').after("<div id='noresults' style='display:none'></div>");
111
+ $('#full_list').after("<div id='noresults' role='status' style='display: none'></div>");
95
112
  }
96
113
 
97
114
  function ignoredKeyPress(event) {
@@ -154,11 +171,14 @@ function partialSearch(searchString, offset) {
154
171
  function searchDone() {
155
172
  searchTimeout = null;
156
173
  highlight();
157
- if ($('#full_list li:visible').size() === 0) {
158
- $('#noresults').text('No results were found.').hide().fadeIn();
174
+ var found = $('#full_list li:visible').size();
175
+ if (found === 0) {
176
+ $('#noresults').text('No results were found.');
159
177
  } else {
160
- $('#noresults').text('').hide();
178
+ // This is read out to screen readers
179
+ $('#noresults').text('There are ' + found + ' results.');
161
180
  }
181
+ $('#noresults').show();
162
182
  $('#content').removeClass('insearch');
163
183
  }
164
184
 
@@ -188,6 +208,12 @@ function expandTo(path) {
188
208
  $target.addClass('clicked');
189
209
  $target.removeClass('collapsed');
190
210
  $target.parentsUntil('#full_list', 'li').removeClass('collapsed');
211
+
212
+ $target.find('a.toggle').attr('aria-expanded', 'true')
213
+ $target.parentsUntil('#full_list', 'li').each(function(i, el) {
214
+ $(el).find('> div > a.toggle').attr('aria-expanded', 'true');
215
+ });
216
+
191
217
  if($target[0]) {
192
218
  window.scrollTo(window.scrollX, $target.offset().top - 250);
193
219
  highlight();
data/doc/method_list.html CHANGED
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html >
3
3
  <head>
4
4
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
5
5
  <meta charset="utf-8" />
@@ -38,7 +38,10 @@
38
38
 
39
39
  </div>
40
40
 
41
- <div id="search">Search: <input type="text" /></div>
41
+ <div id="search">
42
+ <label for="search-class">Search:</label>
43
+ <input id="search-class" type="text" />
44
+ </div>
42
45
  </div>
43
46
 
44
47
  <ul id="full_list" class="method">
@@ -46,8 +49,8 @@
46
49
 
47
50
  <li class="odd ">
48
51
  <div class="item">
49
- <span class='object_link'><a href="Inquery/Query/Chainable.html#call-instance_method" title="Inquery::Query::Chainable#call (method)">#call</a></span>
50
- <small>Inquery::Query::Chainable</small>
52
+ <span class='object_link'><a href="Inquery/Query.html#call-class_method" title="Inquery::Query.call (method)">call</a></span>
53
+ <small>Inquery::Query</small>
51
54
  </div>
52
55
  </li>
53
56
 
@@ -62,31 +65,31 @@
62
65
 
63
66
  <li class="odd ">
64
67
  <div class="item">
65
- <span class='object_link'><a href="Inquery/Query.html#call-class_method" title="Inquery::Query.call (method)">call</a></span>
66
- <small>Inquery::Query</small>
68
+ <span class='object_link'><a href="Inquery/Query/Chainable.html#call-class_method" title="Inquery::Query::Chainable.call (method)">call</a></span>
69
+ <small>Inquery::Query::Chainable</small>
67
70
  </div>
68
71
  </li>
69
72
 
70
73
 
71
74
  <li class="even ">
72
75
  <div class="item">
73
- <span class='object_link'><a href="Inquery/Query/Chainable.html#call-class_method" title="Inquery::Query::Chainable.call (method)">call</a></span>
74
- <small>Inquery::Query::Chainable</small>
76
+ <span class='object_link'><a href="Inquery/Query.html#connection-instance_method" title="Inquery::Query#connection (method)">#connection</a></span>
77
+ <small>Inquery::Query</small>
75
78
  </div>
76
79
  </li>
77
80
 
78
81
 
79
82
  <li class="odd ">
80
83
  <div class="item">
81
- <span class='object_link'><a href="Inquery/Query.html#connection-instance_method" title="Inquery::Query#connection (method)">#connection</a></span>
82
- <small>Inquery::Query</small>
84
+ <span class='object_link'><a href="Inquery/Query/Chainable.html#connection-instance_method" title="Inquery::Query::Chainable#connection (method)">#connection</a></span>
85
+ <small>Inquery::Query::Chainable</small>
83
86
  </div>
84
87
  </li>
85
88
 
86
89
 
87
90
  <li class="even ">
88
91
  <div class="item">
89
- <span class='object_link'><a href="Inquery/Query/Chainable.html#connection-instance_method" title="Inquery::Query::Chainable#connection (method)">#connection</a></span>
92
+ <span class='object_link'><a href="Inquery/Query/Chainable.html#initialize-instance_method" title="Inquery::Query::Chainable#initialize (method)">#initialize</a></span>
90
93
  <small>Inquery::Query::Chainable</small>
91
94
  </div>
92
95
  </li>
@@ -94,8 +97,8 @@
94
97
 
95
98
  <li class="odd ">
96
99
  <div class="item">
97
- <span class='object_link'><a href="Inquery/Query/Chainable.html#initialize-instance_method" title="Inquery::Query::Chainable#initialize (method)">#initialize</a></span>
98
- <small>Inquery::Query::Chainable</small>
100
+ <span class='object_link'><a href="Inquery/MethodAccessibleHash.html#initialize-instance_method" title="Inquery::MethodAccessibleHash#initialize (method)">#initialize</a></span>
101
+ <small>Inquery::MethodAccessibleHash</small>
99
102
  </div>
100
103
  </li>
101
104
 
@@ -108,6 +111,22 @@
108
111
  </li>
109
112
 
110
113
 
114
+ <li class="odd ">
115
+ <div class="item">
116
+ <span class='object_link'><a href="Inquery/MethodAccessibleHash.html#merge-instance_method" title="Inquery::MethodAccessibleHash#merge (method)">#merge</a></span>
117
+ <small>Inquery::MethodAccessibleHash</small>
118
+ </div>
119
+ </li>
120
+
121
+
122
+ <li class="even ">
123
+ <div class="item">
124
+ <span class='object_link'><a href="Inquery/MethodAccessibleHash.html#method_missing-instance_method" title="Inquery::MethodAccessibleHash#method_missing (method)">#method_missing</a></span>
125
+ <small>Inquery::MethodAccessibleHash</small>
126
+ </div>
127
+ </li>
128
+
129
+
111
130
  <li class="odd ">
112
131
  <div class="item">
113
132
  <span class='object_link'><a href="Inquery/Query.html#osparams-instance_method" title="Inquery::Query#osparams (method)">#osparams</a></span>
@@ -149,6 +168,14 @@
149
168
 
150
169
 
151
170
  <li class="even ">
171
+ <div class="item">
172
+ <span class='object_link'><a href="Inquery/MethodAccessibleHash.html#respond_to_missing%3F-instance_method" title="Inquery::MethodAccessibleHash#respond_to_missing? (method)">#respond_to_missing?</a></span>
173
+ <small>Inquery::MethodAccessibleHash</small>
174
+ </div>
175
+ </li>
176
+
177
+
178
+ <li class="odd ">
152
179
  <div class="item">
153
180
  <span class='object_link'><a href="Inquery/Query.html#run-instance_method" title="Inquery::Query#run (method)">#run</a></span>
154
181
  <small>Inquery::Query</small>
@@ -156,7 +183,7 @@
156
183
  </li>
157
184
 
158
185
 
159
- <li class="odd ">
186
+ <li class="even ">
160
187
  <div class="item">
161
188
  <span class='object_link'><a href="Inquery/Query.html#run-class_method" title="Inquery::Query.run (method)">run</a></span>
162
189
  <small>Inquery::Query</small>
@@ -164,7 +191,7 @@
164
191
  </li>
165
192
 
166
193
 
167
- <li class="even ">
194
+ <li class="odd ">
168
195
  <div class="item">
169
196
  <span class='object_link'><a href="Inquery/Mixins/SchemaValidation/ClassMethods.html#schema-instance_method" title="Inquery::Mixins::SchemaValidation::ClassMethods#schema (method)">#schema</a></span>
170
197
  <small>Inquery::Mixins::SchemaValidation::ClassMethods</small>
@@ -172,7 +199,7 @@
172
199
  </li>
173
200
 
174
201
 
175
- <li class="odd ">
202
+ <li class="even ">
176
203
  <div class="item">
177
204
  <span class='object_link'><a href="Inquery/Mixins/SchemaValidation/ClassMethods.html#schema2-instance_method" title="Inquery::Mixins::SchemaValidation::ClassMethods#schema2 (method)">#schema2</a></span>
178
205
  <small>Inquery::Mixins::SchemaValidation::ClassMethods</small>
@@ -180,7 +207,7 @@
180
207
  </li>
181
208
 
182
209
 
183
- <li class="even ">
210
+ <li class="odd ">
184
211
  <div class="item">
185
212
  <span class='object_link'><a href="Inquery/Mixins/SchemaValidation/ClassMethods.html#schema3-instance_method" title="Inquery::Mixins::SchemaValidation::ClassMethods#schema3 (method)">#schema3</a></span>
186
213
  <small>Inquery::Mixins::SchemaValidation::ClassMethods</small>
@@ -188,6 +215,14 @@
188
215
  </li>
189
216
 
190
217
 
218
+ <li class="even ">
219
+ <div class="item">
220
+ <span class='object_link'><a href="Inquery.html#setup-class_method" title="Inquery.setup (method)">setup</a></span>
221
+ <small>Inquery</small>
222
+ </div>
223
+ </li>
224
+
225
+
191
226
  <li class="odd ">
192
227
  <div class="item">
193
228
  <span class='object_link'><a href="Inquery/Mixins/RelationValidation.html#validate_relation!-instance_method" title="Inquery::Mixins::RelationValidation#validate_relation! (method)">#validate_relation!</a></span>
@@ -6,7 +6,7 @@
6
6
  <title>
7
7
  Top Level Namespace
8
8
 
9
- &mdash; Documentation by YARD 0.9.27
9
+ &mdash; Documentation by YARD 0.9.37
10
10
 
11
11
  </title>
12
12
 
@@ -100,9 +100,9 @@
100
100
  </div>
101
101
 
102
102
  <div id="footer">
103
- Generated on Thu May 19 10:36:15 2022 by
104
- <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
- 0.9.27 (ruby-3.0.1).
103
+ Generated on Mon Jan 5 14:06:48 2026 by
104
+ <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
+ 0.9.37 (ruby-3.3.5).
106
106
  </div>
107
107
 
108
108
  </div>
@@ -3,5 +3,6 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gem 'rails', '~> 5.2.6'
6
+ gem 'sqlite3', '~> 1.3.13'
6
7
 
7
8
  gemspec path: '../'
@@ -3,5 +3,6 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gem 'rails', '~> 6.0.4'
6
+ gem 'sqlite3', '~> 1.4.0'
6
7
 
7
8
  gemspec path: '../'
@@ -3,5 +3,6 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gem 'rails', '~> 6.1.4'
6
+ gem 'sqlite3', '~> 1.4.0'
6
7
 
7
8
  gemspec path: '../'
@@ -3,5 +3,6 @@
3
3
  source 'https://rubygems.org'
4
4
 
5
5
  gem 'rails', '~> 7.0.1'
6
+ gem 'sqlite3', '~> 1.4.0'
6
7
 
7
8
  gemspec path: '../'
@@ -2,6 +2,7 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- gem 'rails', '~> 5.1.7'
5
+ gem 'rails', '~> 7.1.0'
6
+ gem 'sqlite3', '>= 1.4'
6
7
 
7
8
  gemspec path: '../'
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'rails', '~> 7.2.0'
6
+ gem 'sqlite3', '>= 2.5'
7
+
8
+ gemspec path: '../'
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'rails', '~> 8.0.0'
6
+ gem 'sqlite3', '>= 2.5'
7
+
8
+ gemspec path: '../'
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'rails', '~> 8.1.0'
6
+ gem 'sqlite3', '>= 2.5'
7
+
8
+ gemspec path: '../'
data/inquery.gemspec CHANGED
@@ -1,46 +1,23 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: inquery 1.0.10 ruby lib
2
+ # stub: inquery 1.1.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "inquery".freeze
6
- s.version = "1.0.10"
6
+ s.version = "1.1.0".freeze
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib".freeze]
10
10
  s.authors = ["Sitrox".freeze]
11
- s.date = "2022-05-19"
12
- s.files = [".github/workflows/rubocop.yml".freeze, ".github/workflows/ruby.yml".freeze, ".gitignore".freeze, ".releaser_config".freeze, ".rubocop.yml".freeze, ".yardopts".freeze, "Appraisals".freeze, "CHANGELOG.md".freeze, "Gemfile".freeze, "LICENSE".freeze, "README.md".freeze, "RUBY_VERSION".freeze, "Rakefile".freeze, "VERSION".freeze, "doc/Inquery.html".freeze, "doc/Inquery/Exceptions.html".freeze, "doc/Inquery/Exceptions/Base.html".freeze, "doc/Inquery/Exceptions/InvalidRelation.html".freeze, "doc/Inquery/Exceptions/UnknownCallSignature.html".freeze, "doc/Inquery/Mixins.html".freeze, "doc/Inquery/Mixins/RawSqlUtils.html".freeze, "doc/Inquery/Mixins/RelationValidation.html".freeze, "doc/Inquery/Mixins/RelationValidation/ClassMethods.html".freeze, "doc/Inquery/Mixins/SchemaValidation.html".freeze, "doc/Inquery/Mixins/SchemaValidation/ClassMethods.html".freeze, "doc/Inquery/Query.html".freeze, "doc/Inquery/Query/Chainable.html".freeze, "doc/_index.html".freeze, "doc/class_list.html".freeze, "doc/css/common.css".freeze, "doc/css/full_list.css".freeze, "doc/css/style.css".freeze, "doc/file.README.html".freeze, "doc/file_list.html".freeze, "doc/frames.html".freeze, "doc/index.html".freeze, "doc/js/app.js".freeze, "doc/js/full_list.js".freeze, "doc/js/jquery.js".freeze, "doc/method_list.html".freeze, "doc/top-level-namespace.html".freeze, "gemfiles/rails_5.1.gemfile".freeze, "gemfiles/rails_5.2.gemfile".freeze, "gemfiles/rails_6.0.gemfile".freeze, "gemfiles/rails_6.1.gemfile".freeze, "gemfiles/rails_7.0.gemfile".freeze, "inquery.gemspec".freeze, "lib/inquery.rb".freeze, "lib/inquery/exceptions.rb".freeze, "lib/inquery/mixins/raw_sql_utils.rb".freeze, "lib/inquery/mixins/relation_validation.rb".freeze, "lib/inquery/mixins/schema_validation.rb".freeze, "lib/inquery/query.rb".freeze, "lib/inquery/query/chainable.rb".freeze, "test/db/models.rb".freeze, "test/db/schema.rb".freeze, "test/inquery/query/chainable_test.rb".freeze, "test/inquery/query_test.rb".freeze, "test/queries/group/fetch_as_json.rb".freeze, "test/queries/group/fetch_green.rb".freeze, "test/queries/group/fetch_red.rb".freeze, "test/queries/group/filter_with_color.rb".freeze, "test/queries/user/fetch_all.rb".freeze, "test/queries/user/fetch_in_group.rb".freeze, "test/queries/user/fetch_in_group_rel.rb".freeze, "test/test_helper.rb".freeze]
13
- s.rubygems_version = "3.2.16".freeze
11
+ s.date = "2026-01-05"
12
+ s.files = [".github/workflows/rubocop.yml".freeze, ".github/workflows/ruby.yml".freeze, ".gitignore".freeze, ".releaser_config".freeze, ".rubocop.yml".freeze, "Appraisals".freeze, "CHANGELOG.md".freeze, "Gemfile".freeze, "Gemfile.lock".freeze, "LICENSE".freeze, "MIGRATION.md".freeze, "README.md".freeze, "RUBY_VERSION".freeze, "Rakefile".freeze, "VERSION".freeze, "doc/Inquery.html".freeze, "doc/Inquery/Exceptions.html".freeze, "doc/Inquery/Exceptions/Base.html".freeze, "doc/Inquery/Exceptions/InvalidRelation.html".freeze, "doc/Inquery/Exceptions/UnknownCallSignature.html".freeze, "doc/Inquery/MethodAccessibleHash.html".freeze, "doc/Inquery/Mixins.html".freeze, "doc/Inquery/Mixins/RawSqlUtils.html".freeze, "doc/Inquery/Mixins/RelationValidation.html".freeze, "doc/Inquery/Mixins/RelationValidation/ClassMethods.html".freeze, "doc/Inquery/Mixins/SchemaValidation.html".freeze, "doc/Inquery/Mixins/SchemaValidation/ClassMethods.html".freeze, "doc/Inquery/Query.html".freeze, "doc/Inquery/Query/Chainable.html".freeze, "doc/_index.html".freeze, "doc/class_list.html".freeze, "doc/css/common.css".freeze, "doc/css/full_list.css".freeze, "doc/css/style.css".freeze, "doc/file.README.html".freeze, "doc/file_list.html".freeze, "doc/frames.html".freeze, "doc/index.html".freeze, "doc/js/app.js".freeze, "doc/js/full_list.js".freeze, "doc/js/jquery.js".freeze, "doc/method_list.html".freeze, "doc/top-level-namespace.html".freeze, "gemfiles/rails_5.2.gemfile".freeze, "gemfiles/rails_6.0.gemfile".freeze, "gemfiles/rails_6.1.gemfile".freeze, "gemfiles/rails_7.0.gemfile".freeze, "gemfiles/rails_7.1.gemfile".freeze, "gemfiles/rails_7.2.gemfile".freeze, "gemfiles/rails_8.0.gemfile".freeze, "gemfiles/rails_8.1.gemfile".freeze, "inquery.gemspec".freeze, "lib/inquery.rb".freeze, "lib/inquery/exceptions.rb".freeze, "lib/inquery/method_accessible_hash.rb".freeze, "lib/inquery/mixins/raw_sql_utils.rb".freeze, "lib/inquery/mixins/relation_validation.rb".freeze, "lib/inquery/mixins/schema_validation.rb".freeze, "lib/inquery/query.rb".freeze, "lib/inquery/query/chainable.rb".freeze, "test/db/models.rb".freeze, "test/db/schema.rb".freeze, "test/inquery/error_handling_test.rb".freeze, "test/inquery/method_accessible_hash_test.rb".freeze, "test/inquery/mixins/raw_sql_utils_test.rb".freeze, "test/inquery/query/chainable_test.rb".freeze, "test/inquery/query_test.rb".freeze, "test/queries/group/fetch_as_json.rb".freeze, "test/queries/group/fetch_green.rb".freeze, "test/queries/group/fetch_red.rb".freeze, "test/queries/group/filter_with_color.rb".freeze, "test/queries/user/fetch_all.rb".freeze, "test/queries/user/fetch_in_group.rb".freeze, "test/queries/user/fetch_in_group_rel.rb".freeze, "test/test_helper.rb".freeze]
13
+ s.homepage = "https://github.com/sitrox/inquery".freeze
14
+ s.rubygems_version = "3.5.18".freeze
14
15
  s.summary = "A skeleton that allows extracting queries into atomic, reusable classes.".freeze
15
- s.test_files = ["test/db/models.rb".freeze, "test/db/schema.rb".freeze, "test/inquery/query/chainable_test.rb".freeze, "test/inquery/query_test.rb".freeze, "test/queries/group/fetch_as_json.rb".freeze, "test/queries/group/fetch_green.rb".freeze, "test/queries/group/fetch_red.rb".freeze, "test/queries/group/filter_with_color.rb".freeze, "test/queries/user/fetch_all.rb".freeze, "test/queries/user/fetch_in_group.rb".freeze, "test/queries/user/fetch_in_group_rel.rb".freeze, "test/test_helper.rb".freeze]
16
+ s.test_files = ["test/db/models.rb".freeze, "test/db/schema.rb".freeze, "test/inquery/error_handling_test.rb".freeze, "test/inquery/method_accessible_hash_test.rb".freeze, "test/inquery/mixins/raw_sql_utils_test.rb".freeze, "test/inquery/query/chainable_test.rb".freeze, "test/inquery/query_test.rb".freeze, "test/queries/group/fetch_as_json.rb".freeze, "test/queries/group/fetch_green.rb".freeze, "test/queries/group/fetch_red.rb".freeze, "test/queries/group/filter_with_color.rb".freeze, "test/queries/user/fetch_all.rb".freeze, "test/queries/user/fetch_in_group.rb".freeze, "test/queries/user/fetch_in_group_rel.rb".freeze, "test/test_helper.rb".freeze]
16
17
 
17
- if s.respond_to? :specification_version then
18
- s.specification_version = 4
19
- end
18
+ s.specification_version = 4
20
19
 
21
- if s.respond_to? :add_runtime_dependency then
22
- s.add_development_dependency(%q<appraisal>.freeze, [">= 0"])
23
- s.add_development_dependency(%q<rake>.freeze, [">= 0"])
24
- s.add_development_dependency(%q<sqlite3>.freeze, [">= 0"])
25
- s.add_development_dependency(%q<haml>.freeze, [">= 0"])
26
- s.add_development_dependency(%q<yard>.freeze, [">= 0"])
27
- s.add_development_dependency(%q<rubocop>.freeze, ["= 1.25"])
28
- s.add_development_dependency(%q<redcarpet>.freeze, [">= 0"])
29
- s.add_runtime_dependency(%q<minitest>.freeze, [">= 0"])
30
- s.add_runtime_dependency(%q<activesupport>.freeze, [">= 0"])
31
- s.add_runtime_dependency(%q<activerecord>.freeze, [">= 0"])
32
- s.add_runtime_dependency(%q<schemacop>.freeze, ["~> 3.0.8"])
33
- else
34
- s.add_dependency(%q<appraisal>.freeze, [">= 0"])
35
- s.add_dependency(%q<rake>.freeze, [">= 0"])
36
- s.add_dependency(%q<sqlite3>.freeze, [">= 0"])
37
- s.add_dependency(%q<haml>.freeze, [">= 0"])
38
- s.add_dependency(%q<yard>.freeze, [">= 0"])
39
- s.add_dependency(%q<rubocop>.freeze, ["= 1.25"])
40
- s.add_dependency(%q<redcarpet>.freeze, [">= 0"])
41
- s.add_dependency(%q<minitest>.freeze, [">= 0"])
42
- s.add_dependency(%q<activesupport>.freeze, [">= 0"])
43
- s.add_dependency(%q<activerecord>.freeze, [">= 0"])
44
- s.add_dependency(%q<schemacop>.freeze, ["~> 3.0.8"])
45
- end
20
+ s.add_runtime_dependency(%q<activesupport>.freeze, [">= 5.1".freeze])
21
+ s.add_runtime_dependency(%q<activerecord>.freeze, [">= 5.1".freeze])
22
+ s.add_runtime_dependency(%q<schemacop>.freeze, [">= 3.0.8".freeze, "< 4.0".freeze])
46
23
  end
@@ -0,0 +1,45 @@
1
+ module Inquery
2
+ # A safe alternative for OpenStruct in Ruby. It behaves exactly the same, but
3
+ # does not define methods on-the-fly but uses `method_missing` instead.
4
+ #
5
+ # Usage example:
6
+ #
7
+ # ```ruby
8
+ # default_options = { foo: :bar }
9
+ # options = MethodAccessibleHash.new(default_options)
10
+ # options[:color] = :green
11
+ # options.foo # => :bar
12
+ # options.color # => green
13
+ # ```
14
+ class MethodAccessibleHash < ::Hash
15
+ # Takes an optional hash as argument and constructs a new
16
+ # MethodAccessibleHash.
17
+ def initialize(hash = {})
18
+ super()
19
+
20
+ hash.each do |key, value|
21
+ self[key.to_sym] = value
22
+ end
23
+ end
24
+
25
+ # @private
26
+ def merge(**hash)
27
+ super(hash.symbolize_keys)
28
+ end
29
+
30
+ # @private
31
+ def method_missing(method, *args, &_block)
32
+ if method.to_s.end_with?('=')
33
+ name = method.to_s.gsub(/=$/, '')
34
+ self[name.to_sym] = args.first
35
+ else
36
+ self[method.to_sym]
37
+ end
38
+ end
39
+
40
+ # @private
41
+ def respond_to_missing?(_method, _include_private = false)
42
+ true
43
+ end
44
+ end
45
+ end
@@ -1,19 +1,45 @@
1
1
  module Inquery
2
2
  module Mixins
3
+ # Provides utilities for working with raw SQL queries in a safe way.
4
+ #
5
+ # This mixin adds two helper methods for executing raw SQL queries:
6
+ # 'san' for sanitizing SQL with parameter substitution, and 'exec_query'
7
+ # for executing sanitized SQL and returning results.
3
8
  module RawSqlUtils
4
9
  extend ActiveSupport::Concern
5
10
 
6
11
  included do
7
- # Sanitizes the SQL and substitutes in the supplied variables. Relies on
8
- # `sanitize_sql_array` from ActiveRecord.
12
+ # Sanitizes SQL and substitutes variables using ActiveRecord's
13
+ # parameterized query mechanism.
14
+ #
15
+ # This method uses ActiveRecord's 'sanitize_sql_array' to safely
16
+ # escape values and prevent SQL injection. Always use this instead of
17
+ # string interpolation when building SQL queries.
18
+ #
19
+ # Example:
20
+ # sql = san("SELECT * FROM users WHERE age > ? AND city = ?", 18, 'NYC')
21
+ # # => "SELECT * FROM users WHERE age > 18 AND city = 'NYC'"
22
+ #
23
+ # @param sql [String] SQL query with '?' placeholders
24
+ # @param variables [Array] Values to substitute for placeholders
25
+ # @return [String] Sanitized SQL with values substituted
9
26
  def san(sql, *variables)
10
27
  ActiveRecord::Base.send(:sanitize_sql_array, [sql, *variables])
11
28
  end
12
29
 
13
- # Executes the sql on the connection provided by calling `connection`,
14
- # which means that the method needs to be defined where this mixin is
15
- # included. The sql passed in should be sanitized.
16
- # Returns an instance of `ActiveRecord::Result`.
30
+ # Executes sanitized SQL and returns the results.
31
+ #
32
+ # The SQL should already be sanitized using the 'san' method or
33
+ # ActiveRecord's query methods. Uses the connection from the 'connection'
34
+ # method, which must be defined in the including class.
35
+ #
36
+ # Example:
37
+ # sql = san("SELECT COUNT(*) as total FROM users WHERE active = ?", true)
38
+ # result = exec_query(sql)
39
+ # result.first['total'] # => 42
40
+ #
41
+ # @param sql [String] Sanitized SQL query to execute
42
+ # @return [ActiveRecord::Result] Query results
17
43
  def exec_query(sql)
18
44
  connection.exec_query(sql, self.class.to_s)
19
45
  end
@@ -91,7 +91,7 @@ module Inquery
91
91
  # ---------------------------------------------------------------
92
92
  fields_count = relation.select_values.size
93
93
 
94
- if fields_count.zero? && options[:default_select] && options[:fields] && (options[:fields]).positive?
94
+ if fields_count.zero? && options[:default_select] && options[:fields]&.positive?
95
95
  relation = relation.select(options[:default_select])
96
96
  fields_count = 1
97
97
  end
@@ -23,7 +23,14 @@ module Inquery
23
23
 
24
24
  # @see schema2
25
25
  def schema(*args, &block)
26
- schema2(*args, &block)
26
+ case Inquery.default_schema_version
27
+ when 2
28
+ schema2(*args, &block)
29
+ when 3
30
+ schema3(*args, &block)
31
+ else
32
+ fail 'Schemacop schema versions supported are 2 and 3.'
33
+ end
27
34
  end
28
35
  end
29
36
  end
@@ -1,25 +1,66 @@
1
1
  module Inquery
2
+ # Chainable query class for queries that input and output ActiveRecord relations.
3
+ #
4
+ # Use this class when you want to build queries that can be chained together
5
+ # or used as ActiveRecord scopes. The query receives a relation, transforms
6
+ # it, and returns a new relation.
7
+ #
8
+ # Example:
9
+ # class FetchActive < Inquery::Query::Chainable
10
+ # relation class: 'User'
11
+ #
12
+ # def call
13
+ # relation.where(active: true)
14
+ # end
15
+ # end
16
+ #
17
+ # User.all.then { |rel| FetchActive.run(rel) }
18
+ # # Or as a scope:
19
+ # class User < ActiveRecord::Base
20
+ # scope :active, FetchActive
21
+ # end
2
22
  class Query::Chainable < Query
3
23
  include Inquery::Mixins::RelationValidation
4
24
 
5
- # Allows using this class as an AR scope.
25
+ # Instantiates the query and executes it, allowing use as an AR scope.
26
+ #
27
+ # This enables chainable queries to be used directly as ActiveRecord scopes:
28
+ # scope :active, FetchActive
29
+ #
30
+ # @param args [Array] Arguments passed to initialize (relation and/or params)
31
+ # @return [ActiveRecord::Relation] The transformed relation
6
32
  def self.call(*args)
7
33
  return new(*args).call
8
34
  end
9
35
 
10
- def call(*args)
11
- fail args.inspect
12
- end
13
-
36
+ # The input ActiveRecord relation that will be transformed by this query.
37
+ #
38
+ # @return [ActiveRecord::Relation]
14
39
  attr_reader :relation
15
40
 
41
+ # Initializes a chainable query with a relation and optional parameters.
42
+ #
43
+ # Supports multiple call signatures:
44
+ # new() - Uses default relation from 'relation' DSL
45
+ # new(relation) - Uses provided relation
46
+ # new(params) - Uses default relation with params
47
+ # new(relation, params) - Uses provided relation and params
48
+ #
49
+ # @param args [Array] Variable arguments for relation and/or params
50
+ # @raise [Inquery::Exceptions::UnknownCallSignature] for invalid arguments
51
+ # @raise [Inquery::Exceptions::InvalidRelation] if relation validation fails
16
52
  def initialize(*args)
17
53
  relation, params = parse_init_args(*args)
18
54
  @relation = validate_relation!(relation)
19
55
  super(params)
20
56
  end
21
57
 
22
- # Override the connection method to (re-)use the connection of the relation
58
+ # Returns the database connection from the relation.
59
+ #
60
+ # This ensures that the query uses the same connection as the input
61
+ # relation, which is important for connection pooling and transactions.
62
+ #
63
+ # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
23
64
  def connection
24
65
  @relation.connection
25
66
  end
@@ -27,32 +68,33 @@ module Inquery
27
68
  private
28
69
 
29
70
  def parse_init_args(*args)
30
- # new(relation)
31
- if (args[0].is_a?(ActiveRecord::Relation) || args[0].class < ActiveRecord::Base) && args[1].nil?
32
- relation = args[0]
33
- params = {}
71
+ first_arg = args[0]
72
+ second_arg = args[1]
73
+
74
+ # new() - no arguments
75
+ return [nil, {}] if args.empty?
34
76
 
35
- # new(params)
36
- elsif args[0].is_a?(Hash) && args[1].nil?
37
- relation = nil
38
- params = args[0]
77
+ # new(relation) or new(params)
78
+ if second_arg.nil?
79
+ if relation_like?(first_arg)
80
+ return [first_arg, {}]
81
+ elsif first_arg.is_a?(Hash)
82
+ return [nil, first_arg]
83
+ end
84
+ end
39
85
 
40
86
  # new(relation, params)
41
- elsif (args[0].is_a?(ActiveRecord::Relation) || args[0].class < ActiveRecord::Base) && args[1].is_a?(Hash)
42
- relation = args[0]
43
- params = args[1]
44
-
45
- # new()
46
- elsif args.empty?
47
- relation = nil
48
- params = {}
49
-
50
- # Unknown
51
- else
52
- fail Inquery::Exceptions::UnknownCallSignature, "Unknown call signature for the query constructor: #{args.collect(&:class)}."
87
+ if relation_like?(first_arg) && second_arg.is_a?(Hash)
88
+ return [first_arg, second_arg]
53
89
  end
54
90
 
55
- return relation, params
91
+ # Unknown signature
92
+ fail Inquery::Exceptions::UnknownCallSignature,
93
+ "Unknown call signature for the query constructor: #{args.collect(&:class)}."
94
+ end
95
+
96
+ def relation_like?(obj)
97
+ obj.is_a?(ActiveRecord::Relation) || (obj.class < ActiveRecord::Base)
56
98
  end
57
99
  end
58
100
  end