Skip to content

Commit

Permalink
Add page and element decorators
Browse files Browse the repository at this point in the history
We will use these decorators to load the elements in an efficient way
to avoid N+1 queries.
  • Loading branch information
tvdeyen committed Jan 19, 2021
1 parent 0dd4c1c commit 0755ffe
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 0 deletions.
33 changes: 33 additions & 0 deletions app/models/alchemy/json_api/element.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Alchemy
module JsonApi
class Element < BaseRecord
include Alchemy::Element::Definitions
include Alchemy::Element::ElementContents

self.table_name = "alchemy_elements"

belongs_to :page, class_name: "Alchemy::JsonApi::Page", inverse_of: :elements
has_many :contents, class_name: "Alchemy::Content", inverse_of: :element

scope :available, -> { where(public: true).where.not(position: nil) }

def parent_element
page.elements.detect do |element|
element.id == parent_element_id
end
end

def nested_elements
@_nested_elements ||= begin
page.elements.select do |element|
element.parent_element_id == id
end
end
end

def nested_element_ids
nested_elements.map(&:id)
end
end
end
end
27 changes: 27 additions & 0 deletions app/models/alchemy/json_api/page.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
module Alchemy
module JsonApi
class Page < BaseRecord
self.table_name = "alchemy_pages"

include Alchemy::Taggable
include Alchemy::Page::PageNatures

belongs_to :language, class_name: "Alchemy::Language"

has_many :elements,
-> { available.order(:position) },
class_name: "Alchemy::JsonApi::Element",
inverse_of: :page

scope :published, -> {
where("#{table_name}.public_on <= :time AND " \
"(#{table_name}.public_until IS NULL " \
"OR #{table_name}.public_until >= :time)", time: Time.current)
}

scope :contentpages, -> { where(layoutpage: false) }
scope :layoutpages, -> { where(layoutpage: true) }
scope :with_language, ->(language_id) { where(language_id: language_id) }
end
end
end
63 changes: 63 additions & 0 deletions spec/models/alchemy/json_api/element_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
require "rails_helper"
require "alchemy/test_support/factories"

RSpec.describe Alchemy::JsonApi::Element, type: :model do
it { should belong_to(:page).class_name("Alchemy::JsonApi::Page") }
it { should have_many(:contents).class_name("Alchemy::Content") }

describe "scopes" do
describe ".available" do
subject(:available) { described_class.available.map(&:id) }

let!(:public_one) { FactoryBot.create(:alchemy_element, public: true) }
let!(:non_public) { FactoryBot.create(:alchemy_element, public: false) }
let!(:trashed) { FactoryBot.create(:alchemy_element, public: true).tap(&:trash!) }

it "returns public available elements" do
# expecting the ids here because the factorys class is not our decorator class
expect(available).to include(public_one.id)
expect(available).to_not include(non_public.id)
expect(available).to_not include(trashed.id)
end
end
end

describe "#parent_element" do
subject { nested_element.parent_element }

let(:page) { FactoryBot.create(:alchemy_page) }
let!(:element) { FactoryBot.create(:alchemy_element, page: page) }
let!(:nested_element) { FactoryBot.create(:alchemy_element, page: page, parent_element: element) }
let!(:not_nested_element) { FactoryBot.create(:alchemy_element, page: page) }

it "returns elements parent element" do
is_expected.to eq(element)
end
end

describe "#nested_elements" do
subject { element.nested_elements }

let(:page) { FactoryBot.create(:alchemy_page) }
let!(:element) { FactoryBot.create(:alchemy_element, page: page) }
let!(:nested_element) { FactoryBot.create(:alchemy_element, page: page, parent_element: element) }
let!(:not_nested_element) { FactoryBot.create(:alchemy_element, page: page) }

it "returns all nested elements" do
is_expected.to eq([nested_element])
end
end

describe "#nested_element_ids" do
subject { element.nested_element_ids }

let(:page) { FactoryBot.create(:alchemy_page) }
let!(:element) { FactoryBot.create(:alchemy_element, page: page) }
let!(:nested_element) { FactoryBot.create(:alchemy_element, page: page, parent_element: element) }
let!(:not_nested_element) { FactoryBot.create(:alchemy_element, page: page) }

it "returns all nested element ids" do
is_expected.to eq([nested_element.id])
end
end
end
56 changes: 56 additions & 0 deletions spec/models/alchemy/json_api/page_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
require "rails_helper"
require "alchemy/test_support/factories"

RSpec.describe Alchemy::JsonApi::Page, type: :model do
it { should belong_to(:language).class_name("Alchemy::Language") }
it { should have_many(:elements).class_name("Alchemy::JsonApi::Element") }

describe "scopes" do
describe ".published" do
subject(:published) { described_class.published.map(&:id) }

let!(:public_one) { FactoryBot.create(:alchemy_page, :public) }
let!(:public_two) { FactoryBot.create(:alchemy_page, :public) }
let!(:non_public_page) { FactoryBot.create(:alchemy_page) }

it "returns public available pages" do
# expecting the ids here because the factorys class is not our decorator class
expect(published).to include(public_one.id)
expect(published).to include(public_two.id)
expect(published).to_not include(non_public_page.id)
end
end

describe ".contentpages" do
let!(:layoutpage) { FactoryBot.create(:alchemy_page, :layoutpage) }
let!(:contentpage) { FactoryBot.create(:alchemy_page) }

it "should return contentpages" do
# expecting the attribute here because the factorys class is not our decorator class
expect(described_class.contentpages.map(&:layoutpage)).to eq([false, false]) # page plus root page
end
end

describe ".layoutpages" do
let!(:layoutpage) { FactoryBot.create(:alchemy_page, :layoutpage) }
let!(:contentpage) { FactoryBot.create(:alchemy_page) }

it "should return layoutpages" do
# expecting the attribute here because the factorys class is not our decorator class
expect(described_class.layoutpages.map(&:layoutpage)).to eq([true])
end
end

describe ".with_language" do
let(:english) { FactoryBot.create(:alchemy_language, :english) }
let(:german) { FactoryBot.create(:alchemy_language, :german) }
let!(:page_en) { FactoryBot.create(:alchemy_page, language: english) }
let!(:page_de) { FactoryBot.create(:alchemy_page, language: german) }

it "should return layoutpages" do
# expecting the attribute here because the factorys class is not our decorator class
expect(described_class.with_language(german.id).map(&:language_id)).to eq([german.id, german.id]) # page plus root page
end
end
end
end

0 comments on commit 0755ffe

Please sign in to comment.