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.
- checksums.yaml +4 -4
- data/.github/workflows/rubocop.yml +1 -1
- data/.github/workflows/ruby.yml +24 -8
- data/.gitignore +2 -2
- data/.rubocop.yml +4 -0
- data/Appraisals +24 -4
- data/CHANGELOG.md +47 -1
- data/Gemfile +17 -1
- data/Gemfile.lock +125 -0
- data/LICENSE +1 -1
- data/MIGRATION.md +260 -0
- data/README.md +31 -9
- data/RUBY_VERSION +1 -1
- data/Rakefile +4 -11
- data/VERSION +1 -1
- data/doc/Inquery/Exceptions/Base.html +4 -4
- data/doc/Inquery/Exceptions/InvalidRelation.html +4 -4
- data/doc/Inquery/Exceptions/UnknownCallSignature.html +4 -4
- data/doc/Inquery/Exceptions.html +4 -4
- data/doc/Inquery/MethodAccessibleHash.html +431 -0
- data/doc/Inquery/Mixins/RawSqlUtils.html +17 -4
- data/doc/Inquery/Mixins/RelationValidation/ClassMethods.html +8 -7
- data/doc/Inquery/Mixins/RelationValidation.html +9 -8
- data/doc/Inquery/Mixins/SchemaValidation/ClassMethods.html +20 -6
- data/doc/Inquery/Mixins/SchemaValidation.html +4 -4
- data/doc/Inquery/Mixins.html +4 -4
- data/doc/Inquery/Query/Chainable.html +207 -90
- data/doc/Inquery/Query.html +401 -73
- data/doc/Inquery.html +121 -6
- data/doc/_index.html +12 -5
- data/doc/class_list.html +6 -3
- data/doc/css/full_list.css +3 -3
- data/doc/css/style.css +6 -0
- data/doc/file.README.html +118 -17
- data/doc/file_list.html +5 -2
- data/doc/frames.html +10 -5
- data/doc/index.html +118 -17
- data/doc/js/app.js +294 -264
- data/doc/js/full_list.js +30 -4
- data/doc/method_list.html +52 -17
- data/doc/top-level-namespace.html +4 -4
- data/gemfiles/rails_5.2.gemfile +1 -0
- data/gemfiles/rails_6.0.gemfile +1 -0
- data/gemfiles/rails_6.1.gemfile +1 -0
- data/gemfiles/rails_7.0.gemfile +1 -0
- data/gemfiles/{rails_5.1.gemfile → rails_7.1.gemfile} +2 -1
- data/gemfiles/rails_7.2.gemfile +8 -0
- data/gemfiles/rails_8.0.gemfile +8 -0
- data/gemfiles/rails_8.1.gemfile +8 -0
- data/inquery.gemspec +11 -34
- data/lib/inquery/method_accessible_hash.rb +45 -0
- data/lib/inquery/mixins/raw_sql_utils.rb +32 -6
- data/lib/inquery/mixins/relation_validation.rb +1 -1
- data/lib/inquery/mixins/schema_validation.rb +8 -1
- data/lib/inquery/query/chainable.rb +69 -27
- data/lib/inquery/query.rb +67 -14
- data/lib/inquery.rb +9 -0
- data/test/inquery/error_handling_test.rb +117 -0
- data/test/inquery/method_accessible_hash_test.rb +85 -0
- data/test/inquery/mixins/raw_sql_utils_test.rb +67 -0
- data/test/inquery/query/chainable_test.rb +78 -0
- data/test/inquery/query_test.rb +86 -0
- data/test/test_helper.rb +11 -0
- metadata +30 -129
- data/.yardopts +0 -1
data/lib/inquery/query.rb
CHANGED
|
@@ -1,24 +1,54 @@
|
|
|
1
1
|
module Inquery
|
|
2
|
+
# Base query class that encapsulates database queries in reusable classes.
|
|
3
|
+
#
|
|
4
|
+
# Subclasses must implement the 'call' method to define the query logic.
|
|
5
|
+
# Optionally, override 'process' to transform the query results.
|
|
6
|
+
#
|
|
7
|
+
# Example:
|
|
8
|
+
# class FetchActiveUsers < Inquery::Query
|
|
9
|
+
# def call
|
|
10
|
+
# User.where(active: true)
|
|
11
|
+
# end
|
|
12
|
+
# end
|
|
13
|
+
#
|
|
14
|
+
# FetchActiveUsers.run # => Returns active users
|
|
2
15
|
class Query
|
|
3
16
|
include Mixins::SchemaValidation
|
|
4
17
|
include Mixins::RawSqlUtils
|
|
5
18
|
|
|
6
19
|
attr_reader :params
|
|
7
20
|
|
|
8
|
-
# Instantiates the query class
|
|
9
|
-
#
|
|
21
|
+
# Instantiates the query class with the given params and executes both
|
|
22
|
+
# 'call' and 'process' methods.
|
|
23
|
+
#
|
|
24
|
+
# This is the primary way to execute queries. It runs the query logic
|
|
25
|
+
# defined in 'call' and then passes the results through 'process' for
|
|
26
|
+
# any post-processing.
|
|
27
|
+
#
|
|
28
|
+
# @param args [Hash] Parameters to pass to the query
|
|
29
|
+
# @return [Object] The processed query results
|
|
30
|
+
# @raise [Schemacop::Exceptions::ValidationError] if params don't match schema
|
|
10
31
|
def self.run(*args)
|
|
11
32
|
new(*args).run
|
|
12
33
|
end
|
|
13
34
|
|
|
14
|
-
# Instantiates the query class
|
|
15
|
-
#
|
|
35
|
+
# Instantiates the query class with the given params and executes only
|
|
36
|
+
# the 'call' method, skipping post-processing.
|
|
37
|
+
#
|
|
38
|
+
# @param args [Hash] Parameters to pass to the query
|
|
39
|
+
# @return [Object] The raw query results
|
|
40
|
+
# @raise [Schemacop::Exceptions::ValidationError] if params don't match schema
|
|
16
41
|
def self.call(*args)
|
|
17
42
|
new(*args).call
|
|
18
43
|
end
|
|
19
44
|
|
|
20
|
-
#
|
|
21
|
-
#
|
|
45
|
+
# Initializes a new query instance with the given parameters.
|
|
46
|
+
#
|
|
47
|
+
# If a schema is defined using 'schema' or 'schema3', the params will be
|
|
48
|
+
# validated against it before the query is executed.
|
|
49
|
+
#
|
|
50
|
+
# @param params [Hash] Parameters for the query
|
|
51
|
+
# @raise [Schemacop::Exceptions::ValidationError] if params don't match schema
|
|
22
52
|
def initialize(params = {})
|
|
23
53
|
@params = params
|
|
24
54
|
|
|
@@ -27,29 +57,52 @@ module Inquery
|
|
|
27
57
|
end
|
|
28
58
|
end
|
|
29
59
|
|
|
30
|
-
#
|
|
60
|
+
# Executes the query by calling 'call' and then 'process'.
|
|
61
|
+
#
|
|
62
|
+
# @return [Object] The processed query results
|
|
31
63
|
def run
|
|
32
64
|
process(call)
|
|
33
65
|
end
|
|
34
66
|
|
|
35
|
-
# Override this method to
|
|
67
|
+
# Override this method in subclasses to define the query logic.
|
|
68
|
+
#
|
|
69
|
+
# @return [Object] Query results (typically an ActiveRecord::Relation)
|
|
70
|
+
# @raise [NotImplementedError] if not overridden in subclass
|
|
36
71
|
def call
|
|
37
72
|
fail NotImplementedError
|
|
38
73
|
end
|
|
39
74
|
|
|
40
|
-
# Override this method to
|
|
75
|
+
# Override this method in subclasses to transform the query results.
|
|
76
|
+
#
|
|
77
|
+
# By default, returns the results unchanged. Common uses include
|
|
78
|
+
# converting to JSON, counting records, or extracting specific fields.
|
|
79
|
+
#
|
|
80
|
+
# @param results [Object] The results from the 'call' method
|
|
81
|
+
# @return [Object] The processed results
|
|
41
82
|
def process(results)
|
|
42
83
|
results
|
|
43
84
|
end
|
|
44
85
|
|
|
45
|
-
# Returns
|
|
46
|
-
#
|
|
86
|
+
# Returns the query params wrapped in a MethodAccessibleHash for
|
|
87
|
+
# convenient access using dot notation.
|
|
88
|
+
#
|
|
89
|
+
# Example:
|
|
90
|
+
# schema3 { str! :name }
|
|
91
|
+
# def call
|
|
92
|
+
# User.where(name: osparams.name) # Access via dot notation
|
|
93
|
+
# end
|
|
94
|
+
#
|
|
95
|
+
# @return [MethodAccessibleHash] Params with method access
|
|
47
96
|
def osparams
|
|
48
|
-
@osparams ||=
|
|
97
|
+
@osparams ||= MethodAccessibleHash.new(params)
|
|
49
98
|
end
|
|
50
99
|
|
|
51
|
-
#
|
|
52
|
-
#
|
|
100
|
+
# Returns the database connection to use for this query.
|
|
101
|
+
#
|
|
102
|
+
# Override this method if you need to use a different connection than
|
|
103
|
+
# the default ActiveRecord connection.
|
|
104
|
+
#
|
|
105
|
+
# @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
|
|
53
106
|
def connection
|
|
54
107
|
ActiveRecord::Base.connection
|
|
55
108
|
end
|
data/lib/inquery.rb
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
module Inquery
|
|
2
|
+
mattr_accessor :default_schema_version
|
|
3
|
+
self.default_schema_version = 2
|
|
4
|
+
|
|
5
|
+
# Setup method that should be called in a dedicated initializer.
|
|
6
|
+
def self.setup
|
|
7
|
+
yield self
|
|
8
|
+
end
|
|
2
9
|
end
|
|
3
10
|
|
|
4
11
|
require 'uri'
|
|
12
|
+
require 'logger'
|
|
5
13
|
require 'schemacop'
|
|
6
14
|
|
|
7
15
|
require 'inquery/exceptions'
|
|
16
|
+
require 'inquery/method_accessible_hash'
|
|
8
17
|
require 'inquery/mixins/schema_validation'
|
|
9
18
|
require 'inquery/mixins/relation_validation'
|
|
10
19
|
require 'inquery/mixins/raw_sql_utils'
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
module Inquery
|
|
4
|
+
class ErrorHandlingTest < Minitest::Test
|
|
5
|
+
include TestHelper
|
|
6
|
+
|
|
7
|
+
def setup
|
|
8
|
+
self.class.setup_db
|
|
9
|
+
self.class.setup_base_data
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def test_query_without_call_method_raises_not_implemented_error
|
|
13
|
+
query_class = Class.new(Inquery::Query)
|
|
14
|
+
|
|
15
|
+
assert_raises(NotImplementedError) do
|
|
16
|
+
query_class.run
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_chainable_query_without_call_method_raises_not_implemented_error
|
|
21
|
+
query_class = Class.new(Inquery::Query::Chainable) do
|
|
22
|
+
relation class: 'User'
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
assert_raises(NotImplementedError) do
|
|
26
|
+
query_class.run(User.all)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def test_invalid_relation_class_raises_error
|
|
31
|
+
assert_raises(NameError) do
|
|
32
|
+
Class.new(Inquery::Query::Chainable) do
|
|
33
|
+
relation class: 'NonExistentClass'
|
|
34
|
+
|
|
35
|
+
def call
|
|
36
|
+
relation
|
|
37
|
+
end
|
|
38
|
+
end.run
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def test_invalid_relation_type_raises_error
|
|
43
|
+
query_class = Class.new(Inquery::Query::Chainable) do
|
|
44
|
+
relation class: 'User'
|
|
45
|
+
|
|
46
|
+
def call
|
|
47
|
+
relation
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
assert_raises(Inquery::Exceptions::UnknownCallSignature) do
|
|
52
|
+
query_class.run('not a relation')
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def test_schema_validation_failure_with_missing_required_param
|
|
57
|
+
query_class = Class.new(Inquery::Query) do
|
|
58
|
+
schema3 do
|
|
59
|
+
str! :name
|
|
60
|
+
int! :age
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def call
|
|
64
|
+
User.all
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
assert_raises(Schemacop::Exceptions::ValidationError) do
|
|
69
|
+
query_class.run(name: 'Alice')
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def test_schema_validation_failure_with_wrong_type
|
|
74
|
+
query_class = Class.new(Inquery::Query) do
|
|
75
|
+
schema3 do
|
|
76
|
+
int! :age
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def call
|
|
80
|
+
User.all
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
assert_raises(Schemacop::Exceptions::ValidationError) do
|
|
85
|
+
query_class.run(age: 'not a number')
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def test_invalid_relation_field_count
|
|
90
|
+
query_class = Class.new(Inquery::Query::Chainable) do
|
|
91
|
+
relation fields: 1
|
|
92
|
+
|
|
93
|
+
def call
|
|
94
|
+
relation
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Passing a relation with more than 1 field should raise an error
|
|
99
|
+
assert_raises(Inquery::Exceptions::InvalidRelation) do
|
|
100
|
+
query_class.run(User.select(:id, :name))
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_unknown_call_signature_raises_error
|
|
105
|
+
query_class = Class.new(Inquery::Query::Chainable) do
|
|
106
|
+
def call
|
|
107
|
+
relation
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Passing invalid arguments should raise UnknownCallSignature
|
|
112
|
+
assert_raises(Inquery::Exceptions::UnknownCallSignature) do
|
|
113
|
+
query_class.new(123, 456, 789)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
module Inquery
|
|
4
|
+
class MethodAccessibleHashTest < Minitest::Test
|
|
5
|
+
def test_new
|
|
6
|
+
hash = MethodAccessibleHash.new(name: 'John Smith', age: 70, 'pension' => 300)
|
|
7
|
+
|
|
8
|
+
assert_equal 'John Smith', hash.name
|
|
9
|
+
assert_equal 70, hash.age
|
|
10
|
+
assert_equal 300, hash.pension
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def test_new_no_arguments
|
|
14
|
+
assert_equal '{}', MethodAccessibleHash.new.to_s
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def test_to_h
|
|
18
|
+
assert MethodAccessibleHash.new.merge(foo: :bar).to_h.instance_of?(::Hash)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_getter
|
|
22
|
+
hash = MethodAccessibleHash.new.merge(foo: :bar, bar: :baz)
|
|
23
|
+
assert_equal :bar, hash.foo
|
|
24
|
+
assert_equal :baz, hash.bar
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_setter
|
|
28
|
+
hash = MethodAccessibleHash.new.merge(foo: :bar)
|
|
29
|
+
assert_equal :bar, hash.foo
|
|
30
|
+
|
|
31
|
+
hash.foo = :x
|
|
32
|
+
hash.bar = :y
|
|
33
|
+
|
|
34
|
+
assert_equal :x, hash.foo
|
|
35
|
+
assert_equal :y, hash.bar
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_reference
|
|
39
|
+
hash = MethodAccessibleHash.new
|
|
40
|
+
hash.foo = 42
|
|
41
|
+
assert_equal 42, hash[:foo]
|
|
42
|
+
assert_equal 42, hash.foo
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def test_comparison
|
|
46
|
+
assert_equal({ foo: :bar }, MethodAccessibleHash.new(foo: :bar))
|
|
47
|
+
refute_equal({ foo: :bar, bar: :baz }, MethodAccessibleHash.new(foo: :bar))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def test_frozen
|
|
51
|
+
hash = MethodAccessibleHash.new(name: 'John Smith', age: 70, pension: 300).freeze
|
|
52
|
+
|
|
53
|
+
assert_equal 70, hash.age
|
|
54
|
+
assert_equal 300, hash.pension
|
|
55
|
+
assert_equal 'John Smith', hash.name
|
|
56
|
+
|
|
57
|
+
assert_raises RuntimeError do
|
|
58
|
+
hash.age = 42
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
assert_raises RuntimeError do
|
|
62
|
+
hash.foo = 42
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
clone = hash.clone
|
|
66
|
+
assert clone.frozen?
|
|
67
|
+
assert_equal 70, clone.age
|
|
68
|
+
|
|
69
|
+
assert_raises RuntimeError do
|
|
70
|
+
clone.age = 42
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
assert_raises RuntimeError do
|
|
74
|
+
clone.foo = 42
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
duplicate = hash.dup
|
|
78
|
+
refute duplicate.frozen?
|
|
79
|
+
|
|
80
|
+
assert_equal 70, duplicate.age
|
|
81
|
+
assert_equal 300, duplicate.pension
|
|
82
|
+
assert_equal 'John Smith', duplicate.name
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
module Inquery
|
|
4
|
+
module Mixins
|
|
5
|
+
class RawSqlUtilsTest < Minitest::Test
|
|
6
|
+
include TestHelper
|
|
7
|
+
|
|
8
|
+
def setup
|
|
9
|
+
self.class.setup_db
|
|
10
|
+
self.class.setup_base_data
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Test query class
|
|
14
|
+
class TestQuery < Inquery::Query
|
|
15
|
+
def call
|
|
16
|
+
# Intentionally empty
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def test_san_sanitizes_sql_with_variables
|
|
21
|
+
query = TestQuery.new
|
|
22
|
+
result = query.san('SELECT * FROM users WHERE id = ?', 1)
|
|
23
|
+
|
|
24
|
+
assert_kind_of String, result
|
|
25
|
+
assert_includes result, 'SELECT * FROM users WHERE id = 1'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def test_san_handles_multiple_variables
|
|
29
|
+
query = TestQuery.new
|
|
30
|
+
result = query.san('SELECT * FROM users WHERE name = ? AND id = ?', 'Alice', 1)
|
|
31
|
+
|
|
32
|
+
assert_kind_of String, result
|
|
33
|
+
assert_includes result, 'SELECT * FROM users WHERE'
|
|
34
|
+
assert_includes result, 'Alice'
|
|
35
|
+
assert_includes result, '1'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def test_san_handles_special_characters
|
|
39
|
+
query = TestQuery.new
|
|
40
|
+
result = query.san('SELECT * FROM users WHERE name = ?', "O'Brien")
|
|
41
|
+
|
|
42
|
+
assert_kind_of String, result
|
|
43
|
+
# ActiveRecord escapes quotes using SQL standard (doubled quotes)
|
|
44
|
+
assert_includes result, "O''Brien"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def test_exec_query_executes_sql
|
|
48
|
+
query = TestQuery.new
|
|
49
|
+
sql = query.san('SELECT * FROM users WHERE id = ?', 1)
|
|
50
|
+
result = query.exec_query(sql)
|
|
51
|
+
|
|
52
|
+
assert_kind_of ActiveRecord::Result, result
|
|
53
|
+
assert_equal 1, result.length
|
|
54
|
+
assert_equal 1, result.first['id']
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def test_exec_query_returns_multiple_rows
|
|
58
|
+
query = TestQuery.new
|
|
59
|
+
sql = 'SELECT * FROM users ORDER BY id'
|
|
60
|
+
result = query.exec_query(sql)
|
|
61
|
+
|
|
62
|
+
assert_kind_of ActiveRecord::Result, result
|
|
63
|
+
assert_operator result.length, :>, 1
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -62,6 +62,84 @@ module Inquery
|
|
|
62
62
|
result = Queries::Group::FilterWithColor.run(Group.where('id > 2'), color: 'green')
|
|
63
63
|
assert_equal Group.find([3]), result.to_a
|
|
64
64
|
end
|
|
65
|
+
|
|
66
|
+
def test_call_not_implemented_error
|
|
67
|
+
# Create an anonymous chainable query class that doesn't override call
|
|
68
|
+
query_class = Class.new(Inquery::Query::Chainable) do
|
|
69
|
+
relation class: 'Group'
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Should raise NotImplementedError when call is not overridden
|
|
73
|
+
assert_raises(NotImplementedError) do
|
|
74
|
+
query_class.run(Group.all)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_chainable_with_default_relation
|
|
79
|
+
query_class = Class.new(Inquery::Query::Chainable) do
|
|
80
|
+
relation class: 'Group'
|
|
81
|
+
|
|
82
|
+
def call
|
|
83
|
+
relation.where(color: 'red')
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Should use default relation when no relation is passed
|
|
88
|
+
result = query_class.run
|
|
89
|
+
assert_equal Group.find([1]), result.to_a
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def test_chainable_with_params_only
|
|
93
|
+
result = Queries::Group::FilterWithColor.run(color: 'red')
|
|
94
|
+
assert_equal Group.find([1]), result.to_a
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_chainable_with_empty_relation
|
|
98
|
+
query_class = Class.new(Inquery::Query::Chainable) do
|
|
99
|
+
relation class: 'Group'
|
|
100
|
+
|
|
101
|
+
def call
|
|
102
|
+
relation.where(color: 'blue')
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
result = query_class.run(Group.where('1=0'))
|
|
107
|
+
assert_empty result.to_a
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def test_chainable_with_different_call_signatures
|
|
111
|
+
query_class = Class.new(Inquery::Query::Chainable) do
|
|
112
|
+
relation class: 'Group'
|
|
113
|
+
schema3 do
|
|
114
|
+
str? :color
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def call
|
|
118
|
+
if osparams.color
|
|
119
|
+
relation.where(color: osparams.color)
|
|
120
|
+
else
|
|
121
|
+
relation
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Test with relation and params
|
|
127
|
+
result1 = query_class.run(Group.all, color: 'red')
|
|
128
|
+
assert_equal Group.find([1]), result1.to_a
|
|
129
|
+
|
|
130
|
+
# Test with params only
|
|
131
|
+
result2 = query_class.run(color: 'green')
|
|
132
|
+
assert_equal Group.find([2, 3]), result2.to_a
|
|
133
|
+
|
|
134
|
+
# Test with relation only (should return all)
|
|
135
|
+
result3 = query_class.run(Group.where('id > 1'))
|
|
136
|
+
assert_equal Group.find([2, 3]), result3.to_a
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def test_relation_returns_active_record_relation
|
|
140
|
+
result = Queries::Group::FetchRed.run
|
|
141
|
+
assert_kind_of ActiveRecord::Relation, result
|
|
142
|
+
end
|
|
65
143
|
end
|
|
66
144
|
end
|
|
67
145
|
end
|
data/test/inquery/query_test.rb
CHANGED
|
@@ -43,5 +43,91 @@ module Inquery
|
|
|
43
43
|
result = Queries::Group::FetchAsJson.run
|
|
44
44
|
assert_equal Group.all.to_json, result
|
|
45
45
|
end
|
|
46
|
+
|
|
47
|
+
def test_query_with_optional_params
|
|
48
|
+
query_class = Class.new(Inquery::Query) do
|
|
49
|
+
schema3 do
|
|
50
|
+
str! :name
|
|
51
|
+
int? :age
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def call
|
|
55
|
+
User.where(name: osparams.name)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# With both params
|
|
60
|
+
result = query_class.run(name: 'Alice', age: 30)
|
|
61
|
+
assert_kind_of ActiveRecord::Relation, result
|
|
62
|
+
|
|
63
|
+
# With only required param
|
|
64
|
+
result = query_class.run(name: 'Alice')
|
|
65
|
+
assert_kind_of ActiveRecord::Relation, result
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def test_query_without_schema
|
|
69
|
+
query_class = Class.new(Inquery::Query) do
|
|
70
|
+
def call
|
|
71
|
+
User.all
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
result = query_class.run
|
|
76
|
+
assert_equal User.all, result
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_query_with_empty_result
|
|
80
|
+
query_class = Class.new(Inquery::Query) do
|
|
81
|
+
def call
|
|
82
|
+
User.where('1=0')
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
result = query_class.run
|
|
87
|
+
assert_empty result.to_a
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def test_query_returns_nil
|
|
91
|
+
query_class = Class.new(Inquery::Query) do
|
|
92
|
+
def call
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
result = query_class.run
|
|
98
|
+
assert_nil result
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_query_with_process_method
|
|
102
|
+
query_class = Class.new(Inquery::Query) do
|
|
103
|
+
def call
|
|
104
|
+
User.all
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def process(results)
|
|
108
|
+
results.count
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
result = query_class.run
|
|
113
|
+
assert_equal 3, result
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def test_osparams_returns_method_accessible_hash
|
|
117
|
+
query_class = Class.new(Inquery::Query) do
|
|
118
|
+
schema3 do
|
|
119
|
+
str! :name
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def call
|
|
123
|
+
osparams
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
result = query_class.run(name: 'Alice')
|
|
128
|
+
assert_kind_of Inquery::MethodAccessibleHash, result
|
|
129
|
+
assert_equal 'Alice', result.name
|
|
130
|
+
assert_equal 'Alice', result[:name]
|
|
131
|
+
end
|
|
46
132
|
end
|
|
47
133
|
end
|
data/test/test_helper.rb
CHANGED
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
begin
|
|
2
|
+
require 'simplecov'
|
|
3
|
+
SimpleCov.start do
|
|
4
|
+
add_filter '/test/'
|
|
5
|
+
add_filter '/vendor/'
|
|
6
|
+
end
|
|
7
|
+
rescue LoadError
|
|
8
|
+
# SimpleCov not available, skip coverage reporting
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
require 'logger'
|
|
1
12
|
require 'active_record'
|
|
2
13
|
require 'minitest/autorun'
|
|
3
14
|
require 'inquery'
|