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

feature: array adapter #3469

Open
wants to merge 60 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
eb9ad52
feature: array adapter
adrianthedev Nov 28, 2024
91d8b41
update ovei list
adrianthedev Dec 7, 2024
5d2973a
wip: hide unsupported actions, implement show view
Paul-Bob Jan 9, 2025
e1ed8cd
some lint
Paul-Bob Jan 9, 2025
e639cb8
revert indentation
Paul-Bob Jan 9, 2025
c789b97
Merge branch 'main' into feature/array-adapter
Paul-Bob Jan 9, 2025
1fa0f72
lint
Paul-Bob Jan 9, 2025
b6868ae
Merge branch 'feature/array-adapter' of github.com:avo-hq/avo into fe…
Paul-Bob Jan 9, 2025
6355d8b
lint
Paul-Bob Jan 9, 2025
921330f
rm `ActiveRecordResource` for now
Paul-Bob Jan 9, 2025
3572078
wip
Paul-Bob Jan 10, 2025
30e4fb5
rm ostruct requirement
Paul-Bob Jan 10, 2025
16dd5f9
make it work with array of active records
Paul-Bob Jan 10, 2025
14cc44d
Merge branch 'main' into feature/array-adapter
Paul-Bob Jan 10, 2025
dd1c32b
has_many field array option
Paul-Bob Jan 10, 2025
da0b783
lint
Paul-Bob Jan 10, 2025
9fd7808
fix
Paul-Bob Jan 10, 2025
b4c6e01
Merge branch 'main' into feature/array-adapter
Paul-Bob Jan 13, 2025
0482080
fix flaky test
Paul-Bob Jan 13, 2025
bcb3e91
fix flaky test
Paul-Bob Jan 13, 2025
1eca2c8
prepare dummy for tests
Paul-Bob Jan 13, 2025
24e266a
lint
Paul-Bob Jan 13, 2025
6fe69cb
index tests
Paul-Bob Jan 13, 2025
0a74f15
lint & flaky test
Paul-Bob Jan 13, 2025
21dbd3c
lint
Paul-Bob Jan 13, 2025
cee07ca
more tests
Paul-Bob Jan 13, 2025
26a62de
lint
Paul-Bob Jan 13, 2025
2f5f5f3
wait_for_loaded on associations
Paul-Bob Jan 13, 2025
ea17356
rm copyable
Paul-Bob Jan 13, 2025
4955d60
rm comments
Paul-Bob Jan 13, 2025
61408b5
array resource check
Paul-Bob Jan 13, 2025
228579d
using_wait_time capybara
Paul-Bob Jan 13, 2025
feda5d5
.
Paul-Bob Jan 13, 2025
cd740df
fix test
Paul-Bob Jan 13, 2025
50de07f
.
Paul-Bob Jan 13, 2025
c42f23e
fix test
Paul-Bob Jan 13, 2025
79058a8
fix test
Paul-Bob Jan 13, 2025
163096b
wip
Paul-Bob Jan 14, 2025
0972488
wip
Paul-Bob Jan 14, 2025
af256db
wip
Paul-Bob Jan 15, 2025
70743a6
wip
Paul-Bob Jan 15, 2025
da96679
wip
Paul-Bob Jan 15, 2025
c94d9fd
fix test
Paul-Bob Jan 15, 2025
846e3dd
refactor wip
Paul-Bob Jan 15, 2025
68a0727
Merge branch 'main' into feature/array-adapter
Paul-Bob Jan 16, 2025
a8b5c5a
refactor still wip
Paul-Bob Jan 16, 2025
15424d9
fix test
Paul-Bob Jan 17, 2025
431435d
wip refactor
Paul-Bob Jan 17, 2025
95367b4
lint
Paul-Bob Jan 17, 2025
c4884bc
Revert "wip refactor"
Paul-Bob Jan 17, 2025
be1dc23
re-try
Paul-Bob Jan 17, 2025
815c2fa
Revert "Revert "wip refactor""
Paul-Bob Jan 17, 2025
adc614b
re-add display
Paul-Bob Jan 17, 2025
504bebb
rm display
Paul-Bob Jan 17, 2025
224a2a3
Merge branch 'main' into feature/array-adapter
Paul-Bob Jan 17, 2025
8ee548f
add description
Paul-Bob Jan 17, 2025
5bcede9
Merge branch 'feature/array-adapter' of github.com:avo-hq/avo into fe…
Paul-Bob Jan 17, 2025
8d7f82d
fix habtm
Paul-Bob Jan 17, 2025
3a9c32a
fix attach_fields on has one
Paul-Bob Jan 17, 2025
432ff04
is_searchable?
Paul-Bob Jan 17, 2025
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
3 changes: 3 additions & 0 deletions app/components/avo/index/resource_controls_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ def can_detach?
end

