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

chore: add method to automatically determine the model associations required to be eager loaded for a decorator #621

Merged
merged 1 commit into from Jun 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
44 changes: 40 additions & 4 deletions lib/pact_broker/api/decorators/base_decorator.rb
Expand Up @@ -4,6 +4,7 @@
require "pact_broker/api/decorators/decorator_context"
require "pact_broker/api/decorators/format_date_time"
require "pact_broker/string_refinements"
require "pact_broker/hash_refinements"

module PactBroker
module Api
Expand All @@ -14,15 +15,17 @@ class BaseDecorator < Roar::Decorator
include PactBroker::Api::PactBrokerUrls
include FormatDateTime
using PactBroker::StringRefinements
using PactBroker::HashRefinements

# Call this method to automatically camelize property names without
# having to define an :as each time.
def self.camelize_property_names
@camelize = true
end

def self.eager_load_associations
representable_attrs.values.select{ | attr| attr[:collection] || attr[:embedded] }.collect{ |attr| attr[:name].to_sym }
end

# Overrides the default property method to add a camelised :as option
# when camelize_property_names has been called for this decorator.
# @override
def self.property(name, options={}, &block)
if options.delete(:camelize) || @camelize
camelized_name = name.to_s.camelcase(false).to_sym
Expand All @@ -31,6 +34,39 @@ def self.property(name, options={}, &block)
super
end
end

# Returns the names of the model associations to eager load for use with this decorator
# @return [Array<Symbol>]
def self.eager_load_associations
if is_collection_resource?
collection_item_decorator_class.eager_load_associations
else
embedded_and_collection_attribute_names
end
end

# Returns true if this class is a decorator for a collection
# @return [true, false]
def self.is_collection_resource?
representable_attrs_without_links = representable_attrs.to_h.without("links")
representable_attrs_without_links.size == 1 &&
representable_attrs_without_links.values.first[:collection] &&
representable_attrs_without_links.values.first[:extend]
end
private_class_method :is_collection_resource?

# Returns the names of the model attributes that are collections, embedded or nested items
# @return [Array<Symbol>]
def self.embedded_and_collection_attribute_names
representable_attrs.values.select{ | attr| attr[:collection] || attr[:embedded] || attr[:nested] }.collect{ |attr| attr[:name].to_sym }
end
private_class_method :embedded_and_collection_attribute_names

# @return [Class] The decorator class used to decorate the items in the collection
def self.collection_item_decorator_class
representable_attrs.to_h.without("links").values.first[:extend].call
end
private_class_method :collection_item_decorator_class
end
end
end
Expand Down
41 changes: 41 additions & 0 deletions spec/lib/pact_broker/api/decorators/base_decorator_spec.rb
@@ -0,0 +1,41 @@
require "pact_broker/api/decorators/base_decorator"

module PactBroker
module Api
module Decorators
describe BaseDecorator do
class TestItemDecorator < BaseDecorator
collection :children
property :foo, embedded: true

property :bar do
property :name
end
property :other
end

class TestItemsDecorator < BaseDecorator
collection :entries, as: :items, embedded: true, :extend => TestItemDecorator

link :self do
"http://foo"
end
end

describe "eager_load_associations" do
context "for an individual resource" do
it "returns the attributes that are collections or embedded" do
expect(TestItemDecorator.eager_load_associations).to eq [:children, :foo, :bar]
end
end

context "for a collection decorator" do
it "returns the eager_load_associations of the class used to decorate the collection items" do
expect(TestItemsDecorator.eager_load_associations).to eq [:children, :foo, :bar]
end
end
end
end
end
end
end
130 changes: 69 additions & 61 deletions spec/lib/pact_broker/api/decorators/integration_decorator_spec.rb
Expand Up @@ -5,79 +5,87 @@ module PactBroker
module Api
module Decorators
describe IntegrationDecorator do
before do
allow(integration_decorator).to receive(:dashboard_url_for_integration).and_return("/dashboard")
allow(integration_decorator).to receive(:matrix_url).and_return("/matrix")
allow(integration_decorator).to receive(:group_url).and_return("/group")
end
describe ".eager_load_associations" do
subject { IntegrationDecorator }

