Skip to content
Open
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
13 changes: 13 additions & 0 deletions lib/perron/feeds.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# frozen_string_literal: true

require "perron/feeds/split"

module Perron
class Feeds
include ActionView::Helpers::TagHelper
include Perron::Feeds::Split

def render(options = {})
html_tags = []
Expand All @@ -22,6 +25,16 @@ def render(options = {})
title = "#{collection.name.humanize} #{type.to_s.humanize} Feed"

html_tags << tag(:link, rel: "alternate", type: MIME_TYPES[type], title: title, href: absolute_url)

next unless feed[:split_by]

split_values(collection.resources, feed[:split_by][:extractor]).each do |value|
split_path = split_path_for(feed, value)
split_url = URI.join(url.root_url, split_path).to_s
split_title = "#{collection.name.humanize}: #{value.to_s.humanize} #{type.to_s.humanize} Feed"

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be a configurable option, similar to path.


html_tags << tag(:link, rel: "alternate", type: MIME_TYPES[type], title: split_title, href: split_url)
end
end
end
end
Expand Down
46 changes: 46 additions & 0 deletions lib/perron/feeds/split.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# frozen_string_literal: true

module Perron
class Feeds
module Split
module_function

def grouped_resources(resources, extractor)
resources
.flat_map { |resource| extract(resource, extractor).map { [it, resource] } }
.group_by(&:first)
.transform_values { it.map(&:last) }
end

def split_values(resources, extractor)
resources
.flat_map { extract(it, extractor) }
.uniq
end

def extract(resource, extractor)
value = case extractor
when Symbol then resource.public_send(extractor)
when Proc then extractor.call(resource)
end

Array(value)
end

def split_path_for(type_config, value)
split_by = type_config[:split_by]

if split_by[:path]
split_by[:path].gsub(":value", value.to_s.parameterize)
else
extension = File.extname(type_config.path)
base_path = type_config.path.delete_suffix(extension)
field = split_by[:extractor]
field_name = field.is_a?(Symbol) ? field.to_s : "custom"

"#{base_path}/#{field_name}/#{value.to_s.parameterize}#{extension}"
end
end
end
end
end
15 changes: 12 additions & 3 deletions lib/perron/resource/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ def configuration

config.feeds = Options.new

config.feeds.atom = ActiveSupport::OrderedOptions.new
config.feeds.atom = FeedTypeConfig.new
config.feeds.atom.enabled = false
config.feeds.atom.path = "feeds/#{collection.name.demodulize.parameterize}.atom"
config.feeds.atom.max_items = 20

config.feeds.json = ActiveSupport::OrderedOptions.new
config.feeds.json = FeedTypeConfig.new
config.feeds.json.enabled = false
config.feeds.json.path = "feeds/#{collection.name.demodulize.parameterize}.json"
config.feeds.json.max_items = 20

config.feeds.rss = ActiveSupport::OrderedOptions.new
config.feeds.rss = FeedTypeConfig.new
config.feeds.rss.enabled = false
config.feeds.rss.path = "feeds/#{collection.name.demodulize.parameterize}.xml"
config.feeds.rss.max_items = 20
Expand Down Expand Up @@ -58,6 +58,15 @@ def respond_to_missing?(name, include_private = false)
end
end
private_constant :Options

class FeedTypeConfig < ActiveSupport::OrderedOptions
def split_by(method_or_lambda = nil, path: nil, &block)
extractor = method_or_lambda || block

self[:split_by] = {extractor: extractor}
self[:split_by][:path] = path if path
end
end
end
end
end
42 changes: 32 additions & 10 deletions lib/perron/site/builder/feeds.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@
require "perron/site/builder/feeds/json"
require "perron/site/builder/feeds/rss"

require "perron/feeds/split"

module Perron
module Site
class Builder
class Feeds
include Perron::Feeds::Split

def initialize(output_path)
@output_path = output_path
end
Expand All @@ -18,21 +22,39 @@ def generate

config = collection.configuration.feeds

if config.atom.enabled
create_file at: config.atom.path, with: Atom.new(collection: collection).generate
end
generate_feed(collection: collection, type_config: config.atom, generator_class: Atom) if config.atom.enabled
generate_feed(collection: collection, type_config: config.json, generator_class: Json) if config.json.enabled
generate_feed(collection: collection, type_config: config.rss, generator_class: Rss) if config.rss.enabled
end
end

private

def generate_feed(collection:, type_config:, generator_class:)
generator = generator_class.new(collection: collection)
content = generator.generate

if config.json.enabled
create_file at: config.json.path, with: Json.new(collection: collection).generate
end
create_file(at: type_config.path, with: content) if content.present?

if config.rss.enabled
create_file at: config.rss.path, with: Rss.new(collection: collection).generate
end
return unless type_config[:split_by]

