avo 3.10.10 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
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