Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Enumerable to Paginator #967

Merged
merged 1 commit into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions bridgetown-core/lib/bridgetown-core/collection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,13 @@ def resources_by_relative_url
# Iterate over Resources by delegating to `resources.each` (supports Enumerable)
def each(...) = resources.each(...)

# Delgates to `resources.last`
# Delegates to `resources.last`
def last(...) = resources.last(...)

# Delgates to `resources.deconstruct`
# Delegates to `resources.deconstruct`
def deconstruct = resources.deconstruct

# Delgates to `resources[]`
# Delegates to `resources[]`
def [](...) = resources.[](...)

# Fetch the static files in this collection.
Expand Down
7 changes: 7 additions & 0 deletions bridgetown-core/test/features/test_pagination.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,17 @@ class TestPagination < BridgetownFeatureTest
create_configuration pagination: { enabled: true, per_page: example[:num] }

create_page "index.liquid", "{{ paginator.resources.size }} {{ paginator.resources[0].title }}", pagination: { collection: "posts" }
create_page "posts.erb", <<~ERB, pagination: { collection: "posts" }
<%= paginator.resources.count %>
<% paginator.each do |post| %>
- <%= post.data.title %>
<% end %>
ERB

run_bridgetown "build"

assert_file_contains "#{example[:posts]} #{example[:title]}", "output/page/#{example[:exist]}/index.html"
assert_file_contains "#{example[:posts]}\n - #{example[:title]}", "output/posts/page/#{example[:exist]}/index.html"
refute_exist "output/page/#{example[:not_exist]}/index.html"
end
end
Expand Down
14 changes: 14 additions & 0 deletions bridgetown-paginate/lib/bridgetown-paginate/paginator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ module Paginate
# Handles the preparation of all the documents based on the current page index
#
class Paginator
include Enumerable

attr_reader :page, :per_page, :documents, :total_documents, :total_pages,
:previous_page, :previous_page_path, :next_page, :next_page_path, :page_path,
:first_page, :first_page_path, :last_page, :last_page_path
Expand Down Expand Up @@ -119,6 +121,18 @@ def to_liquid
"page_trail" => page_trail,
}
end

# Iterate over Resources by delegating to `resources.each` (supports Enumerable)
def each(...) = resources.each(...)

# Delegates to `resources.last`
def last(...) = resources.last(...)

# Delegates to `resources.deconstruct`
def deconstruct = resources.deconstruct

# Delegates to `resources[]`
def [](...) = resources.[](...)
end

# Small utility class that handles individual pagination trails
Expand Down
70 changes: 30 additions & 40 deletions bridgetown-website/src/_docs/resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,30 +52,40 @@ Let's say you add a new blog post by saving `src/_posts/2021-05-10-super-cool-bl
1. It finds the appropriate origin class to load the post. The posts collection file reader uses a special **origin ID** to identify the file (in this case: `repo://posts.collection/_posts/2021-05-10-super-cool-blog-post.md`). Other origin classes could handle different protocols to download content from third-party APIs or load in content directly from scripts.
2. Once the origin provides the post's data it is used to create a model object. The model will be a `Bridgetown::Model::Base` object by default, but you can create your own subclasses to alter and enhance data, or for use in a Ruby-based CMS environment. For example, `class Post < Bridgetown::Model::Base; end` will get used automatically for the `posts` collection (because Bridgetown will use the Rails inflector to map `posts` to `Post`). You can save subclasses in your `plugins` folder, or set up a dedicated `models` folder to be [eager-loaded by Zeitwerk](/docs/plugins#zeitwerk-and-autoloading).
3. The model then "emits" a resource object. The resource is provided a clone of the model data which it can then process for use within templates. Resources may also point to other resources through relations, and templates can access resources through various means (looping through collections, referencing resources by source paths, etc.)
4. The resource is transformed by a pipeline to convert Markdown to HTML, render template code like Liquid or ERB, and any other conversions specified—as well as optionally place the resource output within a converted layout.
4. The resource is transformed by a pipeline to render template code such as ERB or Liquid, convert Markdown to HTML, and any other conversions specified—as well as optionally place the resource output within a converted layout.
5. Finally, a destination object is responsible for determining the resource's "permalink" based on configured criteria or the presence of `permalink` front matter. It will then write out to the output folder using a static file name matching the destination permalink.

