Skip to content

Commit

Permalink
Use the elements finder in render_elements helper
Browse files Browse the repository at this point in the history
  • Loading branch information
tvdeyen committed Feb 21, 2019
1 parent 29bd409 commit 5f28ec4
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 191 deletions.
128 changes: 43 additions & 85 deletions app/helpers/alchemy/elements_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module ElementsHelper
include Alchemy::UrlHelper
include Alchemy::ElementsBlockHelper

# Renders all elements from current page
# Renders elements from given page
#
# == Examples:
#
Expand Down Expand Up @@ -45,53 +45,64 @@ module ElementsHelper
# with: 'contact_teaser'
# }) %>
#
# @param [Hash] options
# Additional options.
# === Custom elements finder:
#
# Having a custom element finder class:
#
# class MyCustomNewsArchive
# def elements
# news_page.elements.available.named('news').order(created_at: :desc)
# end
#
# private
#
# def news_page
# Alchemy::Page.where(page_layout: 'news-archive')
# end
# end
#
# In your view:
#
# <div class="news-archive">
# <%= render_elements finder: MyCustomNewsArchive.new %>
# </div>
#
# @option options [Alchemy::Page|String] :from_page (@page)
# The page the elements are rendered from. You can pass a page_layout String or a {Alchemy::Page} object.
# @option options [Array<String>|String] :only
# A list of element names only to be rendered.
# @option options [Array<String>|String] :except
# A list of element names not to be rendered.
# @option options [Number] :count
# The amount of elements to be rendered (begins with first element found)
# @option options [Array or String] :except ([])
# A list of element names not to be rendered.
# @option options [Number] :offset
# The offset to begin loading elements from
# @option options [Hash] :fallback
# Define elements that are rendered from another page.
# @option options [Alchemy::Page or String] :from_page (@page)
# The page the elements are rendered from. You can pass a page_layout String or a {Alchemy::Page} object.
# @option options [Array or String] :only ([])
# A list of element names only to be rendered.
# @option options [Boolean] :random
# @option options [Boolean] :random (false)
# Randomize the output of elements
# @option options [Boolean] :reverse
# @option options [Boolean] :reverse (false)
# Reverse the rendering order
# @option options [String] :sort_by
# The name of a {Alchemy::Content} to sort the elements by
# @option options [String] :separator
# A string that will be used to join the element partials. Default nil
# A string that will be used to join the element partials.
# @option options [Class] :finder (Alchemy::ElementsFinder)
# A class that will return elements that get rendered.
# Use this for your custom element loading logic in views.
#
def render_elements(options = {})
options = {
from_page: @page,
render_format: 'html',
reverse: false
render_format: 'html'
}.update(options)

pages = pages_holding_elements(options.delete(:from_page))

if pages.blank?
warning('No page to get elements from was found')
return
end

elements = collect_elements_from_pages(pages, options)
finder = options[:finder] || Alchemy::ElementsFinder.new(options)
elements = finder.elements

if options[:sort_by].present?
elements = sort_elements_by_content(
elements,
options.delete(:sort_by),
options[:reverse]
)
buff = []
elements.each_with_index do |element, i|
buff << render_element(element, :view, options, i + 1)
end

render_element_view_partials(elements, options)
buff.join(options[:separator]).html_safe
end

# This helper renders a {Alchemy::Element} partial.
Expand Down Expand Up @@ -246,58 +257,5 @@ def sort_elements_by_content(elements, content_name, reverse = false)

reverse ? sorted_elements.reverse : sorted_elements
end

private

def pages_holding_elements(page)
case page
when String
Language.current.pages.where(
page_layout: page,
restricted: false
).to_a
when Page
page
end
end

def collect_elements_from_pages(page, options)
if page.is_a? Array
elements = page.collect { |p| p.find_elements(options) }.flatten
else
elements = page.find_elements(options)
end
if fallback_required?(elements, options)
elements += fallback_elements(options)
end
elements
end

def fallback_required?(elements, options)
options[:fallback] && elements.detect { |e| e.name == options[:fallback][:for] }.nil?
end

def fallback_elements(options)
fallback_options = options.delete(:fallback)
case fallback_options[:from]
when String
page = Language.current.pages.find_by(
page_layout: fallback_options[:from],
restricted: false
)
when Page
page = fallback_options[:from]
end
return [] if page.blank?
page.elements.not_trashed.named(fallback_options[:with].presence || fallback_options[:for])
end

def render_element_view_partials(elements, options = {})
buff = []
elements.each_with_index do |element, i|
buff << render_element(element, :view, options, i + 1)
end
buff.join(options[:separator]).html_safe
end
end
end
132 changes: 26 additions & 106 deletions spec/helpers/alchemy/elements_helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -68,146 +68,66 @@ module Alchemy
describe "#render_elements" do
subject { helper.render_elements(options) }