def can_edit?
# Disable edit for ArrayResources
return false if @resource.resource_type_array?

return authorize_association_for(:edit) if @reflection.present?

@resource.authorization.authorize_action(:edit, raise_exception: false)
Expand Down
2 changes: 1 addition & 1 deletion app/components/avo/index/table_row_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
parent_resource: @parent_resource
) %>
<% else %>
<td class="text-center">—</td>
<td class="px-3">—</td>
<% end %>
<% end %>
<% if @resource.resource_controls_render_on_the_right? %>
Expand Down
6 changes: 6 additions & 0 deletions app/components/avo/resource_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,18 @@ def detach_path
end

def can_see_the_edit_button?
# Disable edit for ArrayResources
return false if @resource.resource_type_array?

return authorize_association_for(:edit) if @reflection.present?

@resource.authorization.authorize_action(:edit, raise_exception: false)
end

def can_see_the_destroy_button?
# Disable destroy for ArrayResources
return false if @resource.resource_type_array?

@resource.authorization.authorize_action(:destroy, raise_exception: false)
end

Expand Down
2 changes: 1 addition & 1 deletion app/components/avo/views/resource_index_component.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
description: description,
cover_photo: resource.cover_photo,
data: {component: "resources-index"},
display_breadcrumbs: @reflection.blank? || (@reflection.present? && !helpers.turbo_frame_request?)
display_breadcrumbs: (@reflection.blank? && @parent_resource.blank?) || (@reflection.present? && !helpers.turbo_frame_request?)
) do |c| %>
<% c.with_name_slot do %>
<%= render Avo::PanelNameComponent.new name: title, url: (params[:turbo_frame].present? && linkable?) ? field.frame_url(add_turbo_frame: false) : nil, target: :_blank do |panel_name_component| %>
Expand Down
3 changes: 3 additions & 0 deletions app/components/avo/views/resource_index_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ def available_view_types
# The Create button is dependent on the new? policy method.
# The create? should be called only when the user clicks the Save button so the developers gets access to the params from the form.
def can_see_the_create_button?
# Disable creation for ArrayResources
return false if @resource.resource_type_array?

return authorize_association_for(:create) if @reflection.present?

@resource.authorization.authorize_action(:new, raise_exception: false) && !has_reflection_and_is_read_only
Expand Down
7 changes: 7 additions & 0 deletions app/controllers/avo/array_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module Avo
class ArrayController < BaseController
def set_query
@query ||= @resource.fetch_records
end
end
end
15 changes: 13 additions & 2 deletions app/controllers/avo/associations_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,17 @@ def index
@parent_record = @parent_resource.find_record(params[:id], params: params)
@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)

# When array field the records are fetched from the field block, from the parent record or from the resource def records
# When other field type, like has_many the @query is directly fetched from the parent record
base_query = if @field.type == "array"
@resource.fetch_records(Avo::ExecutionContext.new(target: @field.block).handle || @parent_record.try(@field.id))
else
@parent_record.send(association_name)
Dismissed Show dismissed Hide dismissed
end

@query = @related_authorization.apply_policy base_query

@association_field = find_association_field(resource: @parent_resource, association: params[:related_name])

if @association_field.present? && @association_field.scope.present?
Expand Down Expand Up @@ -125,7 +135,8 @@ def set_reflection
end

def set_attachment_class
@attachment_class = @reflection.klass
# @reflection is nil whe using an Array field.
@attachment_class = @reflection&.klass
end

def set_attachment_resource
Expand Down
18 changes: 5 additions & 13 deletions app/controllers/avo/base_application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,6 @@ def resource
Avo.resource_manager.get_resource_by_controller_name @resource_name
end

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

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

reflection = @record.class.reflect_on_association(field&.for_attribute || params[:related_name])

reflected_model = reflection.klass

Avo.resource_manager.get_resource_by_model_class reflected_model
end

