avo 2.15.3 → 2.16.1.pre.1.nativefields

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of avo might be problematic. Click here for more details.

Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -2
  3. data/Gemfile.lock +78 -86
  4. data/app/components/avo/base_component.rb +7 -1
  5. data/app/components/avo/field_wrapper_component.html.erb +40 -0
  6. data/app/components/avo/field_wrapper_component.rb +102 -0
  7. data/app/components/avo/fields/badge_field/show_component.html.erb +1 -1
  8. data/app/components/avo/fields/belongs_to_field/edit_component.html.erb +3 -3
  9. data/app/components/avo/fields/belongs_to_field/index_component.html.erb +2 -1
  10. data/app/components/avo/fields/belongs_to_field/show_component.html.erb +2 -2
  11. data/app/components/avo/fields/belongs_to_field/show_component.rb +8 -0
  12. data/app/components/avo/fields/boolean_field/edit_component.html.erb +1 -1
  13. data/app/components/avo/fields/boolean_field/show_component.html.erb +1 -1
  14. data/app/components/avo/fields/boolean_group_field/edit_component.html.erb +1 -1
  15. data/app/components/avo/fields/boolean_group_field/show_component.html.erb +1 -1
  16. data/app/components/avo/fields/code_field/edit_component.html.erb +2 -2
  17. data/app/components/avo/fields/code_field/show_component.html.erb +2 -2
  18. data/app/components/avo/fields/country_field/edit_component.html.erb +1 -1
  19. data/app/components/avo/fields/country_field/show_component.html.erb +1 -1
  20. data/app/components/avo/fields/date_field/edit_component.html.erb +1 -1
  21. data/app/components/avo/fields/date_field/show_component.html.erb +1 -1
  22. data/app/components/avo/fields/date_time_field/edit_component.html.erb +1 -1
  23. data/app/components/avo/fields/date_time_field/show_component.html.erb +1 -1
  24. data/app/components/avo/fields/edit_component.rb +22 -4
  25. data/app/components/avo/fields/external_image_field/edit_component.html.erb +1 -1
  26. data/app/components/avo/fields/external_image_field/index_component.html.erb +1 -1
  27. data/app/components/avo/fields/external_image_field/show_component.html.erb +1 -1
  28. data/app/components/avo/fields/file_field/edit_component.html.erb +1 -1
  29. data/app/components/avo/fields/file_field/index_component.html.erb +2 -2
  30. data/app/components/avo/fields/file_field/show_component.html.erb +1 -1
  31. data/app/components/avo/fields/files_field/edit_component.html.erb +1 -1
  32. data/app/components/avo/fields/files_field/show_component.html.erb +1 -1
  33. data/app/components/avo/fields/gravatar_field/index_component.html.erb +1 -1
  34. data/app/components/avo/fields/gravatar_field/show_component.html.erb +1 -1
  35. data/app/components/avo/fields/has_one_field/show_component.html.erb +14 -1
  36. data/app/components/avo/fields/has_one_field/show_component.rb +21 -0
  37. data/app/components/avo/fields/id_field/edit_component.html.erb +1 -1
  38. data/app/components/avo/fields/id_field/index_component.html.erb +1 -1
  39. data/app/components/avo/fields/id_field/show_component.html.erb +1 -1
  40. data/app/components/avo/fields/index_component.rb +9 -4
  41. data/app/components/avo/fields/key_value_field/edit_component.html.erb +2 -2
  42. data/app/components/avo/fields/key_value_field/show_component.html.erb +1 -1
  43. data/app/components/avo/fields/markdown_field/edit_component.html.erb +2 -2
  44. data/app/components/avo/fields/markdown_field/show_component.html.erb +1 -1
  45. data/app/components/avo/fields/number_field/edit_component.html.erb +1 -1
  46. data/app/components/avo/fields/number_field/show_component.html.erb +1 -1
  47. data/app/components/avo/fields/password_field/edit_component.html.erb +1 -1
  48. data/app/components/avo/fields/progress_bar_field/edit_component.html.erb +1 -1
  49. data/app/components/avo/fields/progress_bar_field/show_component.html.erb +1 -1
  50. data/app/components/avo/fields/select_field/edit_component.html.erb +1 -2
  51. data/app/components/avo/fields/select_field/show_component.html.erb +1 -1
  52. data/app/components/avo/fields/show_component.rb +36 -1
  53. data/app/components/avo/fields/status_field/edit_component.html.erb +1 -1
  54. data/app/components/avo/fields/status_field/show_component.html.erb +1 -1
  55. data/app/components/avo/fields/tags_field/edit_component.html.erb +1 -5
  56. data/app/components/avo/fields/tags_field/show_component.html.erb +1 -1
  57. data/app/components/avo/fields/text_field/edit_component.html.erb +2 -2
  58. data/app/components/avo/fields/text_field/index_component.html.erb +1 -1
  59. data/app/components/avo/fields/text_field/show_component.html.erb +1 -1
  60. data/app/components/avo/fields/textarea_field/edit_component.html.erb +1 -1
  61. data/app/components/avo/fields/textarea_field/show_component.html.erb +1 -1
  62. data/app/components/avo/fields/trix_field/edit_component.html.erb +13 -13
  63. data/app/components/avo/fields/trix_field/edit_component.rb +19 -1
  64. data/app/components/avo/fields/trix_field/show_component.html.erb +1 -1
  65. data/app/components/avo/index/grid_item_component.html.erb +6 -6
  66. data/app/components/avo/index/grid_item_component.rb +15 -0
  67. data/app/components/avo/index/resource_controls_component.rb +3 -0
  68. data/app/components/avo/index/resource_grid_component.rb +1 -1
  69. data/app/components/avo/resource_component.rb +5 -1
  70. data/app/components/avo/sidebar/item_switcher_component.html.erb +3 -3
  71. data/app/components/avo/sidebar/link_component.html.erb +2 -2
  72. data/app/components/avo/sidebar/link_component.rb +3 -1
  73. data/app/components/avo/views/resource_edit_component.html.erb +47 -28
  74. data/app/components/avo/views/resource_edit_component.rb +26 -9
  75. data/app/components/avo/views/resource_show_component.rb +0 -8
  76. data/app/controllers/avo/actions_controller.rb +7 -5
  77. data/app/controllers/avo/application_controller.rb +22 -3
  78. data/app/controllers/avo/associations_controller.rb +2 -2
  79. data/app/controllers/avo/base_controller.rb +24 -11
  80. data/app/helpers/avo/application_helper.rb +31 -3
  81. data/app/helpers/avo/resources_helper.rb +4 -8
  82. data/app/helpers/avo/url_helpers.rb +8 -0
  83. data/app/javascript/js/controllers/action_controller.js +3 -1
  84. data/app/javascript/js/controllers/fields/date_field_controller.js +21 -1
  85. data/app/javascript/js/controllers/search_controller.js +122 -118
  86. data/app/views/avo/actions/show.html.erb +5 -1
  87. data/avo.gemspec +1 -1
  88. data/config/master.key +1 -0
  89. data/lib/avo/base_action.rb +25 -6
  90. data/lib/avo/base_resource.rb +6 -4
  91. data/lib/avo/base_resource_tool.rb +1 -1
  92. data/lib/avo/concerns/fetches_things.rb +2 -0
  93. data/lib/avo/configuration.rb +2 -0
  94. data/lib/avo/fields/base_field.rb +5 -1
  95. data/lib/avo/fields/belongs_to_field.rb +1 -1
  96. data/lib/avo/fields/has_and_belongs_to_many_field.rb +1 -1
  97. data/lib/avo/fields/has_base_field.rb +5 -1
  98. data/lib/avo/fields/has_many_field.rb +1 -1
  99. data/lib/avo/fields/select_field.rb +1 -0
  100. data/lib/avo/menu/base_item.rb +1 -0
  101. data/lib/avo/menu/builder.rb +4 -2
  102. data/lib/avo/menu/menu.rb +2 -0
  103. data/lib/avo/services/authorization_service.rb +24 -20
  104. data/lib/avo/version.rb +1 -1
  105. data/lib/generators/avo/templates/field/components/edit_component.html.erb.tt +1 -1
  106. data/lib/generators/avo/templates/field/components/show_component.html.erb.tt +1 -1
  107. data/public/avo-assets/avo.base.css +26 -8
  108. data/public/avo-assets/avo.base.js +79 -79
  109. data/public/avo-assets/avo.base.js.map +3 -3
  110. metadata +8 -11
  111. data/app/components/avo/common_field_wrapper_component.html.erb +0 -26
  112. data/app/components/avo/common_field_wrapper_component.rb +0 -46
  113. data/app/components/avo/edit/field_wrapper_component.html.erb +0 -11
  114. data/app/components/avo/edit/field_wrapper_component.rb +0 -21
  115. data/app/components/avo/show/field_wrapper_component.html.erb +0 -9
  116. data/app/components/avo/show/field_wrapper_component.rb +0 -12
