rubanok 0.1.1 โ 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 +59 -0
- data/LICENSE.txt +1 -1
- data/README.md +231 -33
- data/lib/rubanok.rb +8 -4
- data/lib/rubanok/dsl/mapping.rb +22 -5
- data/lib/rubanok/dsl/matching.rb +40 -16
- data/lib/rubanok/processor.rb +144 -0
- data/lib/rubanok/rails/controller.rb +59 -14
- data/lib/rubanok/railtie.rb +6 -0
- data/lib/rubanok/rspec.rb +25 -11
- data/lib/rubanok/rule.rb +32 -24
- 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 +27 -58
- data/.gitignore +0 -12
- data/.rspec +0 -3
- data/.rubocop.yml +0 -63
- data/.travis.yml +0 -24
- data/Gemfile +0 -13
- data/Gemfile.local +0 -2
- data/Rakefile +0 -10
- data/bin/console +0 -8
- data/bin/setup +0 -8
- data/gemfiles/rails42.gemfile +0 -6
- data/gemfiles/rails52.gemfile +0 -6
- data/gemfiles/railsmaster.gemfile +0 -5
- data/lib/rubanok/ext/symbolize_keys.rb +0 -13
- data/lib/rubanok/plane.rb +0 -92
- data/rubanok.gemspec +0 -35
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
@@ -2,6 +2,64 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.4.0 (2021-03-05)
|
6
|
+
|
7
|
+
- Ruby 3.0 compatibility. ([@palkan][])
|
8
|
+
|
9
|
+
- Add RBS. ([@palkan][])
|
10
|
+
|
11
|
+
## 0.3.0 (2020-10-21)
|
12
|
+
|
13
|
+
- Add `filter_with: Symbol | Proc` option to `.map` to allowing filtering the input value. ([@palkan][])
|
14
|
+
|
15
|
+
- Allow specifying `ignore_empty_values: *` per rule. ([@palkan][])
|
16
|
+
|
17
|
+
- Add `prepare` DSL method to transform the input once before the first rule is activated. ([@palkan][])
|
18
|
+
|
19
|
+
When no rules match, the method is not called.
|
20
|
+
Useful when you want to perform some default transformations.
|
21
|
+
|
22
|
+
## 0.2.1 (2019-08-24)
|
23
|
+
|
24
|
+
- Fix bug with trying to add a helper for API controller. ([@palkan][])
|
25
|
+
|
26
|
+
Fixes [#10](https://github.com/palkan/rubanok/issues/10).
|
27
|
+
|
28
|
+
## 0.2.0 (2019-08-23)
|
29
|
+
|
30
|
+
- Add `Process.project` and `rubanok_scope` methods to get the Hash of recognized params. ([@palkan][])
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class PostsProcessor < Rubanok::Processor
|
34
|
+
map(:q) { block }
|
35
|
+
match(:page, :per_page, activate_on: :page) { block }
|
36
|
+
end
|
37
|
+
|
38
|
+
PostsProcessor.project(q: "search_me", filter: "smth", page: 2)
|
39
|
+
# => { q: "search_me", page: 2 }
|
40
|
+
|
41
|
+
class PostsController < ApplicationController
|
42
|
+
def index
|
43
|
+
@filter_params = rubanok_scope
|
44
|
+
# or
|
45
|
+
@filter_params = rubanok_scope params.require(:filter), with: PostsProcessor
|
46
|
+
# ...
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
- Improve naming by using "processor" instead of "plane". ([@palkan][])
|
52
|
+
|
53
|
+
See [the discussion](https://github.com/palkan/rubanok/issues/3).
|
54
|
+
|
55
|
+
**NOTE**: Older API is still available without deprecation.
|
56
|
+
|
57
|
+
- Add `fail_when_no_matches` parameter to `match` method. ([@Earendil95][])
|
58
|
+
|
59
|
+
## 0.1.3 (2019-03-05)
|
60
|
+
|
61
|
+
- Fix using `activate_always: true` with `default` matching clause. ([@palkan][])
|
62
|
+
|
5
63
|
## 0.1.1 (2019-01-16)
|
6
64
|
|
7
65
|
- Fix RSpec matcher to call original implementation instead of returning `nil`. ([@palkan][])
|
@@ -15,3 +73,4 @@ Initial implementation.
|
|
15
73
|
Proposal added.
|
16
74
|
|
17
75
|
[@palkan]: https://github.com/palkan
|
76
|
+
[@Earendil95]: https://github.com/Earendil95
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2018 Vladimir Dementyev
|
3
|
+
Copyright (c) 2018-2020 Vladimir Dementyev
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/README.md
CHANGED
@@ -1,9 +1,12 @@
|
|
1
|
-
[![Gem Version](https://badge.fury.io/rb/rubanok.svg)](https://rubygems.org/gems/rubanok)
|
1
|
+
[![Gem Version](https://badge.fury.io/rb/rubanok.svg)](https://rubygems.org/gems/rubanok)
|
2
|
+
![Build](https://github.com/palkan/rubanok/workflows/Build/badge.svg)
|
2
3
|
|
3
4
|
# Rubanok
|
4
5
|
|
5
6
|
Rubanok provides a DSL to build parameters-based data transformers.
|
6
7
|
|
8
|
+
๐ Read the introduction post: ["Carve your controllers like Papa Carlo"](https://evilmartians.com/chronicles/rubanok-carve-your-rails-controllers-like-papa-carlo)
|
9
|
+
|
7
10
|
The typical usage is to describe all the possible collection manipulation for REST `index` action, e.g. filtering, sorting, searching, pagination, etc..
|
8
11
|
|
9
12
|
So, instead of:
|
@@ -11,12 +14,12 @@ So, instead of:
|
|
11
14
|
```ruby
|
12
15
|
class CourseSessionController < ApplicationController
|
13
16
|
def index
|
14
|
-
@sessions = CourseSession
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
@sessions = CourseSession
|
18
|
+
.search(params[:q])
|
19
|
+
.by_course_type(params[:course_type_id])
|
20
|
+
.by_role(params[:role_id])
|
21
|
+
.paginate(page_params)
|
22
|
+
.order(ordering_params)
|
20
23
|
end
|
21
24
|
end
|
22
25
|
```
|
@@ -26,13 +29,13 @@ You have:
|
|
26
29
|
```ruby
|
27
30
|
class CourseSessionController < ApplicationController
|
28
31
|
def index
|
29
|
-
@sessions =
|
32
|
+
@sessions = rubanok_process(
|
30
33
|
# pass input
|
31
34
|
CourseSession.all,
|
32
35
|
# pass params
|
33
36
|
params,
|
34
|
-
# provide a
|
35
|
-
with:
|
37
|
+
# provide a processor to use
|
38
|
+
with: CourseSessionsProcessor
|
36
39
|
)
|
37
40
|
end
|
38
41
|
end
|
@@ -40,34 +43,42 @@ end
|
|
40
43
|
|
41
44
|
Or we can try to infer all the configuration for you:
|
42
45
|
|
43
|
-
|
44
46
|
```ruby
|
45
47
|
class CourseSessionController < ApplicationController
|
46
48
|
def index
|
47
|
-
@sessions =
|
49
|
+
@sessions = rubanok_process(CourseSession.all)
|
48
50
|
end
|
49
51
|
end
|
50
52
|
```
|
51
53
|
|
52
54
|
Requirements:
|
55
|
+
|
53
56
|
- Ruby ~> 2.5
|
54
|
-
- Rails >=
|
57
|
+
- (optional\*) Rails >= 5.2 (Rails 4.2 should work but we don't test against it anymore)
|
58
|
+
|
59
|
+
\* This gem has no dependency on Rails.
|
55
60
|
|
56
61
|
<a href="https://evilmartians.com/">
|
57
62
|
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
58
63
|
|
59
64
|
## Installation
|
60
65
|
|
61
|
-
|
66
|
+
Add to your `Gemfile`:
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
gem "rubanok"
|
70
|
+
```
|
71
|
+
|
72
|
+
And run `bundle install`.
|
62
73
|
|
63
74
|
## Usage
|
64
75
|
|
65
|
-
The core concept of this library is a _plane_
|
76
|
+
The core concept of this library is a processor (previously called _plane_ or _hand plane_, or "ััะฑะฐะฝะพะบ" in Russian). Processor is responsible for mapping parameters to transformations.
|
66
77
|
|
67
78
|
From the example above:
|
68
79
|
|
69
80
|
```ruby
|
70
|
-
class
|
81
|
+
class CourseSessionsProcessor < Rubanok::Processor
|
71
82
|
# You can map keys
|
72
83
|
map :q do |q:|
|
73
84
|
# `raw` is an accessor for input data
|
@@ -76,7 +87,7 @@ class CourseSessionsPlane < Rubanok::Plane
|
|
76
87
|
end
|
77
88
|
|
78
89
|
# The following code
|
79
|
-
|
90
|
+
CourseSessionsProcessor.call(CourseSession.all, q: "xyz")
|
80
91
|
|
81
92
|
# is equal to
|
82
93
|
CourseSession.all.search("xyz")
|
@@ -85,7 +96,7 @@ CourseSession.all.search("xyz")
|
|
85
96
|
You can map multiple keys at once:
|
86
97
|
|
87
98
|
```ruby
|
88
|
-
class
|
99
|
+
class CourseSessionsProcessor < Rubanok::Processor
|
89
100
|
DEFAULT_PAGE_SIZE = 25
|
90
101
|
|
91
102
|
map :page, :per_page do |page:, per_page: DEFAULT_PAGE_SIZE|
|
@@ -97,9 +108,9 @@ end
|
|
97
108
|
There is also `match` method to handle values:
|
98
109
|
|
99
110
|
```ruby
|
100
|
-
class
|
101
|
-
SORT_ORDERS = %w
|
102
|
-
SORTABLE_FIELDS = %w
|
111
|
+
class CourseSessionsProcessor < Rubanok::Processor
|
112
|
+
SORT_ORDERS = %w[asc desc].freeze
|
113
|
+
SORTABLE_FIELDS = %w[id name created_at].freeze
|
103
114
|
|
104
115
|
match :sort_by, :sort do
|
105
116
|
having "course_id", "desc" do
|
@@ -124,16 +135,94 @@ class CourseSessionsPlane < Rubanok::Plane
|
|
124
135
|
raw.order(sort_by => sort)
|
125
136
|
end
|
126
137
|
end
|
138
|
+
|
139
|
+
# strict matching; if Processor will not match parameter, it will raise Rubanok::UnexpectedInputError
|
140
|
+
# You can handle it in controller, for example, with sending 422 Unprocessable Entity to client
|
141
|
+
match :filter, fail_when_no_matches: true do
|
142
|
+
having "active" do
|
143
|
+
raw.active
|
144
|
+
end
|
145
|
+
|
146
|
+
having "finished" do
|
147
|
+
raw.finished
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
```
|
152
|
+
|
153
|
+
By default, Rubanok will not fail if no matches found in `match` rule. You can change it by setting: `Rubanok.fail_when_no_matches = true`.
|
154
|
+
If in example above you will call `CourseSessionsProcessor.call(CourseSession, filter: 'acitve')`, you will get `Rubanok::UnexpectedInputError: Unexpected input: {:filter=>'acitve'}`.
|
155
|
+
|
156
|
+
**NOTE:** Rubanok only matches exact values; more complex matching could be added in the future.
|
157
|
+
|
158
|
+
### Default transformation
|
159
|
+
|
160
|
+
Sometimes it's useful to perform some transformations before **any** rule is activated.
|
161
|
+
|
162
|
+
There is a special `prepare` method which allows you to define the default transformation:
|
163
|
+
|
164
|
+
```ruby
|
165
|
+
class CourseSearchQueryProcessor < Rubanok::Processor
|
166
|
+
prepare do
|
167
|
+
next if raw&.dig(:query, :bool)
|
168
|
+
|
169
|
+
{query: {bool: {filters: []}}}
|
170
|
+
end
|
171
|
+
|
172
|
+
map :ids do |ids:|
|
173
|
+
raw.dig(:query, :bool, :filters) << {terms: {id: ids}}
|
174
|
+
raw
|
175
|
+
end
|
127
176
|
end
|
128
177
|
```
|
129
178
|
|
130
|
-
|
179
|
+
The block should return a new initial value for the _raw_ input or `nil` (no transformation required).
|
180
|
+
|
181
|
+
The `prepare` callback is not executed if no params match, e.g.:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
CourseSearchQueryProcessor.call(nil, {}) #=> nil
|
185
|
+
|
186
|
+
# But
|
187
|
+
CourseSearchQueryProcessor.call(nil, {ids: [1]}) #=> {query {bool: {filters: [{terms: {ids: [1]}}]}}}
|
188
|
+
|
189
|
+
# Note that we can omit the first argument altogether
|
190
|
+
CourseSearchQueryProcessor.call({ids: [1]})
|
191
|
+
```
|
192
|
+
|
193
|
+
### Getting the matching params
|
194
|
+
|
195
|
+
Sometimes it could be useful to get the params that were used to process the data by Rubanok processor (e.g., you can use this data in views to display the actual filters state).
|
196
|
+
|
197
|
+
In Rails, you can use the `#rubanok_scope` method for that:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
class CourseSessionController < ApplicationController
|
201
|
+
def index
|
202
|
+
@sessions = rubanok_process(CourseSession.all)
|
203
|
+
# Returns the Hash of params recognized by the CourseSessionProcessor.
|
204
|
+
# For example:
|
205
|
+
#
|
206
|
+
# params == {q: "search", role_id: 2, date: "2019-08-22"}
|
207
|
+
# @session_filter == {q: "search", role_id: 2}
|
208
|
+
@sessions_filter = rubanok_scope(
|
209
|
+
params.permit(:q, :role_id),
|
210
|
+
with: CourseSessionProcessor
|
211
|
+
)
|
212
|
+
|
213
|
+
# You can omit all the arguments
|
214
|
+
@sessions_filter = rubanok_scope #=> equals to rubanok_scope(params, with: implicit_rubanok_class)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
You can also accesss `rubanok_scope` in views (it's a helper method).
|
131
220
|
|
132
221
|
### Rule activation
|
133
222
|
|
134
223
|
Rubanok _activates_ a rule by checking whether the corresponding keys are present in the params object. All the fields must be present to apply the rule.
|
135
224
|
|
136
|
-
|
225
|
+
Some fields may be optional, or perhaps even all of them. You can use `activate_on` and `activate_always` options to mark something as an optional key instead of a required one:
|
137
226
|
|
138
227
|
```ruby
|
139
228
|
# Always apply the rule; use default values for keyword args
|
@@ -147,18 +236,52 @@ match :sort_by, :sort, activate_on: :sort_by do
|
|
147
236
|
end
|
148
237
|
```
|
149
238
|
|
150
|
-
By default, Rubanok ignores empty param values (using `#empty?` under the hood) and
|
239
|
+
By default, Rubanok ignores empty param values (using `#empty?` under the hood) and will not run matching rules on those values. For example: `{ q: "" }` and `{ q: nil }` won't activate the `map :q` rule.
|
240
|
+
|
241
|
+
You can change this behaviour by specifying `ignore_empty_values: true` option for a particular rule or enabling this behaviour globally via `Rubanok.ignore_empty_values = true` (enabled by default).
|
242
|
+
|
243
|
+
### Input values filtering
|
151
244
|
|
152
|
-
|
245
|
+
For complex input types, such as arrays, it might be useful to _prepare_ the value before passing to a transforming block or prevent the activation altogether.
|
246
|
+
|
247
|
+
We provide a `filter_with:` option for the `.map` method, which could be used as follows:
|
248
|
+
|
249
|
+
```ruby
|
250
|
+
class PostsProcessor < Rubanok::Processor
|
251
|
+
# We can pass a Proc
|
252
|
+
map :ids, filter_with: ->(vals) { vals.reject(&:blank?).presence } do |ids:|
|
253
|
+
raw.where(id: ids)
|
254
|
+
end
|
255
|
+
|
256
|
+
# or define a class method
|
257
|
+
def self.non_empty_array(val)
|
258
|
+
non_blank = val.reject(&:blank?)
|
259
|
+
return if non_blank.empty?
|
260
|
+
|
261
|
+
non_blank
|
262
|
+
end
|
263
|
+
|
264
|
+
# and pass its name as a filter_with value
|
265
|
+
map :ids, filter_with: :non_empty_array do |ids:|
|
266
|
+
raw.where(id: ids)
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
# Filtered values are used in rules
|
271
|
+
PostsProcessor.call(Post.all, {ids: ["1", ""]}) == Post.where(id: ["1"])
|
272
|
+
|
273
|
+
# When filter returns empty value, the rule is not applied
|
274
|
+
PostsProcessor.call(Post.all, {ids: [nil, ""]}) == Post.all
|
275
|
+
```
|
153
276
|
|
154
277
|
### Testing
|
155
278
|
|
156
|
-
One of the benefits of having
|
279
|
+
One of the benefits of having modification logic contained in its own class is the ability to test modifications in isolation:
|
157
280
|
|
158
281
|
```ruby
|
159
282
|
# For example, with RSpec
|
160
|
-
describe
|
161
|
-
let(:input
|
283
|
+
RSpec.describe CourseSessionsProcessor do
|
284
|
+
let(:input) { CourseSession.all }
|
162
285
|
let(:params) { {} }
|
163
286
|
|
164
287
|
subject { described_class.call(input, params) }
|
@@ -174,19 +297,19 @@ end
|
|
174
297
|
Now in your controller you only have to test that the specific _plane_ is applied:
|
175
298
|
|
176
299
|
```ruby
|
177
|
-
describe CourseSessionController do
|
300
|
+
RSpec.describe CourseSessionController do
|
178
301
|
subject { get :index }
|
179
302
|
|
180
303
|
specify do
|
181
|
-
expect { subject }.to
|
182
|
-
with(
|
304
|
+
expect { subject }.to have_rubanok_processed(CourseSession.all)
|
305
|
+
.with(CourseSessionsProcessor)
|
183
306
|
end
|
184
307
|
end
|
185
308
|
```
|
186
309
|
|
187
310
|
**NOTE**: input matching only checks for the class equality.
|
188
311
|
|
189
|
-
To use `
|
312
|
+
To use `have_rubanok_processed` matcher you must add the following line to your `spec_helper.rb` / `rails_helper.rb` (it's added automatically if RSpec defined and `RAILS_ENV`/`RACK_ENV` is equal to `"test"`):
|
190
313
|
|
191
314
|
```ruby
|
192
315
|
require "rubanok/rspec"
|
@@ -194,10 +317,85 @@ require "rubanok/rspec"
|
|
194
317
|
|
195
318
|
### Rails vs. non-Rails
|
196
319
|
|
197
|
-
Rubanok
|
320
|
+
Rubanok does not require Rails, but it has some useful Rails extensions such as `rubanok_process` helper for controllers (included automatically into `ActionController::Base` and `ActionController::API`).
|
198
321
|
|
199
322
|
If you use `ActionController::Metal` you must include the `Rubanok::Controller` module yourself.
|
200
323
|
|
324
|
+
### Processor class inference in Rails controllers
|
325
|
+
|
326
|
+
By default, `rubanok_process` uses the following algorithm to define a processor class: `"#{controller_path.classify.pluralize}Processor".safe_constantize`.
|
327
|
+
|
328
|
+
You can change this by overriding the `#implicit_rubanok_class` method:
|
329
|
+
|
330
|
+
```ruby
|
331
|
+
class ApplicationController < ActionController::Smth
|
332
|
+
# override the `implicit_rubanok_class` method
|
333
|
+
def implicit_rubanok_class
|
334
|
+
"#{controller_path.classify.pluralize}Scoper".safe_constantize
|
335
|
+
end
|
336
|
+
end
|
337
|
+
```
|
338
|
+
|
339
|
+
Now you can use it like this:
|
340
|
+
|
341
|
+
```ruby
|
342
|
+
class CourseSessionsController < ApplicationController
|
343
|
+
def index
|
344
|
+
@sessions = rubanok_process(CourseSession.all, params)
|
345
|
+
# which equals to
|
346
|
+
@sessions = CourseSessionsScoper.call(CourseSession.all, params.to_unsafe_h)
|
347
|
+
end
|
348
|
+
end
|
349
|
+
```
|
350
|
+
|
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
|
+
|
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
|
+
|
389
|
+
## Questions & Answers
|
390
|
+
|
391
|
+
- **Where to put my processor/plane classes?**
|
392
|
+
|
393
|
+
I put mine under `app/planes` (as `<resources>_plane.rb`) in my Rails app.
|
394
|
+
|
395
|
+
- **I don't like the naming ("planes" โ๏ธ?), can I still use the library?**
|
396
|
+
|
397
|
+
Good newsโthe default naming [has been changed](https://github.com/palkan/rubanok/pull/8). "Planes" are still available if you prefer them (just like me ๐).
|
398
|
+
|
201
399
|
## Contributing
|
202
400
|
|
203
401
|
Bug reports and pull requests are welcome on GitHub at https://github.com/palkan/rubanok.
|