avo 3.10.10 → 3.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -4
  3. data/Gemfile.lock +51 -49
  4. data/app/components/avo/actions_component.html.erb +10 -6
  5. data/app/components/avo/actions_component.rb +37 -55
  6. data/app/components/avo/button_component.rb +3 -3
  7. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +8 -4
  8. data/app/components/avo/fields/belongs_to_field/show_component.rb +1 -1
  9. data/app/components/avo/fields/common/files/list_viewer_component.rb +1 -1
  10. data/app/components/avo/fields/has_one_field/show_component.rb +3 -3
  11. data/app/components/avo/index/resource_controls_component.rb +9 -5
  12. data/app/components/avo/paginator_component.html.erb +1 -1
  13. data/app/components/avo/paginator_component.rb +1 -1
  14. data/app/components/avo/panel_component.html.erb +6 -4
  15. data/app/components/avo/resource_component.rb +3 -2
  16. data/app/components/avo/resource_sidebar_component.html.erb +1 -1
  17. data/app/components/avo/views/resource_edit_component.html.erb +2 -2
  18. data/app/components/avo/views/resource_index_component.html.erb +2 -2
  19. data/app/components/avo/views/resource_index_component.rb +17 -4
  20. data/app/components/avo/views/resource_show_component.html.erb +2 -2
  21. data/app/controllers/avo/actions_controller.rb +1 -1
  22. data/app/controllers/avo/application_controller.rb +1 -1
  23. data/app/controllers/avo/associations_controller.rb +55 -18
  24. data/app/controllers/avo/base_controller.rb +7 -2
  25. data/app/controllers/avo/search_controller.rb +1 -1
  26. data/app/helpers/avo/application_helper.rb +1 -1
  27. data/app/javascript/avo.base.js +8 -0
  28. data/app/views/avo/actions/show.html.erb +3 -3
  29. data/app/views/avo/associations/new.html.erb +45 -20
  30. data/app/views/avo/base/close_modal_and_reload_field.turbo_stream.erb +1 -1
  31. data/app/views/layouts/avo/application.html.erb +1 -2
  32. data/config/initializers/pagy.rb +1 -1
  33. data/lib/avo/base_action.rb +1 -0
  34. data/lib/avo/engine.rb +8 -4
  35. data/lib/avo/fields/base_field.rb +2 -2
  36. data/lib/avo/fields/belongs_to_field.rb +14 -8
  37. data/lib/avo/fields/concerns/is_required.rb +1 -1
  38. data/lib/avo/fields/has_base_field.rb +3 -1
  39. data/lib/avo/fields/has_one_field.rb +1 -1
  40. data/lib/avo/fields_execution_context.rb +13 -0
  41. data/lib/avo/resources/base.rb +32 -22
  42. data/lib/avo/version.rb +1 -1
  43. data/lib/avo.rb +10 -8
  44. data/lib/generators/avo/templates/initializer/avo.tt +3 -3
  45. data/lib/generators/avo/templates/locales/avo.ar.yml +1 -0
  46. data/lib/generators/avo/templates/locales/avo.de.yml +1 -0
  47. data/lib/generators/avo/templates/locales/avo.en.yml +1 -0
  48. data/lib/generators/avo/templates/locales/avo.es.yml +1 -0
  49. data/lib/generators/avo/templates/locales/avo.fr.yml +1 -0
  50. data/lib/generators/avo/templates/locales/avo.it.yml +1 -0
  51. data/lib/generators/avo/templates/locales/avo.ja.yml +1 -0
  52. data/lib/generators/avo/templates/locales/avo.nb.yml +1 -0
  53. data/lib/generators/avo/templates/locales/avo.nl.yml +1 -0
  54. data/lib/generators/avo/templates/locales/avo.nn.yml +1 -0
  55. data/lib/generators/avo/templates/locales/avo.pl.yml +1 -0
  56. data/lib/generators/avo/templates/locales/avo.pt-BR.yml +1 -0
  57. data/lib/generators/avo/templates/locales/avo.pt.yml +1 -0
  58. data/lib/generators/avo/templates/locales/avo.ro.yml +1 -0
  59. data/lib/generators/avo/templates/locales/avo.ru.yml +1 -0
  60. data/lib/generators/avo/templates/locales/avo.tr.yml +1 -0
  61. data/lib/generators/avo/templates/locales/avo.uk.yml +1 -0
  62. data/lib/generators/avo/templates/locales/avo.zh.yml +1 -0
  63. data/lib/tasks/avo_tasks.rake +1 -1
  64. data/public/avo-assets/avo.base.css +49 -2
  65. data/public/avo-assets/avo.base.js +129 -129
  66. data/public/avo-assets/avo.base.js.map +3 -3
  67. data/tailwind.preset.js +2 -3
  68. metadata +4 -3
  69. /data/{lib → app}/avo/base_resource.rb +0 -0
