rubanok 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +36 -0
- data/lib/rubanok.rb +1 -0
- data/lib/rubanok/dsl/mapping.rb +4 -4
- data/lib/rubanok/dsl/matching.rb +13 -8
- data/lib/rubanok/processor.rb +9 -8
- data/lib/rubanok/rule.rb +20 -21
- data/lib/rubanok/version.rb +1 -1
- data/sig/rubanok.rbs +6 -0
- data/sig/rubanok/dsl/mapping.rbs +18 -0
- data/sig/rubanok/dsl/matching.rbs +44 -0
- data/sig/rubanok/processor.rbs +53 -0
- data/sig/rubanok/rule.rbs +29 -0
- data/sig/rubanok/version.rbs +3 -0
- data/sig/typeprof.rb +25 -0
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90f6ca9dfd61a6f143eff1f3ab3c2d7e41502c992879af785dcf414af15249fd
|
4
|
+
data.tar.gz: 8807cbb2a680e9fbc739a9f0214fb4753e24ee55e69da72d9b45c7f121900dd9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2e015d7c3c517e42e5fe42037035db950cc87f9bd0099b38580c03ad156c36d0133681bd659451e8bdb58f15254799ce8d7b3158a1dc998210e3a425867a521d
|
7
|
+
data.tar.gz: 6c22e9a876e86f144726e8dbbe79b42f9d3052feb6f9c31688f7259872a5eed1e5ac66b031b890e0d1b4eceba7d3903331a073f2e13321b9f3d45c9fe53229d2
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -350,6 +350,42 @@ end
|
|
350
350
|
|
351
351
|
**NOTE:** the `planish` method is still available and it uses `#{controller_path.classify.pluralize}Plane".safe_constantize` under the hood (via the `#implicit_plane_class` method).
|
352
352
|
|
353
|
+
## Using with RBS/Steep
|
354
|
+
|
355
|
+
_Read ["Climbing Steep hills, or adopting Ruby 3 types with RBS"](https://evilmartians.com/chronicles/climbing-steep-hills-or-adopting-ruby-types) for the context._
|
356
|
+
|
357
|
+
Rubanok comes with Ruby type signatures (RBS).
|
358
|
+
|
359
|
+
To use them with Steep, add `library "rubanok"` to your Steepfile.
|
360
|
+
|
361
|
+
Since Rubanok provides DSL with implicit context switching (via `instance_eval`), you need to provide type hints for the type checker to help it
|
362
|
+
figure out the current context. Here is an example:
|
363
|
+
|
364
|
+
```ruby
|
365
|
+
class MyProcessor < Rubanok::Processor
|
366
|
+
map :q do |q:|
|
367
|
+
# @type self : Rubanok::Processor
|
368
|
+
raw
|
369
|
+
end
|
370
|
+
|
371
|
+
match :sort_by, :sort, activate_on: :sort_by do
|
372
|
+
# @type self : Rubanok::DSL::Matching::Rule
|
373
|
+
having "status", "asc" do
|
374
|
+
# @type self : Rubanok::Processor
|
375
|
+
raw
|
376
|
+
end
|
377
|
+
|
378
|
+
# @type self : Rubanok::DSL::Matching::Rule
|
379
|
+
default do |sort_by:, sort: "asc"|
|
380
|
+
# @type self : Rubanok::Processor
|
381
|
+
raw
|
382
|
+
end
|
383
|
+
end
|
384
|
+
end
|
385
|
+
```
|
386
|
+
|
387
|
+
Yeah, a lot of annotations 😞 Welcome to the type-safe world!
|
388
|
+
|
353
389
|
## Questions & Answers
|
354
390
|
|
355
391
|
- **Where to put my processor/plane classes?**
|
data/lib/rubanok.rb
CHANGED
data/lib/rubanok/dsl/mapping.rb
CHANGED
@@ -21,8 +21,8 @@ module Rubanok
|
|
21
21
|
end
|
22
22
|
|
23
23
|
module ClassMethods
|
24
|
-
def map(*fields,
|
25
|
-
filter =
|
24
|
+
def map(*fields, activate_on: fields, activate_always: false, ignore_empty_values: Rubanok.ignore_empty_values, filter_with: nil, &block)
|
25
|
+
filter = filter_with
|
26
26
|
|
27
27
|
if filter.is_a?(Symbol)
|
28
28
|
respond_to?(filter) || raise(
|
@@ -30,10 +30,10 @@ module Rubanok
|
|
30
30
|
"Unknown class method #{filter} for #{self}. " \
|
31
31
|
"Make sure that a filter method is defined before the call to .map."
|
32
32
|
)
|
33
|
-
|
33
|
+
filter = method(filter)
|
34
34
|
end
|
35
35
|
|
36
|
-
rule = Rule.new(fields,
|
36
|
+
rule = Rule.new(fields, activate_on: activate_on, activate_always: activate_always, ignore_empty_values: ignore_empty_values, filter_with: filter)
|
37
37
|
|
38
38
|
define_method(rule.to_method_name, &block)
|
39
39
|
|
data/lib/rubanok/dsl/matching.rb
CHANGED
@@ -22,8 +22,8 @@ module Rubanok
|
|
22
22
|
class Clause < Rubanok::Rule
|
23
23
|
attr_reader :values, :id, :block
|
24
24
|
|
25
|
-
def initialize(id, fields, values
|
26
|
-
super(fields,
|
25
|
+
def initialize(id, fields, values, activate_on: fields, activate_always: false, &block)
|
26
|
+
super(fields, activate_on: activate_on, activate_always: activate_always)
|
27
27
|
@id = id
|
28
28
|
@block = block
|
29
29
|
@values = Hash[fields.take(values.size).zip(values)].freeze
|
@@ -39,7 +39,7 @@ module Rubanok
|
|
39
39
|
|
40
40
|
attr_reader :clauses
|
41
41
|
|
42
|
-
def initialize(
|
42
|
+
def initialize(fields, activate_on: fields, activate_always: false, ignore_empty_values: Rubanok.ignore_empty_values, filter_with: nil)
|
43
43
|
super
|
44
44
|
@clauses = []
|
45
45
|
end
|
@@ -55,7 +55,7 @@ module Rubanok
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def default(&block)
|
58
|
-
clauses << Clause.new("#{to_method_name}_default", fields, activate_always: true, &block)
|
58
|
+
clauses << Clause.new("#{to_method_name}_default", fields, [], activate_always: true, &block)
|
59
59
|
end
|
60
60
|
|
61
61
|
private
|
@@ -67,16 +67,21 @@ module Rubanok
|
|
67
67
|
end
|
68
68
|
|
69
69
|
module ClassMethods
|
70
|
-
def match(*fields,
|
71
|
-
rule = Rule.new(fields,
|
70
|
+
def match(*fields, activate_on: fields, activate_always: false, fail_when_no_matches: nil, &block)
|
71
|
+
rule = Rule.new(fields, activate_on: activate_on, activate_always: activate_always)
|
72
72
|
|
73
73
|
rule.instance_eval(&block)
|
74
74
|
|
75
75
|
define_method(rule.to_method_name) do |params = {}|
|
76
|
+
params ||= {} if params.nil?
|
77
|
+
|
76
78
|
clause = rule.matching_clause(params)
|
77
|
-
next default_match_handler(rule, params, options[:fail_when_no_matches]) unless clause
|
78
79
|
|
79
|
-
|
80
|
+
if clause
|
81
|
+
apply_rule! clause, params
|
82
|
+
else
|
83
|
+
default_match_handler(rule, params, fail_when_no_matches)
|
84
|
+
end
|
80
85
|
end
|
81
86
|
|
82
87
|
rule.clauses.each do |clause|
|
data/lib/rubanok/processor.rb
CHANGED
@@ -32,15 +32,15 @@ module Rubanok
|
|
32
32
|
include DSL::Matching
|
33
33
|
include DSL::Mapping
|
34
34
|
|
35
|
+
UNDEFINED = Object.new
|
36
|
+
|
35
37
|
class << self
|
36
|
-
def call(
|
37
|
-
input, params =
|
38
|
-
|
39
|
-
|
40
|
-
else
|
41
|
-
args
|
42
|
-
end
|
38
|
+
def call(input, params = UNDEFINED)
|
39
|
+
input, params = nil, input if params == UNDEFINED
|
40
|
+
|
41
|
+
raise ArgumentError, "Params could not be nil" if params.nil?
|
43
42
|
|
43
|
+
# @type var params: untyped
|
44
44
|
new(input).call(params)
|
45
45
|
end
|
46
46
|
|
@@ -75,7 +75,7 @@ module Rubanok
|
|
75
75
|
# by the rules
|
76
76
|
def project(params)
|
77
77
|
params = params.transform_keys(&:to_sym)
|
78
|
-
params.slice(*fields_set)
|
78
|
+
params.slice(*fields_set.to_a)
|
79
79
|
end
|
80
80
|
|
81
81
|
# DSL to define the #prepare method
|
@@ -86,6 +86,7 @@ module Rubanok
|
|
86
86
|
|
87
87
|
def initialize(input)
|
88
88
|
@input = input
|
89
|
+
@prepared = false
|
89
90
|
end
|
90
91
|
|
91
92
|
def call(params)
|
data/lib/rubanok/rule.rb
CHANGED
@@ -1,15 +1,30 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Rubanok
|
4
|
+
using(Module.new do
|
5
|
+
refine NilClass do
|
6
|
+
def empty?
|
7
|
+
true
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
refine Object do
|
12
|
+
def empty?
|
13
|
+
false
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end)
|
17
|
+
|
4
18
|
class Rule # :nodoc:
|
5
19
|
UNDEFINED = Object.new
|
6
20
|
|
7
|
-
attr_reader :
|
21
|
+
attr_reader :fields, :activate_on, :activate_always, :ignore_empty_values, :filter_with
|
8
22
|
|
9
23
|
def initialize(fields, activate_on: fields, activate_always: false, ignore_empty_values: Rubanok.ignore_empty_values, filter_with: nil)
|
10
|
-
@owner = owner
|
11
24
|
@fields = fields.freeze
|
12
|
-
@activate_on
|
25
|
+
# @type var activate_on: Array[Symbol]
|
26
|
+
activate_on = Array(activate_on)
|
27
|
+
@activate_on = activate_on.freeze
|
13
28
|
@activate_always = activate_always
|
14
29
|
@ignore_empty_values = ignore_empty_values
|
15
30
|
@filter_with = filter_with
|
@@ -18,9 +33,7 @@ module Rubanok
|
|
18
33
|
def project(params)
|
19
34
|
fields.each_with_object({}) do |field, acc|
|
20
35
|
val = fetch_value params, field
|
21
|
-
|
22
|
-
|
23
|
-
acc[field] = val
|
36
|
+
acc[field] = val if val != UNDEFINED
|
24
37
|
acc
|
25
38
|
end
|
26
39
|
end
|
@@ -46,27 +59,13 @@ module Rubanok
|
|
46
59
|
|
47
60
|
val = params[field]
|
48
61
|
|
49
|
-
val = filter_with
|
62
|
+
val = filter_with&.call(val) if filter_with
|
50
63
|
|
51
64
|
return UNDEFINED if empty?(val)
|
52
65
|
|
53
66
|
val
|
54
67
|
end
|
55
68
|
|
56
|
-
using(Module.new do
|
57
|
-
refine NilClass do
|
58
|
-
def empty?
|
59
|
-
true
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
refine Object do
|
64
|
-
def empty?
|
65
|
-
false
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end)
|
69
|
-
|
70
69
|
def empty?(val)
|
71
70
|
return false unless ignore_empty_values
|
72
71
|
|
data/lib/rubanok/version.rb
CHANGED
data/sig/rubanok.rbs
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module Rubanok
|
2
|
+
module DSL
|
3
|
+
module Mapping : Processor
|
4
|
+
class Rule < Rubanok::Rule
|
5
|
+
METHOD_PREFIX: String
|
6
|
+
|
7
|
+
private
|
8
|
+
def build_method_name: () -> String
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods : Module, _RulesAdding
|
12
|
+
def map: (*field fields, ?activate_on: (field | Array[field]), ?activate_always: bool, ?ignore_empty_values: bool, ?filter_with: Symbol) { () -> input } -> void
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.included: (singleton(Processor) base) -> void
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Rubanok
|
2
|
+
class UnexpectedInputError < StandardError
|
3
|
+
end
|
4
|
+
|
5
|
+
module DSL
|
6
|
+
module Matching : Processor
|
7
|
+
class Rule < Rubanok::Rule
|
8
|
+
METHOD_PREFIX: String
|
9
|
+
@method_name: String
|
10
|
+
@fields: Array[field]
|
11
|
+
|
12
|
+
class Clause < Rubanok::Rule
|
13
|
+
@fields: Array[field]
|
14
|
+
|
15
|
+
attr_reader values: params
|
16
|
+
attr_reader id: String
|
17
|
+
attr_reader block: ^() -> input
|
18
|
+
|
19
|
+
def initialize: (String id, Array[field] fields, untyped values, ?activate_on: field | Array[field], ?activate_always: bool) { () -> input } -> void
|
20
|
+
def applicable?: (params) -> bool
|
21
|
+
|
22
|
+
alias to_method_name id
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader clauses: Array[Clause]
|
26
|
+
|
27
|
+
def matching_clause: (hash params) -> Clause?
|
28
|
+
def having: (*untyped values) { () -> input } -> void
|
29
|
+
def default: () { () -> input } -> void
|
30
|
+
|
31
|
+
private
|
32
|
+
def build_method_name: () -> String
|
33
|
+
end
|
34
|
+
|
35
|
+
module ClassMethods : Module, _RulesAdding, Matching
|
36
|
+
def match: (*field fields, ?activate_on: field | Array[field], ?activate_always: bool, ?fail_when_no_matches: bool?) { (Rule) -> void } -> void
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.included: (singleton(Processor) base) -> void
|
40
|
+
|
41
|
+
def default_match_handler: (Rule rule, hash params, bool? fail_when_no_matches) -> void
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Rubanok
|
2
|
+
# Transformation parameters
|
3
|
+
type params = Hash[Symbol | String, untyped]
|
4
|
+
# Untyped Hash
|
5
|
+
type hash = Hash[untyped, untyped]
|
6
|
+
type field = Symbol
|
7
|
+
# Transformation target (we assume that input and output types are the same)
|
8
|
+
type input = Object?
|
9
|
+
|
10
|
+
interface _RulesAdding
|
11
|
+
def add_rule: (Rule rule) -> void
|
12
|
+
end
|
13
|
+
|
14
|
+
class Processor
|
15
|
+
extend DSL::Matching::ClassMethods
|
16
|
+
|
17
|
+
extend DSL::Mapping::ClassMethods
|
18
|
+
|
19
|
+
extend _RulesAdding
|
20
|
+
|
21
|
+
UNDEFINED: Object
|
22
|
+
|
23
|
+
self.@rules: Array[Rule]
|
24
|
+
self.@fields_set: Set[field]
|
25
|
+
|
26
|
+
def self.superclass: () -> singleton(Processor)
|
27
|
+
|
28
|
+
def self.call: (input, params) -> input
|
29
|
+
| (params) -> input
|
30
|
+
def self.rules: () -> Array[Rule]
|
31
|
+
def self.fields_set: () -> Set[field]
|
32
|
+
def self.project: (params) -> params
|
33
|
+
def self.prepare: () { () -> input } -> void
|
34
|
+
|
35
|
+
def initialize: (input) -> void
|
36
|
+
def call: (params) -> input
|
37
|
+
|
38
|
+
attr_accessor input: input
|
39
|
+
|
40
|
+
private
|
41
|
+
attr_accessor prepared: bool
|
42
|
+
|
43
|
+
alias raw input
|
44
|
+
alias prepared? prepared
|
45
|
+
|
46
|
+
def apply_rule!: (Rule rule, params) -> void
|
47
|
+
def prepare: () -> input
|
48
|
+
def prepare!: () -> void
|
49
|
+
def rules: () -> Array[Rule]
|
50
|
+
end
|
51
|
+
|
52
|
+
Plane: singleton(Processor)
|
53
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Rubanok
|
2
|
+
class Rule
|
3
|
+
UNDEFINED: Object
|
4
|
+
|
5
|
+
@method_name: String
|
6
|
+
|
7
|
+
attr_reader fields: Array[field]
|
8
|
+
attr_reader activate_on: Array[field]
|
9
|
+
attr_reader activate_always: bool
|
10
|
+
attr_reader ignore_empty_values: bool
|
11
|
+
attr_reader filter_with: Method?
|
12
|
+
|
13
|
+
%a{rbs:test:skip} def initialize: (
|
14
|
+
Array[field] fields,
|
15
|
+
?activate_on: field | Array[field],
|
16
|
+
?activate_always: bool,
|
17
|
+
?ignore_empty_values: bool,
|
18
|
+
?filter_with: Method?
|
19
|
+
) -> void
|
20
|
+
def project: (params) -> params
|
21
|
+
def applicable?: (params) -> bool
|
22
|
+
def to_method_name: () -> String
|
23
|
+
|
24
|
+
private
|
25
|
+
def build_method_name: () -> String
|
26
|
+
def fetch_value: (params, field) -> Object
|
27
|
+
def empty?: (untyped) -> bool
|
28
|
+
end
|
29
|
+
end
|
data/sig/typeprof.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Run typeprofiler:
|
4
|
+
#
|
5
|
+
# typeprof -Ilib sig/typeprof.rb
|
6
|
+
require "rubanok"
|
7
|
+
|
8
|
+
processor = Class.new(Rubanok::Processor) do
|
9
|
+
map :q do |q:|
|
10
|
+
raw
|
11
|
+
end
|
12
|
+
|
13
|
+
match :sort_by, :sort, activate_on: :sort_by do
|
14
|
+
having "status", "asc" do
|
15
|
+
raw
|
16
|
+
end
|
17
|
+
|
18
|
+
default do |sort_by:, sort: "asc"|
|
19
|
+
raw
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
processor.project({q: "search", sort_by: "name"})
|
25
|
+
processor.call([], {q: "search", sort_by: "name"})
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubanok
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vladimir Dementyev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: actionpack
|
@@ -113,6 +113,13 @@ files:
|
|
113
113
|
- lib/rubanok/rspec.rb
|
114
114
|
- lib/rubanok/rule.rb
|
115
115
|
- lib/rubanok/version.rb
|
116
|
+
- sig/rubanok.rbs
|
117
|
+
- sig/rubanok/dsl/mapping.rbs
|
118
|
+
- sig/rubanok/dsl/matching.rbs
|
119
|
+
- sig/rubanok/processor.rbs
|
120
|
+
- sig/rubanok/rule.rbs
|
121
|
+
- sig/rubanok/version.rbs
|
122
|
+
- sig/typeprof.rb
|
116
123
|
homepage: https://github.com/palkan/rubanok
|
117
124
|
licenses:
|
118
125
|
- MIT
|