## Accessing Resources in Templates

{% raw %}
The simplest way to access resources in your templates is to use the `collections` variable, available in both Liquid and Ruby-based templates.

```liquid
Title: {{ collections.genre.title }}

First URL: {{ collections.genre.resources[0].relative_url }}
```
The simplest way to access resources in your templates is to use the `collections` variable, available in both Ruby and Liquid templates.

```eruby
Title: <%= collections.genre.metadata.title %>

First URL: <%= collections.genre.resources[0].relative_url %>
```

```liquid
Title: {{ collections.genre.title }}

First URL: {{ collections.genre.resources[0].relative_url }}
```

### Loops and Pagination

You can easily loop through collection resources by name, e.g., `collections.posts.resources`:

```eruby
<% collections.posts.each do |post| %>
<article>
<a href="<%= post.relative_url %>"><h2><%= post.data.title %></h2></a>

<p><%= post.data.description %></p>
</article>
<% end %>
```

```liquid
{% for post in collections.posts.resources %}
<article>
Expand All @@ -86,8 +96,10 @@ You can easily loop through collection resources by name, e.g., `collections.pos
{% endfor %}
```

Sometimes you'll likely want to use a paginator:

```eruby
<% collections.posts.resources.each do |post| %>
<% paginator.each do |post| %>
<article>
<a href="<%= post.relative_url %>"><h2><%= post.data.title %></h2></a>

Expand All @@ -96,8 +108,6 @@ You can easily loop through collection resources by name, e.g., `collections.pos
<% end %>
```

Sometimes you'll likely want to use a paginator:

```liquid
{% for post in paginator.resources %}
<article>
Expand All @@ -108,16 +118,6 @@ Sometimes you'll likely want to use a paginator:
{% endfor %}
```

```eruby
<% paginator.resources.each do |post| %>
<article>
<a href="<%= post.relative_url %>"><h2><%= post.data.title %></h2></a>

<p><%= post.data.description %></p>
</article>
<% end %>
```

Read more about [how the paginator works here](/docs/content/pagination). You can also [refer to how collections work](/docs/collections) and how you can also create your own custom collections.

## Taxonomies
Expand Down Expand Up @@ -161,14 +161,6 @@ genres:

You can access taxonomies for resources in your templates as:

```liquid
Title: {{ site.taxonomy_types.genres.metadata.title }}

{% for term in resource.taxonomies.genres.terms %}
Term: {{ term.label }}
{% endfor %}
```

```eruby
Title: <%= site.taxonomy_types.genres.metadata.title %>

Expand All @@ -177,6 +169,14 @@ Title: <%= site.taxonomy_types.genres.metadata.title %>
<% end %>
```

```liquid
Title: {{ site.taxonomy_types.genres.metadata.title }}

{% for term in resource.taxonomies.genres.terms %}
Term: {{ term.label }}
{% endfor %}
```

## Resource Relations

You can configure one-to-one, one-to-many, or many-to-many relations between resources in different collections. You can then add the necessary references via front matter or metadata from an API request and access those relations in your templates, plugins, and components.
Expand Down Expand Up @@ -288,17 +288,7 @@ output << "[bridgetown](https://github.com/bridgetownrb/bridgetown)"
markdownify output.join("\n")
```

Now obviously it's silly to build up Markdown content in an array of strings in a Ruby code file…but imagine building or using third-party <abbr title="Domain-Specific Languages">DSLs</abbr> to generate sophisticated markup and advanced structural documents of all kinds. [Arbre](https://activeadmin.github.io/arbre/) is but one example of a Ruby-first approach to creating templates.

```ruby
# What if your .rb template looked like this?

Arbre::Context.new do
h1 "Hello World"

para "I'm a Ruby template. w00t"
end
```
Now that obviously looks rather cumbersome and error-prone. Wouldn't it be great if there were a more—shall we say—_streamlined_ approach to pure Ruby templating? [Thankfully there is!](/docs/template-engines/erb-and-beyond#streamlined)

## Resource Extensions

Expand Down
Loading