@@ -1,8 +1,8 @@
1
- <%= content_tag :div, data: data_attributes, class: classes do %>
1
+ <%= content_tag :div, class: classes, data: data_attributes do %>
2
2
  <%= render Avo::CoverPhotoComponent.new cover_photo: @cover_photo %>
3
3
 
4
4
  <% if render_header? %>
5
- <div data-target="panel-header" class="flex flex-col flex-1 w-full mb-4">
5
+ <div data-target="panel-header" class="flex flex-col w-full mb-4">
6
6
  <div class="flex justify-center sm:justify-start flex-col sm:flex-row w-full flex-1 has-cover-photo:mt-0">
7
7
  <%= render Avo::ProfilePhotoComponent.new profile_photo: @profile_photo %>
8
8
  <div class="flex flex-col flex-1 w-full">
@@ -55,9 +55,11 @@
55
55
  </div>
56
56
  <% end %>
57
57
  <% if bare_content? %>
58
- <div data-target="panel-bare-content" class="relative <% if sidebar? %> has-sidebar <% end %>">
58
+ <%= content_tag :div, class: class_names("relative flex flex-1 flex-col", "has-sidebar": sidebar?), data: {
59
+ target: :"panel-bare-content"
60
+ } do %>
59
61
  <%= bare_content %>
60
- </div>
62
+ <% end %>
61
63
  <% end %>
62
64
  <% if footer_tools? %>
63
65
  <div
@@ -36,7 +36,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
36
36
  def detach_path
37
37
  return "/" if @reflection.blank?
38
38
 
39
- helpers.resource_detach_path(params[:resource_name], params[:id], @reflection.name.to_s, @resource.record.to_param)
39
+ helpers.resource_detach_path(params[:resource_name], params[:id], @reflection.name.to_s, @resource.record_param)
40
40
  end
41
41
 
42
42
  def can_see_the_edit_button?
@@ -149,6 +149,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
149
149
  label: actions_list.label,
150
150
  size: actions_list.size,
151
151
  icon: actions_list.icon,
152
+ title: actions_list.title,
152
153
  as_row_control: instance_of?(Avo::Index::ResourceControlsComponent)
153
154
  )
154
155
  end
@@ -174,7 +175,7 @@ class Avo::ResourceComponent < Avo::BaseComponent
174
175
  target: "control:destroy",
175
176
  control: :destroy,
176
177
  tippy: control.title ? :tooltip : nil,
177
- "resource-id": @resource.record.id,
178
+ "resource-id": @resource.record_param,
178
179
  } do
179
180
  control.label
180
181
  end
@@ -2,7 +2,7 @@
2
2
  data-component-name="<%= self.class.to_s.underscore %>"
3
3
  data-component-index="<%= index %>"
4
4
  data-resource-name="<%= @resource.class.to_s %>"
5
- data-record-id="<%= @resource.record.id %>"
5
+ data-record-id="<%= @resource.record_param %>"
6
6
  >
7
7
  <%= render Avo::Items::VisibleItemsComponent.new(
8
8
  resource: @resource,
@@ -3,9 +3,9 @@
3
3
  data: {
4
4
  model_name: @resource.model_name.to_s,
5
5
  resource_name: @resource.class.to_s,
6
- record_id: @resource.record.id,
6
+ record_id: @resource.record_param,
7
7
  selected_resources_name: @resource.model_key,
8
- selected_resources: [@resource.record.id],
8
+ selected_resources: [@resource.record_param],
9
9
  **@resource.stimulus_data_attributes
10
10
  } do %>
11
11
  <%= render_cards_component %>
@@ -11,7 +11,7 @@
11
11
  description: description,
12
12
  cover_photo: resource.cover_photo,
13
13
  data: {component: "resources-index"},
14
- display_breadcrumbs: @reflection.blank?
14
+ display_breadcrumbs: @reflection.blank? || (@reflection.present? && !helpers.turbo_frame_request?)
15
15
  ) do |c| %>
