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

fix: verify type when fetching associations fields #3231

Merged
merged 3 commits into from
Sep 12, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions app/components/avo/base_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
class Avo::BaseComponent < ViewComponent::Base
extend Literal::Properties
include Turbo::FramesHelper
include Avo::Concerns::FindAssociationField

def has_with_trial(ability)
Avo.license.has_with_trial(ability)
Expand All @@ -12,8 +13,7 @@ def has_with_trial(ability)

# Use the @parent_resource to fetch the field using the @reflection name.
def field
reflection_name = params[:related_name]&.to_sym || @reflection.name
@parent_resource.get_field(reflection_name)
find_association_field(resource: @parent_resource, association: params[:related_name] || @reflection.name)
rescue
nil
end
Expand Down
3 changes: 2 additions & 1 deletion app/controllers/avo/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class ApplicationController < ::ActionController::Base
include Avo::ApplicationHelper
include Avo::UrlHelpers
include Avo::Concerns::Breadcrumbs
include Avo::Concerns::FindAssociationField

protect_from_forgery with: :exception
around_action :set_avo_locale
Expand Down Expand Up @@ -85,7 +86,7 @@ def resource

def related_resource
# Find the field from the parent resource
field = @resource.get_field params[:related_name]
field = find_association_field(resource: @resource, association: params[:related_name])

return field.use_resource if field&.use_resource.present?

Expand Down
4 changes: 2 additions & 2 deletions app/controllers/avo/associations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def index
@parent_resource.hydrate(record: @parent_record)
association_name = BaseResource.valid_association_name(@parent_record, association_from_params)
@query = @related_authorization.apply_policy @parent_record.send(association_name)
@association_field = @parent_resource.get_field params[:related_name]
@association_field = find_association_field(resource: @parent_resource, association: params[:related_name])

if @association_field.present? && @association_field.scope.present?
@query = Avo::ExecutionContext.new(target: @association_field.scope, query: @query, parent: @parent_record).handle
Expand Down Expand Up @@ -126,7 +126,7 @@ def set_attachment_record
end

def set_reflection_field
@field = @resource.get_field(@related_resource_name.to_sym)
@field = find_association_field(resource: @resource, association: @related_resource_name)
@field.hydrate(resource: @resource, record: @record, view: Avo::ViewInquirer.new(:new))
rescue
end
Expand Down
21 changes: 21 additions & 0 deletions lib/avo/concerns/find_association_field.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module Avo
module Concerns
module FindAssociationField
# The supported association types are defined in the ASSOCIATIONS constant.
unless defined?(ASSOCIATIONS)
ASSOCIATIONS = ["belongs_to", "has_one", "has_many", "has_and_belongs_to_many"]
end

# This method is used to find an association field for a given resource.
# Ideally, the exact type of the association should be known in advance.
# However, there are cases where the type is unknown
# and the method will return the first matching association field
# based on the provided association name.
def find_association_field(resource:, association:)
resource.get_field_definitions.find do |field|
(field.id == association.to_sym) && field.type.in?(ASSOCIATIONS)
end
end
end
end
end
6 changes: 6 additions & 0 deletions spec/dummy/app/avo/resources/store.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ def fields
field :location, as: :has_one
end

# Intentionally use the same ID as the :has_many field to test whether the correct association field
# is retrieved during rendering of the association.
field :patrons, as: :tags do
record.patrons.map(&:name)
end

field :patrons,
as: :has_many,
through: :patronships,
Expand Down
18 changes: 18 additions & 0 deletions spec/system/avo/has_many_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -105,4 +105,22 @@ def destroy
end
end
end

describe "duplicated field id" do
let!(:store) { create(:store) }

it "render tags and has many field" do
StorePatron.create!(user:, store:, review: "some review")
visit avo.resources_store_path(store)

# Find user name on tags field
expect(page).to have_css('div[data-field-id="patrons"] div[data-target="tag-component"]', text: user.name)

# Find user name on has many field
within("tr[data-record-id='#{user.id}']") do
expect(page).to have_text(user.first_name)
expect(page).to have_text(user.last_name)
end
end
end
end
Loading