Skip to content

Commit baacdda

Browse files
committed
Optional definition params option
1 parent bae044a commit baacdda

File tree

5 files changed

+103
-27
lines changed

5 files changed

+103
-27
lines changed

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
## v2.3.1
44

5-
* Add timestamp on when routes where generated into banner
5+
* Add timestamp on when routes.js was generated into banner.
66
* Fix application specified directly without proc. [#323](https://github.com/railsware/js-routes/issues/323)
7+
* Support `optional_definition_params` option. See [Related Docs](./Readme.md#optional-definition-params).
78

89
## v2.3.0
910

Readme.md

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ There are several possible ways to setup JsRoutes:
4242

4343
### Quick Start
4444

45-
Setup [Rack Middleware](https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack)
46-
to automatically generate and maintain `routes.js` file and corresponding
45+
Setup [Rack Middleware](https://guides.rubyonrails.org/rails_on_rack.html#action-dispatcher-middleware-stack)
46+
to automatically generate and maintain `routes.js` file and corresponding
4747
[Typescript definitions](https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html) `routes.d.ts`:
4848

4949
#### Use a Generator
@@ -70,7 +70,7 @@ import {post_path} from '../routes';
7070
alert(post_path(1))
7171
```
7272

73-
Upgrade js building process to update js-routes files in `Rakefile`:
73+
Upgrade js building process to update js-routes files in `Rakefile`:
7474

7575
``` ruby
7676
task "javascript:build" => "js:routes"
@@ -235,7 +235,7 @@ JsRoutes.definitions! # to output to file
235235
JsRoutes.definitions # to output to string
236236
```
237237

238-
Even more advanced setups can be achieved by setting `module_type` to `DTS` inside [configuration](#module_type)
238+
Even more advanced setups can be achieved by setting `module_type` to `DTS` inside [configuration](#module_type)
239239
which will cause any `JsRoutes` instance to generate defintions instead of routes themselves.
240240

241241
<div id="sprockets"></div>
@@ -324,9 +324,13 @@ Options to configure JavaScript file generator. These options are only available
324324
* Sample route call when option is set to true: `users() // => /users`
325325
* `application` - a key to specify which rails engine you want to generate routes too.
326326
* This option allows to only generate routes for a specific rails engine, that is mounted into routes instead of all Rails app routes
327-
* Default: `Rails.application`
327+
* It is recommended to wrap the value with `lambda`. This will reduce the reliance on order during initialization your application.
328+
* Default: `-> { Rails.application }`
328329
* `file` - a file location where generated routes are stored
329330
* Default: `app/javascript/routes.js` if setup with Webpacker, otherwise `app/assets/javascripts/routes.js` if setup with Sprockets.
331+
* `optional_definition_params` - make all route paramters in definition optional
332+
* See [related compatibility issue](#optional-definition-params)
333+
* Default: `false`
330334

331335
<div id="formatter-options"></div>
332336

@@ -358,33 +362,33 @@ import {
358362
user_path, user_project_path, company_path
359363
} from 'routes';
360364

361-
users_path()
365+
users_path()
362366
// => "/users"
363367

364-
user_path(1)
368+
user_path(1)
365369
// => "/users/1"
366-
367-
user_path(1, {format: 'json'})
370+
371+
user_path(1, {format: 'json'})
368372
// => "/users/1.json"
369373

370-
user_path(1, {anchor: 'profile'})
374+
user_path(1, {anchor: 'profile'})
371375
// => "/users/1#profile"
372376

373-
new_user_project_path(1, {format: 'json'})
377+
new_user_project_path(1, {format: 'json'})
374378
// => "/users/1/projects/new.json"
375379

376-
user_project_path(1,2, {q: 'hello', custom: true})
380+
user_project_path(1,2, {q: 'hello', custom: true})
377381
// => "/users/1/projects/2?q=hello&custom=true"
378382

379-
user_project_path(1,2, {hello: ['world', 'mars']})
383+
user_project_path(1,2, {hello: ['world', 'mars']})
380384
// => "/users/1/projects/2?hello%5B%5D=world&hello%5B%5D=mars"
381385

382386
var google = {id: 1, name: "Google"};
383-
company_path(google)
387+
company_path(google)
384388
// => "/companies/1"
385389

386390
var google = {id: 1, name: "Google", to_param: "google"};
387-
company_path(google)
391+
company_path(google)
388392
// => "/companies/google"
389393
```
390394

@@ -427,14 +431,63 @@ In this case you would need to pass a special key to help:
427431
``` js
428432
import {company_project_path} from '../routes'
429433

430-
company_project_path({company_id: 1, id: 2}) // => Not enough parameters
431-
company_project_path({company_id: 1, id: 2, _options: true}) // => "/companies/1/projects/2"
434+
company_project_path({company_id: 1, id: 2})
435+
// => Not enough parameters
436+
company_project_path({company_id: 1, id: 2, _options: true})
437+
// => "/companies/1/projects/2"
438+
```
439+
440+
Use `special_options_key` to configure the `_options` parameter name.
441+
442+
<div id="optional-definition-params"></div>
443+
444+
### Rails required parameters specified as optional
445+
446+
Rails is very flexible on how route parameters can be specified.
447+
All of the following calls will make the same result:
448+
449+
``` ruby
450+
# Given route
451+
# /inboxes/:inbox_id/messages/:message_id/attachments/:id
452+
# every call below returns:
453+
# => "/inboxes/1/messages/2/attachments/3"
454+
455+
inbox_message_attachment_path(1, 2, 3)
456+
inbox_message_attachment_path(1, 2, id: 3)
457+
inbox_message_attachment_path(1, message_id: 2, id: 3)
458+
inbox_message_attachment_path(inbox_id: 1, message_id: 2, id: 3)
459+
460+
# including these mad versions
461+
inbox_message_attachment_path(2, inbox_id: 1, id: 3)
462+
inbox_message_attachment_path(1, 3, message_id: 2)
463+
inbox_message_attachment_path(3, inbox_id: 1, message_id: 2)
432464
```
433465

466+
While all of these methods are supported by JsRoutes, it is impossible to support them in `DTS` type definitions.
467+
If you are using routes like this, use the following configuration that will prevent required parameters presence to be validated by definition:
468+
469+
``` ruby
470+
JsRoutes.configure do |c|
471+
c.optional_definition_params = true
472+
end
473+
```
474+
475+
This will enforce the following route signature:
476+
477+
``` typescript
478+
export const inbox_message_attachment_path: ((
479+
inbox_id?: RequiredRouteParameter,
480+
message_id?: RequiredRouteParameter,
481+
id?: RequiredRouteParameter,
482+
options?: RouteOptions
483+
) => string) & RouteHelperExtras;
484+
```
485+
486+
That will make every call above valid.
434487

435488
## What about security?
436489

437-
JsRoutes itself does not have security holes.
490+
JsRoutes itself does not have security holes.
438491
It makes URLs without access protection more reachable by potential attacker.
439492
If that is an issue for you, you may use one of the following solutions:
440493

@@ -487,5 +540,3 @@ Advantages of this one are:
487540
* Full rails compatibility
488541
* Support Rails `#to_param` convention for seo optimized paths
489542
* Well tested
490-
491-

lib/js_routes/configuration.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ class Configuration
3737
attr_accessor :documentation
3838
sig { returns(T.nilable(String)) }
3939
attr_accessor :module_type
40+
sig { returns(T::Boolean) }
41+
attr_accessor :optional_definition_params
4042

4143
sig {params(attributes: T.nilable(Options)).void }
4244
def initialize(attributes = nil)
@@ -54,6 +56,7 @@ def initialize(attributes = nil)
5456
@application = T.let(-> { Rails.application }, ApplicationCaller)
5557
@module_type = T.let('ESM', T.nilable(String))
5658
@documentation = T.let(true, T::Boolean)
59+
@optional_definition_params = T.let(false, T::Boolean)
5760

5861
return unless attributes
5962
assign(attributes)

lib/js_routes/route.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ def body(absolute)
6666
sig { returns(String) }
6767
def definition_body
6868
options_type = optional_parts_type ? "#{optional_parts_type} & RouteOptions" : "RouteOptions"
69-
args = required_parts.map{|p| "#{apply_case(p)}: RequiredRouteParameter"}
69+
predicate = @configuration.optional_definition_params ? '?' : ''
70+
args = required_parts.map{|p| "#{apply_case(p)}#{predicate}: RequiredRouteParameter"}
7071
args << "options?: #{options_type}"
7172
"((\n#{args.join(",\n").indent(2)}\n) => string) & RouteHelperExtras"
7273
end

spec/js_routes/module_types/dts_spec.rb

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,24 @@
66

77
describe JsRoutes, "compatibility with DTS" do
88

9-
INCLUDE_OPTION = [/^inboxes$/, /^inbox_message_attachment$/]
10-
OPTIONS = {module_type: 'DTS', include: INCLUDE_OPTION}
11-
129
let(:extra_options) do
1310
{}
1411
end
1512

1613
let(:options) do
17-
{ module_type: 'DTS', include: INCLUDE_OPTION, **extra_options }
14+
{
15+
module_type: 'DTS',
16+
include: [/^inboxes$/, /^inbox_message_attachment$/],
17+
**extra_options
18+
}
1819
end
1920

2021
let(:generated_js) do
21-
JsRoutes.generate(**options)
22+
JsRoutes.generate(
23+
module_type: 'DTS',
24+
include: [/^inboxes$/, /^inbox_message_attachment$/],
25+
**extra_options
26+
)
2227
end
2328

2429
context "when file is generated" do
@@ -69,6 +74,21 @@
6974
end
7075
end
7176

77+
context "when optional_definition_params specified" do
78+
let(:extra_options) { { optional_definition_params: true } }
79+
80+
it "makes all route params optional" do
81+
expect(generated_js).to include(<<~JS.rstrip)
82+
export const inbox_message_attachment_path: ((
83+
inbox_id?: RequiredRouteParameter,
84+
message_id?: RequiredRouteParameter,
85+
id?: RequiredRouteParameter,
86+
options?: RouteOptions
87+
) => string) & RouteHelperExtras;
88+
JS
89+
end
90+
end
91+
7292
it "exports route helpers" do
7393
expect(generated_js).to include(<<-DOC.rstrip)
7494
/**

0 commit comments

Comments
 (0)