@@ -25,6 +25,8 @@ export default class extends Controller {
25
25
 
26
26
  debouncedFetch = debouncePromise(fetch, this.searchDebounce);
27
27
 
28
+ destroyMethod;
29
+
28
30
  get dataset() {
29
31
  return this.autocompleteTarget.dataset
30
32
  }
@@ -56,97 +58,52 @@ export default class extends Controller {
56
58
  return this.dataset.searchResource === 'global'
57
59
  }
58
60
 
59
- searchUrl(query) {
60
- const url = URI()
61
-
62
- return url.segment(this.searchSegments()).search(this.searchParams(query)).toString()
63
- }
64
-
65
- searchSegments() {
66
- let segments = [
67
- window.Avo.configuration.root_path,
68
- 'avo_api',
69
- this.dataset.searchResource,
70
- 'search',
71
- ]
72
-
73
- if (this.isGlobalSearch) {
74
- segments = [window.Avo.configuration.root_path, 'avo_api', 'search']
75
- }
76
-
77
- return segments
78
- }
79
-
80
- searchParams(query) {
81
- let params = {
82
- q: query,
83
- global: false,
84
- }
85
-
86
- if (this.isGlobalSearch) {
87
- params.global = true
88
- }
61
+ connect() {
62
+ const that = this
89
63
 
90
- if (this.isBelongsToSearch || this.isHasManySearch) {
91
- params = this.addAssociationParams(params)
92
- params = this.addReflectionParams(params)
64
+ this.buttonTarget.onclick = () => this.showSearchPanel()
93
65
 
94
- if (this.isBelongsToSearch) {
95
- params = {
96
- ...params,
97
- // eslint-disable-next-line camelcase
98
- via_parent_resource_id: this.dataset.viaParentResourceId,
99
- // eslint-disable-next-line camelcase
100
- via_parent_resource_class: this.dataset.viaParentResourceClass,
101
- // eslint-disable-next-line camelcase
102
- via_relation: this.dataset.viaRelation,
103
- }
66
+ this.clearValueTargets.forEach((target) => {
67
+ if (target.getAttribute('value') && this.hasClearButtonTarget) {
68
+ this.clearButtonTarget.classList.remove('hidden')
104
69
  }
105
- }
106
-
107
- return params
108
- }
70
+ })
109
71
 
110
- addAssociationParams(params) {
111
- params = {
112
- ...params,
113
- // eslint-disable-next-line camelcase
114
- via_association: this.dataset.viaAssociation,
115
- // eslint-disable-next-line camelcase
116
- via_association_id: this.dataset.viaAssociationId,
72
+ if (this.isGlobalSearch) {
73
+ Mousetrap.bind(['command+k', 'ctrl+k'], () => this.showSearchPanel())
117
74
  }
118
75
 
119
- return params
120
- }
121
-
122
- addReflectionParams(params) {
123
- params = {
124
- ...params,
125
- // eslint-disable-next-line camelcase
126
- via_reflection_class: this.dataset.viaReflectionClass,
127
- // eslint-disable-next-line camelcase
128
- via_reflection_id: this.dataset.viaReflectionId,
129
- }
76
+ autocomplete({
77
+ container: this.autocompleteTarget,
78
+ placeholder: this.translationKeys.placeholder,
79
+ translations: {
80
+ detachedCancelButtonText: this.translationKeys.cancel_button,
81
+ },
82
+ autoFocus: true,
83
+ openOnFocus: true,
84
+ detachedMediaQuery: '',
85
+ getSources: ({ query }) => {
86
+ document.body.classList.add('search-loading')
87
+ const endpoint = that.searchUrl(query)
130
88
 
131
- return params
132
- }
89
+ return that
90
+ .debouncedFetch(endpoint)
91
+ .then((response) => {
92
+ document.body.classList.remove('search-loading')
133
93
 
134
- handleOnSelect({ item }) {
135
- if (this.isBelongsToSearch) {
136
- this.updateFieldAttribute(this.hiddenIdTarget, 'value', item._id)
137
- this.updateFieldAttribute(this.buttonTarget, 'value', item._label)
94
+ return response.json()
95
+ })
96
+ .then((data) => Object.keys(data).map((resourceName) => that.addSource(resourceName, data[resourceName])))
97
+ },
98
+ })
138
99
 
139
- document.querySelector('.aa-DetachedOverlay').remove()
100
+ // document.addEventListener('turbo:before-render', destroy)
101
+ // this.destroyMethod = destroy
140
102
 
141
- if (this.hasClearButtonTarget) {
142
- this.clearButtonTarget.classList.remove('hidden')
143
- }
144
- } else {
145
- Turbo.visit(item._url, { action: 'advance' })
103
+ // When using search for belongs-to
104
+ if (this.buttonTarget.dataset.shouldBeDisabled !== 'true') {
105
+ this.buttonTarget.removeAttribute('disabled')
146
106
  }
147
-
148
- // On searchable belongs to the class `aa-Detached` remains on the body making it unscrollable
149
- document.body.classList.remove('aa-Detached')
150
107
  }
151
108
 
152
109
  addSource(resourceName, data) {
@@ -221,59 +178,106 @@ export default class extends Controller {
221
178
  }
222
179
  }
223
180
 
224
- showSearchPanel() {
225
- this.autocompleteTarget.querySelector('button').click()
181
+ handleOnSelect({ item }) {
182
+ if (this.isBelongsToSearch) {
183
+ this.updateFieldAttribute(this.hiddenIdTarget, 'value', item._id)
184
+ this.updateFieldAttribute(this.buttonTarget, 'value', item._label)
185
+
186
+ document.querySelector('.aa-DetachedOverlay').remove()
187
+
188
+ if (this.hasClearButtonTarget) {
189
+ this.clearButtonTarget.classList.remove('hidden')
190
+ }
191
+ } else {
192
+ Turbo.visit(item._url, { action: 'advance' })
193
+ }
194
+
195
+ // On searchable belongs to the class `aa-Detached` remains on the body making it unscrollable
196
+ document.body.classList.remove('aa-Detached')
226
197
  }
227
198
 
228
- clearValue() {
229
- this.clearValueTargets.map((t) => this.updateFieldAttribute(t, 'value', ''))
230
- this.clearButtonTarget.classList.add('hidden')
199
+ searchUrl(query) {
200
+ const url = URI()
201
+
202
+ return url.segment(this.searchSegments()).search(this.searchParams(query)).toString()
231
203
  }
232
204
 
233
- connect() {
234
- const that = this
205
+ searchSegments() {
206
+ let segments = [
207
+ window.Avo.configuration.root_path,
208
+ 'avo_api',
209
+ this.dataset.searchResource,
210
+ 'search',
211
+ ]
235
212
 
236
- this.buttonTarget.onclick = () => this.showSearchPanel()
213
+ if (this.isGlobalSearch) {
214
+ segments = [window.Avo.configuration.root_path, 'avo_api', 'search']
215
+ }
237
216
 
238
- this.clearValueTargets.forEach((target) => {
239
- if (target.getAttribute('value') && this.hasClearButtonTarget) {
240
- this.clearButtonTarget.classList.remove('hidden')
241
- }
242
- })
217
+ return segments
218
+ }
219
+
220
+ searchParams(query) {
221
+ let params = {
222
+ q: query,
223
+ global: false,
224
+ }
243
225
 
244
226
  if (this.isGlobalSearch) {
245
- Mousetrap.bind(['command+k', 'ctrl+k'], () => this.showSearchPanel())
227
+ params.global = true
246
228
  }
247
229
 
248
- const { destroy } = autocomplete({
249
- container: this.autocompleteTarget,
250
- placeholder: this.translationKeys.placeholder,
251
- translations: {
252
- detachedCancelButtonText: this.translationKeys.cancel_button,
253
- },
254
- openOnFocus: true,
255
- detachedMediaQuery: '',
256
- getSources: ({ query }) => {
257
- document.body.classList.add('search-loading')
258
- const endpoint = that.searchUrl(query)
230
+ if (this.isBelongsToSearch || this.isHasManySearch) {
231
+ params = this.addAssociationParams(params)
232
+ params = this.addReflectionParams(params)
259
233
 
260
- return that
261
- .debouncedFetch(endpoint)
262
- .then((response) => {
263
- document.body.classList.remove('search-loading')
234
+ if (this.isBelongsToSearch) {
235
+ params = {
236
+ ...params,
237
+ // eslint-disable-next-line camelcase
238
+ via_parent_resource_id: this.dataset.viaParentResourceId,
239
+ // eslint-disable-next-line camelcase
240
+ via_parent_resource_class: this.dataset.viaParentResourceClass,
241
+ // eslint-disable-next-line camelcase
242
+ via_relation: this.dataset.viaRelation,
243
+ }
244
+ }
245
+ }
264
246
 
265
- return response.json()
266
- })
267
- .then((data) => Object.keys(data).map((resourceName) => that.addSource(resourceName, data[resourceName])))
268
- },
269
- })
247
+ return params
248
+ }
270
249
 
271
- document.addEventListener('turbo:before-render', destroy)
250
+ addAssociationParams(params) {
251
+ params = {
252
+ ...params,
253
+ // eslint-disable-next-line camelcase
254
+ via_association: this.dataset.viaAssociation,
255
+ // eslint-disable-next-line camelcase
256
+ via_association_id: this.dataset.viaAssociationId,
257
+ }
272
258
 
273
- // When using search for belongs-to
274
- if (this.buttonTarget.dataset.shouldBeDisabled !== 'true') {
275
- this.buttonTarget.removeAttribute('disabled')
259
+ return params
260
+ }
261
+
262
+ addReflectionParams(params) {
263
+ params = {
264
+ ...params,
265
+ // eslint-disable-next-line camelcase
266
+ via_reflection_class: this.dataset.viaReflectionClass,
267
+ // eslint-disable-next-line camelcase
268
+ via_reflection_id: this.dataset.viaReflectionId,
276
269
  }
270
+
271
+ return params
272
+ }
273
+
274
+ showSearchPanel() {
275
+ this.autocompleteTarget.querySelector('button').click()
276
+ }
277
+
278
+ clearValue() {
279
+ this.clearValueTargets.map((target) => this.updateFieldAttribute(target, 'value', ''))
280
+ this.clearButtonTarget.classList.add('hidden')
277
281
  }
278
282
 
279
283
  // Private
@@ -25,7 +25,11 @@
25
25
  <% if @action.get_fields.present? %>
26
26
  <div class="mt-4">
27
27
  <% @action.get_fields.each_with_index do |field, index| %>
28
- <%= render field.component_for_view(:edit).new field: field, resource: @resource, index: index, form: form, displayed_in_modal: true %>
28
+ <%= render field
29
+ .hydrate(resource: @resource, model: @resource.model, user: @resource.user, view: @view)
30
+ .component_for_view(:edit)
31
+ .new(field: field, resource: @resource, index: index, form: form, displayed_in_modal: true)
32
+ %>
29
33
  <% end %>
30
34
  </div>
31
35
  <% end %>
data/avo.gemspec CHANGED
@@ -41,7 +41,7 @@ Gem::Specification.new do |spec|
41
41
  spec.add_dependency "active_link_to"
42
42
  spec.add_dependency "image_processing"
43
43
  spec.add_dependency "view_component", "2.60"
44
- spec.add_dependency "hotwire-rails"
44
+ spec.add_dependency "turbo-rails"
45
45
  spec.add_dependency "addressable"
46
46
  spec.add_dependency "meta-tags"
47
47
  spec.add_dependency "breadcrumbs_on_rails"
data/config/master.key ADDED
@@ -0,0 +1 @@
1
+ 2aeb23d82b909d9c6b5abb62f7058c2a
@@ -73,26 +73,36 @@ module Avo
73
73
  def get_attributes_for_action
74
74
  get_fields.map do |field|
75
75
  [field.id, field.value]
76
- end
77
- .to_h
76
+ end.to_h
78
77
  end
79
78
 
80
79
  def handle_action(**args)
81
80
  models, fields, current_user, resource = args.values_at(:models, :fields, :current_user, :resource)
82
- avo_fields = get_fields.map { |field| [field.id, field] }.to_h
81
+ # Fetching the field definitions and not the actual fields (get_fields) because they will break if the user uses a `visible` block and adds a condition using the `params` variable. The params are different in the show method and the handle method.
82
+ action_fields = get_field_definitions.map { |field| [field.id, field] }.to_h
83
+
84
+ # For some fields, like belongs_to, the id and database_id differ (user vs user_id).
85
+ # That's why we need to fetch the database_id for when we process the action.
86
+ action_fields_by_database_id = action_fields.map do |id, value|
87
+ [value.database_id.to_sym, value]
88
+ end.to_h
83
89
 
84
90
  if fields.present?
85
91
  processed_fields = fields.to_unsafe_h.map do |name, value|
86
- [name, avo_fields[name.to_sym].resolve_attribute(value)]
92
+ field = action_fields_by_database_id[name.to_sym]
93
+
94
+ next if field.blank?
95
+
96
+ [name, field.resolve_attribute(value)]
87
97
  end
88
98
 
89
- processed_fields = processed_fields.to_h
99
+ processed_fields = processed_fields.reject(&:blank?).to_h
90
100
  else
91
101
  processed_fields = {}
92
102
  end
93
103
 
94
104
  args = {
95
- fields: processed_fields,
105
+ fields: processed_fields.with_indifferent_access,
96
106
  current_user: current_user,
97
107
  resource: resource
98
108
  }
@@ -175,6 +185,15 @@ module Avo
175
185
  self
176
186
  end
177
187
 
188
+ # We're overriding this method to hydrate with the proper resource attribute.
189
+ def hydrate_fields(model: nil, view: nil)
190
+ fields.map do |field|
191
+ field.hydrate(model: @model, view: @view, resource: resource)
192
+ end
193
+
194
+ self
195
+ end
196
+
178
197
  private
179
198
 
180
199
  def add_message(body, type = :info)
@@ -30,6 +30,7 @@ module Avo
30
30
  class_attribute :search_query, default: nil
31
31
  class_attribute :search_query_help, default: ""
32
32
  class_attribute :includes, default: []
33
+ class_attribute :authorization_policy
33
34
  class_attribute :translation_key
34
35
  class_attribute :default_view_type, default: :table
35
36
  class_attribute :devise_password_optional, default: false
@@ -92,7 +93,7 @@ module Avo
92
93
  end
93
94
 
94
95
  def authorization
95
- Avo::Services::AuthorizationService.new Avo::App.current_user
96
+ Avo::Services::AuthorizationService.new Avo::App.current_user, model_class, policy_class: authorization_policy
96
97
  end
97
98
 
98
99
  def order_actions
@@ -263,7 +264,7 @@ module Avo
263
264
  field.computed
264
265
  end
265
266
  .map do |field|
266
- [field.database_id(model).to_s, field]
267
+ [field.database_id.to_s, field]
267
268
  end
268
269
  .to_h
269
270
 
@@ -285,8 +286,9 @@ module Avo
285
286
  model
286
287
  end
287
288
 
288
- def authorization
289
- Avo::Services::AuthorizationService.new(user, model || model_class)
289
+ def authorization(user: nil)
290
+ current_user = user || Avo::App.current_user
291
+ Avo::Services::AuthorizationService.new(current_user, model || model_class, policy_class: authorization_policy)
290
292
  end
291
293
 
292
294
  def file_hash
@@ -13,7 +13,7 @@ module Avo
13
13
 
14
14
  def initialize(**args)
15
15
  # Set the visibility
16
- show_on :show
16
+ show_on Avo.configuration.resource_default_view
17
17
 
18
18
  show_on args[:show_on] if args[:show_on].present?
19
19
  hide_on args[:hide_on] if args[:hide_on].present?
@@ -48,9 +48,11 @@ module Avo
48
48
  end
49
49
 
50
50
  # Returns the Avo resource by singular snake_cased name
51
+ # From all the resources that use the same model_class, it will fetch the first one in alphabetical order
51
52
  #
52
53
  # get_resource_by_name('User') => UserResource
53
54
  # get_resource_by_name(User) => UserResource
55
+
54
56
  def get_resource_by_model_name(klass)
55
57
  # Fetch the mappings imposed by the user.
56
58
  # If they are present, use those ones.
@@ -36,6 +36,7 @@ module Avo
36
36
  attr_accessor :profile_menu
37
37
  attr_accessor :model_resource_mapping
38
38
  attr_accessor :tabs_style
39
+ attr_accessor :resource_default_view
39
40
  attr_writer :branding
40
41
 
41
42
  def initialize
@@ -83,6 +84,7 @@ module Avo
83
84
  @profile_menu = nil
84
85
  @model_resource_mapping = {}
85
86
  @tabs_style = :tabs
87
+ @resource_default_view = :show
86
88
  end
87
89
 
88
90
  def current_user_method(&block)
@@ -76,6 +76,8 @@ module Avo
76
76
  @as_description = args[:as_description] || false
77
77
  @index_text_align = args[:index_text_align] || :left
78
78
  @html = args[:html] || nil
79
+ @view = args[:view] || nil
80
+ @value = args[:value] || nil
79
81
 
80
82
  @args = args
81
83
 
@@ -155,6 +157,8 @@ module Avo
155
157
  end
156
158
 
157
159
  def value(property = nil)
160
+ return @value if @value.present?
161
+
158
162
  property ||= id
159
163
 
160
164
  # Get model value
@@ -191,7 +195,7 @@ module Avo
191
195
  end
192
196
 
193
197
  # Try to see if the field has a different database ID than it's name
194
- def database_id(model)
198
+ def database_id
195
199
  foreign_key
196
200
  rescue
197
201
  id
@@ -210,7 +210,7 @@ module Avo
210
210
  model
211
211
  end
212
212
 
213
- def database_id(model)
213
+ def database_id
214
214
  # If the field is a polymorphic value, return the polymorphic_type as key and pre-fill the _id in fill_field.
215
215
  return "#{polymorphic_as}_type" if polymorphic_as.present?
216
216
 
@@ -5,7 +5,7 @@ module Avo
5
5
  args[:updatable] = false
6
6
 
7
7
  hide_on :all
8
- show_on :show
8
+ show_on Avo.configuration.resource_default_view
9
9
 
10
10
  super(id, **args, &block)
11
11
  end
@@ -30,7 +30,7 @@ module Avo
30
30
  end
31
31
 
32
32
  def resource
33
- Avo::App.get_resource_by_model_name @model.class
33
+ @resource || Avo::App.get_resource_by_model_name(@model.class)
34
34
  end
35
35
 
36
36
  def turbo_frame
@@ -108,6 +108,10 @@ module Avo
108
108
  def frame_id
109
109
  use_resource.present? ? use_resource.route_key.to_sym : @id
110
110
  end
111
+
112
+ def default_view
113
+ Avo.configuration.skip_show_view ? :edit : :show
114
+ end
111
115
  end
112
116
  end
113
117
  end
@@ -5,7 +5,7 @@ module Avo
5
5
  args[:updatable] = false
6
6
 
7
7
  hide_on :all
8
- show_on :show
8
+ show_on Avo.configuration.resource_default_view
9
9
 
10
10
  super(id, **args, &block)
11
11
  end
@@ -30,6 +30,7 @@ module Avo
30
30
  if display_value
31
31
  options.invert
32
32
  else
33
+ # We need to use the label attribute as the option value because Rails casts it like that
33
34
  options.map { |label, value| [label, label] }.to_h
34
35
  end
35
36
  elsif display_value
@@ -9,6 +9,7 @@ class Avo::Menu::BaseItem
9
9
  option :items, default: proc { [] }
10
10
  option :name, default: proc { "" }
11
11
  option :visible, default: proc { true }
12
+ option :data, default: proc { {} }
12
13
 
13
14
  def visible?
14
15
  return visible if visible.in? [true, false]
@@ -22,9 +22,11 @@ class Avo::Menu::Builder
22
22
  end
23
23
 
24
24
  # Adds a link
25
- def link(name, **args)
26
- @menu.items << Avo::Menu::Link.new(name: name, **args)
25
+ def link(name, path = nil, **args)
26
+ path ||= args[:path]
27
+ @menu.items << Avo::Menu::Link.new(name: name, path: path, **args)
27
28
  end
29
+ alias_method :link_to, :link
28
30
 
29
31
  # Validates and adds a resource
30
32
  def resource(name, **args)
data/lib/avo/menu/menu.rb CHANGED
@@ -1,2 +1,4 @@
1
1
  class Avo::Menu::Menu < OpenStruct
2
+
3
+
2
4
  end
@@ -5,13 +5,14 @@ module Avo
5
5
  attr_accessor :record
6
6
 
7
7
  class << self
8
- def authorize(user, record, action, **args)
8
+ def authorize(user, record, action, policy_class: nil, **args)
9
9
  return true if skip_authorization
10
10
  return true if user.nil?
11
11
 
12
+ policy_class ||= Pundit.policy(user, record)&.class
12
13
  begin
13
- if Pundit.policy user, record
14
- Pundit.authorize user, record, action
14
+ if policy_class&.new(user, record)
15
+ Pundit.authorize user, record, action, policy_class: policy_class
15
16
  end
16
17
 
17
18
  true
@@ -28,7 +29,7 @@ module Avo
28
29
  end
29
30
  end
30
31
 
31
- def authorize_action(user, record, action, **args)
32
+ def authorize_action(user, record, action, policy_class: nil, **args)
32
33
  action = Avo.configuration.authorization_methods.stringify_keys[action.to_s] || action
33
34
 
34
35
  # If no action passed we should raise error if the user wants that.
@@ -41,16 +42,18 @@ module Avo
41
42
 
42
43
  # Add the question mark if it's missing
43
44
  action = "#{action}?" unless action.end_with? "?"
44
-
45
- authorize user, record, action, **args
45
+ authorize(user, record, action, policy_class: policy_class, **args)
46
46
  end
47
47
 
48
- def apply_policy(user, model)
49
- return model if skip_authorization
50
- return model if user.nil?
48
+ def apply_policy(user, model, policy_class: nil)
49
+ return model if skip_authorization || user.nil?
51
50
 
52
51
  begin
53
- Pundit.policy_scope! user, model
52
+ if policy_class
53
+ policy_class::Scope.new(user, model).resolve
54
+ else
55
+ Pundit.policy_scope! user, model
56
+ end
54
57
  rescue Pundit::NotDefinedError => e
55
58
  return model unless Avo.configuration.raise_error_on_missing_policy
56
59
 
@@ -68,12 +71,12 @@ module Avo
68
71
  end.to_h
69
72
  end
70
73
 
71
- def get_policy(user, record)
72
- Pundit.policy user, record
73
- end
74
+ def defined_methods(user, record, policy_class: nil, **args)
75
+ return Pundit.policy!(user, record).methods if policy_class.nil?
74
76
 
75
- def defined_methods(user, record, **args)
76
- Pundit.policy!(user, record).methods
77
+ # I'm aware this will not raise a Pundit error.
78
+ # Should the policy not exist, it will however raise an uninitialized constant error, which is probably what we want when specifying a custom policy
79
+ policy_class.new(user, record).methods
77
80
  rescue Pundit::NotDefinedError => e
78
81
  return [] unless Avo.configuration.raise_error_on_missing_policy
79
82
 
@@ -87,13 +90,14 @@ module Avo
87
90
  end
88
91
  end
89
92
 
90
- def initialize(user = nil, record = nil)
93
+ def initialize(user = nil, record = nil, policy_class: nil)
91
94
  @user = user
92
95
  @record = record
96
+ @policy_class = policy_class || Pundit.policy(user, record)&.class
93
97
  end
94
98
 
95
99
  def authorize(action, **args)
96
- self.class.authorize(user, record, action, **args)
100
+ self.class.authorize(user, record, action, policy_class: @policy_class, **args)
97
101
  end
98
102
 
99
103
  def set_record(record)
@@ -109,15 +113,15 @@ module Avo
109
113
  end
110
114
 
111
115
  def authorize_action(action, **args)
112
- self.class.authorize_action(user, record, action, **args)
116
+ self.class.authorize_action(user, record, action, policy_class: @policy_class, **args)
113
117
  end
114
118
 
115
119
  def apply_policy(model)
116
- self.class.apply_policy(user, model)
120
+ self.class.apply_policy(user, model, policy_class: @policy_class)
117
121
  end
118
122
 
119
123
  def defined_methods(model, **args)
120
- self.class.defined_methods(user, model, **args)
124
+ self.class.defined_methods(user, model, policy_class: @policy_class, **args)
121
125
  end
122
126
 
123
127
  def has_method?(method, **args)