let(:another_element) { build_stubbed(:alchemy_element, page: page) }
let(:elements) { [element, another_element] }
let(:page) { create(:alchemy_page, :public) }
let!(:element) { create(:alchemy_element, name: 'headline', page: page) }
let!(:another_element) { create(:alchemy_element, page: page) }

context 'without any options' do
let(:options) { {} }

before do
expect(page).to receive(:find_elements).and_return(elements)
end

it "should render all elements from page." do
it "should render all elements from current page." do
is_expected.to have_selector("##{element.name}_#{element.id}")
is_expected.to have_selector("##{another_element.name}_#{another_element.id}")
end
end

context "with from_page option" do
context 'is a page object' do
let(:another_page) { build_stubbed(:alchemy_page, :public) }
let(:options) { {from_page: another_page} }
let(:another_page) { create(:alchemy_page, :public) }

before do
expect(another_page).to receive(:find_elements).and_return(elements)
let(:options) do
{ from_page: another_page }
end

let!(:element) { create(:alchemy_element, name: 'headline', page: another_page) }
let!(:another_element) { create(:alchemy_element, page: another_page) }

it "should render all elements from that page." do
is_expected.to have_selector("##{element.name}_#{element.id}")
is_expected.to have_selector("##{another_element.name}_#{another_element.id}")
end
end

context 'is a string' do
let(:another_page) { build_stubbed(:alchemy_page, :public) }
let(:another_element) { build_stubbed(:alchemy_element, page: another_page) }
let(:other_elements) { [another_element] }
let(:options) { {from_page: 'news'} }

before do
allow(Language).to receive(:current).and_return double(pages: double(where: pages))
expect(another_page).to receive(:find_elements).and_return(other_elements)
context 'if from_page is nil' do
let(:options) do
{ from_page: nil }
end

context 'and one page can be found by page layout' do
let(:pages) { [another_page] }

it "it renders all elements from that page." do
is_expected.to have_selector("##{another_element.name}_#{another_element.id}")
end
end

context 'and an array of pages has been found' do
let(:pages) { [page, another_page] }

before do
expect(page).to receive(:find_elements).and_return(elements)
end

it 'renders elements from these pages' do
is_expected.to have_selector("##{element.name}_#{element.id}")
is_expected.to have_selector("##{another_element.name}_#{another_element.id}")
end
end
it { is_expected.to be_empty }
end
end

context 'if page is nil' do
let(:options) { {from_page: nil} }
it { is_expected.to be_blank }
end

context 'with sort_by and reverse option given' do
let(:options) { {sort_by: true, reverse: true} }
let(:sorted_elements) { [another_element, element] }

before do
expect(elements).to receive(:sort_by).and_return(sorted_elements)
expect(sorted_elements).to receive(:reverse).and_return(elements)
expect(page).to receive(:find_elements).and_return(elements)
end

it "renders the sorted elements in reverse order" do
is_expected.not_to be_blank
end
end

context 'with sort_by option given' do
let(:options) { {sort_by: 'title'} }
let(:sorted_elements) { [another_element, element] }

before do
expect(elements).to receive(:sort_by).and_return(sorted_elements)
expect(elements).not_to receive(:reverse)
expect(page).to receive(:find_elements).and_return(elements)
end
context 'with option separator given' do
let(:options) { {separator: '<hr>'} }

it "renders the elements in the order of given content name" do
is_expected.not_to be_blank
it "joins element partials with given string" do
is_expected.to have_selector('hr')
end
end

context "with option fallback" do
let(:another_page) { build_stubbed(:alchemy_page, :public, name: 'Another Page', page_layout: 'news') }
let(:another_element) { build_stubbed(:alchemy_element, page: another_page, name: 'news') }
let(:elements) { [another_element] }

context 'with string given as :fallback_from' do
let(:options) { {fallback: {for: 'higgs', with: 'news', from: 'news'}} }

before do
allow(Language).to receive(:current).and_return double(pages: double(find_by: another_page))
allow(another_page).to receive(:elements).and_return double(not_trashed: double(named: elements))
end

it "renders the fallback element" do
is_expected.to have_selector("#news_#{another_element.id}")
end
end

context 'with page given as :fallback_from' do
let(:options) { {fallback: {for: 'higgs', with: 'news', from: another_page}} }

before do
allow(another_page).to receive(:elements).and_return double(not_trashed: double(named: elements))
end

it "renders the fallback element" do
is_expected.to have_selector("#news_#{another_element.id}")
context 'with custom elements finder' do
class MyCustomBlogElementsFinder
def elements
[Alchemy::Element.new(name: 'news', id: 10001)]
end
end
end

context 'with option separator given' do
let(:options) { {separator: '<hr>'} }

before do
expect(page).to receive(:find_elements).and_return(elements)
let(:options) do
{ finder: MyCustomBlogElementsFinder.new }
end

it "joins element partials with given string" do
is_expected.to have_selector('hr')
it 'uses that to load elements to render' do
is_expected.to have_selector("#news_10001")
end
end
end
Expand Down

0 comments on commit 5f28ec4

Please sign in to comment.