def set_resource_name
@resource_name = resource_name
end
Expand All @@ -118,6 +105,11 @@ def detect_fields
end

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

raise Avo::MissingResourceError.new(related_resource_name) if related_resource.nil?

action_view = action_name.to_sym
Expand Down
28 changes: 8 additions & 20 deletions app/controllers/avo/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,7 @@ def index
set_index_params
set_filters
set_actions

# If we don't get a query object predefined from a child controller like associations, just spin one up
unless defined? @query
@query = @resource.class.query_scope
end
set_query

# Eager load the associations
if @resource.includes.present?
Expand All @@ -46,7 +42,7 @@ def index
end
end

apply_sorting
apply_sorting if @index_params[:sort_by]

# Apply filters to the current query
filters_to_be_applied.each do |filter_class, filter_value|
Expand Down Expand Up @@ -310,18 +306,7 @@ def set_index_params
set_pagination_params

# Sorting
if params[:sort_by].present?
@index_params[:sort_by] = params[:sort_by]
elsif @resource.model_class.present?
available_columns = @resource.model_class.column_names
default_sort_column = @resource.default_sort_column

if available_columns.include?(default_sort_column.to_s)
@index_params[:sort_by] = default_sort_column
elsif available_columns.include?("created_at")
@index_params[:sort_by] = :created_at
end
end
@index_params[:sort_by] = params[:sort_by] || @resource.sort_by_param

@index_params[:sort_direction] = params[:sort_direction] || @resource.default_sort_direction

Expand Down Expand Up @@ -608,8 +593,6 @@ def apply_pagination
end

def apply_sorting
return if @index_params[:sort_by].nil?

sort_by = @index_params[:sort_by].to_sym
if sort_by != :created_at
@query = @query.unscope(:order)
Expand Down Expand Up @@ -650,5 +633,10 @@ def set_pagination_params

@index_params[:per_page] = cookies[:per_page] || Avo.configuration.per_page
end

# If we don't get a query object predefined from a child controller like associations, just spin one up
def set_query
@query ||= @resource.class.query_scope
end
end
end
5 changes: 3 additions & 2 deletions app/views/avo/partials/_table_header.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@
<% fields.each_with_index do |field, index| %>
<%
first_option = Avo.configuration.first_sorting_option.to_s
second_option = first_option == "desc" ? "asc" : "desc"

if params[:sort_by] == field.id.to_s
second_option = first_option == "desc" ? "asc" : "desc"

if params[:sort_direction] == second_option
sort_by = nil
else
Expand Down Expand Up @@ -65,7 +66,7 @@
} do %>
<%= content_tag :div,
class: "relative flex items-center justify-between w-full" do %>
<% if field.sortable %>
<% if field.sortable && @resource.sorting_supported? %>
<%= link_to params.permit!.merge(sort_by: sort_by, sort_direction: sort_direction),
class: class_names("flex-1 flex justify-between", text_classes),
'data-turbo-frame': params[:turbo_frame] do %>
Expand Down
1 change: 1 addition & 0 deletions config/initializers/pagy.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "pagy/extras/trim"
require "pagy/extras/countless"
require "pagy/extras/array"
if ::Pagy::VERSION >= ::Gem::Version.new("9.0")
require "pagy/extras/size"
end
Expand Down
2 changes: 1 addition & 1 deletion lib/avo/concerns/find_association_field.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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"]
ASSOCIATIONS = ["belongs_to", "has_one", "has_many", "has_and_belongs_to_many", "array"]
end

# This method is used to find an association field for a given resource.
Expand Down
7 changes: 5 additions & 2 deletions lib/avo/concerns/has_items.rb
Original file line number Diff line number Diff line change
Expand Up @@ -326,8 +326,11 @@ def is_standalone?(item)
def hydrate_item(item)
return unless item.respond_to? :hydrate

res = self.class.ancestors.include?(Avo::BaseResource) ? self : resource
item.hydrate(view: view, resource: res)
item.hydrate(
view: view,
# Use self when this is executed from a resource context, call resource otherwise.
resource: self.class.ancestors.include?(Avo::Resources::Base) ? self : resource
)
end
end
end
Expand Down
1 change: 1 addition & 0 deletions lib/avo/concerns/pagination.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ module Pagination
PAGINATION_METHOD = {
default: :pagy,
countless: :pagy_countless,
array: :pagy_array,
}
end

