Skip to content

Commit

Permalink
Add documentation about new Roda routes features
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredcwhite committed Jul 14, 2024
1 parent d2bebff commit e4b445d
Showing 1 changed file with 96 additions and 27 deletions.
123 changes: 96 additions & 27 deletions bridgetown-website/src/_docs/routes.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ end
This route handles any `/preview/:collection/:path` URLs which are accessed just like any other statically-generated resource. It will find a content item via a repo origin ID and render that item as HTML. For example: `/preview/posts/_posts%2F2022-01-10-hello-world.md` would SSR the Markdown content located in `src/_posts/2022-01-10-hello-world.md`.

{%@ Note do %}
If you're wondering "but, uh, where's the HTML rendering part?!", the Bridgetown Roda configuration automatically handles the rendering of any models or resources which are returned in a route block.
If you're wondering "but, uh, where's the HTML rendering part?!", the Bridgetown Roda configuration automatically handles the rendering of any models or resources which are returned in a route block. This is based on a "callable" interface which you can also use for your own custom objects! More on that down below.
{% end %}

SSR is great for generating preview content on-the-fly, but you can use it for any number of instances where it’s not feasible to pre-build your content. In addition, you can use SSR to “refresh” stale content…for example, you could pre-build all your product pages statically, but then request a newer version of the page (or better yet, just a fragment of it) whenever the static page is viewed which would then contain the up-to-date pricing (perhaps coming from a PostgreSQL database or some other external data source). And if you cache _that_ data using Redis in, say, 10-minute increments, you’ve just built yourself an extremely performant e-commerce solution. This is only a single example!
Expand Down Expand Up @@ -94,10 +94,10 @@ end

### Accessing the Current Site, Collections, and Resources

You can use the `bridgetown_site` helper in your Roda code to access the current site object. From there, you can access data, collections, and resources for aid in rendering. For example, if you knew of a particular resource by title you wanted to find, you could write:
You can use the `site` helper (also aliased `bridgetown_site`) in your Roda code to access the current site object. From there, you can access data, collections, and resources for aid in rendering. For example, if you knew of a particular resource by title you wanted to find, you could write:

```ruby
bridgetown_site.collections.posts.resources.find { _1.data.title == "My Post" }
site.collections.posts.find { _1.data.title == "My Post" }
```

You can return a resource at the end of any Roda block to have it render out automatically, or you could pass it along as data to some other resource, or use some resource data within a return string or hash value (which autoconverts to JSON).
Expand All @@ -107,7 +107,7 @@ You can return a resource at the end of any Roda block to have it render out aut

By default, all available collections are read in when the Roda server boots up. This might not be a big deal in production since it's a one-time procedure, but bear in mind that on large sites, having all that data loaded in memory could prove costly. In addition, in development, any time you make a change to a file and the site rebuilds, resources are re-read into memory.

You can configure collections, including the built-in pages and posts collections, to be skipped when your site's running in SSR mode. Just set `skip_for_ssr` to `true` for collection metadata in your config file. For example, to skip reading posts in `config/initializers.rb`:
You can configure collections, including even the built-in pages and posts collections, to be skipped when your site's running in SSR mode. Just set `skip_for_ssr` to `true` for collection metadata in your config file. For example, to skip reading posts in `config/initializers.rb`:

