parascope 0.1.0 → 0.2.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/README.md +29 -15
- data/bin/console +12 -1
- data/lib/parascope/query.rb +47 -32
- data/lib/parascope/query/api_block.rb +47 -3
- data/lib/parascope/query/api_methods.rb +14 -3
- data/lib/parascope/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d24fcd353f9b0a8dc2091b654e35c4f6b3f973c4
|
4
|
+
data.tar.gz: 23a9825594b4ab16faa6deb34b4da2b5232fdd8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c27284dc7a29103d72502a5db3232eca92fb99341df633e0f77c7c905a269e85bdc75ecf4a45e8a5945b94e0dbb72a289aa76fe7b24f41341d64421fec547e44
|
7
|
+
data.tar.gz: f5ae369d56e2ae924a358e950e8196127c1c31bc8a9c13d833fe479a6dcf36489b678502f3cebfee398f04d5281625afbef2c92ace9b24e2f37cafacb35fce73
|
data/README.md
CHANGED
@@ -28,7 +28,7 @@ Or install it yourself as:
|
|
28
28
|
|
29
29
|
## Usage
|
30
30
|
|
31
|
-
Despite the fact `
|
31
|
+
Despite the fact `parascope` was intended to help building ActiveRecord relations
|
32
32
|
via scopes or query methods, it's usage is not limited to ActiveRecord cases and
|
33
33
|
may be used with any arbitrary classes and objects. In fact, the only gem's dependency
|
34
34
|
is `hashie`, and for development and testing, `OpenStruct` instance is used as a
|
@@ -44,12 +44,21 @@ scope manipulations using `query_by`, `sift_by` and other class methods bellow.
|
|
44
44
|
|
45
45
|
- `query_by(*presence_fields, **value_fields, &block)` declares a scope-generation query
|
46
46
|
block that will be executed if, and only if all values of query params at the keys of
|
47
|
-
`presence_fields` are present in activesupport's definition of presence and all
|
48
|
-
|
47
|
+
`presence_fields` are present in activesupport's definition of presence and all `value_fields`
|
48
|
+
are present in query params as is. The block is executed in context of query
|
49
49
|
object. All values of specified params are yielded to the block. If the block
|
50
|
-
returns a non-nil value, it becomes a new scope for
|
51
|
-
there can be multiple `query_by` block definitions.
|
52
|
-
|
50
|
+
returns a non-nil value, it becomes a new scope for subsequent processing. Of course,
|
51
|
+
there can be multiple `query_by` block definitions. Methods accepts additional options:
|
52
|
+
- `:index` - allows to specify order of query block applications. By default all query
|
53
|
+
blocks have index of 0;
|
54
|
+
- `:if` - specifies condition according to which query should be applied. If Symbol
|
55
|
+
or String is passed, calls corresponding method. If Proc is passed, it is executed
|
56
|
+
in context of query object. Note that this is optional condition, and does not
|
57
|
+
overwrite original param-based condition for a query block that should always be met.
|
58
|
+
- `:unless` - the same as `:if` option, but with reversed boolean check.
|
59
|
+
|
60
|
+
- `query(&block)` declares scope-generation block that is always executed. As `query_by`,
|
61
|
+
accepts `:index`, `:if` and `:unless` options.
|
53
62
|
|
54
63
|
- `sift_by(*presence_fields, **value_fields, &block)` method is used to hoist sets of
|
55
64
|
query definitions that should be applied if, and only if, all specified values
|
@@ -57,6 +66,9 @@ scope manipulations using `query_by`, `sift_by` and other class methods bellow.
|
|
57
66
|
values of specified fields are yielded to the block. Such `sift_by` definitions
|
58
67
|
may be nested in any depth.
|
59
68
|
|
69
|
+
- `sifter` alias for `sift_by`. Results in a more readable construct when a single
|
70
|
+
presence field is passed. For example, `sifter(:paginated)`.
|
71
|
+
|
60
72
|
- `base_scope(&block)` method is used to define a base scope as a starting point
|
61
73
|
of scope-generating process. If this method is called from `sift_by` block,
|
62
74
|
top-level base scope is yielded to the method block. Note that `base_scope` will
|
@@ -86,14 +98,16 @@ scope manipulations using `query_by`, `sift_by` and other class methods bellow.
|
|
86
98
|
it's mutated version corresponding to passed `query_by` arguments.
|
87
99
|
|
88
100
|
- `guard(&block)` executes a passed `block`. If this execution returns falsy value,
|
89
|
-
`
|
101
|
+
`GuardViolationError` is raised. You can use this method to ensure safety of param
|
90
102
|
values interpolation to a SQL string in a `query_by` block for example.
|
91
103
|
|
92
|
-
- `resolved_scope(override_params =
|
93
|
-
all queries and sifted queries that fit to query params applied to
|
94
|
-
Optionally, additional params may be passed to override the ones passed on
|
95
|
-
initialization.
|
96
|
-
|
104
|
+
- `resolved_scope(*presence_keys, override_params = {})` returns a resulting scope
|
105
|
+
generated by all queries and sifted queries that fit to query params applied to
|
106
|
+
base scope. Optionally, additional params may be passed to override the ones passed on
|
107
|
+
initialization. For convinience, you may pass list of keys that should be resolved
|
108
|
+
to `true` with params (for example, `resolved_scope(:with_projects)` instead of
|
109
|
+
`resolved_scope(with_projects: true)`). It's the main `Query` instance method that
|
110
|
+
returns the sole purpose of it's instances.
|
97
111
|
|
98
112
|
### Usage example with ActiveRecord Relation as a scope
|
99
113
|
|
@@ -120,12 +134,12 @@ class UserQuery < Parascope::Query
|
|
120
134
|
|
121
135
|
base_scope { |scope| scope.order(scol => sdir) }
|
122
136
|
|
123
|
-
query_by(
|
137
|
+
query_by(sort_column: 'name') do
|
124
138
|
scope.reorder("CONCAT(first_name, ' ', last_name) #{sdir}")
|
125
139
|
end
|
126
140
|
end
|
127
141
|
|
128
|
-
|
142
|
+
sifter :with_projects do
|
129
143
|
base_scope { |scope| scope.joins(:projects) }
|
130
144
|
|
131
145
|
query_by :project_name do |name|
|
@@ -138,7 +152,7 @@ class UserQuery < Parascope::Query
|
|
138
152
|
end
|
139
153
|
|
140
154
|
def project_users
|
141
|
-
@project_users ||= resolved_scope(with_projects
|
155
|
+
@project_users ||= resolved_scope(:with_projects)
|
142
156
|
end
|
143
157
|
end
|
144
158
|
|
data/bin/console
CHANGED
@@ -45,12 +45,23 @@ class Query < Parascope::Query
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
sift_by :other_baz do |other|
|
49
|
+
query { scope.tap{ scope.other_baz = other } }
|
50
|
+
end
|
51
|
+
|
52
|
+
query(if: :condition?) { scope.tap{ scope.by_instance_condition = true } }
|
53
|
+
query(unless: :bad_condition?) { scope.tap{ scope.not_bad_condition = true } }
|
54
|
+
|
55
|
+
def condition?
|
56
|
+
!!params[:condition]
|
57
|
+
end
|
58
|
+
|
48
59
|
def upcase(str)
|
49
60
|
str.upcase
|
50
61
|
end
|
51
62
|
end
|
52
63
|
|
53
|
-
q = Query.new(foo: 'foo', bar: 'bar', baz: 'baz', bak: 'bak', nested_baz: 'nb')
|
64
|
+
q = Query.new(foo: 'foo', bar: 'bar', baz: 'baz', bak: 'bak', nested_baz: 'nb', other_baz: 'ob')
|
54
65
|
|
55
66
|
require "pry"
|
56
67
|
Pry.start
|
data/lib/parascope/query.rb
CHANGED
@@ -9,7 +9,9 @@ module Parascope
|
|
9
9
|
extend ApiMethods
|
10
10
|
|
11
11
|
UndefinedScopeError = Class.new(StandardError)
|
12
|
-
|
12
|
+
GuardViolationError = Class.new(ArgumentError)
|
13
|
+
# for backward-compatability
|
14
|
+
UnpermittedError = GuardViolationError
|
13
15
|
|
14
16
|
attr_reader :params
|
15
17
|
def_delegator :params, :[]
|
@@ -25,7 +27,8 @@ module Parascope
|
|
25
27
|
def initialize(params, scope: nil, **attrs)
|
26
28
|
@params = Hashie::Mash.new(klass.defaults).merge(params || {})
|
27
29
|
@scope = scope unless scope.nil?
|
28
|
-
@attrs = attrs
|
30
|
+
@attrs = attrs.freeze
|
31
|
+
@base_params = @params
|
29
32
|
define_attr_readers
|
30
33
|
end
|
31
34
|
|
@@ -48,10 +51,11 @@ module Parascope
|
|
48
51
|
scope
|
49
52
|
end
|
50
53
|
|
51
|
-
def resolved_scope(
|
52
|
-
|
54
|
+
def resolved_scope(*args)
|
55
|
+
arg_params = args.pop if args.last.is_a?(Hash)
|
56
|
+
return sifted_instance.resolved_scope! if arg_params.nil? && args.empty?
|
53
57
|
|
54
|
-
clone_with_params(
|
58
|
+
clone_with_params(trues(args).merge(arg_params || {})).resolved_scope
|
55
59
|
end
|
56
60
|
|
57
61
|
def klass
|
@@ -65,9 +69,9 @@ module Parascope
|
|
65
69
|
attr_reader :attrs
|
66
70
|
|
67
71
|
def sifted_instance
|
68
|
-
|
72
|
+
blocks = klass.sift_blocks.select{ |block| block.fits?(self) }
|
69
73
|
|
70
|
-
|
74
|
+
blocks.size > 0 ? sifted_instance_for(blocks) : self
|
71
75
|
end
|
72
76
|
|
73
77
|
def resolved_scope!
|
@@ -78,37 +82,31 @@ module Parascope
|
|
78
82
|
end
|
79
83
|
|
80
84
|
def apply_block!
|
81
|
-
if block && block.fits?(
|
85
|
+
if block && block.fits?(self)
|
82
86
|
scope = instance_exec(*block.values_for(params), &block.block)
|
83
87
|
@scope = scope unless scope.nil?
|
84
88
|
end
|
85
89
|
self
|
86
90
|
end
|
87
91
|
|
88
|
-
def sifted!(
|
92
|
+
def sifted!(query, blocks)
|
89
93
|
@attrs = query.attrs
|
90
94
|
define_attr_readers
|
91
95
|
singleton_class.query_blocks.replace query.klass.query_blocks.dup
|
92
96
|
singleton_class.guard_blocks.replace query.klass.guard_blocks.dup
|
93
97
|
singleton_class.base_scope(&query.klass.base_scope)
|
94
|
-
|
98
|
+
blocks.each do |block|
|
99
|
+
singleton_class.instance_exec(*block.values_for(params), &block.block)
|
100
|
+
end
|
95
101
|
params.replace(singleton_class.defaults.merge(params))
|
96
102
|
@sifted = true
|
97
103
|
end
|
98
104
|
|
99
|
-
|
100
|
-
|
101
|
-
def guard_all
|
102
|
-
klass.guard_blocks.each{ |block| guard(&block) }
|
103
|
-
end
|
104
|
-
|
105
|
-
def guard(&block)
|
106
|
-
unless instance_exec(&block)
|
107
|
-
fail UnpermittedError, "processing is not allowed by guard block\non #{block.source_location.join(':')}"
|
108
|
-
end
|
105
|
+
def sifted?
|
106
|
+
!!@sifted
|
109
107
|
end
|
110
108
|
|
111
|
-
def clone_with_scope(scope, block)
|
109
|
+
def clone_with_scope(scope, block = nil)
|
112
110
|
clone.tap do |query|
|
113
111
|
query.scope = scope
|
114
112
|
query.block = block
|
@@ -116,28 +114,45 @@ module Parascope
|
|
116
114
|
end
|
117
115
|
|
118
116
|
def clone_with_params(other_params)
|
119
|
-
|
120
|
-
query.params =
|
117
|
+
dup.tap do |query|
|
118
|
+
query.params = @base_params.merge(other_params)
|
119
|
+
query.remove_instance_variable('@sifted') if query.instance_variable_defined?('@sifted')
|
120
|
+
query.remove_instance_variable('@scope') if query.instance_variable_defined?('@scope')
|
121
|
+
query.define_attr_readers
|
121
122
|
end
|
122
123
|
end
|
123
124
|
|
124
|
-
def
|
125
|
+
def clone_sifted_with(blocks)
|
125
126
|
dup.tap do |query|
|
126
|
-
query.sifted!(
|
127
|
+
query.sifted!(self, blocks)
|
127
128
|
end
|
128
129
|
end
|
129
130
|
|
130
|
-
def
|
131
|
-
|
131
|
+
def define_attr_readers
|
132
|
+
@attrs.each do |name, value|
|
133
|
+
define_singleton_method(name){ value }
|
134
|
+
end
|
132
135
|
end
|
133
136
|
|
134
|
-
|
135
|
-
|
137
|
+
private
|
138
|
+
|
139
|
+
def guard_all
|
140
|
+
klass.guard_blocks.each{ |block| guard(&block) }
|
136
141
|
end
|
137
142
|
|
138
|
-
def
|
139
|
-
|
140
|
-
|
143
|
+
def guard(&block)
|
144
|
+
unless instance_exec(&block)
|
145
|
+
fail GuardViolationError, "guard block violated on #{block.source_location.join(':')}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def sifted_instance_for(blocks)
|
150
|
+
clone_sifted_with(blocks).sifted_instance
|
151
|
+
end
|
152
|
+
|
153
|
+
def trues(keys)
|
154
|
+
keys.each_with_object({}) do |key, hash|
|
155
|
+
hash[key] = true
|
141
156
|
end
|
142
157
|
end
|
143
158
|
end
|
@@ -1,7 +1,22 @@
|
|
1
1
|
module Parascope
|
2
|
-
class Query::ApiBlock
|
3
|
-
|
4
|
-
|
2
|
+
class Query::ApiBlock
|
3
|
+
OPTION_KEYS = %i[index if unless].freeze
|
4
|
+
private_constant :OPTION_KEYS
|
5
|
+
|
6
|
+
attr_reader :presence_fields, :value_fields, :block, :options
|
7
|
+
|
8
|
+
def initialize(presence_fields:, value_fields:, block:)
|
9
|
+
@options = extract_options!(value_fields)
|
10
|
+
|
11
|
+
@presence_fields, @value_fields, @block =
|
12
|
+
presence_fields, value_fields, block
|
13
|
+
end
|
14
|
+
|
15
|
+
def fits?(query)
|
16
|
+
return false unless conditions_met_by?(query)
|
17
|
+
|
18
|
+
(presence_fields.size == 0 && value_fields.size == 0) ||
|
19
|
+
values_for(query.params).all?{ |value| present?(value) }
|
5
20
|
end
|
6
21
|
|
7
22
|
def values_for(params)
|
@@ -12,12 +27,41 @@ module Parascope
|
|
12
27
|
value.respond_to?(:empty?) ? !value.empty? : !!value
|
13
28
|
end
|
14
29
|
|
30
|
+
def index
|
31
|
+
options[:index] || 0
|
32
|
+
end
|
33
|
+
|
15
34
|
private
|
16
35
|
|
36
|
+
def extract_options!(fields)
|
37
|
+
fields.keys.each_with_object({}) do |key, options|
|
38
|
+
options[key] = fields.delete(key) if OPTION_KEYS.include?(key)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
17
42
|
def valued_values_for(params)
|
18
43
|
value_fields.map do |field, required_value|
|
19
44
|
params[field] == required_value && required_value
|
20
45
|
end
|
21
46
|
end
|
47
|
+
|
48
|
+
def conditions_met_by?(query)
|
49
|
+
condition_met?(query, :if) && condition_met?(query, :unless)
|
50
|
+
end
|
51
|
+
|
52
|
+
def condition_met?(query, key)
|
53
|
+
return true unless options.key?(key)
|
54
|
+
|
55
|
+
condition = options[key]
|
56
|
+
|
57
|
+
value =
|
58
|
+
case condition
|
59
|
+
when String, Symbol then query.send(condition)
|
60
|
+
when Proc then query.instance_exec(&condition)
|
61
|
+
else condition
|
62
|
+
end
|
63
|
+
|
64
|
+
key == :if ? value : !value
|
65
|
+
end
|
22
66
|
end
|
23
67
|
end
|
@@ -15,13 +15,24 @@ module Parascope
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def sift_by(*presence_fields, **value_fields, &block)
|
18
|
-
sift_blocks.push Query::ApiBlock.new(
|
18
|
+
sift_blocks.push Query::ApiBlock.new(
|
19
|
+
presence_fields: presence_fields,
|
20
|
+
value_fields: value_fields,
|
21
|
+
block: block
|
22
|
+
)
|
19
23
|
end
|
20
24
|
|
21
|
-
def query_by(*presence_fields,
|
22
|
-
query_blocks.push Query::ApiBlock.new(
|
25
|
+
def query_by(*presence_fields, **value_fields, &block)
|
26
|
+
query_blocks.push Query::ApiBlock.new(
|
27
|
+
presence_fields: presence_fields,
|
28
|
+
value_fields: value_fields,
|
29
|
+
block: block
|
30
|
+
)
|
23
31
|
end
|
24
32
|
|
33
|
+
alias_method :sifter, :sift_by
|
34
|
+
alias_method :query, :query_by
|
35
|
+
|
25
36
|
def guard(&block)
|
26
37
|
guard_blocks.push block
|
27
38
|
end
|
data/lib/parascope/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: parascope
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Artem Kuzko
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-05-
|
11
|
+
date: 2016-05-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: hashie
|