16
16
  <% c.with_name_slot do %>
17
17
  <%= 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| %>
@@ -78,7 +78,7 @@
78
78
  <% end %>
79
79
  <% if view_type.to_sym == :table || view_type.to_sym == :map %>
80
80
  <% if @records.present? %>
81
- <div class="mt-4">
81
+ <div class="mt-4 w-full">
82
82
  <%= render Avo::PaginatorComponent.new pagy: @pagy, turbo_frame: turbo_frame || "none", index_params: @index_params, resource: @resource, parent_record: parent_record, discreet_pagination: field&.discreet_pagination %>
83
83
  </div>
84
84
  <% end %>
@@ -66,10 +66,20 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
66
66
  end
67
67
 
68
68
  def can_attach?
69
- klass = @reflection
70
- klass = @reflection.through_reflection if klass.is_a? ::ActiveRecord::Reflection::ThroughReflection
69
+ return false if has_reflection_and_is_read_only
71
70
 
72
- @reflection.present? && klass.is_a?(::ActiveRecord::Reflection::HasManyReflection) && !has_reflection_and_is_read_only && authorize_association_for(:attach)
71
+ reflection_class = if @reflection.is_a?(::ActiveRecord::Reflection::ThroughReflection)
72
+ @reflection.through_reflection.class
73
+ else
74
+ @reflection.class
75
+ end
76
+
77
+ return false unless reflection_class.in? [
78
+ ActiveRecord::Reflection::HasManyReflection,
79
+ ActiveRecord::Reflection::HasAndBelongsToManyReflection
80
+ ]
81
+
82
+ authorize_association_for(:attach)
73
83
  end
74
84
 
75
85
  def create_path
@@ -82,7 +92,10 @@ class Avo::Views::ResourceIndexComponent < Avo::ResourceComponent
82
92
  via_record_id: @parent_record.to_param
83
93
  }
84
94
 
85
- if @reflection.is_a? ActiveRecord::Reflection::ThroughReflection
95
+ if @reflection.class.in? [
96
+ ActiveRecord::Reflection::ThroughReflection,
97
+ ActiveRecord::Reflection::HasAndBelongsToManyReflection
98
+ ]
86
99
  args[:via_relation] = params[:resource_name]
87
100
  end
88
101
 
@@ -2,9 +2,9 @@
2
2
  data: {
3
3
  model_name: @resource.model_name.to_s,
4
4
  resource_name: @resource.class.to_s,
5
- record_id: @resource.record.id,
5
+ record_id: @resource.record_param,
6
6
  selected_resources_name: @resource.model_key,
7
- selected_resources: [@resource.record.to_param],
7
+ selected_resources: [@resource.record_param],
8
8
  **@resource.stimulus_data_attributes
9
9
  } do %>
10
10
  <%= content_tag :div, class: 'space-y-12' do %>
@@ -4,7 +4,7 @@ module Avo
4
4
  class ActionsController < ApplicationController
5
5
  before_action :set_resource_name
6
6
  before_action :set_resource
7
- before_action :set_record, only: :show, if: ->(request) do
7
+ before_action :set_record, only: [:show, :handle], if: ->(request) do
8
8
  # Try to se the record only if the user is on the record page.
9
9
  # set_record will fail if it's tried to be used from the Index page.
10
10
  request.params[:id].present?
@@ -89,7 +89,7 @@ module Avo
89
89
 
90
90
  return field.use_resource if field&.use_resource.present?
91
91
 
92
- reflection = @record._reflections.with_indifferent_access[field&.for_attribute || params[:related_name]]
92
+ reflection = @record.class.reflect_on_association(field&.for_attribute || params[:related_name])
93
93
 
94
94
  reflected_model = reflection.klass
95
95
 
