avo 4.2.1 → 4.2.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fa5b3df25e8f70d0ebd453dcf38e3c3260cd626618101a3edadafacc3c39be4d
4
- data.tar.gz: 805ec8526f3d208c66845ca7545056ef728141d51747ff93b6550f6d9aae1634
3
+ metadata.gz: 1620a7d5690df6cfe97e3974ba9d57d119a76c76d2c5912b3e1aafa739a77354
4
+ data.tar.gz: f9cd6ac8c65332ce478fa66602143dc7de32d7af7e7a929da2b2dc0a359fd54b
5
5
  SHA512:
6
- metadata.gz: 03306b5fabef7bb35e86f3202b288d2f15480338ca895b261bfd65a95c10f13fb0aae5ac2c3c934cd70230e6994f4d38a146fd6db7c024d2f339478730e6d3d6
7
- data.tar.gz: '01784259e97cba8e1a734075155f983ff9ab4c80ab8e9c76abeb364ce3ed50996ca813b67d02677dc0a56fa9539eec068dd781265a9296ec1d21ca2124634fa6'
6
+ metadata.gz: 656a367baa63315680ee644fa4c5d143288fa2049a1f78b964d563ccb0a0436a914519e0d74a2bb801189e3d8052321e6c8a1952b8d7c5e2ad1e1304151b6554
7
+ data.tar.gz: 2cef7657d7651585d0dd4db94d3dcf959c41189579dc53f4ef29c1e18bf35470ef32d5790608166cef97b52d942abaf509f65d0caa4bc7285515041917c1dc10
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- avo (4.2.1)
4
+ avo (4.2.3)
5
5
  actionview (>= 6.1)
6
6
  active_link_to
7
7
  activerecord (>= 6.1)
@@ -1,5 +1,5 @@
1
1
  <%= index_field_wrapper(**field_wrapper_args) do %>
