modular_routes 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 954ed3363ece12cbe51eca5df6783f6066d49b3b5320dbcf7e9e10d0acf4bc05
4
- data.tar.gz: 80fd6d08afa02b8502999c5ed5d37ca6104b64ef4714464f6adb0407d00fb364
3
+ metadata.gz: 5341830eca8ac468d9bf1843691214bf42e7806a9dedee7aa363227b39f2fb1a
4
+ data.tar.gz: d0ce8a5bd66d31555ab827efafaec86e512939ba8c21470e6738ede33da9a200
5
5
  SHA512:
6
- metadata.gz: 8415af3f02b945890d14770c4e92d1bb2dfb3779773bdff86f6a87aa3e1417cb1c67fb6710c581b33031dc6bdf350fce97e15aa245c2fb7f07327e031c942397
7
- data.tar.gz: 6aad4a4706157f87818541122dc520f83eb22eca43167d08162b40eb575546c82e674c90d83d0882be7aa1d98e82f09429e922aa1aaac4dfbc514710f878c73e
6
+ metadata.gz: f8ad0fa09d007407fb020109a93b33d7cf6c6b21a041759d5c556b76cb15cfd8c109ea10dac1b9d670d52cee9d1ff43dfe0c9357e88cdcffea3b92a46032d11a
7
+ data.tar.gz: c4581eede9b12a585ae8d409ad91a5405e4fbf4b040187aa1a41cd94f3599346c5ab0d5e53b709a4d2a37a11f6a93965e1c25da6c2098a1297295efdf7bcb3a2
data/CHANGELOG.md CHANGED
@@ -1,5 +1,71 @@
1
- ## [Unreleased]
1
+ ## [v0.2.0] - 2021-07-21
2
2
 
3
- ## [0.1.0] - 2021-05-24
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
- _A simple way of having dedicated controllers for each of your route actions in Rails._
8
+ _Dedicated controllers for each of your Rails route actions._
9
9
 
10
- [![gem version](https://img.shields.io/gem/v/modular_routes)][gem]
10
+ [![gem version](https://img.shields.io/gem/v/modular_routes?color=blue)][gem]
11
11
  [![CI](https://github.com/vitoravelino/modular_routes/actions/workflows/ci.yml/badge.svg)][ci]
12
12
  [![Coverage](https://github.com/vitoravelino/modular_routes/actions/workflows/coverage.yml/badge.svg)][coverage]
13
13
  [![Maintainability](https://img.shields.io/codeclimate/maintainability/vitoravelino/modular_routes)][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: 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.
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 organizes 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.
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
- `modular_route` uses `resource` and `resources` route helpers behind the scenes. So you can pretty much use almost everything you already use with them except for a few [limitations](#limitations) that will be explained later.
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) section, using modular routes we now have
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
- modular_route resources: :articles, all: true do
228
- collection do
229
- post :stats
230
- end
229
+ modular_routes do
230
+ resources :articles do
231
+ collection do
232
+ post :stats
233
+ end
231
234
 
232
- member do
233
- post :archive
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
- modular_route resources: :articles, all: true do
244
- post :stats, on: :collection
245
- post :archive, on: :member
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 the routes for the RESTful with `:only` and `:except` similar to what you can do in Rails by default.
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
- modular_route resources: :articles, only: [:index, :show]
273
- ```
274
+ modular_routes do
275
+ resources :articles, only: [:index, :show]
274
276
 
275
- ```ruby
276
- modular_route resources: :articles, except: [:destroy]
277
+ resources :comments, except: [:destroy]
278
+ end
277
279
  ```
278
280
 
279
281
  ### Renaming paths
280
282
 
281
- As in Rails you can simply use `:path` attribute.
283
+ As in Rails you can use `:path` to rename route paths.
282
284
 
283
285
  ```ruby
284
- modular_route resources: :articles, all: true, path: 'posts'
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
- ### API mode
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
- ### Limitations
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
- namespace :books, path: 'books/:book_id' do
317
- modular_route resources: :reviews, all: true
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.
@@ -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/options"
8
- require_relative "modular_routes/route/non_restful"
9
- require_relative "modular_routes/route/restful"
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(route_options)
10
- @route_options = route_options
11
- @routes = build_resource_routes
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, options = {}|
16
- mutable_options = { on: @on }
17
- merged_options = mutable_options.merge(options)
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
- @routes.unshift(Route::NonRestful.new(method, action, merged_options, @route_options))
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
- @on = :collection
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
- @on = :member
32
- yield
33
- ensure
34
- @on = nil
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
- private def build_resource_routes
38
- @route_options.actions.map { |action| build_resource_route(action) }
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 build_resource_route(action)
42
- Route::Restful.new(@route_options, action)
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 modular_route(options, &block)
6
- route_options = Options.new(options.merge(api_only: api_only?))
7
- route_builder = Builder.new(route_options)
8
- route_builder.instance_eval(&block) if block
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ModularRoutes
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
@@ -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 = "A simple way of having dedicated controllers for each of your route actions in Rails"
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.1.1
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-15 00:00:00.000000000 Z
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: A simple way of having dedicated controllers for each of your route actions
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/options.rb
152
- - lib/modular_routes/route/non_restful.rb
153
- - lib/modular_routes/route/restful.rb
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: A simple way of having dedicated controllers for each of your route actions
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