@@ -12,6 +12,7 @@ module Avo
12
12
  before_action :set_attachment_class, only: [:show, :index, :new, :create, :destroy]
13
13
  before_action :set_attachment_resource, only: [:show, :index, :new, :create, :destroy]
14
14
  before_action :set_attachment_record, only: [:create, :destroy]
15
+ before_action :set_attach_fields, only: [:new, :create]
15
16
  before_action :authorize_index_action, only: :index
16
17
  before_action :authorize_attach_action, only: :new
17
18
  before_action :authorize_detach_action, only: :destroy
@@ -54,7 +55,7 @@ module Avo
54
55
  end
55
56
 
56
57
  @options = query.all.map do |record|
57
- [@attachment_resource.new(record: record).record_title, record.id]
58
+ [@attachment_resource.new(record: record).record_title, record.to_param]
58
59
  end
59
60
  end
60
61
  end
@@ -67,6 +68,7 @@ module Avo
67
68
  notice: t("avo.attachment_class_attached", attachment_class: @related_resource.name)
68
69
  }
69
70
  else
71
+ flash[:error] = t("avo.attachment_failed", attachment_class: @related_resource.name)
70
72
  format.turbo_stream {
71
73
  render turbo_stream: turbo_stream.append("alerts", partial: "avo/partials/all_alerts")
72
74
  }
@@ -78,7 +80,9 @@ module Avo
78
80
  association_name = BaseResource.valid_association_name(@record, association_from_params)
79
81
 
80
82
  perform_action_and_record_errors do
81
- if reflection_class == "HasManyReflection"
83
+ if through_reflection? && additional_params.present?
84
+ new_join_record.save
85
+ elsif has_many_reflection? || through_reflection?
82
86
  @record.send(association_name) << @attachment_record
83
87
  else
84
88
  @record.send(:"#{association_name}=", @attachment_record)
@@ -90,9 +94,9 @@ module Avo
90
94
  def destroy
91
95
  association_name = BaseResource.valid_association_name(@record, @field.for_attribute || params[:related_name])
92
96
 
93
- if reflection.instance_of? ActiveRecord::Reflection::ThroughReflection
97
+ if through_reflection?
94
98
  join_record.destroy!
95
- elsif reflection_class == "HasManyReflection"
99
+ elsif has_many_reflection?
96
100
  @record.send(association_name).delete @attachment_record
97
101
  else
98
102
  @record.send(:"#{association_name}=", nil)
@@ -106,7 +110,7 @@ module Avo
106
110
  private
107
111
 
108
112
  def set_reflection
109
- @reflection = @record._reflections.with_indifferent_access[association_from_params]
113
+ @reflection = @record.class.reflect_on_association(association_from_params)
110
114
  end
111
115
 
112
116
  def set_attachment_class
@@ -132,12 +136,11 @@ module Avo
132
136
  end
133
137
 
134
138
  def reflection_class
135
- reflection = @record._reflections.with_indifferent_access[association_from_params]
136
-
137
- klass = reflection.class.name.demodulize.to_s
138
- klass = reflection.through_reflection.class.name.demodulize.to_s if klass == "ThroughReflection"
139
-
140
- klass
139
+ if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection)
140
+ @reflection.through_reflection.class
141
+ else
142
+ @reflection.class
143
+ end
141
144
  end
142
145
 
143
146
  def authorize_if_defined(method, record = @record)
@@ -172,21 +175,55 @@ module Avo
172
175
  @field&.for_attribute || params[:related_name]
173
176
  end
174
177
 
175
- def reflection
176
- @record.class.reflections.with_indifferent_access[association_from_params]
177
- end
178
-
179
178
  def source_foreign_key
180
- reflection.source_reflection.foreign_key
179
+ @reflection.source_reflection.foreign_key
181
180
  end
182
181
 
183
182
  def through_foreign_key
184
- reflection.through_reflection.foreign_key
183
+ @reflection.through_reflection.foreign_key
185
184
  end
186
185
 
187
186
  def join_record