Expand Down
17 changes: 17 additions & 0 deletions lib/avo/fields/array_field.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
module Avo
module Fields
class ArrayField < ManyFrameBaseField
def translated_name(default:)
t(translation_key, count: 2, default: default_name).capitalize
end

def view_component_name
"HasManyField"
end

def resource_class(params)
use_resource || Avo.resource_manager.get_resource_by_name(@id.to_s)
end
end
end
end
Original file line number Diff line number Diff line change
@@ -1,35 +1,18 @@
module Avo
module Fields
class HasBaseField < BaseField
include Avo::Fields::Concerns::IsSearchable
class FrameBaseField < BaseField
include Avo::Fields::Concerns::UseResource
include Avo::Fields::Concerns::ReloadIcon
include Avo::Fields::Concerns::LinkableTitle
include Avo::Concerns::HasDescription

attr_accessor :display
attr_accessor :scope
attr_accessor :attach_scope
attr_accessor :description
attr_accessor :discreet_pagination
attr_accessor :hide_search_input
attr_reader :link_to_child_resource
attr_reader :attach_fields

def initialize(id, **args, &block)
super(id, **args, &block)
@scope = args[:scope].present? ? args[:scope] : nil
@attach_scope = args[:attach_scope].present? ? args[:attach_scope] : nil
@display = args[:display].present? ? args[:display] : :show
@searchable = args[:searchable] == true
@hide_search_input = args[:hide_search_input] || false
@description = args[:description]
@use_resource = args[:use_resource] || nil
@discreet_pagination = args[:discreet_pagination] || false
# Defaults to nil so that if not set falls back to `link_to_child_resource` defined in the resource
@link_to_child_resource = args[:link_to_child_resource]
@reloadable = args[:reloadable].present? ? args[:reloadable] : false
@linkable = args[:linkable].present? ? args[:linkable] : false
@attach_fields = args[:attach_fields]

@use_resource = args[:use_resource]
@reloadable = args[:reloadable]
@linkable = args[:linkable]
end

def field_resource
Expand Down Expand Up @@ -122,6 +105,18 @@ def query_params(add_turbo_frame: true)
}.compact
end

def resource_class(params)
return use_resource if use_resource.present?

return Avo.resource_manager.get_resource_by_name @id.to_s if @array

reflection = @record.class.reflect_on_association(@for_attribute || params[:related_name])

reflected_model = reflection.klass

Avo.resource_manager.get_resource_by_model_class reflected_model
end

private

def frame_id
Expand Down
10 changes: 1 addition & 9 deletions lib/avo/fields/has_and_belongs_to_many_field.rb
Original file line number Diff line number Diff line change
@@ -1,14 +1,6 @@
module Avo
module Fields
class HasAndBelongsToManyField < HasBaseField
def initialize(id, **args, &block)
args[:updatable] = false

only_on Avo.configuration.resource_default_view

super(id, **args, &block)
end

class HasAndBelongsToManyField < HasManyBaseField
def view_component_name
"HasManyField"
end
Expand Down
13 changes: 13 additions & 0 deletions lib/avo/fields/has_many_base_field.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module Avo
module Fields
class HasManyBaseField < ManyFrameBaseField
attr_accessor :attach_scope

def initialize(id, **args, &block)
super(id, **args, &block)

@attach_scope = args[:attach_scope]
end
end
end
end
14 changes: 9 additions & 5 deletions lib/avo/fields/has_many_field.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
module Avo
module Fields
class HasManyField < HasBaseField
def initialize(id, **args, &block)
args[:updatable] = false

only_on Avo.configuration.resource_default_view
class HasManyField < HasManyBaseField
attr_reader :link_to_child_resource,
:attach_fields

def initialize(id, **args, &block)
super(id, **args, &block)

@attach_scope = args[:attach_scope]
# Defaults to nil so that if not set falls back to `link_to_child_resource` defined in the resource
@link_to_child_resource = args[:link_to_child_resource]
@attach_fields = args[:attach_fields]
end

def translated_name(default:)
Expand Down
2 changes: 1 addition & 1 deletion lib/avo/fields/has_one_field.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module Avo
module Fields
class HasOneField < HasBaseField
class HasOneField < FrameBaseField
attr_accessor :relation_method

def initialize(id, **args, &block)
Expand Down
Loading
Loading