Skip to content

Commit

Permalink
Merge pull request #307 from tightenco/jbk/route-model-binding
Browse files Browse the repository at this point in the history
Add support for custom scoped route model bindings
  • Loading branch information
bakerkretzmar authored Jun 30, 2020
2 parents b6ea70f + e96b469 commit efab169
Show file tree
Hide file tree
Showing 11 changed files with 219 additions and 15 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Breaking changes are marked with ⚠️.
**Added**

- Document the `check()` method ([#294](https://github.com/tightenco/ziggy/pull/294)) and how to install and use Ziggy via `npm` and over a CDN ([#299](https://github.com/tightenco/ziggy/pull/299))
- Add support for [custom scoped route model bindings](https://laravel.com/docs/7.x/routing#implicit-binding), e.g. `/users/{user}/posts/{post:slug}` ([#307](https://github.com/tightenco/ziggy/pull/307))

**Changed**

Expand Down
3 changes: 3 additions & 0 deletions src/RoutePayload.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,9 @@ protected function nameKeyedRoutes()

return collect($route)->only(['uri', 'methods'])
->put('domain', $route->domain())
->when(method_exists($route, 'bindingFields'), function ($collection) use ($route) {
return $collection->put('bindings', $route->bindingFields());
})
->when($middleware = config('ziggy.middleware'), function ($collection) use ($middleware, $route) {
if (is_array($middleware)) {
return $collection->put('middleware', collect($route->middleware())->intersect($middleware)->values());
Expand Down
19 changes: 15 additions & 4 deletions src/js/route.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,22 @@ class Router extends String {
delete this.urlParams[keyName];
}

// The block above is what requires us to assign tagValue below
// instead of returning - if multiple *objects* are passed as
// params, numericParamIndices will be true and each object will
// be assigned above, which means !tagValue will evaluate to
// false, skipping the block below.

// If a value wasn't provided for this named parameter explicitly,
// but the object that was passed contains an ID, that object
// was probably a model, so we use the ID.
// Note that we are not explicitly ensuring here that the template
// doesn't have an ID param (`this.template.indexOf('{id}') == -1`)
// because we don't need to - if it does, we won't get this far.
if (!tagValue && !this.urlParams[keyName] && this.urlParams['id']) {

let bindingKey = this.ziggy.namedRoutes[this.name]?.bindings?.[keyName];

if (bindingKey && !this.urlParams[keyName] && this.urlParams[bindingKey]) {
tagValue = this.urlParams[bindingKey];
delete this.urlParams[bindingKey];
} else if (!tagValue && !this.urlParams[keyName] && this.urlParams['id']) {
tagValue = this.urlParams['id']
delete this.urlParams['id'];
}
Expand All @@ -97,6 +106,8 @@ class Router extends String {
// If an object was passed and has an id, return it
if (tagValue.id) {
return encodeURIComponent(tagValue.id);
} else if (tagValue[bindingKey]) {
return encodeURIComponent(tagValue[bindingKey])
}

return encodeURIComponent(tagValue);
Expand Down
7 changes: 7 additions & 0 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ protected function getPackageProviders($app)
];
}

protected function laravelVersion(int $v = null)
{
$version = (int) head(explode('.', app()->version()));

return isset($v) ? $version >= $v : $version;
}

protected function assertJsonContains(array $haystack, array $needle)
{
$actual = json_encode(Arr::sortRecursive(
Expand Down
20 changes: 16 additions & 4 deletions tests/Unit/BladeRouteGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,19 @@ function generator_outputs_non_domain_named_routes_with_expected_structure()

$generator = (new BladeRouteGenerator($router));

$this->assertEquals([
$expected = [
'postComments.index' => [
'uri' => 'posts/{post}/comments',
'methods' => ['GET', 'HEAD'],
'domain' => null,
],
], $generator->getRoutePayload()->toArray());
];

if ($this->laravelVersion(7)) {
$expected['postComments.index']['bindings'] = [];
}

$this->assertEquals($expected, $generator->getRoutePayload()->toArray());
}

/** @test */
Expand All @@ -56,13 +62,19 @@ function generator_outputs_domain_as_defined()

$generator = (new BladeRouteGenerator($router));

$this->assertEquals([
$expected = [
'postComments.index' => [
'uri' => 'posts/{post}/comments',
'methods' => ['GET', 'HEAD'],
'domain' => '{account}.myapp.com',
],
], $generator->getRoutePayload()->toArray());
];

if ($this->laravelVersion(7)) {
$expected['postComments.index']['bindings'] = [];
}

$this->assertEquals($expected, $generator->getRoutePayload()->toArray());
}

/** @test */
Expand Down
36 changes: 32 additions & 4 deletions tests/Unit/CommandRouteGeneratorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,14 @@ function file_is_created_with_the_expected_structure_when_named_routes_exist()

Artisan::call('ziggy:generate');

$this->assertFileEquals('./tests/fixtures/ziggy.js', base_path('resources/js/ziggy.js'));
if ($this->laravelVersion(7)) {
$this->assertFileEquals('./tests/fixtures/ziggy.js', base_path('resources/js/ziggy.js'));
} else {
$this->assertSame(
str_replace(',"bindings":[]', '', file_get_contents(__DIR__ . '/../fixtures/ziggy.js')),
file_get_contents(base_path('resources/js/ziggy.js'))
);
}
}

/** @test */
Expand All @@ -57,7 +64,14 @@ function file_is_created_with_a_custom_url()

Artisan::call('ziggy:generate', ['--url' => 'http://example.org']);

$this->assertFileEquals('./tests/fixtures/custom-url.js', base_path('resources/js/ziggy.js'));
if ($this->laravelVersion(7)) {
$this->assertFileEquals('./tests/fixtures/custom-url.js', base_path('resources/js/ziggy.js'));
} else {
$this->assertSame(
str_replace(',"bindings":[]', '', file_get_contents(__DIR__ . '/../fixtures/custom-url.js')),
file_get_contents(base_path('resources/js/ziggy.js'))
);
}
}

/** @test */
Expand Down Expand Up @@ -87,11 +101,25 @@ function file_is_created_with_the_expected_group()

Artisan::call('ziggy:generate');

$this->assertFileEquals('./tests/fixtures/ziggy.js', base_path('resources/js/ziggy.js'));
if ($this->laravelVersion(7)) {
$this->assertFileEquals('./tests/fixtures/ziggy.js', base_path('resources/js/ziggy.js'));
} else {
$this->assertSame(
str_replace(',"bindings":[]', '', file_get_contents(__DIR__ . '/../fixtures/ziggy.js')),
file_get_contents(base_path('resources/js/ziggy.js'))
);
}

Artisan::call('ziggy:generate', ['path' => 'resources/js/admin.js', '--group' => 'admin']);

$this->assertFileEquals('./tests/fixtures/admin.js', base_path('resources/js/admin.js'));
if ($this->laravelVersion(7)) {
$this->assertFileEquals('./tests/fixtures/admin.js', base_path('resources/js/admin.js'));
} else {
$this->assertSame(
str_replace(',"bindings":[]', '', file_get_contents(__DIR__ . '/../fixtures/admin.js')),
file_get_contents(base_path('resources/js/admin.js'))
);
}
}

protected function tearDown(): void
Expand Down
116 changes: 116 additions & 0 deletions tests/Unit/RoutePayloadTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ public function setUp(): void
})
->name('admin.users.index')->middleware('role:admin');

if ($this->laravelVersion(7)) {
$this->router->get('/posts/{post}/comments/{comment:uuid}', function () {
return '';
})->name('postComments.show');
}

$this->router->getRoutes()->refreshNameLookups();
}

Expand Down Expand Up @@ -72,6 +78,12 @@ public function only_matching_routes_included_with_include_enabled()
],
];