188
- reflection.through_reflection.klass.find_by(source_foreign_key => @attachment_record.id,
187
+ @reflection.through_reflection.klass.find_by(source_foreign_key => @attachment_record.id,
189
188
  through_foreign_key => @record.id)
190
189
  end
190
+
191
+ def has_many_reflection?
192
+ reflection_class.in? [
193
+ ActiveRecord::Reflection::HasManyReflection,
194
+ ActiveRecord::Reflection::HasAndBelongsToManyReflection
195
+ ]
196
+ end
197
+
198
+ def through_reflection?
199
+ @reflection.instance_of? ActiveRecord::Reflection::ThroughReflection
200
+ end
201
+
202
+ def additional_params
203
+ @additional_params ||= params[:fields].permit(@attach_fields&.map(&:id))
204
+ end
205
+
206
+ def set_attach_fields
207
+ @attach_fields = if @field.attach_fields.present?
208
+ Avo::FieldsExecutionContext.new(target: @field.attach_fields)
209
+ .detect_fields
210
+ .items_holder
211
+ .items
212
+ end
213
+ end
214
+
215
+ def new_join_record
216
+ @resource.fill_record(
217
+ @reflection.through_reflection.klass.new,
218
+ additional_params.merge(
219
+ {
220
+ source_foreign_key => @attachment_record.id,
221
+ through_foreign_key => @record.id
222
+ }
223
+ ),
224
+ fields: @attach_fields,
225
+ extra_params: [source_foreign_key, through_foreign_key]
226
+ )
227
+ end
191
228
  end
192
229
  end
@@ -18,6 +18,11 @@ module Avo
18
18
 
19
19
  def index
20
20
  @page_title = @resource.plural_name.humanize
21
+
22
+ if @reflection.present? && !turbo_frame_request?
23
+ add_breadcrumb @record.class.to_s.pluralize, resources_path(resource: @parent_resource)
24
+ add_breadcrumb @parent_resource.record_title, resource_path(record: @record, resource: @parent_resource)
25
+ end
21
26
  add_breadcrumb @resource.plural_name.humanize
22
27
 
23
28
  set_index_params
@@ -121,7 +126,7 @@ module Avo
121
126
  def create
122
127
  # This means that the record has been created through another parent record and we need to attach it somehow.
123
128
  if params[:via_record_id].present? && params[:via_belongs_to_resource_class].nil?
124
- @reflection = @record._reflections.with_indifferent_access[params[:via_relation]]
129
+ @reflection = @record.class.reflect_on_association(params[:via_relation])
125
130
  # Figure out what kind of association does the record have with the parent record
126
131
 
127
132
  # Fills in the required info for belongs_to and has_many
@@ -134,7 +139,7 @@ module Avo
134
139
  end
135
140
 
136
141
  # For when working with has_one, has_one_through, has_many_through, has_and_belongs_to_many, polymorphic
137
- if @reflection.is_a? ActiveRecord::Reflection::ThroughReflection
142
+ if @reflection.is_a?(ActiveRecord::Reflection::ThroughReflection) || @reflection.is_a?(ActiveRecord::Reflection::HasAndBelongsToManyReflection)
138
143
  # find the record
139
144
  via_resource = Avo.resource_manager.get_resource_by_model_class(params[:via_relation_class])
140
145
  @related_record = via_resource.find_record params[:via_record_id], params: params
@@ -129,7 +129,7 @@ module Avo
129
129
  end
130
130
 
131
131
  {
132
- _id: record.id,
132
+ _id: record.to_param,
133
133
  _label: highlighted_title,
134
134
  _url: resource.class.fetch_search(:result_path, record: resource.record) || record_path
135
135
  }
@@ -134,7 +134,7 @@ module Avo
134
134
  end
135
135
 
136
136
  def frame_id(resource)
137
- ["frame", resource.model_name.singular, resource.record.id].compact.join("-")
137
+ ["frame", resource.model_name.singular, resource.record_param].compact.join("-")
138
138
  end
139
139
 
140
140
  def chart_color(index)
@@ -49,6 +49,14 @@ function initTippy() {
49
49
 
50
50
  return title
51
51
  },
52
+ onShow(tooltipInstance) {
53
+ // Don't render tooltip if there is no content.
54
+ if (tooltipInstance.props.content === null || tooltipInstance.props.content.length === 0) {
55
+ return false
56
+ }
57
+
58
+ return tooltipInstance
59
+ },
52
60
  })
53
61
  }
54
62
 
@@ -11,7 +11,7 @@
11
11
  class="hidden text-slate-800"
12
12
  >
13
13
  <%= form_with scope: 'fields',
14
- url: Avo::Services::URIService.parse(@resource.records_path).append_paths("actions").to_s,
14
+ url: @action.link_arguments(resource: @resource).first,
15
15
  local: true,
16
16
  html: {
17
17
  novalidate: true,
@@ -29,7 +29,6 @@
29
29
  <div class="flex-1 flex">
30
30
  <%= @action.get_message %>
31
31
  </div>
32
- <%= hidden_field_tag :action_id, @action.to_param %>
33
32
  <%= form.hidden_field :avo_resource_ids, value: params[:id] || params[:resource_ids], 'data-action-target': 'resourceIds' %>
34
33
  <%= form.hidden_field :avo_selected_query, 'data-action-target': 'selectedAllQuery' %>
35
34
  <%= form.hidden_field :arguments, value: params[:arguments] %>
@@ -58,7 +57,8 @@
58
57
  size: :sm,
59
58
  data: {
60
59
  target: :submit_action,
61
- }, autofocus: @fields.reject { |field| field.is_a?(Avo::Fields::HiddenField) }.empty? do %>
60
+ },
61
+ autofocus: @fields.reject { |field| field.is_a?(Avo::Fields::HiddenField) }.empty? do %>
62
62
  <%= @action.confirm_button_label %>
63
63
  <% end %>
64
64
  <% end %>
@@ -18,31 +18,56 @@
18
18
  } do |form| %>
19
19
  <%= render Avo::ModalComponent.new do |c| %>
20
20
  <% c.with_heading do %>
21
- <%= t 'avo.choose_item', item: @related_resource.name.downcase %>
21
+ <%= t 'avo.choose_item', item: @field.name.singularize.downcase %>
22
22
  <% end %>
23
23
 
24
24
  <div class="flex-1 flex items-center justify-center px-0 lg:px-8 text-lg mt-8 mb-12">
25
- <% if @field.is_searchable? %>
26
- <%= render Avo::Pro::SearchableAssociations::AutocompleteComponent.new form: form,
27
- classes: input_classes("w-full"),
28
- field: @field,
29
- model_key: @field.target_resource&.model_key,
30
- foreign_key: 'related_id',
31
- resource: @resource,
32
- view: :new
33
- %>
34
- <% else %>
35
- <div class="flex-1 flex flex-col items-center justify-center px-0 md:px-24 text-base">
36
- <%= form.select :related_id, options_for_select(@options, nil),
37
- {
25
+ <div class="flex-1 flex flex-col items-center justify-center px-0 md:px-24 text-base">
26
+ <div class="w-full">
27
+ <% if @field.is_searchable? %>
28
+ <%= field_wrapper stacked: true,
29
+ field: @field,
30
+ view: Avo::ViewInquirer.new("edit"),
31
+ form:,
32
+ index: 0,
33
+ resource: @resource,
34
+ label_for: @field.id,
35
+ label: @field.name.singularize.downcase do %>
36
+ <%= render Avo::Pro::SearchableAssociations::AutocompleteComponent.new form: form,
37
+ classes: input_classes("w-full"),
38
+ field: @field,
39
+ model_key: @field.target_resource&.model_key,
40
+ foreign_key: 'related_id',
41
+ resource: @resource,
42
+ view: :new
43
+ %>
44
+ <% end %>
45
+ <% else %>
46
+ <%= avo_edit_field :related_id,
47
+ as: :select,
48
+ form: form,
49
+ name: @field.name.singularize,
50
+ options: options_for_select(@options,
51
+ nil),
38
52
  include_blank: t('avo.choose_an_option'),
39
- },
40
- {
41
- class: input_classes('w-full'),
42
- }
43
- %>
53
+ stacked: true,
54
+ classes: 'w-full'
55
+ %>
56
+ <% end %>
57
+ <% @attach_fields&.each_with_index do |field, index| %>
58
+ <%= render(Avo::Items::SwitcherComponent.new(
59
+ resource: @related_resource,
60
+ item: field,
61
+ index: index + 1,
62
+ view: @view,
63
+ form: form,
64
+ field_component_extra_args: {
65
+ stacked: true,
66
+ classes: 'w-full'}
67
+ )) %>
68
+ <% end %>
44
69
  </div>
