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 PageDrop to provide Liquid templates with data #7992

Merged
merged 7 commits into from Mar 26, 2020
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/_docs/pages.md
Expand Up @@ -34,3 +34,38 @@ If you have a lot of pages, you can organize them into subfolders. The same subf
## Changing the output URL

You might want to have a particular folder structure for your source files that changes for the built site. With [permalinks](/docs/permalinks) you have full control of the output URL.

## Liquid Representation

From Jekyll 4.1 onwards, there is a minor change in how instances of `Jekyll::Page` are exposed to layouts and other Liquid
templates. `Jekyll::Page` instances now use a `Liquid::Drop` instead of a `Hash`. This change results in greater performance
for a site with numerous *standlone pages not within a collection*.

### For plugin developers

While end-users do not need to take any extra action due to this change, plugin authors depending on the existing behavior *may*
need to make minor changes to their plugins.

If a `Jekyll::Page` subclass' `to_liquid` method calls `super`, it will have to be slightly modified.
```ruby
class Foo::BarPage < Jekyll::Page
def to_liquid(*)
payload = super # This needs to be changed to `super.to_h`
# to obtain a Hash as in v4.0.0.

do_something(payload) # Logic specific to `Foo::BarPage` objects
end
end
```

`Jekyll::Page` subclasses won't inherit the optimization automatically until the next major version. However, plugin authors
can opt-in to the optimization in their subclasses by wrapping the temporary `liquid_drop` method if the subclass doesn't
override the superclass method:
```ruby
class Foo::BarPage < Jekyll::Page
def to_liquid(*)
liquid_drop # Returns an instance of `Jekyll::Drops::PageDrop`.
# Will be removed in Jekyll 5.0.
end
end
```
14 changes: 14 additions & 0 deletions lib/jekyll/drops/page_drop.rb
@@ -0,0 +1,14 @@
# frozen_string_literal: true

module Jekyll
module Drops
class PageDrop < Drop
extend Forwardable

mutable false

def_delegators :@obj, :content, :dir, :name, :path, :url
private def_delegator :@obj, :data, :fallback_data
end
end
end
32 changes: 32 additions & 0 deletions lib/jekyll/page.rb
Expand Up @@ -70,6 +70,38 @@ def dir
end
end

# For backwards-compatibility in subclasses that do not redefine
# the `:to_liquid` method, stash existing definition under a new name
#
# TODO: Remove in Jekyll 5.0
alias_method :legacy_to_liquid, :to_liquid
private :legacy_to_liquid

# Private
# Subclasses can choose to optimize their `:to_liquid` method by wrapping
# it around this definition.
#
# TODO: Remove in Jekyll 5.0
def liquid_drop
@liquid_drop ||= begin
defaults = site.frontmatter_defaults.all(relative_path, type)
unless defaults.empty?
Utils.deep_merge_hashes!(data, Utils.deep_merge_hashes!(defaults, data))
end
Drops::PageDrop.new(self)
end
end
private :liquid_drop

# Public
#
# Liquid representation of current page
#
# TODO: Remove optional parameter in Jekyll 5.0
def to_liquid(attrs = nil)
self.class == Jekyll::Page ? liquid_drop : legacy_to_liquid(attrs)
end

# The full path and filename of the post. Defined in the YAML of the post
# body.
#
Expand Down
36 changes: 36 additions & 0 deletions test/test_page.rb
Expand Up @@ -50,6 +50,42 @@ def do_render(page)
assert_equal "/+/%25%23%20+.html", @page.url
end

should "be exposed to Liquid as a Liquid::Drop subclass" do
page = setup_page("properties.html")
liquid_rep = page.to_liquid
refute_equal Hash, liquid_rep.class
assert_equal true, liquid_rep.is_a?(Liquid::Drop)
assert_equal Jekyll::Drops::PageDrop, liquid_rep.class
end

should "make attributes accessible for use in Liquid templates" do
page = setup_page("/contacts", "index.html")
template = Liquid::Template.parse(<<~TEXT)
Name: {{ page.name }}
Path: {{ page.path }}
URL: {{ page.url }}
TEXT
expected = <<~TEXT
Name: index.html
Path: contacts/index.html
URL: /contacts/
TEXT
assert_equal(expected, template.render!("page" => page.to_liquid))
end

should "make front matter data accessible for use in Liquid templates" do
page = setup_page("properties.html")
template = Liquid::Template.parse(<<~TEXT)
TITLE: {{ page.title }}
FOO: {{ page.foo }}
TEXT
expected = <<~TEXT
TITLE: Properties Page
FOO: bar
TEXT
assert_equal expected, template.render!("page" => page.to_liquid)
end

context "in a directory hierarchy" do
should "create URL based on filename" do
@page = setup_page("/contacts", "bar.html")
Expand Down
6 changes: 6 additions & 0 deletions test/test_page_without_a_file.rb
Expand Up @@ -76,6 +76,12 @@ def render_and_write
end
end
end

should "be exposed to Liquid as a Hash" do
liquid_rep = @page.to_liquid
refute_equal Jekyll::Drops::PageDrop, liquid_rep.class
assert_equal Hash, liquid_rep.class
end
end

context "with site-wide permalink configuration" do
Expand Down