grouped_resources(collection.resources, type_config[:split_by][:extractor]).each do |value, group|
path = split_path_for(type_config, value)
config = split_config(type_config, value, path)
content = generator_class.new(collection: collection, resources: group, feed_config: config).generate

create_file(at: path, with: content) if content.present?
end
end

private
def split_config(type_config, value, split_path)
ActiveSupport::OrderedOptions.new.tap do |config|
type_config.each { |key, value| config[key] = value unless key == :split_by }

config.path = split_path
config.title = "#{type_config.title.presence || Perron.configuration.site_name}: #{value.to_s.humanize}"
end
end

def create_file(at:, with:)
return if with.blank?
Expand Down
10 changes: 8 additions & 2 deletions lib/perron/site/builder/feeds/atom.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ class Atom
include Feeds::Author
include Feeds::Template

def initialize(collection:)
def initialize(collection:, resources: nil, feed_config: nil)
@collection = collection
@resources = resources
@feed_config = feed_config
@configuration = Perron.configuration
end

Expand All @@ -28,12 +30,16 @@ def generate
private

def resources
@resource ||= @collection.resources
(@resources || @collection.resources)
.reject { it.metadata.feed == false }
.sort_by { it.metadata.published_at || it.metadata.updated_at || Time.current }
.reverse
.take(feed_configuration.max_items)
end

def feed_configuration
@feed_config || super
end
end
end
end
Expand Down
10 changes: 8 additions & 2 deletions lib/perron/site/builder/feeds/json.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ class Json
include Feeds::Author
include Feeds::Template

def initialize(collection:)
def initialize(collection:, resources: nil, feed_config: nil)
@collection = collection
@resources = resources
@feed_config = feed_config
@configuration = Perron.configuration
end

Expand All @@ -28,12 +30,16 @@ def generate
private

def resources
@resource ||= @collection.resources
(@resources || @collection.resources)
.reject { it.metadata.feed == false }
.sort_by { it.metadata.published_at || it.metadata.updated_at || Time.current }
.reverse
.take(feed_configuration.max_items)
end

def feed_configuration
@feed_config || super
end
end
end
end
Expand Down
10 changes: 8 additions & 2 deletions lib/perron/site/builder/feeds/rss.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ class Rss
include Feeds::Author
include Feeds::Template

def initialize(collection:)
def initialize(collection:, resources: nil, feed_config: nil)
@collection = collection
@resources = resources
@feed_config = feed_config
@configuration = Perron.configuration
end

Expand All @@ -28,12 +30,16 @@ def generate
private

def resources
@resource ||= @collection.resources
(@resources || @collection.resources)
.reject { it.metadata.feed == false }
.sort_by { it.metadata.published_at || it.metadata.updated_at || Time.current }
.reverse
.take(feed_configuration.max_items)
end

def feed_configuration
@feed_config || super
end
end
end
end
Expand Down
37 changes: 34 additions & 3 deletions test/perron/feeds_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,14 @@ class Perron::FeedsTest < ActionDispatch::IntegrationTest
end

teardown do
Content::Post.configure { it.feeds.rss.enabled = false }
Content::Post.configure { it.feeds.atom.enabled = false }
Content::Post.configure { it.feeds.json.enabled = false }
Content::Post.configure do |config|
config.feeds.rss.enabled = false
config.feeds.atom.enabled = false
config.feeds.json.enabled = false

config.feeds.atom[:split_by] = nil
end

Content::Page.configure { it.feeds.rss.enabled = false }
end

Expand Down Expand Up @@ -39,6 +44,32 @@ class Perron::FeedsTest < ActionDispatch::IntegrationTest
assert_select document, 'link[href*="feeds/posts.xml"]', count: 0, message: "Posts feed should be excluded"
end

test "renders split feed link tags when split_by is configured" do
Content::Post.configure { it.feeds.atom.split_by :category }

document = rendered_document

assert_select document, 'link[rel="alternate"][type="application/atom+xml"][href*="feeds/posts.atom"]', count: 1
assert_select document, 'link[rel="alternate"][type="application/atom+xml"][href*="feeds/posts/category/news.atom"][title*="News"]', count: 1
assert_select document, 'link[rel="alternate"][type="application/atom+xml"][href*="feeds/posts/category/tutorial.atom"][title*="Tutorial"]', count: 1
end

test "does not render split feed links when split_by is not configured" do
document = rendered_document

assert_select document, 'link[href*="category/"]', count: 0
end

test "renders split feed link tags respect :only and :except options" do
Content::Post.configure { it.feeds.atom.split_by :category }

document = rendered_document(only: [:posts])
assert_select document, 'link[href*="feeds/posts/category/"]', count: 2

document = rendered_document(only: [:pages])
assert_select document, 'link[href*="feeds/posts/category/"]', count: 0
end

test "returns an empty document if no matching feeds are found" do
document = rendered_document(only: [:nonexistent_collection])

Expand Down
Loading
Loading