45
- <% end %>
70
+ </div>
46
71
  </div>
47
72
 
48
73
  <% c.with_controls do %>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <turbo-stream action="update-belongs-to"
4
4
  data-relation-name="<%= params[:via_relation] %>"
5
- data-target-record-id="<%= @record.id %>"
5
+ data-target-record-id="<%= @record.to_param %>"
6
6
  data-target-resource-label="<%= @resource.record_title %>"
7
7
  data-target-resource-class="<%= @record.class.name %>">
8
8
  </turbo-stream>
@@ -18,13 +18,12 @@
18
18
  <%= javascript_include_tag "/avo-assets/avo.base", "data-turbo-track": "reload", defer: true %>
19
19
  <% else %>
20
20
  <%= javascript_include_tag "avo.base", "data-turbo-track": "reload", defer: true %>
21
- <% if Rails.env.development? %>
21
+ <% if Rails.env.development? && defined?(Hotwire::Livereload) %>
22
22
  <%= javascript_include_tag "hotwire-livereload", defer: true %>
23
23
  <% end %>
24
24
  <% end %>
25
25
  <%= render Avo::AssetManager::JavascriptComponent.new asset_manager: Avo.asset_manager %>
26
26
  <%= render partial: "avo/partials/head" %>
27
- <%= turbo_refreshes_with method: :replace, scroll: :reset %>
28
27
  <%= content_for :head %>
29
28
  </head>
30
29
  <body class="bg-application os-mac">
@@ -1,6 +1,6 @@
1
1
  require "pagy/extras/trim"
2
2
  require "pagy/extras/countless"
3
- if ::Pagy::VERSION > ::Gem::Version.new("9.0")
3
+ if ::Pagy::VERSION >= ::Gem::Version.new("9.0")
4
4
  require "pagy/extras/size"
5
5
  end
6
6
 
@@ -33,6 +33,7 @@ module Avo
33
33
  delegate :avo, to: :view_context
34
34
  delegate :main_app, to: :view_context
35
35
  delegate :to_param, to: :class
36
+ delegate :link_arguments, to: :class
36
37
 
37
38
  class << self
38
39
  delegate :context, to: ::Avo::Current
data/lib/avo/engine.rb CHANGED
@@ -49,11 +49,15 @@ module Avo
49
49
  # This undoes Rails' previous nested directories behavior in the `app` dir.
50
50
  # More on this: https://github.com/fxn/zeitwerk/issues/250
51
51
  avo_directory = Rails.root.join("app", "avo").to_s
52
- ActiveSupport::Dependencies.autoload_paths.delete(avo_directory)
52
+ engine_avo_directory = Avo::Engine.root.join("app", "avo").to_s
53
53
 
54
- if Dir.exist?(avo_directory)
55
- Rails.autoloaders.main.push_dir(avo_directory, namespace: Avo)
56
- app.config.watchable_dirs[avo_directory] = [:rb]
54
+ [avo_directory, engine_avo_directory].each do |directory_path|
55
+ ActiveSupport::Dependencies.autoload_paths.delete(directory_path)
56
+
57
+ if Dir.exist?(directory_path)
58
+ Rails.autoloaders.main.push_dir(directory_path, namespace: Avo)
59
+ app.config.watchable_dirs[directory_path] = [:rb]
60
+ end
57
61
  end
58
62
  end
59
63
 
@@ -242,7 +242,7 @@ module Avo
242
242
  end
243
243
 
244
244
  def record_errors
245
- record.nil? ? {} : record.errors
245
+ record.present? ? record.errors : {}
246
246
  end
247
247
 
248
248
  def type
@@ -313,7 +313,7 @@ module Avo
313
313
  def get_resource_by_model_class(model_class)
314
314
  resource = Avo.resource_manager.get_resource_by_model_class(model_class)
315
315
 