if ($this->laravelVersion(7)) {
foreach ($expected as $key => $route) {
$expected[$key]['bindings'] = [];
}
}

$this->assertEquals($expected, $routes->toArray());
}

Expand All @@ -95,6 +107,21 @@ public function only_matching_routes_excluded_with_exclude_enabled()
],
];

if ($this->laravelVersion(7)) {
foreach ($expected as $key => $route) {
$expected[$key]['bindings'] = [];
}

$expected['postComments.show'] = [
'uri' => 'posts/{post}/comments/{comment}',
'methods' => ['GET', 'HEAD'],
'domain' => null,
'bindings' => [
'comment' => 'uuid',
],
];
}

$this->assertEquals($expected, $routes->toArray());
}

Expand Down Expand Up @@ -125,6 +152,12 @@ public function existence_of_only_config_causes_routes_to_be_included()
],
];

if ($this->laravelVersion(7)) {
foreach ($expected as $key => $route) {
$expected[$key]['bindings'] = [];
}
}

$this->assertEquals($expected, $routes->toArray());
}

Expand All @@ -150,6 +183,21 @@ public function existence_of_except_config_causes_routes_to_be_excluded()
],
];

if ($this->laravelVersion(7)) {
foreach ($expected as $key => $route) {
$expected[$key]['bindings'] = [];
}

$expected['postComments.show'] = [
'uri' => 'posts/{post}/comments/{comment}',
'methods' => ['GET', 'HEAD'],
'domain' => null,
'bindings' => [
'comment' => 'uuid',
],
];
}