let(:integration) do
instance_double(PactBroker::Integrations::Integration,
consumer: consumer,
provider: provider,
verification_status_for_latest_pact: pseudo_branch_verification_status
)
its(:eager_load_associations) { is_expected.to eq [:consumer, :provider] }
end
let(:consumer) { double("consumer", name: "the consumer") }
let(:provider) { double("provider", name: "the provider") }
let(:pseudo_branch_verification_status) { double("pseudo_branch_verification_status", to_s: "some_status") }

let(:options) { { user_options: { base_url: "http://example.org" } } }
let(:expected_hash) do
{
"consumer" => {
"name" => "the consumer"
},
"provider" => {
"name" => "the provider"
},
"_links" => {
"pb:dashboard" => {
"href" => "/dashboard"
describe "#to_json" do
before do
allow(integration_decorator).to receive(:dashboard_url_for_integration).and_return("/dashboard")
allow(integration_decorator).to receive(:matrix_url).and_return("/matrix")
allow(integration_decorator).to receive(:group_url).and_return("/group")
end

let(:integration) do
instance_double(PactBroker::Integrations::Integration,
consumer: consumer,
provider: provider,
verification_status_for_latest_pact: pseudo_branch_verification_status
)
end
let(:consumer) { double("consumer", name: "the consumer") }
let(:provider) { double("provider", name: "the provider") }
let(:pseudo_branch_verification_status) { double("pseudo_branch_verification_status", to_s: "some_status") }

let(:options) { { user_options: { base_url: "http://example.org" } } }
let(:expected_hash) do
{
"consumer" => {
"name" => "the consumer"
},
"pb:matrix" => {
"title" => "Matrix of pacts/verification results for the consumer and the provider",
"href" => "/matrix"
"provider" => {
"name" => "the provider"
},
"pb:group" => {
"href" => "/group"
"_links" => {
"pb:dashboard" => {
"href" => "/dashboard"
},
"pb:matrix" => {
"title" => "Matrix of pacts/verification results for the consumer and the provider",
"href" => "/matrix"
},
"pb:group" => {
"href" => "/group"
}
}
}
}
end
end

let(:integration_decorator) { IntegrationDecorator.new(integration) }
let(:json) { integration_decorator.to_json(options) }
subject { JSON.parse(json) }
let(:integration_decorator) { IntegrationDecorator.new(integration) }
let(:json) { integration_decorator.to_json(options) }
subject { JSON.parse(json) }

it "generates a hash" do
expect(subject).to match_pact expected_hash
end
it "generates a hash" do
expect(subject).to match_pact expected_hash
end

it "generates the correct link for the dashboard" do
expect(integration_decorator).to receive(:dashboard_url_for_integration).with(
"the consumer",
"the provider",
"http://example.org"
)
subject
end
it "generates the correct link for the dashboard" do
expect(integration_decorator).to receive(:dashboard_url_for_integration).with(
"the consumer",
"the provider",
"http://example.org"
)
subject
end

it "generates the correct link for the matrix" do
expect(integration_decorator).to receive(:matrix_url).with(
"the consumer",
"the provider",
"http://example.org"
)
subject
end
it "generates the correct link for the matrix" do
expect(integration_decorator).to receive(:matrix_url).with(
"the consumer",
"the provider",
"http://example.org"
)
subject
end

it "generates the correct group url for the matrix" do
expect(integration_decorator).to receive(:group_url).with(
"the consumer",
"http://example.org"
)
subject
it "generates the correct group url for the matrix" do
expect(integration_decorator).to receive(:group_url).with(
"the consumer",
"http://example.org"
)
subject
end
end
end
end
Expand Down