316
- resource || (raise Avo::MissingResourceError.new(model_class))
316
+ resource || (raise Avo::MissingResourceError.new(model_class, id))
317
317
  end
318
318
  end
319
319
  end
@@ -125,7 +125,7 @@ module Avo
125
125
  end
126
126
 
127
127
  query.all.map do |record|
128
- [resource.new(record: record).record_title, record.id]
128
+ [resource.new(record: record).record_title, record.to_param]
129
129
  end
130
130
  end
131
131
 
@@ -204,13 +204,19 @@ module Avo
204
204
  record.send(:"#{polymorphic_as}_type=", valid_model_class)
205
205
 
206
206
  # If the type is blank, reset the id too.
207
- if valid_model_class.blank?
207
+ id_from_param = params["#{polymorphic_as}_id"]
208
+
209
+ if valid_model_class.blank? || id_from_param.blank?
208
210
  record.send(:"#{polymorphic_as}_id=", nil)
209
211
  else
210
- record.send(:"#{polymorphic_as}_id=", params["#{polymorphic_as}_id"])
212
+ record_id = target_resource(record:, polymorphic_model_class: value.safe_constantize).find_record(id_from_param).id
213
+
214
+ record.send(:"#{polymorphic_as}_id=", record_id)
211
215
  end
212
216
  else
213
- record.send(:"#{key}=", value)
217
+ record_id = value.blank? ? value : target_resource(record:).find_record(value).id
218
+
219
+ record.send(:"#{key}=", record_id)
214
220
  end
215
221
 
216
222
  record
@@ -231,19 +237,19 @@ module Avo
231
237
  id
232
238
  end
233
239
 
234
- def target_resource
240
+ def target_resource(record: @record, polymorphic_model_class: value&.class)
235
241
  @target_resource ||= if use_resource.present?
236
242
  use_resource
237
243
  elsif is_polymorphic?
238
- if value.present?
239
- get_resource_by_model_class(value.class)
244
+ if polymorphic_model_class.present?
245
+ get_resource_by_model_class(polymorphic_model_class)
240
246
  else
241
247
  return nil
242
248
  end
243
249
  else
244
250
  reflection_key = polymorphic_as || id
245
251
 
246
- reflection_object = @record._reflections.with_indifferent_access[reflection_key]
252
+ reflection_object = record.class.reflect_on_association(reflection_key)
247
253
 
248
254
  if reflection_object.klass.present?
249
255
  get_resource_by_model_class(reflection_object.klass.to_s)
@@ -13,7 +13,7 @@ module Avo
13
13
  private
14
14
 
15
15
  def required_from_validators
16
- return false if record.nil?
16
+ return false unless record.present?
17
17
 
18
18
  validators.any? do |validator|
19
19
  validator.is_a? ActiveModel::Validations::PresenceValidator
@@ -13,6 +13,7 @@ module Avo
13
13
  attr_accessor :discreet_pagination
14
14
  attr_accessor :hide_search_input
15
15
  attr_reader :link_to_child_resource
16
+ attr_reader :attach_fields
16
17
 
17
18
  def initialize(id, **args, &block)
18
19
  super(id, **args, &block)
@@ -27,6 +28,7 @@ module Avo
27
28
  @link_to_child_resource = args[:link_to_child_resource] || false
28
29
  @reloadable = args[:reloadable].present? ? args[:reloadable] : false
29
30
  @linkable = args[:linkable].present? ? args[:linkable] : false
31
+ @attach_fields = args[:attach_fields]
30
32
  end
31
33
 
32
34
  def field_resource
@@ -59,7 +61,7 @@ module Avo
59
61
  end
60
62
 
61
63
  def target_resource
62
- reflection = @record._reflections.with_indifferent_access[association_name]
64
+ reflection = @record.class.reflect_on_association(association_name)
63
65
 
64
66
  if reflection.klass.present?
65
67
  get_resource_by_model_class(reflection.klass.to_s)
@@ -19,7 +19,7 @@ module Avo
19
19
 
20
20
  def frame_url
21
21
  Avo::Services::URIService.parse(field_resource.record_path)
22
- .append_paths(id, value.id)
22
+ .append_paths(id, value.to_param)
23
23
  .append_query(query_params)
24
24
  .to_s
25
25
  end