$this->assertEquals($expected, $routes->toArray());
}

Expand Down Expand Up @@ -196,6 +244,21 @@ public function existence_of_both_configs_returns_unfiltered_routes()
],
];

if ($this->laravelVersion(7)) {
foreach ($expected as $key => $route) {
$expected[$key]['bindings'] = [];
}

$expected['postComments.show'] = [
'uri' => 'posts/{post}/comments/{comment}',
'methods' => ['GET', 'HEAD'],
'domain' => null,
'bindings' => [
'comment' => 'uuid',
],
];
}

$this->assertEquals($expected, $routes->toArray());
}

Expand Down Expand Up @@ -233,6 +296,12 @@ public function only_matching_routes_included_with_group_enabled()
],
];

if ($this->laravelVersion(7)) {
foreach ($expected as $key => $route) {
$expected[$key]['bindings'] = [];
}
}

$this->assertEquals($expected, $routes->toArray());
}

Expand Down Expand Up @@ -274,6 +343,21 @@ public function non_existence_of_group_returns_unfiltered_routes()
],
];

if ($this->laravelVersion(7)) {
foreach ($expected as $key => $route) {
$expected[$key]['bindings'] = [];
}

$expected['postComments.show'] = [
'uri' => 'posts/{post}/comments/{comment}',
'methods' => ['GET', 'HEAD'],
'domain' => null,
'bindings' => [
'comment' => 'uuid',
],
];
}

$this->assertEquals($expected, $routes->toArray());
}

Expand Down Expand Up @@ -325,6 +409,22 @@ public function retrieves_middleware_if_config_is_set()
],
];

if ($this->laravelVersion(7)) {
foreach ($expected as $key => $route) {
$expected[$key]['bindings'] = [];
}

$expected['postComments.show'] = [
'uri' => 'posts/{post}/comments/{comment}',
'methods' => ['GET', 'HEAD'],
'domain' => null,
'middleware' => [],
'bindings' => [
'comment' => 'uuid',
],
];
}

$this->assertEquals($expected, $routes->toArray());
}

Expand Down Expand Up @@ -376,6 +476,22 @@ public function retrieves_only_configured_middleware()
],
];

if ($this->laravelVersion(7)) {
foreach ($expected as $key => $route) {
$expected[$key]['bindings'] = [];
}

$expected['postComments.show'] = [
'uri' => 'posts/{post}/comments/{comment}',
'methods' => ['GET', 'HEAD'],
'domain' => null,
'middleware' => [],
'bindings' => [
'comment' => 'uuid',
],
];
}

$this->assertEquals($expected, $routes->toArray());
}
}
2 changes: 1 addition & 1 deletion tests/fixtures/admin.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var Ziggy = {
namedRoutes: {"admin.dashboard":{"uri":"admin","methods":["GET","HEAD"],"domain":null}},
namedRoutes: {"admin.dashboard":{"uri":"admin","methods":["GET","HEAD"],"domain":null,"bindings":[]}},
baseUrl: 'http://myapp.com/',
baseProtocol: 'http',
baseDomain: 'myapp.com',
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/custom-url.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var Ziggy = {
namedRoutes: {"postComments.index":{"uri":"posts\/{post}\/comments","methods":["GET","HEAD"],"domain":null}},
namedRoutes: {"postComments.index":{"uri":"posts\/{post}\/comments","methods":["GET","HEAD"],"domain":null,"bindings":[]}},
baseUrl: 'http://example.org/',
baseProtocol: 'http',
baseDomain: 'example.org',
Expand Down
2 changes: 1 addition & 1 deletion tests/fixtures/ziggy.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
var Ziggy = {
namedRoutes: {"postComments.index":{"uri":"posts\/{post}\/comments","methods":["GET","HEAD"],"domain":null}},
namedRoutes: {"postComments.index":{"uri":"posts\/{post}\/comments","methods":["GET","HEAD"],"domain":null,"bindings":[]}},
baseUrl: 'http://myapp.com/',
baseProtocol: 'http',
baseDomain: 'myapp.com',
Expand Down
Loading

0 comments on commit efab169

Please sign in to comment.