recourse 4.4.3 → 4.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba7f8f62f4a4488b30a0ccc90cc550d98e56dc6a717380b368d5f2a51e06f0c6
4
- data.tar.gz: 6311ab19960ad5bc92151cfde18dfa4a8610c067ecb08c00021340d19b7e9a2c
3
+ metadata.gz: 10cc1a031240c0430a970cea2dc0c79460e3b6dfe131ce6e8a854aed364a485c
4
+ data.tar.gz: 631f40d0bc652b90cdaf960864ea5c47f7baa55ea1808b4a366a3d26ff021220
5
5
  SHA512:
6
- metadata.gz: 34d609a74f655281b6e4438571a00d4fc554fd431f4e07d3058ba8b84529f800a0e4c798f709ec806989ed9519da2045301658be5314fb0a09317cdcc213ffc9
7
- data.tar.gz: 142f6ec366b5e45fcffc6afb92ebcf8cbe84ce6e4918e7d4ee50b33144048263fb35f9b519655ea16beb4816e679ec2e4a1311c286881bf7477fcda8a70fffe7
6
+ metadata.gz: 43892125e4332125787621108a81ccea1de70de89c66c6cdf686d68f54d7d8eda0c7251e964e2a74df8c5a9492ec8d26b8cf99c5443fa246bc91aebe0de8b13d
7
+ data.tar.gz: be7fa5f7c3853ba0ccecb21f5b290ab0ac38f00f1165d541ab670ff35decd71f9df154cc09836ecc7432153d3035dad9ccd290e449cba08d36976fbd579c9965
data/CHANGELOG.md CHANGED
@@ -5,6 +5,28 @@ All notable changes to this project will be documented in this file.
5
5
  For more information about changelogs, check [Keep a Changelog](http://keepachangelog.com) and
6
6
  [Vandamme](http://tech-angels.github.io/vandamme).
7
7
 
8
+ ## 4.5.0 - 2026-09-08
9
+
10
+ * [Fix] A row with a bookmark square is as tall as a row without one: the square no longer claims a button's minimum height
11
+
12
+ * [Feature] The arrow after a web address opens it in a new tab, while the words still open it in this one
13
+
14
+ * [Fix] A production rake task boots without Rails 8.2 warning that Action Controller and Active Record were loaded early
15
+
16
+ `recourses` defined each missing controller as the routes drew, which loaded the host's
17
+ `RecoursesController` and everything above it, and asked a nested name for its model the
18
+ same way. A boot that will not eager load — a `db:migrate` on release — draws its routes
19
+ before it is done, and Rails 8.2 logs a warning for every component loaded then. The
20
+ controller now waits for the app's first request or job, and the model is looked up as a
21
+ constant, without loading it.
22
+
23
+ ## 4.4.4 - 2026-09-08
24
+
25
+ * [Fix] On a phone the search form keeps its place beside the breadcrumb once a filter is picked
26
+
27
+ A form sized by its content widened with the words a picked option put on a toggle
28
+ and wrapped under the crumbs.
29
+
8
30
  ## 4.4.3 - 2026-09-08
9
31
 
10
32
  * [Fix] A counted tab reads `1 franchise` again, without the extra space 4.4.0 put between the figure and the word
data/README.md CHANGED
@@ -33,16 +33,16 @@ end
33
33
  Every recourse with `index` gets a sidebar entry (in routes order), a keyboard shortcut on its
34
34
  first letter, and a paginated, searchable, sortable, filterable index table:
35
35
 
36
- [image]
36
+ <img width="3824" height="2274" alt="Image" src="https://github.com/user-attachments/assets/f952d3e5-c320-464b-b166-77c7ab8f9071" />
37
37
 
38
38
  Every recourse with `show` gets a detail page where attributes are displayed with the appropriate
39
39
  formatting, or masked if sensitive:
40
40
 
41
- [image]
41
+ <img width="3824" height="2274" alt="Image" src="https://github.com/user-attachments/assets/515f63c4-8a9b-422e-95c4-88d6647911d8" />
42
42
 
43
43
  Every recourse with `new` or `edit` gets a form with appropriate browser formatting and validation:
44
44
 
45
- [image]
45
+ <img width="3824" height="1220" alt="Image" src="https://github.com/user-attachments/assets/ebe5108b-bbd1-4cd1-a635-89eef0e6fc50" />
46
46
 
47
47
  Every recourse with `destroy` gets a button with a detailed confirmation message:
48
48
 
@@ -50,7 +50,7 @@ Every recourse with `destroy` gets a button with a detailed confirmation message
50
50
 
51
51
  Every nested recourse gets namespaced after the parent:
52
52
 
53
- [image]
53
+ <img width="3824" height="2274" alt="Image" src="https://github.com/user-attachments/assets/9b242335-a25b-4fa8-8023-422538d235b0" />
54
54
 
55
55
 
56
56
  ## Step 2. Enhance your models
@@ -134,6 +134,9 @@ Recourse.color = :orange
134
134
  Recourse.bookmarks = -> { Keepsake.where agent: Current.agent }
135
135
  ```
136
136
 
137
+ <img width="3824" height="1220" alt="Image" src="https://github.com/user-attachments/assets/d1db4adb-0b5c-4e41-8bb6-cf72a35288f0" />
138
+
139
+
137
140
  ## Development
138
141
 
139
142
  ```bash
@@ -265,12 +265,19 @@
265
265
  next to one, so it takes the line's height and not `.btn`'s own minimum —
266
266
  which would leave the row under every table taller than the words in it. */
267
267
  .recourse-limit { min-height: 0; }
268
+ /* The bookmark square is an icon in a row of text, not a control in a form, so it
269
+ takes the line's height too: `.btn`'s minimum made every row with one taller
270
+ than the rows without. */
271
+ .recourse-bookmark { min-height: 0; }
268
272
  /* Stacked, each control is a row of its own rather than a fragment of one:
269
273
  there is no second control beside it to share the line with. */
270
274
  @media (width < 768px) {
271
- .recourse-search { max-width: none; }
275
+ /* The room the breadcrumb leaves, and never more: a form sized by its content
276
+ widened with the words a picked option put on a toggle, and wrapped under
277
+ the crumbs. A basis of nothing and leave to shrink hold it in its row. */
278
+ .recourse-search { flex: 1 1 0; min-width: 0; max-width: none; }
272
279
  .recourse-search-box,
273
- .recourse-search .combobox-toggle { flex: 1 1 100%; }
280
+ .recourse-search .combobox-toggle { flex: 1 1 100%; min-width: 0; }
274
281
  /* Safari zooms in on a field whose text is under 16px, an absolute figure: the
275
282
  small controls are 14px, and so is the body on a phone, so the controls say
276
283
  16px themselves and focusing one moves nothing. */
@@ -45,6 +45,7 @@ en:
45
45
  # What a menu with several picks reads as, the first named and the rest counted.
46
46
  more: "%{first} + %{count} more"
47
47
  new: New %{model}
48
+ new_tab: Open in a new tab
48
49
  no_results: No results found
49
50
  none: "No %{models}."
50
51
  optional: Optional
@@ -1,16 +1,43 @@
1
1
  module Recourse
2
2
  # Defines the controller classes a host app has not written for itself.
3
3
  module Controllers
4
+ @held = []
5
+ @lock = Mutex.new
6
+
4
7
  # Creates a controller for the named resource unless the host app has one. The
5
8
  # name arrives with whatever namespace it was drawn in — `admin/contacts` — so
6
- # the constant lands where Rails will look for it.
7
- def self.define_missing(path, base = nil)
9
+ # the constant lands where Rails will look for it. A block names the superclass,
10
+ # `RecoursesController` by default, and is called only once the class is made.
11
+ def self.define_missing(path, &base)
8
12
  class_name = "#{path.camelize}Controller"
9
13
  # const_defined? is true for a Zeitwerk autoload, so files on disk count too.
10
14
  return if Object.const_defined? class_name
11
15
 
12
- namespace(class_name.deconstantize).const_set class_name.demodulize,
13
- Class.new(base || RecoursesController)
16
+ booting? ? hold(class_name, base) : define(class_name, base)
17
+ end
18
+
19
+ # Makes the controllers held back while the app booted. The executor runs this
20
+ # ahead of the app's first request or job, once every class may be loaded.
21
+ def self.define_held
22
+ @lock.synchronize { define(*@held.shift) until @held.empty? }
23
+ end
24
+
25
+ private_class_method def self.define(class_name, base)
26
+ superclass = base ? base.call : RecoursesController
27
+ namespace(class_name.deconstantize).const_set class_name.demodulize, Class.new(superclass)
28
+ end
29
+
30
+ # Rails 8.2 warns when Action Controller is loaded during the boot of an app that
31
+ # will not eager load — a rake task's, which draws its routes before it is done
32
+ # and never dispatches to a controller — so the class waits for the first run.
33
+ private_class_method def self.booting?
34
+ app = Rails.application
35
+ app.present? && !app.config.eager_load && !app.initialized?
36
+ end
37
+
38
+ private_class_method def self.hold(class_name, base)
39
+ Rails.application.executor.to_run { Controllers.define_held } if @held.empty?
40
+ @held << [class_name, base]
14
41
  end
15
42
 
16
43
  # The module a namespaced controller belongs in, made where the host has none of
@@ -50,13 +50,15 @@ module Recourse
50
50
 
51
51
  # Kept or not, one square either way: the same path with the verb reversed, so
52
52
  # the button toggles by flipping the method Rails already wrote into the form.
53
+ # `recourse-bookmark` takes back the height `.btn` claims, so the row stays as
54
+ # tall as one with no square in it.
53
55
  def bookmark_button(record)
54
56
  kept = bookmarked_ids.include? record.id
55
57
  label = t "recourse.#{kept ? 'unbookmark' : 'bookmark'}"
56
58
 
57
59
  button_to bookmark_icon(kept, label), bookmark_url(record),
58
60
  method: kept ? :delete : :post, form_class: 'd-inline-block',
59
- class: 'btn btn-sm btn-link p-0 border-0 lh-1',
61
+ class: 'btn btn-sm btn-link p-0 border-0 lh-1 recourse-bookmark',
60
62
  **bookmark_data(kept, label)
61
63
  end
62
64
 
@@ -79,15 +79,21 @@ module Recourse
79
79
  value.is_a?(String) && value.match?(WEB_URL)
80
80
  end
81
81
 
82
- # Bootstrap's icon link, saying the value leads somewhere the way text cannot.
83
- # What it reads is the host, and an ellipsis where the address goes further --
84
- # a path is how a machine finds the page, and the href is already carrying it.
82
+ # The words lead where the value points, in this tab, the way any link does; the
83
+ # arrow after them Bootstrap's icon link, stepping under the cursor opens the
84
+ # same address in a new tab. Either reads as the host, and an ellipsis where the
85
+ # address goes further: a path is how a machine finds the page, and the href has it.
85
86
  def url_link(value)
86
87
  host, rest = value.match(WEB_URL).captures
87
88
  said = rest.delete_suffix('/').empty? ? host : "#{host}/…"
88
89
 
89
- tag.a safe_join([said, icon_tag(:point_right)], ' '),
90
- href: value, class: 'icon-link icon-link-hover'
90
+ safe_join [tag.a(said, href: value), new_tab_arrow(value)], ' '
91
+ end
92
+
93
+ def new_tab_arrow(value)
94
+ tag.a icon_tag(:point_right), href: value, target: '_blank', rel: 'noopener',
95
+ class: 'icon-link icon-link-hover',
96
+ aria: { label: t('recourse.new_tab') }
91
97
  end
92
98
  end
93
99
  end
@@ -18,8 +18,9 @@ module Recourse
18
18
  # Whether this resource has rows to address one at a time. A bookmark names one
19
19
  # row, which means nothing for a name with no class behind it at all — and one was
20
20
  # drawn anyway, at `/placeholders/:placeholder_id/bookmark`, where nothing linked
21
- # to it and anything reaching it raised.
22
- def addressable_rows? = Recourse.model?(parent_resource.name).present?
21
+ # to it and anything reaching it raised. Asked of the constant rather than the
22
+ # class: loading a model while the routes draw is what Rails 8.2 warns about.
23
+ def addressable_rows? = Object.const_defined? Recourse.model_name(parent_resource.name)
23
24
 
24
25
  # The row a table's bookmark square writes: one record kept by whoever is looking,
25
26
  # at `/places/5/bookmark`. Deliberately not recorded through `Recourse.nest` — it
@@ -28,7 +29,7 @@ module Recourse
28
29
  # since the gem drew it: one segment under the resource, always.
29
30
  def draw_bookmark
30
31
  path = [current_module, 'bookmarks'].compact.join '/'
31
- Controllers.define_missing path, ::BookmarksController
32
+ Controllers.define_missing(path) { ::BookmarksController }
32
33
  resource :bookmark, only: %i[create destroy]
33
34
  end
34
35
  end
@@ -1,4 +1,4 @@
1
1
  module Recourse
2
2
  # Version of the gem, read by the gemspec and by hosts checking compatibility.
3
- VERSION = '4.4.3'
3
+ VERSION = '4.5.0'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recourse
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.3
4
+ version: 4.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob