Skip to content

Commit

Permalink
Make it easy for Roda routes to render "callable" objects (#892)
Browse files Browse the repository at this point in the history
* Make it easy for Roda routes to render "callable" objects

* Test additional code paths
  • Loading branch information
jaredcwhite authored May 4, 2024
1 parent 377c9ad commit 9015c29
Show file tree
Hide file tree
Showing 11 changed files with 69 additions and 16 deletions.
9 changes: 7 additions & 2 deletions bridgetown-core/lib/bridgetown-core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -379,9 +379,7 @@ def build_errors_path
)
end
end
end

module Bridgetown
module CoreExt; end
module Model; end

Expand All @@ -395,6 +393,13 @@ def self.register_extension(mod)
end
end
end

# mixin for identity so Roda knows to call renderable objects
module RodaCallable
def self.===(other)
other.class < self
end
end
end

Zeitwerk::Loader.new.tap do |loader|
Expand Down
2 changes: 1 addition & 1 deletion bridgetown-core/lib/bridgetown-core/component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ def render_in(view_context, &block)
# Subclasses can override this method to return a string from their own
# template handling.
def template
call || _renderer.render(self)
(method(:call).arity.zero? ? call : nil) || _renderer.render(self)
end

# Typically not used but here as a compatibility nod toward ViewComponent.
Expand Down
6 changes: 6 additions & 0 deletions bridgetown-core/lib/bridgetown-core/model/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
module Bridgetown
module Model
class Base
include Bridgetown::RodaCallable
include ActiveModel::Model
extend ActiveModel::Callbacks
define_model_callbacks :load, :save, :destroy
Expand Down Expand Up @@ -102,6 +103,11 @@ def render_as_resource
to_resource.read!.transform!
end

# Converts this model to a resource and returns the full output
#
# @return [String]
def call(*) = render_as_resource.output

# override if need be
# @return [Bridgetown::Site]
def site
Expand Down
6 changes: 6 additions & 0 deletions bridgetown-core/lib/bridgetown-core/resource/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ module Bridgetown
module Resource
class Base # rubocop:todo Metrics/ClassLength
include Comparable
include Bridgetown::RodaCallable
include Bridgetown::Publishable
include Bridgetown::LayoutPlaceable
include Bridgetown::LiquidRenderable
Expand Down Expand Up @@ -171,6 +172,11 @@ def transform! # rubocop:todo Metrics/CyclomaticComplexity
self
end

# Transforms the resource and returns the full output
#
# @return [String]
def call(*) = transform!.output

def trigger_hooks(hook_name, *args)
Bridgetown::Hooks.trigger collection.label.to_sym, hook_name, self, *args if collection
Bridgetown::Hooks.trigger :resources, hook_name, self, *args
Expand Down
11 changes: 0 additions & 11 deletions bridgetown-core/lib/roda/plugins/bridgetown_server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,6 @@ def self.load_dependencies(app) # rubocop:disable Metrics
"500 Internal Server Error"
end

# This lets us return models or resources directly in Roda response blocks
app.plugin :custom_block_results

app.handle_block_result Bridgetown::Model::Base do |result|
result.render_as_resource.output
end

app.handle_block_result Bridgetown::Resource::Base do |result|
result.transform!.output
end

# TODO: there may be a better way to do this, see `exception_page_css` instance method
ExceptionPage.class_eval do # rubocop:disable Metrics/BlockLength
def self.css
Expand Down
11 changes: 11 additions & 0 deletions bridgetown-core/lib/roda/plugins/bridgetown_ssr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ module InstanceMethods
def bridgetown_site
self.class.opts[:bridgetown_site]
end

alias_method :site, :bridgetown_site
end

def self.load_dependencies(app)
app.plugin :custom_block_results

# This lets us return callable objects directly in Roda response blocks
app.handle_block_result(Bridgetown::RodaCallable) do |callable|
callable.(self)
end
end

def self.configure(app, _opts = {}, &)
Expand Down
2 changes: 2 additions & 0 deletions bridgetown-core/test/ssr/config.ru
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ require "bridgetown-core/rack/boot"

Bridgetown::Rack.boot

require_relative "src/_components/UseRoda" # normally Zeitwerk would take care of this for us

run RodaApp.freeze.app # see server/roda_app.rb
2 changes: 1 addition & 1 deletion bridgetown-core/test/ssr/server/routes/hello.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class Routes::Hello < Bridgetown::Rack::Routes
priority :lowest

route do |r|
saved_value = bridgetown_site.data.save_value
saved_value = site.data.save_value

# route: GET /hello/:name
r.get "hello", String do |name|
Expand Down
6 changes: 5 additions & 1 deletion bridgetown-core/test/ssr/server/routes/render_resource.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@ class Routes::RenderResource < Bridgetown::Rack::Routes
# route: GET /render_resource
r.get "render_resource" do
# Roda should know how to autorender the resource
bridgetown_site.collections.pages.resources.find { _1.id == "repo://pages.collection/index.md" }
site.collections.pages.find { _1.id == "repo://pages.collection/index.md" }
end

r.get "render_model" do
# Roda should know how to autorender the model as a resource
Bridgetown::Model::Base.find("repo://pages.collection/test_doc.md")
end

r.get "render_component", String do |title|
UseRoda.new(title:)
end
end
end
22 changes: 22 additions & 0 deletions bridgetown-core/test/ssr/src/_components/UseRoda.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

class UseRoda < Bridgetown::Component
include Bridgetown::RodaCallable

def initialize(title:) # rubocop:disable Lint/MissingSuper
@title = title.upcase
end

def template
"<rss>#{@title} #{@testing}</rss>" # not real RSS =)
end

def call(app)
app.request => r
@testing = r.env["rack.test"]

app.response["Content-Type"] = "application/rss+xml"

render_in(app)
end
end
8 changes: 8 additions & 0 deletions bridgetown-core/test/test_ssr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,13 @@ def site
assert last_response.ok?
assert_includes last_response.body, "<p class=\"test\">THIS IS A <em>TEST</em>.</p>"
end

should "return rendered component" do
get "/render_component/wow"

assert last_response.ok?
assert_equal "application/rss+xml", last_response["Content-Type"]
assert_equal "<rss>WOW true</rss>", last_response.body
end
end
end

0 comments on commit 9015c29

Please sign in to comment.