modular_routes 0.1.1 → 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/CHANGELOG.md +68 -2
- data/README.md +108 -43
- data/lib/modular_routes.rb +9 -3
- data/lib/modular_routes/builder.rb +74 -21
- data/lib/modular_routes/extension.rb +5 -4
- data/lib/modular_routes/routable.rb +14 -0
- data/lib/modular_routes/routable/non_restful.rb +27 -0
- data/lib/modular_routes/routable/restful.rb +40 -0
- data/lib/modular_routes/routable/standalone.rb +34 -0
- data/lib/modular_routes/scopable.rb +15 -0
- data/lib/modular_routes/scopable/namespace.rb +30 -0
- data/lib/modular_routes/scopable/resource.rb +62 -0
- data/lib/modular_routes/scopable/scope.rb +30 -0
- data/lib/modular_routes/scopable/single_resource.rb +21 -0
- data/lib/modular_routes/version.rb +1 -1
- data/modular_routes.gemspec +1 -1
- metadata +13 -9
- data/lib/modular_routes/options.rb +0 -50
- data/lib/modular_routes/route/non_restful.rb +0 -48
- data/lib/modular_routes/route/restful.rb +0 -31
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5341830eca8ac468d9bf1843691214bf42e7806a9dedee7aa363227b39f2fb1a
|
4
|
+
data.tar.gz: d0ce8a5bd66d31555ab827efafaec86e512939ba8c21470e6738ede33da9a200
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f8ad0fa09d007407fb020109a93b33d7cf6c6b21a041759d5c556b76cb15cfd8c109ea10dac1b9d670d52cee9d1ff43dfe0c9357e88cdcffea3b92a46032d11a
|
7
|
+
data.tar.gz: c4581eede9b12a585ae8d409ad91a5405e4fbf4b040187aa1a41cd94f3599346c5ab0d5e53b709a4d2a37a11f6a93965e1c25da6c2098a1297295efdf7bcb3a2
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,71 @@
|
|
1
|
-
## [
|
1
|
+
## [v0.2.0] - 2021-07-21
|
2
2
|
|
3
|
-
|
3
|
+
**REMOVED**
|
4
|
+
|
5
|
+
- `modular_route` helper was removed due to lack of syntax flexibility and a bit of inline verbosity.
|
6
|
+
|
7
|
+
**NEW**
|
8
|
+
|
9
|
+
- `modular_routes` helper was added to fix the problems encountered on the previous helper. Check the example below:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
modular_routes do
|
13
|
+
resources :books
|
14
|
+
|
15
|
+
get :about, to: "about#index"
|
16
|
+
end
|
17
|
+
```
|
18
|
+
|
19
|
+
The idea was to bring simplicity and proximity to what you already write in your routes file.
|
20
|
+
|
21
|
+
- `namespace` support`
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
namespace :v1 do
|
25
|
+
resources :books
|
26
|
+
end
|
27
|
+
```
|
28
|
+
|
29
|
+
It falls back to Rails default behavior.
|
30
|
+
|
31
|
+
- `scope` support
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
scope :v1 do
|
35
|
+
resources :books
|
36
|
+
end
|
37
|
+
|
38
|
+
scope module: :v1 do
|
39
|
+
resources :books
|
40
|
+
end
|
41
|
+
```
|
42
|
+
|
43
|
+
It falls back to Rails default behavior. In this example it recognizes `/v1/books` and `/books` expecting `BooksController` and `V1::BooksController` respectively.
|
44
|
+
|
45
|
+
- Nested resources support
|
46
|
+
|
47
|
+
```ruby
|
48
|
+
modular_routes do
|
49
|
+
resources :books do
|
50
|
+
resources :comments
|
51
|
+
end
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
It recognizes paths like `/books/1/comments/2`.
|
56
|
+
|
57
|
+
- Standalone (non-resourceful) routes
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
modular_routes do
|
61
|
+
get :about, to: "about#index"
|
62
|
+
end
|
63
|
+
```
|
64
|
+
|
65
|
+
It expects `About::IndexController` to exist in `controllers/about/index_controller.rb`. They don't belong to a resourceful scope.
|
66
|
+
|
67
|
+
If `to` doesn't match `controller#action` pattern, it falls back to Rails default behavior.
|
68
|
+
|
69
|
+
## [0.1.1] - 2021-07-15
|
4
70
|
|
5
71
|
- Initial release
|
data/README.md
CHANGED
@@ -5,16 +5,18 @@
|
|
5
5
|
|
6
6
|
# Modular Routes
|
7
7
|
|
8
|
-
|
8
|
+
_Dedicated controllers for each of your Rails route actions._
|
9
9
|
|
10
|
-
[][gem]
|
10
|
+
[][gem]
|
11
11
|
[][ci]
|
12
12
|
[][coverage]
|
13
13
|
[][codeclimate]
|
14
14
|
|
15
15
|
If you've ever used [Hanami routes](https://guides.hanamirb.org/v1.3/routing/restful-resources/) or already use dedicated controllers for each route action, this gem might be useful.
|
16
16
|
|
17
|
-
Disclaimer
|
17
|
+
**Disclaimer:** There's no better/worse nor right/wrong approach, it's up to you to decide how you prefer to organize the controllers and routes of your application.
|
18
|
+
|
19
|
+
Docs: [Unreleased](https://github.com/vitoravelino/modular_routes/blob/main/README.md), [v0.2.0](https://github.com/vitoravelino/modular_routes/blob/v0.2.0/README.md), [v0.1.1](https://github.com/vitoravelino/modular_routes/blob/v0.1.1/README.md)
|
18
20
|
|
19
21
|
## Motivation
|
20
22
|
|
@@ -32,7 +34,7 @@ Let's imagine that you have to design a full RESTful resource named `articles` w
|
|
32
34
|
| GET | /articles/stats |
|
33
35
|
| POST | /articles/:id/archive |
|
34
36
|
|
35
|
-
How would you organize the controllers and routes of this application
|
37
|
+
**How would you organize the controllers and routes of this application?**
|
36
38
|
|
37
39
|
The most common approach is to have all the actions (RESTful and customs) in the same controller.
|
38
40
|
|
@@ -195,7 +197,7 @@ end
|
|
195
197
|
|
196
198
|
This is the best approach in my opinion because your controller will contain only code related to that specific route action. It will also be easier to test and maintain the code.
|
197
199
|
|
198
|
-
If you've decided to go with the last approach, unless you
|
200
|
+
If you've decided to go with the last approach, unless you organize your routes in [separated files](https://guides.rubyonrails.org/routing.html#breaking-up-very-large-route-file-into-multiple-small-ones), your `config/routes.rb` might get really messy as your application grows due to verbosity.
|
199
201
|
|
200
202
|
So, what if we had a simpler way of doing all of that? Let's take a look at how modular routes can help us.
|
201
203
|
|
@@ -217,20 +219,22 @@ Or install it yourself as:
|
|
217
219
|
|
218
220
|
## Usage
|
219
221
|
|
220
|
-
`
|
222
|
+
`modular_routes` uses Rails route helpers behind the scenes. So you can pretty much use everything except for a few [limitations](#limitations) that will be detailed later.
|
221
223
|
|
222
|
-
For the same example used in the [motivation](#motivation)
|
224
|
+
For the same example used in the [motivation](#motivation), using modular routes we now have
|
223
225
|
|
224
226
|
```ruby
|
225
227
|
# routes.rb
|
226
228
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
229
|
+
modular_routes do
|
230
|
+
resources :articles do
|
231
|
+
collection do
|
232
|
+
post :stats
|
233
|
+
end
|
231
234
|
|
232
|
-
|
233
|
-
|
235
|
+
member do
|
236
|
+
post :archive
|
237
|
+
end
|
234
238
|
end
|
235
239
|
end
|
236
240
|
```
|
@@ -240,16 +244,14 @@ or to be shorter
|
|
240
244
|
```ruby
|
241
245
|
# routes.rb
|
242
246
|
|
243
|
-
|
244
|
-
|
245
|
-
|
247
|
+
modular_routes do
|
248
|
+
resources :articles do
|
249
|
+
post :stats, on: :collection
|
250
|
+
post :archive, on: :member
|
251
|
+
end
|
246
252
|
end
|
247
253
|
```
|
248
254
|
|
249
|
-
The only mandatory option to use `modular_route` helper is to pass `:resources` or `:resource` as key with the following resource name.
|
250
|
-
|
251
|
-
To generate all RESTful routes, you must pass `all: true`. Otherwise nothing will happen unless you pass a block with additional routes.
|
252
|
-
|
253
255
|
The output routes for the code above would be
|
254
256
|
|
255
257
|
| HTTP Verb | Path | Controller#Action | Named Route Helper |
|
@@ -266,22 +268,24 @@ The output routes for the code above would be
|
|
266
268
|
|
267
269
|
### Restricting routes
|
268
270
|
|
269
|
-
You can restrict
|
271
|
+
You can restrict resource RESTful routes with `:only` and `:except` similar to what you can do in Rails.
|
270
272
|
|
271
273
|
```ruby
|
272
|
-
|
273
|
-
|
274
|
+
modular_routes do
|
275
|
+
resources :articles, only: [:index, :show]
|
274
276
|
|
275
|
-
|
276
|
-
|
277
|
+
resources :comments, except: [:destroy]
|
278
|
+
end
|
277
279
|
```
|
278
280
|
|
279
281
|
### Renaming paths
|
280
282
|
|
281
|
-
As in Rails you can
|
283
|
+
As in Rails you can use `:path` to rename route paths.
|
282
284
|
|
283
285
|
```ruby
|
284
|
-
|
286
|
+
modular_routes do
|
287
|
+
resources :articles, path: 'posts'
|
288
|
+
end
|
285
289
|
```
|
286
290
|
|
287
291
|
is going to produce
|
@@ -296,25 +300,15 @@ is going to produce
|
|
296
300
|
| PATCH/PUT | /posts/:id | articles/update#call | article_path(:id) |
|
297
301
|
| DELETE | /posts/:id | articles/destroy#call | article_path(:id) |
|
298
302
|
|
299
|
-
###
|
300
|
-
|
301
|
-
If your Rails app is with API only mode, then `:edit` and `:new` actions won't be applied.
|
303
|
+
### Nesting
|
302
304
|
|
303
|
-
|
304
|
-
|
305
|
-
Some of the restrictions are:
|
306
|
-
|
307
|
-
- `concerns` won't work
|
308
|
-
- `constraints` only as option and not block
|
309
|
-
- no support for nesting
|
310
|
-
|
311
|
-
#### Nesting
|
312
|
-
|
313
|
-
Even without nesting support you can emulate what the expected behaviour would be mixing `namespace` and `path` as detailed below
|
305
|
+
As of version `0.2.0`, modular routes supports nesting just like Rails.
|
314
306
|
|
315
307
|
```ruby
|
316
|
-
|
317
|
-
|
308
|
+
modular_routes do
|
309
|
+
resources :books, only: [] do
|
310
|
+
resources :reviews
|
311
|
+
end
|
318
312
|
end
|
319
313
|
```
|
320
314
|
|
@@ -330,6 +324,77 @@ The output routes for that would be
|
|
330
324
|
| PATCH/PUT | /books/:book_id/reviews/:id | books/reviews/update#call | books_reviews_path(:id) |
|
331
325
|
| DELETE | /books/:book_id/reviews/:id | books/reviews/destroy#call | books_reviews_path(:id) |
|
332
326
|
|
327
|
+
### Non-resourceful routes (standalone)
|
328
|
+
|
329
|
+
Sometimes you want to declare a non-resourceful routes and its straightforward without modular routes:
|
330
|
+
|
331
|
+
```ruby
|
332
|
+
get :about, to: "about/show#call"
|
333
|
+
```
|
334
|
+
|
335
|
+
Even being pretty simple, with modular routes you can omit the `#call` action like
|
336
|
+
|
337
|
+
```ruby
|
338
|
+
modular_routes do
|
339
|
+
get :about, to: "about#show"
|
340
|
+
end
|
341
|
+
```
|
342
|
+
|
343
|
+
It expects `About::IndexController` to exist in `controllers/about/index_controller.rb`.
|
344
|
+
|
345
|
+
If `to` doesn't match `controller#action` pattern, it falls back to Rails default behavior.
|
346
|
+
|
347
|
+
### Scope
|
348
|
+
|
349
|
+
`scope` falls back to Rails default behavior, so you can use it just like you would do it outside modular routes.
|
350
|
+
|
351
|
+
```ruby
|
352
|
+
modular_routes do
|
353
|
+
scope :v1 do
|
354
|
+
resources :books
|
355
|
+
end
|
356
|
+
|
357
|
+
scope module: :v1 do
|
358
|
+
resources :books
|
359
|
+
end
|
360
|
+
end
|
361
|
+
```
|
362
|
+
|
363
|
+
In this example it recognizes `/v1/books` and `/books` expecting `BooksController` and `V1::BooksController` respectively.
|
364
|
+
|
365
|
+
### Namespace
|
366
|
+
|
367
|
+
As `scope`, `namespace` also falls back to Rails default behavior:
|
368
|
+
|
369
|
+
```ruby
|
370
|
+
modular_routes do
|
371
|
+
namespace :v1 do
|
372
|
+
resources :books
|
373
|
+
end
|
374
|
+
end
|
375
|
+
```
|
376
|
+
|
377
|
+
| HTTP Verb | Path | Controller#Action | Named Route Helper |
|
378
|
+
| --------- | ------------------ | --------------------- | ------------------- |
|
379
|
+
| GET | /v1/books | v1/books/index#call | books_path |
|
380
|
+
| GET | /v1/books/new | v1/books/new#call | new_book_path |
|
381
|
+
| POST | /v1/books | v1/books/create#call | books_path |
|
382
|
+
| GET | /v1/books/:id | v1/books/show#call | book_path(:id) |
|
383
|
+
| GET | /v1/books/:id/edit | v1/books/edit#call | edit_book_path(:id) |
|
384
|
+
| PATCH/PUT | /v1/books/:id | v1/books/update#call | book_path(:id) |
|
385
|
+
| DELETE | /v1/books/:id | v1/books/destroy#call | book_path(:id) |
|
386
|
+
|
387
|
+
### API mode
|
388
|
+
|
389
|
+
When `config.api_only` is set to `true`, `:edit` and `:new` routes won't be applied for resources.
|
390
|
+
|
391
|
+
### Limitations
|
392
|
+
|
393
|
+
- `constraints` are supported via `scope :constraints` and options
|
394
|
+
- `concerns` are not supported inside `modular_routes` block but can be declared outside and used as options
|
395
|
+
|
396
|
+
Let us know more limitations by creating a [new issue](https://github.com/vitoravelino/modular_routes/issues/new).
|
397
|
+
|
333
398
|
## Development
|
334
399
|
|
335
400
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/lib/modular_routes.rb
CHANGED
@@ -4,9 +4,15 @@ require "action_dispatch"
|
|
4
4
|
|
5
5
|
require_relative "modular_routes/builder"
|
6
6
|
require_relative "modular_routes/extension"
|
7
|
-
require_relative "modular_routes/
|
8
|
-
require_relative "modular_routes/
|
9
|
-
require_relative "modular_routes/
|
7
|
+
require_relative "modular_routes/routable"
|
8
|
+
require_relative "modular_routes/routable/non_restful"
|
9
|
+
require_relative "modular_routes/routable/restful"
|
10
|
+
require_relative "modular_routes/routable/standalone"
|
11
|
+
require_relative "modular_routes/scopable"
|
12
|
+
require_relative "modular_routes/scopable/namespace"
|
13
|
+
require_relative "modular_routes/scopable/resource"
|
14
|
+
require_relative "modular_routes/scopable/scope"
|
15
|
+
require_relative "modular_routes/scopable/single_resource"
|
10
16
|
require_relative "modular_routes/version"
|
11
17
|
|
12
18
|
module ModularRoutes
|
@@ -6,40 +6,93 @@ module ModularRoutes
|
|
6
6
|
|
7
7
|
attr_reader :routes
|
8
8
|
|
9
|
-
def initialize(
|
10
|
-
@
|
11
|
-
@
|
9
|
+
def initialize(api_only:)
|
10
|
+
@api_only = api_only
|
11
|
+
@scopes = []
|
12
|
+
@routes = []
|
12
13
|
end
|
13
14
|
|
14
15
|
HTTP_METHODS.each do |method|
|
15
|
-
define_method(method) do |action,
|
16
|
-
|
17
|
-
|
16
|
+
define_method(method) do |action, action_options = {}|
|
17
|
+
options = { on: @on }.merge(action_options)
|
18
|
+
routable = build_routable(method, action, options)
|
18
19
|
|
19
|
-
|
20
|
+
if current_scope
|
21
|
+
current_scope.add(routable)
|
22
|
+
else
|
23
|
+
@routes.unshift(routable)
|
24
|
+
end
|
20
25
|
end
|
21
26
|
end
|
22
27
|
|
23
|
-
def collection
|
24
|
-
|
25
|
-
yield
|
26
|
-
ensure
|
27
|
-
@on = nil
|
28
|
+
def collection(&block)
|
29
|
+
apply_inner_scope(:collection, &block)
|
28
30
|
end
|
29
31
|
|
30
|
-
def member
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
32
|
+
def member(&block)
|
33
|
+
apply_inner_scope(:member, &block)
|
34
|
+
end
|
35
|
+
|
36
|
+
def new(&block)
|
37
|
+
apply_inner_scope(:new, &block)
|
38
|
+
end
|
39
|
+
|
40
|
+
def namespace(namespace_name, **options, &block)
|
41
|
+
apply_scopable(:namespace, namespace_name, options, &block)
|
35
42
|
end
|
36
43
|
|
37
|
-
|
38
|
-
|
44
|
+
def scope(*args, **options, &block)
|
45
|
+
apply_scopable(:scope, args, options, &block)
|
46
|
+
end
|
47
|
+
|
48
|
+
def resources(resources_name, **options, &block)
|
49
|
+
apply_scopable(:resources, resources_name, options, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def resource(resource_name, **options, &block)
|
53
|
+
apply_scopable(:resource, resource_name, options, &block)
|
54
|
+
end
|
55
|
+
|
56
|
+
private def build_routable(method, action, options)
|
57
|
+
routable = if current_scope&.resource?
|
58
|
+
:non_restful
|
59
|
+
else
|
60
|
+
:standalone
|
61
|
+
end
|
62
|
+
|
63
|
+
Routable.for(routable, method, action, options)
|
64
|
+
end
|
65
|
+
|
66
|
+
private def build_scopable(type, name, options)
|
67
|
+
Scopable.for(type, name, options.merge(api_only: @api_only))
|
68
|
+
end
|
69
|
+
|
70
|
+
private def apply_scopable(type, name, options, &block)
|
71
|
+
scopable = build_scopable(type, name, options)
|
72
|
+
current_scope&.add(scopable)
|
73
|
+
|
74
|
+
apply_scope(scopable, &block)
|
75
|
+
end
|
76
|
+
|
77
|
+
private def apply_scope(scopable, &block)
|
78
|
+
@scopes << scopable
|
79
|
+
|
80
|
+
block&.call
|
81
|
+
|
82
|
+
@scopes.pop
|
83
|
+
@routes.unshift(scopable) unless current_scope
|
84
|
+
end
|
85
|
+
|
86
|
+
private def apply_inner_scope(type)
|
87
|
+
@on = type
|
88
|
+
|
89
|
+
yield
|
90
|
+
|
91
|
+
@on = nil
|
39
92
|
end
|
40
93
|
|
41
|
-
private def
|
42
|
-
|
94
|
+
private def current_scope
|
95
|
+
@scopes.last
|
43
96
|
end
|
44
97
|
end
|
45
98
|
end
|
@@ -2,10 +2,11 @@
|
|
2
2
|
|
3
3
|
module ModularRoutes
|
4
4
|
module Extension
|
5
|
-
def
|
6
|
-
|
7
|
-
|
8
|
-
route_builder.
|
5
|
+
def modular_routes(**options, &block)
|
6
|
+
api_only = options.fetch(:api_only, api_only?)
|
7
|
+
|
8
|
+
route_builder = Builder.new(api_only: api_only)
|
9
|
+
route_builder.instance_eval(&block)
|
9
10
|
route_builder.routes.each { |route| route.apply(self) }
|
10
11
|
end
|
11
12
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModularRoutes
|
4
|
+
module Routable
|
5
|
+
def self.for(type, *args)
|
6
|
+
case type
|
7
|
+
when :standalone then Standalone.new(*args)
|
8
|
+
when :non_restful then NonRestful.new(*args)
|
9
|
+
when :restful then Restful.new(*args)
|
10
|
+
else raise NotImplementedError
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModularRoutes
|
4
|
+
module Routable
|
5
|
+
class NonRestful
|
6
|
+
def initialize(http_method, action, options)
|
7
|
+
@http_method = http_method
|
8
|
+
@action = action
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def apply(mapper)
|
13
|
+
mapper.public_send(@http_method, @action, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
private def options
|
17
|
+
mutable_options = {
|
18
|
+
to: "#{@action}#call",
|
19
|
+
}
|
20
|
+
|
21
|
+
mutable_options.merge(@options)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private_constant :NonRestful
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModularRoutes
|
4
|
+
module Routable
|
5
|
+
class Restful
|
6
|
+
def initialize(action, resource)
|
7
|
+
@action = action
|
8
|
+
@resource = resource
|
9
|
+
end
|
10
|
+
|
11
|
+
def apply(mapper)
|
12
|
+
mapper.public_send(resource_type, resource_name, options)
|
13
|
+
end
|
14
|
+
|
15
|
+
private def resource_type
|
16
|
+
@resource.resource_type
|
17
|
+
end
|
18
|
+
|
19
|
+
private def resource_name
|
20
|
+
@resource.name
|
21
|
+
end
|
22
|
+
|
23
|
+
private def resource_options
|
24
|
+
@resource.options
|
25
|
+
end
|
26
|
+
|
27
|
+
private def options
|
28
|
+
immutable = {
|
29
|
+
controller: @action,
|
30
|
+
only: @action,
|
31
|
+
action: :call,
|
32
|
+
}
|
33
|
+
|
34
|
+
resource_options.merge(immutable)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private_constant :Restful
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModularRoutes
|
4
|
+
module Routable
|
5
|
+
class Standalone
|
6
|
+
def initialize(http_method, action, options)
|
7
|
+
@http_method = http_method
|
8
|
+
@action = action
|
9
|
+
@options = options
|
10
|
+
end
|
11
|
+
|
12
|
+
def apply(mapper)
|
13
|
+
mapper.public_send(@http_method, @action, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
private def options
|
17
|
+
to = @options.fetch(:to, nil)
|
18
|
+
|
19
|
+
if namespace_controller_pattern?(to)
|
20
|
+
namespace, controller = to.split("#")
|
21
|
+
@options[:to] = "#{namespace}/#{controller}#call"
|
22
|
+
end
|
23
|
+
|
24
|
+
@options
|
25
|
+
end
|
26
|
+
|
27
|
+
private def namespace_controller_pattern?(obj)
|
28
|
+
obj.is_a?(String) && obj.include?("#")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
private_constant :Standalone
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModularRoutes
|
4
|
+
module Scopable
|
5
|
+
def self.for(type, *args)
|
6
|
+
case type
|
7
|
+
when :namespace then Namespace.new(*args)
|
8
|
+
when :resources then Resource.new(*args)
|
9
|
+
when :resource then SingleResource.new(*args)
|
10
|
+
when :scope then Scope.new(*args)
|
11
|
+
else raise NotImplementedError
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModularRoutes
|
4
|
+
module Scopable
|
5
|
+
class Namespace
|
6
|
+
def initialize(name, options)
|
7
|
+
@name = name
|
8
|
+
@options = options.except(:api_only)
|
9
|
+
|
10
|
+
@children = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(route_or_scope)
|
14
|
+
@children << route_or_scope
|
15
|
+
end
|
16
|
+
|
17
|
+
def resource?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply(mapper)
|
22
|
+
mapper.namespace(@name, @options) do
|
23
|
+
@children.each { |route_or_scope| route_or_scope.apply(mapper) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private_constant :Namespace
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModularRoutes
|
4
|
+
module Scopable
|
5
|
+
class Resource
|
6
|
+
attr_reader :name
|
7
|
+
attr_reader :options
|
8
|
+
|
9
|
+
def initialize(name, options)
|
10
|
+
@name = name
|
11
|
+
@children = []
|
12
|
+
|
13
|
+
@api_only = options.delete(:api_only)
|
14
|
+
@only = options.delete(:only) { default_actions }
|
15
|
+
@except = options.delete(:except)
|
16
|
+
@options = { module: name, only: [] }.merge(options)
|
17
|
+
end
|
18
|
+
|
19
|
+
def resource?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
|
23
|
+
def resource_type
|
24
|
+
:resources
|
25
|
+
end
|
26
|
+
|
27
|
+
def add(route_or_scope)
|
28
|
+
@children << route_or_scope
|
29
|
+
end
|
30
|
+
|
31
|
+
def apply(mapper)
|
32
|
+
mapper.public_send(resource_type, @name, @options) do
|
33
|
+
@children.each { |route_or_scope| route_or_scope.apply(mapper) }
|
34
|
+
end
|
35
|
+
|
36
|
+
apply_restful_actions(mapper)
|
37
|
+
end
|
38
|
+
|
39
|
+
private def actions
|
40
|
+
return default_actions - Array(@except) if @except
|
41
|
+
|
42
|
+
Array(@only)
|
43
|
+
end
|
44
|
+
|
45
|
+
private def default_actions
|
46
|
+
if @api_only
|
47
|
+
[:index, :create, :show, :update, :destroy]
|
48
|
+
else
|
49
|
+
[:index, :create, :new, :edit, :show, :update, :destroy]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
private def apply_restful_actions(mapper)
|
54
|
+
actions.each do |action|
|
55
|
+
Routable.for(:restful, action, self).apply(mapper)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private_constant :Resource
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModularRoutes
|
4
|
+
module Scopable
|
5
|
+
class Scope
|
6
|
+
def initialize(args, options)
|
7
|
+
@args = args
|
8
|
+
@options = options.except(:api_only)
|
9
|
+
|
10
|
+
@children = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(route_or_scope)
|
14
|
+
@children << route_or_scope
|
15
|
+
end
|
16
|
+
|
17
|
+
def resource?
|
18
|
+
false
|
19
|
+
end
|
20
|
+
|
21
|
+
def apply(mapper)
|
22
|
+
mapper.scope(*@args, @options) do
|
23
|
+
@children.each { |route_or_scope| route_or_scope.apply(mapper) }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private_constant :Scope
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ModularRoutes
|
4
|
+
module Scopable
|
5
|
+
class SingleResource < Resource
|
6
|
+
def resource_type
|
7
|
+
:resource
|
8
|
+
end
|
9
|
+
|
10
|
+
private def default_actions
|
11
|
+
if @api_only
|
12
|
+
[:create, :show, :update, :destroy]
|
13
|
+
else
|
14
|
+
[:create, :new, :edit, :show, :update, :destroy]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private_constant :SingleResource
|
20
|
+
end
|
21
|
+
end
|
data/modular_routes.gemspec
CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.authors = ["Vítor Avelino"]
|
9
9
|
spec.email = ["contact@vitoravelino.me"]
|
10
10
|
|
11
|
-
spec.summary = "
|
11
|
+
spec.summary = "Dedicated controllers for each of your Rails route actions"
|
12
12
|
spec.description = spec.summary
|
13
13
|
spec.homepage = "https://github.com/vitoravelino/modular_routes"
|
14
14
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: modular_routes
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vítor Avelino
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-07-
|
11
|
+
date: 2021-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -122,8 +122,7 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
-
description:
|
126
|
-
in Rails
|
125
|
+
description: Dedicated controllers for each of your Rails route actions
|
127
126
|
email:
|
128
127
|
- contact@vitoravelino.me
|
129
128
|
executables: []
|
@@ -148,9 +147,15 @@ files:
|
|
148
147
|
- lib/modular_routes.rb
|
149
148
|
- lib/modular_routes/builder.rb
|
150
149
|
- lib/modular_routes/extension.rb
|
151
|
-
- lib/modular_routes/
|
152
|
-
- lib/modular_routes/
|
153
|
-
- lib/modular_routes/
|
150
|
+
- lib/modular_routes/routable.rb
|
151
|
+
- lib/modular_routes/routable/non_restful.rb
|
152
|
+
- lib/modular_routes/routable/restful.rb
|
153
|
+
- lib/modular_routes/routable/standalone.rb
|
154
|
+
- lib/modular_routes/scopable.rb
|
155
|
+
- lib/modular_routes/scopable/namespace.rb
|
156
|
+
- lib/modular_routes/scopable/resource.rb
|
157
|
+
- lib/modular_routes/scopable/scope.rb
|
158
|
+
- lib/modular_routes/scopable/single_resource.rb
|
154
159
|
- lib/modular_routes/version.rb
|
155
160
|
- modular_routes.gemspec
|
156
161
|
homepage: https://github.com/vitoravelino/modular_routes
|
@@ -177,6 +182,5 @@ requirements: []
|
|
177
182
|
rubygems_version: 3.2.15
|
178
183
|
signing_key:
|
179
184
|
specification_version: 4
|
180
|
-
summary:
|
181
|
-
in Rails
|
185
|
+
summary: Dedicated controllers for each of your Rails route actions
|
182
186
|
test_files: []
|
@@ -1,50 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ModularRoutes
|
4
|
-
class Options
|
5
|
-
attr_reader :method
|
6
|
-
attr_reader :module
|
7
|
-
attr_reader :resource
|
8
|
-
attr_reader :options
|
9
|
-
|
10
|
-
def initialize(options)
|
11
|
-
@options = options
|
12
|
-
@method = extract_resource_method
|
13
|
-
@resource = @options.delete(@method)
|
14
|
-
@module = @options.fetch(:module, @resource)
|
15
|
-
@api = @options.delete(:api_only) { false }
|
16
|
-
|
17
|
-
@only = @options.delete(:only)
|
18
|
-
@except = @options.delete(:except)
|
19
|
-
@all = @options.delete(:all)
|
20
|
-
end
|
21
|
-
|
22
|
-
def singular?
|
23
|
-
method == :resource
|
24
|
-
end
|
25
|
-
|
26
|
-
def actions
|
27
|
-
return default_actions if @all
|
28
|
-
|
29
|
-
return default_actions - Array(@except) if @except
|
30
|
-
|
31
|
-
Array(@only)
|
32
|
-
end
|
33
|
-
|
34
|
-
private def default_actions
|
35
|
-
actions = [:index, :create, :new, :edit, :show, :update, :destroy]
|
36
|
-
|
37
|
-
actions -= [:index] if singular?
|
38
|
-
actions -= [:edit, :new] if @api
|
39
|
-
|
40
|
-
actions
|
41
|
-
end
|
42
|
-
|
43
|
-
private def extract_resource_method
|
44
|
-
return :resource if options.include?(:resource)
|
45
|
-
return :resources if options.include?(:resources)
|
46
|
-
|
47
|
-
raise ArgumentError, "you must specify :resource or :resources for `modular_route`"
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
@@ -1,48 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ModularRoutes
|
4
|
-
module Route
|
5
|
-
class NonRestful
|
6
|
-
attr_reader :http_method
|
7
|
-
attr_reader :action
|
8
|
-
|
9
|
-
def initialize(http_method, action, options, scope_options)
|
10
|
-
@http_method = http_method
|
11
|
-
@action = action
|
12
|
-
@options = options
|
13
|
-
@scope_options = scope_options
|
14
|
-
|
15
|
-
unless @options.fetch(:on, nil)
|
16
|
-
raise ArgumentError,
|
17
|
-
"Non-RESTful route should be declared inside `member`/`collection` block or using `:on` key"
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def apply(mapper)
|
22
|
-
method = @scope_options.method
|
23
|
-
resource = @scope_options.resource
|
24
|
-
|
25
|
-
mapper.public_send(method, resource, resource_options) do
|
26
|
-
mapper.public_send(http_method, action, options)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
private def resource_options
|
31
|
-
immutable_options = {
|
32
|
-
only: [],
|
33
|
-
module: @scope_options.module,
|
34
|
-
}
|
35
|
-
|
36
|
-
@scope_options.options.merge(immutable_options)
|
37
|
-
end
|
38
|
-
|
39
|
-
private def options
|
40
|
-
mutable_options = {
|
41
|
-
to: "#{action}#call",
|
42
|
-
}
|
43
|
-
|
44
|
-
mutable_options.merge(@options)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
@@ -1,31 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module ModularRoutes
|
4
|
-
module Route
|
5
|
-
class Restful
|
6
|
-
def initialize(route_options, action)
|
7
|
-
@module = route_options.module
|
8
|
-
@resource = route_options.resource
|
9
|
-
@options = route_options.options
|
10
|
-
@method = route_options.method
|
11
|
-
@action = action
|
12
|
-
end
|
13
|
-
|
14
|
-
def apply(mapper)
|
15
|
-
mapper.public_send(@method, @resource, resource_options)
|
16
|
-
end
|
17
|
-
|
18
|
-
private def resource_options
|
19
|
-
immutable_options = {
|
20
|
-
controller: @action,
|
21
|
-
only: @action,
|
22
|
-
action: :call,
|
23
|
-
}
|
24
|
-
|
25
|
-
mutable_options = { module: @module }
|
26
|
-
|
27
|
-
mutable_options.merge(@options).merge(immutable_options)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|