inquery 0.0.1
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 +7 -0
- data/.gitignore +16 -0
- data/.releaser_config +4 -0
- data/.rubocop.yml +42 -0
- data/.travis.yml +9 -0
- data/.yardopts +1 -0
- data/Gemfile +4 -0
- data/LICENSE +21 -0
- data/README.md +288 -0
- data/RUBY_VERSION +1 -0
- data/Rakefile +36 -0
- data/VERSION +1 -0
- data/doc/Inquery.html +119 -0
- data/doc/Inquery/Exceptions.html +115 -0
- data/doc/Inquery/Exceptions/Base.html +127 -0
- data/doc/Inquery/Exceptions/InvalidRelation.html +131 -0
- data/doc/Inquery/Exceptions/UnknownCallSignature.html +131 -0
- data/doc/Inquery/Mixins.html +117 -0
- data/doc/Inquery/Mixins/RelationValidation.html +334 -0
- data/doc/Inquery/Mixins/RelationValidation/ClassMethods.html +190 -0
- data/doc/Inquery/Mixins/SchemaValidation.html +124 -0
- data/doc/Inquery/Mixins/SchemaValidation/ClassMethods.html +192 -0
- data/doc/Inquery/Query.html +736 -0
- data/doc/Inquery/Query/Chainable.html +476 -0
- data/doc/_index.html +254 -0
- data/doc/class_list.html +58 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +57 -0
- data/doc/css/style.css +339 -0
- data/doc/file.README.html +365 -0
- data/doc/file_list.html +60 -0
- data/doc/frames.html +26 -0
- data/doc/index.html +365 -0
- data/doc/js/app.js +219 -0
- data/doc/js/full_list.js +181 -0
- data/doc/js/jquery.js +4 -0
- data/doc/method_list.html +147 -0
- data/doc/top-level-namespace.html +112 -0
- data/inquery.gemspec +58 -0
- data/lib/inquery.rb +10 -0
- data/lib/inquery/exceptions.rb +7 -0
- data/lib/inquery/mixins/relation_validation.rb +100 -0
- data/lib/inquery/mixins/schema_validation.rb +27 -0
- data/lib/inquery/query.rb +50 -0
- data/lib/inquery/query/chainable.rb +53 -0
- data/test/db/models.rb +20 -0
- data/test/db/schema.rb +20 -0
- data/test/inquery/query/chainable_test.rb +67 -0
- data/test/inquery/query_test.rb +47 -0
- data/test/queries/group/fetch_as_json.rb +13 -0
- data/test/queries/group/fetch_green.rb +11 -0
- data/test/queries/group/fetch_red.rb +11 -0
- data/test/queries/group/filter_with_color.rb +12 -0
- data/test/queries/user/fetch_all.rb +9 -0
- data/test/queries/user/fetch_in_group.rb +13 -0
- data/test/queries/user/fetch_in_group_rel.rb +17 -0
- data/test/test_helper.rb +26 -0
- metadata +265 -0
data/lib/inquery.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module Inquery
|
2
|
+
module Mixins
|
3
|
+
module RelationValidation
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
OPTIONS_SCHEMA = {
|
7
|
+
hash: {
|
8
|
+
class: { type: String, null: true, required: false },
|
9
|
+
fields: { type: :integer, null: true, required: false },
|
10
|
+
default_select: { type: :symbol, null: true, required: false },
|
11
|
+
default: { type: [Proc, FalseClass], null: true, required: false }
|
12
|
+
}
|
13
|
+
}
|
14
|
+
|
15
|
+
DEFAULT_OPTIONS = {
|
16
|
+
# Allows to restrict the class (attribute `klass`) of the relation. Use
|
17
|
+
# `nil` to not perform any checks. The `class` attribute will also be
|
18
|
+
# taken to infer a default if no relation is given and you didn't
|
19
|
+
# specify any `default`.
|
20
|
+
class: nil,
|
21
|
+
|
22
|
+
# This allows to specify a default relation that will be taken if no
|
23
|
+
# relation is given. This must be specified as a Proc returning the
|
24
|
+
# relation. Set this to `false` for no default. If this is set to `nil`,
|
25
|
+
# it will try to infer the default from the option `class` (if given).
|
26
|
+
default: nil,
|
27
|
+
|
28
|
+
# Allows to restrict the number of fields / values the relation must
|
29
|
+
# select. This is particularly useful if you're using the query as a
|
30
|
+
# subquery and need it to return exactly one field. Use `nil` to not
|
31
|
+
# perform any checks.
|
32
|
+
fields: nil,
|
33
|
+
|
34
|
+
# If this is set to a symbol, the relation does not have any select
|
35
|
+
# fields specified (`select_values` is empty) and `fields` is > 0, it
|
36
|
+
# will automatically select the given field. Use `nil` to disable this
|
37
|
+
# behavior.
|
38
|
+
default_select: :id
|
39
|
+
}
|
40
|
+
|
41
|
+
included do
|
42
|
+
class_attribute :_relation_options
|
43
|
+
end
|
44
|
+
|
45
|
+
module ClassMethods
|
46
|
+
# Allows to configure the parameters of the relation this query operates
|
47
|
+
# on. See {OPTIONS_SCHEMA} for documentation of the options hash.
|
48
|
+
def relation(options = {})
|
49
|
+
Schemacop.validate!(OPTIONS_SCHEMA, options)
|
50
|
+
self._relation_options = options
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Validates (and possibly alters) the given relation according to the
|
55
|
+
# options specified at class level using the `relation` method.
|
56
|
+
def validate_relation!(relation)
|
57
|
+
options = DEFAULT_OPTIONS.dup
|
58
|
+
options.merge!(self.class._relation_options.dup) if self.class._relation_options
|
59
|
+
|
60
|
+
relation_class = options[:class].try(:constantize)
|
61
|
+
|
62
|
+
# ---------------------------------------------------------------
|
63
|
+
# Validate presence
|
64
|
+
# ---------------------------------------------------------------
|
65
|
+
if relation.nil?
|
66
|
+
if options[:default]
|
67
|
+
relation = options[:default].call
|
68
|
+
elsif options[:default].nil? && relation_class
|
69
|
+
relation = relation_class.all
|
70
|
+
else
|
71
|
+
fail Inquery::Exceptions::InvalidRelation, 'A relation must be given for this query.'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# ---------------------------------------------------------------
|
76
|
+
# Validate class
|
77
|
+
# ---------------------------------------------------------------
|
78
|
+
if relation_class && relation_class != relation.klass
|
79
|
+
fail Inquery::Exceptions::InvalidRelation, "Unexpected relation class '#{relation.klass}' for this query, expected a '#{relation_class}'."
|
80
|
+
end
|
81
|
+
|
82
|
+
# ---------------------------------------------------------------
|
83
|
+
# Validate selected fields
|
84
|
+
# ---------------------------------------------------------------
|
85
|
+
fields_count = relation.select_values.size
|
86
|
+
|
87
|
+
if fields_count == 0 && options[:default_select] && options[:fields] && options[:fields] > 0
|
88
|
+
relation = relation.select(options[:default_select])
|
89
|
+
fields_count = 1
|
90
|
+
end
|
91
|
+
|
92
|
+
if !options[:fields].nil? && fields_count != options[:fields]
|
93
|
+
fail Inquery::Exceptions::InvalidRelation, "Expected given relation to select #{options[:fields]} field(s) but got #{fields_count}."
|
94
|
+
end
|
95
|
+
|
96
|
+
return relation
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Inquery
|
2
|
+
module Mixins
|
3
|
+
module SchemaValidation
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
class_attribute :_schema
|
8
|
+
self._schema = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
def schema(schema)
|
13
|
+
fail 'Schema must be a hash.' unless schema.is_a?(Hash)
|
14
|
+
|
15
|
+
unless schema[:type]
|
16
|
+
schema = {
|
17
|
+
type: :hash,
|
18
|
+
hash: schema
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
self._schema = schema
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Inquery
|
2
|
+
class Query
|
3
|
+
include Mixins::SchemaValidation
|
4
|
+
|
5
|
+
attr_reader :params
|
6
|
+
|
7
|
+
# Instantiates the query class using the given arguments
|
8
|
+
# and runs `call` and `process` on it.
|
9
|
+
def self.run(*args)
|
10
|
+
new(*args).run
|
11
|
+
end
|
12
|
+
|
13
|
+
# Instantiates the query class using the given arguments
|
14
|
+
# and runs `call` on it.
|
15
|
+
def self.call(*args)
|
16
|
+
new(*args).call
|
17
|
+
end
|
18
|
+
|
19
|
+
# Instantiates the query class and validates the given params hash (if there
|
20
|
+
# was a validation schema specified).
|
21
|
+
def initialize(params = {})
|
22
|
+
@params = params
|
23
|
+
|
24
|
+
if self.class._schema
|
25
|
+
Schemacop.validate!(self.class._schema, @params)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Runs both `call` and `process`.
|
30
|
+
def run
|
31
|
+
process(call)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Override this method to perform the actual query.
|
35
|
+
def call
|
36
|
+
fail NotImplementedError
|
37
|
+
end
|
38
|
+
|
39
|
+
# Override this method to perform an optional result postprocessing.
|
40
|
+
def process(results)
|
41
|
+
results
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns a copy of the query's params, wrapped in an OpenStruct object for
|
45
|
+
# easyer access.
|
46
|
+
def osparams
|
47
|
+
@osparams ||= OpenStruct.new(params)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Inquery
|
2
|
+
class Query::Chainable < Query
|
3
|
+
include Inquery::Mixins::RelationValidation
|
4
|
+
|
5
|
+
# Allows using this class as an AR scope.
|
6
|
+
def self.call(*args)
|
7
|
+
return new(*args).call
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(*args)
|
11
|
+
fail args.inspect
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :relation
|
15
|
+
|
16
|
+
def initialize(*args)
|
17
|
+
relation, params = parse_init_args(*args)
|
18
|
+
@relation = validate_relation!(relation)
|
19
|
+
super(params)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def parse_init_args(*args)
|
25
|
+
# new(relation)
|
26
|
+
if (args[0].is_a?(ActiveRecord::Relation) || args[0].class < ActiveRecord::Base) && args[1].nil?
|
27
|
+
relation = args[0]
|
28
|
+
params = {}
|
29
|
+
|
30
|
+
# new(params)
|
31
|
+
elsif args[0].is_a?(Hash) && args[1].nil?
|
32
|
+
relation = nil
|
33
|
+
params = args[0]
|
34
|
+
|
35
|
+
# new(relation, params)
|
36
|
+
elsif (args[0].is_a?(ActiveRecord::Relation) || args[0].class < ActiveRecord::Base) && args[1].is_a?(Hash)
|
37
|
+
relation = args[0]
|
38
|
+
params = args[1]
|
39
|
+
|
40
|
+
# new()
|
41
|
+
elsif args.empty?
|
42
|
+
relation = nil
|
43
|
+
params = {}
|
44
|
+
|
45
|
+
# Unknown
|
46
|
+
else
|
47
|
+
fail Inquery::Exceptions::UnknownCallSignature, "Unknown call signature for the query constructor: #{args.collect(&:class)}."
|
48
|
+
end
|
49
|
+
|
50
|
+
return relation, params
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/test/db/models.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'queries/group/fetch_green'
|
2
|
+
require 'queries/group/fetch_red'
|
3
|
+
|
4
|
+
class GroupsUser < ActiveRecord::Base
|
5
|
+
belongs_to :group
|
6
|
+
belongs_to :user
|
7
|
+
end
|
8
|
+
|
9
|
+
class User < ActiveRecord::Base
|
10
|
+
has_many :groups_users
|
11
|
+
has_many :groups, through: :groups_users
|
12
|
+
end
|
13
|
+
|
14
|
+
class Group < ActiveRecord::Base
|
15
|
+
has_many :groups_users
|
16
|
+
has_many :users, through: :groups_users
|
17
|
+
|
18
|
+
scope :red, Queries::Group::FetchRed
|
19
|
+
scope :green, Queries::Group::FetchGreen
|
20
|
+
end
|
data/test/db/schema.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
ActiveRecord::Schema.define do
|
2
|
+
self.verbose = false
|
3
|
+
|
4
|
+
create_table :groups, force: true do |t|
|
5
|
+
t.string :name
|
6
|
+
t.string :color
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
create_table :users, force: true do |t|
|
11
|
+
t.string :name
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :groups_users, force: true do |t|
|
16
|
+
t.integer :group_id
|
17
|
+
t.integer :user_id
|
18
|
+
t.timestamps
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
require 'queries/user/fetch_in_group_rel'
|
4
|
+
require 'queries/group/filter_with_color'
|
5
|
+
|
6
|
+
module Inquery
|
7
|
+
class Query
|
8
|
+
class ChainableTest < Minitest::Unit::TestCase
|
9
|
+
include TestHelper
|
10
|
+
|
11
|
+
def setup
|
12
|
+
self.class.setup_db
|
13
|
+
self.class.setup_base_data
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_fetch_users_in_group_rels
|
17
|
+
result = Queries::User::FetchInGroupRel.run(Group.where('ID IN (1, 2)'))
|
18
|
+
assert_equal User.find([1, 2, 3]), result.to_a
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_fetch_users_in_group_rels_with_select
|
22
|
+
# Fetch all groups user 1 is in
|
23
|
+
group_ids_rel = GroupsUser.where(user_id: 1).select(:group_id)
|
24
|
+
|
25
|
+
# Fetch all users that are in these groups
|
26
|
+
result = Queries::User::FetchInGroupRel.run(group_ids_rel)
|
27
|
+
assert_equal User.find([1, 2, 3]), result.to_a
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_fetch_red_groups
|
31
|
+
result = Queries::Group::FetchRed.run
|
32
|
+
assert_equal Group.find([1]), result.to_a
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_fetch_red_groups_via_scope
|
36
|
+
result = Group.red
|
37
|
+
assert_equal Group.find([1]), result.to_a
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_fetch_green_groups_via_scope
|
41
|
+
result = Group.green
|
42
|
+
assert_equal Group.find([2, 3]), result.to_a
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_fetch_green_groups_via_scope_with_where
|
46
|
+
# Where before
|
47
|
+
result = Group.where('id > 2').green
|
48
|
+
assert_equal Group.find([3]), result.to_a
|
49
|
+
|
50
|
+
# Where after
|
51
|
+
result = Group.green.where('id > 2')
|
52
|
+
assert_equal Group.find([3]), result.to_a
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_fetch_green_groups_with_where
|
56
|
+
# With default scope
|
57
|
+
result = Queries::Group::FetchGreen.run(Group.where('id > 2'))
|
58
|
+
assert_equal Group.find([3]), result.to_a
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_filter_with_color
|
62
|
+
result = Queries::Group::FilterWithColor.run(Group.where('id > 2'), color: 'green')
|
63
|
+
assert_equal Group.find([3]), result.to_a
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require 'queries/user/fetch_all'
|
3
|
+
require 'queries/user/fetch_in_group'
|
4
|
+
require 'queries/group/fetch_as_json'
|
5
|
+
|
6
|
+
module Inquery
|
7
|
+
class QueryTest < Minitest::Unit::TestCase
|
8
|
+
include TestHelper
|
9
|
+
|
10
|
+
def setup
|
11
|
+
self.class.setup_db
|
12
|
+
self.class.setup_base_data
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_fetch_all_users
|
16
|
+
result = Queries::User::FetchAll.run
|
17
|
+
assert_equal User.find([1, 2, 3]), result
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_fetch_users_in_group
|
21
|
+
result = Queries::User::FetchInGroup.run(group_id: 1)
|
22
|
+
assert_equal User.find([1, 2]), result.to_a
|
23
|
+
|
24
|
+
result = Queries::User::FetchInGroup.run(group_id: 2)
|
25
|
+
assert_equal User.find([1, 3]), result.to_a
|
26
|
+
|
27
|
+
result = Queries::User::FetchInGroup.run(group_id: 3)
|
28
|
+
assert_equal User.find([2]), result.to_a
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_fetch_users_in_group_with_invalid_schema
|
32
|
+
assert_raises Schemacop::Exceptions::Validation do
|
33
|
+
Queries::User::FetchInGroup.run
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_fetch_groups_as_json
|
38
|
+
result = Queries::Group::FetchAsJson.call
|
39
|
+
assert_equal Group.all, result
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_fetch_groups_as_json_with_process
|
43
|
+
result = Queries::Group::FetchAsJson.run
|
44
|
+
assert_equal Group.all.to_json, result
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|