```ruby
Bridgetown.configure do
Expand Down Expand Up @@ -142,31 +142,33 @@ init :"bridgetown-routes"

A file-based route is comprised of two parts:

* A Roda block at the top, contained within special delimiters. This block is processed initially when the route URL is accessed, and before any template rendering has begun.
* A view template underneath the Roda block, rendered via the `render_with` method inside the Roda block.
* A Roda block at the top, contained within special delimiters. This block is processed initially when the route URL is accessed, and before any template rendering has begun. You can include Ruby front matter here, just like you would with static resource content.
* A view template underneath the Roda block, which is rendered in the correct format based on the file extension (ERB, etc.) and has access to front matter and local variables from the Roda block.

A Roda block can contain a single route handler via `r.get`, or you can add additional handling of HTTP methods (`r.post`, etc.) or even sub-routes—though it's recommended to stay simple and use individual file-based routes as much as possible. Note that if even if you define multiple route types in your Roda block, you only have a single template per-route.
A Roda block can contain a single route handler via `r.get`, or you can add additional handling of HTTP methods (`r.post`, etc.) or even sub-routes—though it's recommended to stay simple and use individual file-based routes as much as possible. Note that if even if you define multiple route types in your Roda block, you can only have a single template per-route.

Let's take a look at how this all works. First, an example of a route saved to `src/_routes/items/index.erb`. It provides the `/items` URL which shows a list of item links:

```eruby
---<%
# route: /items
r.get do
render_with data: {
layout: :page,
title: "Dynamic Items",
items: [
{ number: 1, slug: "123-abc" },
{ number: 2, slug: "456-def" },
{ number: 3, slug: "789-xyz" },
]
}
# sample data:
items = [
{ number: 1, slug: "123-abc" },
{ number: 2, slug: "456-def" },
{ number: 3, slug: "789-xyz" },
]
render_with do
layout :page
title "Dynamic Items"
end
end
%>---
<ul>
<% data.items.each do |item| %>
<% items.each do |item| %>
<li><a href="/items/<%= item[:slug] %>">Item #<%= item[:number] %></a></li>
<% end %>
</ul>
Expand All @@ -181,39 +183,76 @@ r.get do
item_id, *item_sku = r.params[:slug].split("-")
item_sku = item_sku.join("-")
render_with data: {
layout: :page,
title: "Item Page",
item_id: item_id,
item_sku: item_sku
}
render_with do
layout :page
title "Item Page"
end
end
%>---
<p><strong>Item ID:</strong> <%= data.item_id %></p>
<p><strong>Item ID:</strong> <%= item_id %></p>
<p><strong>Item SKU:</strong> <%= data.item_sku %></p>
<p><strong>Item SKU:</strong> <%= item_sku %></p>
```

This is a contrived example of course, but you can easily imagine loading a specific item from a data source based on the incoming parameter(s) and providing that item data to the view, all within a single file.

You can even use placeholders in folder names! A route saved to `src/_routes/books/[id]/chapter/[chapter_id].erb` would match to something like `/books/234259/chapter/5` and let you access `r.params[:id]` and `r.params[:chapter_id]`. Pretty nifty.

<!--
Testing is straightforward as well. Simply place `.test.rb` files alongside your routes, and you’ll be able to use Capybara to write **fast** integration tests including interactions requiring Javascript (assuming Cuprite is also installed). (_docs coming soon_)
-->

### Route Template Delimiters
### Route Template Delimiters, Front Matter Syntax

Bridgetown lets you use a few different delimiters for the Roda block at the top, depending on your template format. For example, `---<%` and `%>---` would work well for an `.erb` file, but `###ruby` and `###` would be ideal for an `.rb` file.

See [Ruby Front Matter](/docs/front-matter#the-power-of-ruby-in-front-matter) for additional details (not that a Roda block is front matter, but the delimiters used are the same).
See [Ruby Front Matter](/docs/front-matter#the-power-of-ruby-in-front-matter) for additional details.

The Roda block also excepts a couple of different styles of specifying front matter. You can use `render_with do ... end` as in the examples above, but you can also use a data hash instead:

```ruby
hsh = { layout: :page, title: "I'm a Page!" }

render_with(data: hsh)
```

Note that if you use that syntax, additional local variables will _not_ be copied down to the view template.

Finally, Bridgetown lets you simplify your code even more if your block doesn't need to include any special `get`/`post` etc. casing and you just want to use local variables for front matter by letting you omit `render_with` entirely:

```eruby
---<%
referrer = params[:ref]
referrer = "=)" unless AllowedValidator.valid?(referrer) # just a demo
title = "Thank You!"
%>---
<h1><%= title %></h1>
<p>We appreciate your business, <%= referrer %></h1>
```

### Routes in Islands Architecture

You can add routes folders inside of one or more islands. For example, you could add a route file at `src/_islands/paradise/routes/dreamy.erb`, and the URL would then resolve to the island name plus the route name (`/paradise/dreamy`). If you name your route file `index.(ext)`, then the route path would be just the island name (`/paradise`).

For more information about islands, read our [Islands Architecture documentation](/docs/islands).

### Adding Additional Route Paths

If you'd like to add any arbitrary path as a location for route files—even outside of the project root—you can do so in your `config/initializers.rb`:

```ruby
Bridgetown.configure do |config|
# configuration here

init :"bridgetown-routes", additional_source_paths: File.expand_path("more_routes", "#{root_dir}/..")
end
```

## URL Helpers

You can use the `relative_url` and `absolute_url` helpers within your Roda code any time you need to reference a particular URL, to ensure any base path or locale prefix gets added automatically. It also will work with any object which responds to a method like `relative_url` or `url`. For example:
Expand All @@ -224,6 +263,36 @@ r.redirect relative_url("/path/to/page")
r.redirect relative_url(obj)
```

## Callable Objects for Rendering within Blocks

When authoring Roda blocks, you have the option of returning resources to be rendered out as HTML or other text-based formats.

But it's also possible to return any object which includes the `Bridgetown::RodaCallable` mixin and defines a `call` method which accepts the Roda application as the argument. For example, if you wrote an object which generates an RSS feed, you could use it like so:

```ruby
class MyRssFeed
include Bridgetown::RodaCallable

def call(app)
app => { request:, response: } # now you have those as local variables

feed_xml = generate_the_feed # an exercise left to the reader

response["Content-Type"] = "application/rss+xml" # set the correct content type
feed_xml # return XML string
end
end
```

```ruby
# Use the object directly in a Roda block:
r.get "/my-feed.xml" do
MyRssFeed.new
end
```

For the Roda-curious, we've enabled this behavior via our own custom handler for the `custom_block_results` Roda plugin.

<!--
## Roda Helpers
Expand Down

0 comments on commit e4b445d

Please sign in to comment.