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 support for autoloading nested related objects on ingredients #72

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
56 changes: 51 additions & 5 deletions app/controllers/alchemy/json_api/pages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ def show
def render_pages_json(allowed)
# Only load pages with all includes when browser cache is stale
jsonapi_filter(page_scope_with_includes, allowed) do |filtered|
# decorate with our page model that has a eager loaded elements collection
filtered_pages = filtered.result.map { |page| api_page(page) }
jsonapi_paginate(filtered_pages) do |paginated|
render jsonapi: paginated
jsonapi_paginate(filtered.result) do |paginated|
# decorate with our page model that has a eager loaded elements collection
decorated_pages = preload_ingredients(paginated).map { |page| api_page(page) }
render jsonapi: decorated_pages
end
end
end
Expand Down Expand Up @@ -74,7 +74,9 @@ def jsonapi_meta(pages)
end

def load_page
@page = load_page_by_id || load_page_by_urlname || raise(ActiveRecord::RecordNotFound)
@page = preload_ingredients(
[load_page_by_id || load_page_by_urlname || raise(ActiveRecord::RecordNotFound)]
).first
end

def load_page_by_id
Expand Down Expand Up @@ -109,6 +111,14 @@ def page_scope_with_includes
)
end

def preload_ingredients(scope)
Copy link
Member

Choose a reason for hiding this comment

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

We removed essence support. This can probably be removed now.

if params[:include]&.match?(/ingredients/)
Alchemy::JsonApi::Page.preload_ingredient_relations(scope, page_version_type)
else
scope
end
end

def page_version_type
:public_version
end
Expand All @@ -129,6 +139,42 @@ def base_page_scope
def jsonapi_serializer_class(_resource, _is_collection)
::Alchemy::JsonApi::PageSerializer
end

# These overrides have to be in place until
# https://github.com/stas/jsonapi.rb/pull/91
Copy link
Member

Choose a reason for hiding this comment

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

since this has been merged, can this be removed?

# is merged and released
def jsonapi_paginate(resources)
@_jsonapi_original_size = resources.size
super
end

def jsonapi_pagination_meta(resources)
return {} unless JSONAPI::Rails.is_collection?(resources)

_, limit, page = jsonapi_pagination_params

numbers = { current: page }

total = @_jsonapi_original_size

last_page = [1, (total.to_f / limit).ceil].max

if page > 1
numbers[:first] = 1
numbers[:prev] = page - 1
end

if page < last_page
numbers[:next] = page + 1
numbers[:last] = last_page
end

if total.present?
numbers[:records] = total
end

numbers
end
end
end
end
17 changes: 17 additions & 0 deletions app/models/alchemy/json_api/page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,23 @@ def Page.ransackable_attributes(_auth_object = nil)

module JsonApi
class Page < SimpleDelegator
def self.preload_ingredient_relations(pages, page_version_type)
pages.map { |page| page.send(page_version_type) }.flat_map(&:elements).flat_map(&:ingredients).group_by do |ingredient|
"Alchemy::JsonApi::Ingredient#{ingredient.type.demodulize}Serializer".constantize.preload_relations
end.each do |preload_relations, ingredients|
preload(records: ingredients.map(&:related_object).compact, associations: preload_relations)
end
pages
end

def self.preload(records:, associations:)
if Rails::VERSION::MAJOR >= 7
ActiveRecord::Associations::Preloader.new(records: records, associations: associations).call
else
ActiveRecord::Associations::Preloader.new.preload(records, associations)
end
end

attr_reader :page_version_type, :page_version

def initialize(page, page_version_type: :public_version)
Expand Down
6 changes: 6 additions & 0 deletions app/serializers/alchemy/json_api/base_serializer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ class BaseSerializer
include JSONAPI::Serializer

set_key_transform Alchemy::JsonApi.key_transform

# This method can be overwritten in individual serializers to fetch objects that belong to the related object in some form.
# This takes an Array of relation names that can be passed to the Rails preloader.
def self.preload_relations
[]
end
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ module JsonApi
class IngredientPictureSerializer < BaseSerializer
include IngredientSerializer

def self.preload_relations
[:thumbs]
end

attributes(
:title,
:caption,
Expand Down
6 changes: 6 additions & 0 deletions spec/serializers/alchemy/json_api/base_serializer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,10 @@
expect(described_class).to receive(:set_key_transform).with(:underscore)
load Alchemy::JsonApi::Engine.root.join("app/serializers/alchemy/json_api/base_serializer.rb")
end

describe ".preload_relations" do
subject { described_class.preload_relations }

it { is_expected.to eq([]) }
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@

it_behaves_like "an ingredient serializer"

describe ".preload_relations" do
subject { described_class.preload_relations }

it { is_expected.to eq([:thumbs]) }
end

describe "attributes" do
subject { serializer.serializable_hash[:data][:attributes] }

Expand Down