2
- <%= content_tag :div, data: {
2
+ <%= content_tag :div, class: "inline-block", data: {
3
3
  controller: "date-field",
4
4
  date_field_view_value: @view,
5
5
  date_field_enable_time_value: true,
@@ -1,5 +1,5 @@
1
1
  <%= index_field_wrapper(**field_wrapper_args) do %>
2
- <%= content_tag :div, data: {
2
+ <%= content_tag :div, class: "inline-block", data: {
3
3
  controller: "date-field",
4
4
  date_field_view_value: @view,
5
5
  date_field_enable_time_value: true,
@@ -80,6 +80,18 @@ module Avo
80
80
  else
81
81
  []
82
82
  end
83
+ # A record may be deleted between the moment it was checked on the index and
84
+ # the moment the action is submitted, and the bulk lookup above raises for the
85
+ # whole batch when that happens. Fall back to the resource's own per-record
86
+ # lookup so custom `find_record_method`s and FriendlyId keep working, and drop
87
+ # only the records that are really gone. This is O(n) queries on purpose: it
88
+ # runs only on the race, never on the happy path.
89
+ rescue ActiveRecord::RecordNotFound
90
+ ids.filter_map do |id|
91
+ @resource.find_record(id, params: params)
92
+ rescue ActiveRecord::RecordNotFound
93
+ nil
94
+ end
83
95
  end
84
96
 
85
97
  def set_fields
@@ -125,6 +125,14 @@ module Avo
125
125
 
126
126
  return if @reflection.blank? && @field.type == "array"
127
127
 
128
+ if @reflection.blank?
129
+ raise Avo::MissingAssociationError.new(
130
+ @record.class,
131
+ association_from_params,
132
+ @field
133
+ )
134
+ end
135
+
128
136
  # Ensure inverse_of is present on STI
129
137
  if !@record.class.descends_from_active_record? && @reflection.inverse_of.blank? && Rails.env.development?
130
138
  raise "Avo relies on the 'inverse_of' option to establish the inverse association and perform some specific logic.\n" \
data/avo.gemspec CHANGED
@@ -33,8 +33,17 @@ Gem::Specification.new do |spec|
33
33
 
34
34
  # NOTE: `public/` is rejected below — Avo 4 ships precompiled assets from
35
35
  # `app/assets/builds`, and a stale `public/avo-assets` dir was what bloated some builds.
36
+ #
37
+ # `app/assets/builds` is gitignored and `Dir` does not read .gitignore, so whatever a
38
+ # releaser's working copy happens to hold there is packaged. Only `app/assets/builds/avo/`
39
+ # is ours — every build script writes there. 4.2.2 shipped `avo.base.js`, `avo.custom.js`,
40
+ # `late-registration.js` and `avo.base.css` from the top level: output of the build scripts
41
+ # as they stood before #3971 moved them into `avo/`, left on one machine since July 2025.
42
+ # It cost 22MB, and the stray `avo.custom.js` took over that Sprockets logical path in any
43
+ # host app that had an `avo.custom.js` of its own — silently replacing the host's file.
36
44
  spec.files = Dir["{bin,app,config,db,lib,public}/**/*", "Rakefile", "README.md", "avo.gemspec", "Gemfile", "Gemfile.lock", "tailwind.preset.js", "tailwind.custom.js", "safelist.txt"]
37
45
  .reject { |f| f.start_with?("public/") }
46
+ .reject { |f| f.start_with?("app/assets/builds/") && !f.start_with?("app/assets/builds/avo/") }
38
47
 
39
48
  spec.add_dependency "activerecord", ">= 6.1"
40
49
  spec.add_dependency "activesupport", ">= 6.1"
@@ -729,6 +729,13 @@ module Avo
729
729
  }
730
730
  end
731
731
 
732
+ def description(additional_attributes = {})
733
+ translated = t("#{translation_key}.description", default: nil)
734
+ return translated if translated.present?
735
+
736
+ super
737
+ end
738
+
732
739
  def entity_loader(entity)
733
740
  instance_variable_get(:"@#{entity.to_s.pluralize}_loader")
734
741
  end
@@ -252,6 +252,7 @@ When an index spans multiple pages, checking "Select all" offers to select **eve
252
252
  ## Gotchas
253
253
 
254
254
  - **`query` is always an array.** Even a single-record action gets `[record]`. Use `query.first` for the one-record case; don't call record methods on `query` directly. `records` is an alias.
255
+ - **Records deleted after selection are omitted.** Another user may delete a checked record before the action request arrives. Avo drops that missing record and still passes every surviving selection to `handle`; action authors do not need to rescue `ActiveRecord::RecordNotFound` for this race. `query` can therefore come back **empty** when every selected record is gone — guard with `return error "No record selected" if query.blank?` if that matters.
255
256
  - **"My action doesn't show up" is usually the policy.** With Pundit, `act_on?` in the resource's policy gates action visibility (and `authorize` on the action gates it further). Check the policy first. See the **`avo-authorization`** skill.
256
257
  - **The modal is a NEW request.** Params from the Index/Show page that opened it are **not** available in `fields`/`handle`. To prefill from the triggering page, parse `request.referer`:
257
258
  ```ruby
@@ -160,7 +160,7 @@ Turning a long `belongs_to` / `has_many` picker into a type-to-search field is t
160
160
  ## Gotchas
161
161
 
162
162
  - **Set `inverse_of` on the model association.** Avo relies on it to resolve the reciprocal; missing it causes wrong/empty attach lists and save bugs. Set it on both sides.
163
- - **The Rails association must exist first.** Adding `field :x, as: :has_many` does nothing if the model has no `has_many :x`.
163
+ - **The Rails association must exist first.** Adding `field :x, as: :has_many` does nothing if the model has no `has_many :x` — the association frame raises `Avo::MissingAssociationError`, naming the model, the field, and the declaration to add (`has_many :orders`).
164
164
  - **`has_*` fields are hidden on Edit by default.** Add `show_on: :edit` to surface them on the form. For editing the related record *in* the form (not just displaying it), use `nested` — which needs the `avo-nested` gem.
165
165
  - **Attach/detach/create/destroy buttons come from the *target* resource's Pundit policy**, and the method names are **plural, matching the association name**: `attach_users?`, `detach_users?`, `create_users?`, `destroy_users?`, `view_users?`, `show_users?` — *not* the singular `detach_user?`. This is the #1 "why isn't my button showing" cause. Cross-link `avo-authorization`.
166
166
  - **`can_create: true` is still vetoed by the policy.** If the target resource's `create?` returns `false`, no create link appears regardless of `can_create`.
@@ -74,8 +74,11 @@ es:
74
74
  zero: "usuarios"
75
75
  one: "usuario"
76
76
  other: "usuarios"
77
+ description: "Los usuarios de la aplicación"
77
78
  ```
78
79
 
80
+ `description` fills the resource's panel description and, per the cascade, beats `self.description` on the class.
81
+
79
82
  Omit `self.translation_key` and Avo derives it from the class name, **namespace included** — `Avo::Resources::Galaxy::Planet` defaults to `avo.resource_translations.galaxy/planet`. So for a plain resource you often only need the YAML, no Ruby change.
80
83
 
81
84
  ### 3. Localize an action
@@ -119,6 +119,8 @@ class Avo::Resources::User < Avo::BaseResource
119
119
  end
120
120
  ```
121
121
 
122
+ Avo also resolves `description` below the resource's translation key — `avo.resource_translations.user.description` supplies the User resource description. Per the cascade, the locale file **wins** and `self.description` is the fallback, so an app can keep an English default in code and translate over it — **avo-i18n**.
123
+
122
124
  - `self.description` is rendered as **raw HTML** — never feed it user-editable data (stored-XSS risk). A block gets `record`, `resource`, `view`, `current_user`, `params`.
123
125
  - `self.color` takes a Symbol or String from Avo's palette — `red`, `orange`, `amber`, `yellow`, `lime`, `green`, `emerald`, `teal`, `cyan`, `sky`, `blue`, `indigo`, `violet`, `purple`, `fuchsia`, `pink`, `rose`. It tints the icon **stroke only** (label and hover/active backgrounds stay neutral), adapts to light and dark themes, and an unknown name silently renders the neutral icon. The tint follows the resource to its breadcrumb initials chip and, with Advanced Search installed, to the resource group headers in global search results. A `color:` on the menu entry overrides it.
124
126
  - `self.cover`/`self.avatar` were named `cover_photo`/`profile_photo` in Avo 3. A **Symbol** `source:` renders nothing for unpersisted (new) records — use a block if you want a placeholder on `new`/`index`.
@@ -260,7 +262,7 @@ Search (`self.search`), grid/map view types, record reordering, and i18n live on
260
262
  - **Two resources, one model → wrong one wins.** Avo resolves the default alphabetically. Set `config.model_resource_mapping` and/or `use_resource:` on associations.
261
263
  - **Secondary / namespaced / oddly-named resources need `self.model_class`** (or the matching namespace) or Avo can't infer the model. Namespaced resources whose namespace matches the model's namespace infer automatically.
262
264
  - **Array resources are Beta:** no sorting, and `records` re-runs every request. Cache inside `records` for large sets, or move to an HTTP Resource.
263
- - **`find_record_method` in batch contexts:** `id` arrives as an Array for bulk actions — return a collection (`query.where(...)`) in that branch, not a single record.
265
+ - **`find_record_method` in batch contexts:** `id` arrives as an Array for bulk actions — return a collection (`query.where(...)`) in that branch, not a single record. If that branch raises `ActiveRecord::RecordNotFound` (as `query.find(id)` does when one record was deleted between selection and submit), Avo retries the **scalar** branch once per id and drops the ones that are gone — so keep the scalar branch able to handle every id the array branch receives.
264
266
  - **`visible_on_sidebar` only affects the auto-generated menu.** If the app uses the menu editor, control visibility in its `visible` block instead.
265
267
  - **Don't re-invent fields/associations here.** Field DSL is the avo-fields skill; `belongs_to`/`has_many`/`use_resource` is avo-associations.
266
268
  - **Verify before writing.** Option names drift between versions — check the docs URLs above or the app's installed Avo source rather than trusting memory.
@@ -163,7 +163,7 @@ Check the project's own instructions for how — in this order: `AGENTS.md`, `CL
163
163
  - **The update alone is not the upgrade.** Bumping the gems and skipping the guide is the single most common way an admin breaks after an "upgrade" — and the breakage often surfaces days later, in a view nobody opened.
164
164
  - **Apply sections oldest → newest.** They're written as a chain; applying 4.0.12's change before 4.0.7's can leave you editing code the earlier section was about to rename.
165
165
  - **Silent default flips pass tests.** Sections that change a default (authorization strictness, confirmation modals, expanded filters) break nothing visible in CI and change runtime behavior. Call these out individually.
166
- - **A section can be triggered by the app's locale files, not its Ruby.** Avo has been moving label resolution so a derived i18n key wins *over* the class attribute — actions in `4.0.17`, then cards, dashboards and scopes in `avo-dashboards` / `avo-scopes` `4.1.2`. Nothing in the app's Ruby changed, so grepping for an API name finds nothing; the trigger is a key the app already defines under `avo.action_translations`, `avo.card_translations`, `avo.dashboard_translations` or `avo.scope_translations`. Where one exists the class attribute stops being read **even when it's a lambda**, which is then never called and computes nothing, with no error. Grep those roots in `config/locales`, and move the collision with `self.translation_key` (or a value at the registration site) rather than deleting the key — **avo-i18n**.
166
+ - **A section can be triggered by the app's locale files, not its Ruby.** Avo has been moving label resolution so a derived i18n key wins *over* the class attribute — actions in `4.0.17`, then cards, dashboards and scopes in `avo-dashboards` / `avo-scopes` `4.1.2`, then resource descriptions in `4.2.2`. Nothing in the app's Ruby changed, so grepping for an API name finds nothing; the trigger is a key the app already defines under `avo.action_translations`, `avo.card_translations`, `avo.dashboard_translations`, `avo.scope_translations`, or `avo.resource_translations.<resource>.description`. Where one exists the class attribute stops being read **even when it's a lambda**, which is then never called and computes nothing, with no error. Grep those roots in `config/locales`, and move the collision with `self.translation_key` (or a value at the registration site) rather than deleting the key — **avo-i18n**.
167
167
  - **Add-on gems version independently.** `avo-kanban 0.1.17 → 0.1.18` has its own section even when Avo core barely moved. Work the lock diff, not just the core version.
168
168
  - **Assets after upgrade.** A GitHub-sourced install ships no precompiled assets — re-run `rake avo:build-assets` or the admin renders unstyled (**avo-setup**).
169
169
  - **Don't invent a migration.** If a version's change isn't in the guide or the release notes, stop and ask rather than guessing at the new API.
data/lib/avo/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Avo
2
- VERSION = "4.2.1" unless const_defined?(:VERSION)
2
+ VERSION = "4.2.3" unless const_defined?(:VERSION)
3
3
  end
data/lib/avo.rb CHANGED
@@ -102,6 +102,18 @@ module Avo
102
102
  end
103
103
  end
104
104
 
105
+ # Exception raised when an association field has no matching model association.
106
+ class MissingAssociationError < StandardError
107
+ def initialize(model_class, association_name, field)
108
+ macro = field.type.to_s
109
+ super(
110
+ "Failed to find the :#{association_name} association on #{model_class} while rendering the :#{field.id} field.\n" \
111
+ "Define `#{macro} :#{association_name}` on #{model_class}, or update the Avo field to use an association that exists.\n" \
112
+ "More info on https://docs.avohq.io/#{Avo::VERSION[0]}.0/associations.html."
113
+ )
114
+ end
115
+ end
116
+
105
117
  class ResourceNotFoundError < StandardError
106
118
  def initialize(resource_name)
107
119
  super(
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: avo
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.2.1
4
+ version: 4.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrian Marin