recourse 4.3.0 → 4.4.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: 00e1253a6c0a7e462efb88f9c46765d51cbc90b20073cf72fefc38ac468d31b2
4
- data.tar.gz: 51271fdc26d6a06cf53089e747f70a842b51d71ffa1fe163e4dc08927e1ba005
3
+ metadata.gz: 063f5551f5b89bbb95660522a2a0177b9570f96961c5d5e28cfa83877ece91bf
4
+ data.tar.gz: 9f778ea100e50837483382e814936c1fc0697e462151445b144b7472e115132b
5
5
  SHA512:
6
- metadata.gz: 3526cb175143ce2fae2919654b9f7afb7c1e44e6188462084e56c784f81aaf624cc128b760499991d79bf809fab97b14a50156c15f1e4969fff710309bfee4e2
7
- data.tar.gz: a4aac6c9576b269c9df0f66fa4f0e60ca4e8da4461500be1a534caa2f25f33ff6c08d88e4e7ec431edc15d3ad4cb6c9af80725245f45d82557586640c79cab46
6
+ metadata.gz: 8538e6114d90e004f0b2f895bb5a0431c3569ddb6c05e4c93e3618aaa5056d7b44d24e974a197f546a387bfde58922ade7a25f5697444fce60637ff60864ce0c
7
+ data.tar.gz: 1b7776b83766984e18861aade3f0f79938358c24cba295c4bedca84c8f21b0aaefb1bad095da258bbcead01ea9a2ac6a1d5a2598d155935335189482a78283a0
data/CHANGELOG.md CHANGED
@@ -5,6 +5,17 @@ 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.4.0 - 2026-09-08
9
+
10
+ * [Fix] Tapping the light/dark toggle or the log out on a phone leaves no tint behind
11
+ * [Feature] On a phone a resource's breadcrumb is its icon alone, leaving the row to the search box and the button
12
+ * [Feature] On a phone a card's tabs are their icons and figures: Show and Edit their
13
+ icons, a counted tab its icon and its number
14
+ * [Feature] On a phone the whole `Franchises: 21` of a stacked counter cell is the link
15
+ * [Fix] A touch screen shows no tooltips, a tap being what would open the link beneath one
16
+ * [Fix] Safari no longer zooms in on the search box or a filter: the controls are 16px on a phone, where the body itself is 14
17
+ * [Fix] A sidebar link tapped on a phone leaves no hover tint on it or on the link the next page puts under the finger
18
+
8
19
  ## 4.3.0 - 2026-09-08
9
20
 
10
21
  * [Feature] On a phone a stacked cell reads as one line, `Estimate hi: 600`
data/README.md CHANGED
@@ -1,14 +1,6 @@
1
1
  # Recourse
2
2
 
3
- Admin screens for a Rails app, drawn from its models. One word in `config/routes.rb`
4
- serves a resource's seven actions — a paginated, searchable, sortable, filterable index
5
- table, a record's page, its form and its delete — with no controller, view or partial
6
- added to the app. What each screen shows is read off the model: its columns, its
7
- validators, its associations and its indexes.
8
-
9
- Everything a host may call is listed below with a link to its reference on
10
- [RubyDoc](https://rubydoc.info/gems/recourse). Every default can be overridden by
11
- defining the same thing in the app.
3
+ Swap two characters in your `config/routes.rb` and get awesome admin screens for your Rails app.
12
4
 
13
5
  ## How to install
14
6
 
@@ -26,80 +18,44 @@ breaking change. Rails 8.1 and Ruby 3.2 are the minimum; the pages need Turbo, w
26
18
  `turbo-rails` brings, and nothing else — Bootstrap and its icons are vendored and served
27
19
  by the engine, so a host without an asset pipeline gets the same screens.
28
20
 
29
- ## In `config/routes.rb`
21
+ ## Step 1. Edit your routes
30
22
 
31
- [`recourses`](https://rubydoc.info/gems/recourse/Recourse/Routes#recourses-instance_method)
32
- is `resources` with a controller and views supplied. It takes the same `only:` and
33
- `except:`, and a block nests children under the parent:
23
+ This is as easy as replacing any call to `resources` to use [`recourses`](https://rubydoc.info/gems/recourse/Recourse/Routes#recourses-instance_method) instead:
34
24
 
35
- ```ruby
36
- scope module: :admin do
37
- recourses :providers do
38
- recourses :markets, only: :index # a counted tab on each provider
39
- recourses :retrievals, only: :create # a button on each provider
40
- end
41
- recourses :searches, only: %i[index show new create]
25
+ ```diff
26
+ - resources :posts, only: %i[ index show ] do
27
+ + recourses :posts, only: %i[ index show ] do
28
+ - resources :comments, except: :destroy
29
+ + recourses :comments, except: :destroy
42
30
  end
43
31
  ```
44
32
 
45
- - Every resource with an `index` gets a sidebar entry, in routes order, with a keyboard
46
- shortcut on its first letter. `only: []` declares a resource with no page of its own.
47
- - A nested `index` is a tab on the parent's card, counted where a `counter_cache` exists.
48
- - A nested `create` with no index is a button on the parent's page, named after the
49
- route. The host answers it in a controller of its own, or the gem creates the record.
50
- - Nested controllers are namespaced after the parent (`Admin::Providers::MarketsController`),
51
- and a nested index lists only the parent's rows.
52
- - Do you name a route `exit`? The sidebar ends with a log-out button submitting a
53
- `DELETE` to it:
33
+ Every recourse with `index` gets a sidebar entry (in routes order), a keyboard shortcut on its
34
+ first letter, and a paginated, searchable, sortable, filterable index table:
54
35
 
55
- ```ruby
56
- resource :session, only: :destroy, as: :exit
57
- ```
36
+ [image]
58
37
 
59
- ## In `app/controllers/recourses_controller.rb`
38
+ Every recourse with `show` gets a detail page where attributes are displayed with the appropriate
39
+ formatting, or masked if sensitive:
60
40
 
61
- Every controller the gem defines inherits
62
- [`RecoursesController`](https://rubydoc.info/gems/recourse/Recourse/BaseController),
63
- which the host defines to put behavior above every screen at once:
41
+ [image]
64
42
 
65
- ```ruby
66
- class RecoursesController < Recourse::BaseController
67
- before_action :authenticate!
68
- end
69
- ```
43
+ Every recourse with `new` or `edit` gets a form with appropriate browser formatting and validation:
70
44
 
71
- A controller the app already defines is left alone.
45
+ [image]
72
46
 
73
- ## In `config/initializers/recourse.rb`
47
+ Every recourse with `destroy` gets a button with a detailed confirmation message:
74
48
 
75
- - [`Recourse.color=`](https://rubydoc.info/gems/recourse/Recourse#color%3D-class_method):
76
- which Bootstrap family is primary on every page. Readers switch light and dark themselves.
77
- - [`Recourse.bookmarks=`](https://rubydoc.info/gems/recourse/Recourse#bookmarks%3D-class_method):
78
- a Proc answering the viewer's bookmark rows. Every table of a model with a `has_many`
79
- at that class then opens with a square to keep a row by, kept rows first.
49
+ [image]
80
50
 
81
- ```ruby
82
- Recourse.color = :orange
83
- Recourse.bookmarks = -> { Keepsake.where agent: Current.agent }
84
- ```
51
+ Every nested recourse gets namespaced after the parent:
85
52
 
86
- ## In a model
53
+ [image]
87
54
 
88
- Every Active Record model answers these; a model overrides one in a
89
- [`Recoursive`](https://rubydoc.info/gems/recourse/Recourse/Recoursive) concern of its own.
90
55
 
91
- ```ruby
92
- # app/models/market/recoursive.rb
93
- module Market::Recoursive extend ActiveSupport::Concern
94
- class_methods do
95
- def recourse_label = :slug # what a menu and a cell call one
96
- def recourse_hidden = %i[callback_url payload] # off every screen
97
- def recourse_displayed = :created_at # back on the table
98
- def recourse_order = { size: :desc } # how the index sorts
99
- def recourse_icon = :question # a Unicon concept
100
- end
101
- end
102
- ```
56
+ ## Step 2. Enhance your models
57
+
58
+ To change the content displayed in the `index` table of a model, override any of these class methods:
103
59
 
104
60
  | Hook | Default | Decides |
105
61
  | --- | --- | --- |
@@ -109,44 +65,74 @@ end
109
65
  | [`recourse_order`](https://rubydoc.info/gems/recourse/Recourse/Recoursive#recourse_order-instance_method) | `:id` | the index's order, a Symbol or a Hash; rows with nothing in the column come last |
110
66
  | [`recourse_icon`](https://rubydoc.info/gems/recourse/Recourse/Recoursive#recourse_icon-instance_method) | the model's name | the icon on the sidebar, the crumbs and the tabs |
111
67
 
112
- What needs no hook, being read off the model:
113
-
114
- - The index eager-loads every `belongs_to` the table names, sorts by whatever an index
115
- covers, searches through its indexed string columns and the labels behind its foreign
116
- keys, marks what it matched, and offers a filter menu per enum, boolean and `belongs_to`.
117
- - Saving a row refreshes every open index of it.
118
- - A form field per column, carrying the rules the validators state: a length is a
119
- `maxlength`, a format a `pattern`, a numericality a numeric keyboard.
120
- - An enum is a badge on a page and a menu on a form; a foreign key is a menu of the
121
- records it points at, or a field to type the label into where there are too many.
122
- - Values formatted by kind: delimited integers, money, phone numbers, dates and times in
123
- the reader's own time zone, a `<time>` saying how long ago, a URL as a link, a list as
124
- a count that opens.
125
- - An encrypted column stays off the table, arrives masked on the record's page behind a
126
- `Show`, and is edited in the clear.
127
- - The delete button names what goes with the record, counted one association down.
128
-
129
- ## In `app/views`
130
-
131
- Do you want your own cells? Add `app/views/<resources>/_row.html.erb` and build it from
132
- [`column`](https://rubydoc.info/gems/recourse/Recourse/Helpers/Cells#column-instance_method),
68
+ For instance, this would yield a more compact `index` than the default configuration:
69
+
70
+ ```ruby
71
+ class Post < ActiveRecord::Base
72
+ def self.recourse_label = :title # Label a post with its title
73
+ def self.recourse_hidden = %i[ip_address] # Hide the IP address from the views
74
+ def self.recourse_displayed = :created_at # Display the created_at in the views
75
+ def self.recourse_order = { published_at: :desc } # Sort posts by last published first
76
+ def self.recourse_icon = :question # Represent Posts with a question icon
77
+ end
78
+ ````
79
+
80
+ [image]
81
+
82
+ ## Step 3. Delete your views
83
+
84
+ If you are happy with the generated HTML files, then be happy to skip this step!
85
+ If you want even more configuration, then just add a `app/views/<resources>/_row.html.erb` partial.
86
+ You can use [`column`](https://rubydoc.info/gems/recourse/Recourse/Helpers/Cells#column-instance_method),
133
87
  [`sort_header`](https://rubydoc.info/gems/recourse/Recourse/Helpers/Sorts#sort_header-instance_method)
134
- and [`search_highlight`](https://rubydoc.info/gems/recourse/Recourse/Helpers/Searches#search_highlight-instance_method):
88
+ and [`search_highlight`](https://rubydoc.info/gems/recourse/Recourse/Helpers/Searches#search_highlight-instance_method)
89
+ to define the columns you want:
135
90
 
136
91
  ```erb
137
92
  <%# app/views/admin/providers/_row.html.erb %>
138
93
  <%# locals: (provider:) -%>
94
+
139
95
  <%= column header: sort_header(:name) do %>
140
96
  <%= search_highlight provider.name, :name %>
141
97
  <% end %>
142
- <%= column header: 'Phone' do %><%= number_to_phone provider.phone %><% end %>
98
+
99
+ <%= column header: 'Phone' do %>
100
+ <%= number_to_phone provider.phone %>
101
+ <% end %>
102
+ ```
103
+
104
+ [image]
105
+
106
+ ## Step 4. Secure your controllers
107
+
108
+ Every controller the gem defines inherits
109
+ [`RecoursesController`](https://rubydoc.info/gems/recourse/Recourse/BaseController),
110
+ which the host defines to put behavior above every screen at once:
111
+
112
+ ```ruby
113
+ class RecoursesController < Recourse::BaseController
114
+ before_action :authenticate!
115
+ end
143
116
  ```
144
117
 
145
- Any template or partial of the gem's is replaced by defining it in the app, under the
146
- resource (`app/views/admin/providers/index.html.erb`) or for every resource at once
147
- (`app/views/recourses/_sidebar.html.erb`). Every string the gem shows is a key under
148
- `recourse` in `config/locales/recourse.en.yml`, so a host rewords `Add provider` in a
149
- locale file of its own.
118
+ A controller the app already defines is left alone.
119
+
120
+
121
+ ## Step 5. Enjoy the extras
122
+
123
+ Here are some bonus features the gem provides. Customize them by editing their values in
124
+ your `config/initializers/recourse.rb` file:
125
+
126
+ - [`Recourse.color=`](https://rubydoc.info/gems/recourse/Recourse#color%3D-class_method):
127
+ which Bootstrap family is primary on every page. Readers switch light and dark themselves.
128
+ - [`Recourse.bookmarks=`](https://rubydoc.info/gems/recourse/Recourse#bookmarks%3D-class_method):
129
+ a Proc answering the viewer's bookmark rows. Every table of a model with a `has_many`
130
+ at that class then opens with a square to keep a row by, kept rows first.
131
+
132
+ ```ruby
133
+ Recourse.color = :orange
134
+ Recourse.bookmarks = -> { Keepsake.where agent: Current.agent }
135
+ ```
150
136
 
151
137
  ## Development
152
138
 
@@ -5,12 +5,17 @@ import { Tooltip } from '/recourse/bootstrap.bundle.min.js'
5
5
  // and a Stimulus lifecycle is what survives Turbo redrawing the table: connect
6
6
  // makes it, disconnect takes it down before the element goes, so a sorted or
7
7
  // searched table never strands one over an element that left.
8
+ //
9
+ // None where nothing can hover: on a touch screen the tap that would open a link
10
+ // is also what shows a tooltip, and a word popping up over a tap reads as a hitch.
8
11
  export default class extends Controller {
9
12
  connect() {
13
+ if (matchMedia('(hover: none)').matches) return
14
+
10
15
  this.tooltip = Tooltip.getOrCreateInstance(this.element)
11
16
  }
12
17
 
13
18
  disconnect() {
14
- this.tooltip.dispose()
19
+ this.tooltip?.dispose()
15
20
  }
16
21
  }
@@ -107,13 +107,15 @@
107
107
  scrolled to it is a page Safari zooms out of and draws the navbar short on. */
108
108
  html, .recourse-shell { overflow-x: hidden; }
109
109
 
110
- /* A finger does not hover. The tint a pointer leaves on the row under it would
111
- stay on whatever row was last touched, so a touch screen gets none. */
110
+ /* A finger does not hover. The tint a pointer leaves on the row under it, or on
111
+ the link under it, would stay on whatever was last touched or land on whatever
112
+ the redrawn page put under the finger — so a touch screen gets none. */
112
113
  @media (hover: none) {
113
114
  .table-hover > tbody > tr:hover > * {
114
115
  --bs-table-color-state: initial;
115
116
  --bs-table-bg-state: initial;
116
117
  }
118
+ .nav-link:hover:not(.active) { background-color: transparent; }
117
119
  }
118
120
 
119
121
  /* The navbar and the sidebar stack over the main column: a menu opened from a
@@ -148,6 +150,13 @@
148
150
  font-weight: var(--bs-font-weight-semibold);
149
151
  }
150
152
  .sm\:table-stacked > tbody > tr > td:first-child { font-weight: inherit; }
153
+ /* A counter's heading goes inside its link, so the whole `Franchises: 21` is
154
+ one thing to tap; the cell's own heading steps aside for it. */
155
+ .sm\:table-stacked > tbody > tr > td.recourse-counter::before { content: none; }
156
+ .sm\:table-stacked > tbody > tr > td.recourse-counter > [data-cell]::before {
157
+ content: attr(data-cell) ': ';
158
+ font-weight: var(--bs-font-weight-semibold);
159
+ }
151
160
  .sm\:table-stacked > tbody > tr > td:not(:first-child) + td::before { margin-block-start: 0; }
152
161
  .sm\:table-stacked > tbody > tr > td { overflow-wrap: anywhere; }
153
162
  }
@@ -260,9 +269,10 @@
260
269
  .recourse-search { max-width: none; }
261
270
  .recourse-search-box,
262
271
  .recourse-search .combobox-toggle { flex: 1 1 100%; }
263
- /* Safari zooms in on a field whose text is under 16px, and the small controls
264
- are 14px: on a phone they take the body's size, and focusing one moves nothing. */
265
- .recourse-search { --bs-btn-input-sm-font-size: var(--bs-body-font-size); }
272
+ /* Safari zooms in on a field whose text is under 16px, an absolute figure: the
273
+ small controls are 14px, and so is the body on a phone, so the controls say
274
+ 16px themselves and focusing one moves nothing. */
275
+ .recourse-search .form-control { font-size: 16px; }
266
276
  }
267
277
 
268
278
  /* Held Option marks the letter each sidebar link answers to. Marked rather than
@@ -382,8 +392,12 @@
382
392
  .recourse-foot { display: flex; }
383
393
  .recourse-foot > * { flex: 1 1 0; display: flex; justify-content: center; }
384
394
  .recourse-foot .nav-link { border-radius: var(--bs-radius-5); }
385
- /* A hand cursor alone says little on an icon: a tint under it says it is a control. */
386
- .recourse-foot .nav-link:hover { background-color: var(--bs-bg-1); color: var(--bs-fg-body); }
395
+ /* A hand cursor alone says little on an icon: a tint under it says it is a control.
396
+ Only where something can hover: a finger's tap leaves `:hover` set until the next
397
+ tap elsewhere, and a tint that stays reads as a button stuck down. */
398
+ @media (hover: hover) {
399
+ .recourse-foot .nav-link:hover { background-color: var(--bs-bg-1); color: var(--bs-fg-body); }
400
+ }
387
401
 
388
402
  /* The foot sits at the bottom of the sidebar and stretches across it while the
389
403
  sidebar is a column, and is simply the last item of the row while it is not
@@ -415,6 +429,20 @@
415
429
  }
416
430
  }
417
431
 
432
+ /* A phone's card has room for a row of tabs only as pictures and figures: Show
433
+ and Edit are their icons, a counted tab its icon and its number. A tab with
434
+ neither keeps its word, there being nothing else to stand for it. */
435
+ @media (width < 768px) {
436
+ .recourse-tab-word { display: none; }
437
+ }
438
+
439
+ /* And the trail at the top: a resource's crumb is its icon alone on a phone, so the
440
+ search box and the button beside it have the row. A record's crumb has no icon
441
+ and keeps its words. */
442
+ @media (width < 768px) {
443
+ .recourse-crumb-word { display: none; }
444
+ }
445
+
418
446
  /* While the sidebar is a row its foot has room for words, so each icon is followed
419
447
  by its own; as a column the words are read but not seen, the way Bootstrap's
420
448
  `.visually-hidden` does it. */
@@ -9,7 +9,7 @@
9
9
  <%# A crumb naming a record — the parent a nested page is under — links
10
10
  without an icon: the crumb before it already carries the resource's. -%>
11
11
  <li class='breadcrumb-item'>
12
- <%= link_to resource ? resource_label(resource, title) : title, path,
12
+ <%= link_to resource ? crumb_label(resource, title) : title, path,
13
13
  class: class_names('breadcrumb-link', 'gap-2': resource) %>
14
14
  </li>
15
15
  <% elsif index < crumbs.size - 1 %>
@@ -21,7 +21,7 @@
21
21
  </li>
22
22
  <% elsif resource %>
23
23
  <li class='breadcrumb-item' aria-current='page'>
24
- <span class='breadcrumb-link active gap-2'><%= resource_label resource, title %></span>
24
+ <span class='breadcrumb-link active gap-2'><%= crumb_label resource, title %></span>
25
25
  </li>
26
26
  <% else %>
27
27
  <%# A leaf names a page rather than a resource — `New market`, or a record by
@@ -60,7 +60,9 @@ module Recourse
60
60
  end
61
61
 
62
62
  def tab_label(action)
63
- safe_join [icon_tag(Actions::ICONS[action]), t("recourse.#{action}")], ' '
63
+ word = tag.span t("recourse.#{action}"), class: 'recourse-tab-word'
64
+
65
+ safe_join [icon_tag(Actions::ICONS[action]), word], ' '
64
66
  end
65
67
  end
66
68
  end
@@ -21,6 +21,9 @@ module Recourse
21
21
  count = number_with_delimiter value
22
22
  counted = counter_counted count, value, association
23
23
  named = counter_naming count, association
24
+ # The heading again, for a stacked table to draw inside the link rather than
25
+ # before it: `Franchises: 21` is then one thing to tap.
26
+ named[:data] = { cell: Recourse.model_title(association.klass) }
24
27
  path = resource_controller_path
25
28
  nested = nested_path_of path, association
26
29
  return tag.span(counted, **named) unless nested && routed?(nested, 'index')
@@ -63,6 +63,17 @@ module Recourse
63
63
  safe_join [icon && tag.i(class: "bi bi-#{icon}"), name].compact, ' '
64
64
  end
65
65
 
66
+ # The same for a crumb, whose words a phone drops where the icon stands for them:
67
+ # the row at the top has a search box and a button to fit beside the trail.
68
+ def crumb_label(resource, title)
69
+ icon = Recourse.known_icon resource
70
+ return title unless icon
71
+
72
+ word = tag.span title, class: 'recourse-crumb-word'
73
+
74
+ safe_join [tag.i(class: "bi bi-#{icon}"), word], ' '
75
+ end
76
+
66
77
  # Only a page beneath the index names itself, and names what it is showing.
67
78
  def breadcrumb_leaf
68
79
  case controller.action_name
@@ -29,27 +29,30 @@ module Recourse
29
29
  [lead.presence&.upcase_first, title].compact.join ' '
30
30
  end
31
31
 
32
- # The icon is the counted model's own, whatever leads the words beside it.
32
+ # The icon is the counted model's own, whatever leads the words beside it. The
33
+ # words are wrapped where a picture or a figure stands beside them, so a phone,
34
+ # which has room for neither, keeps the picture and the figure and drops the words.
33
35
  def association_tab_label(record, association, namespace)
34
36
  icon = Recourse.known_model_icon association.klass
37
+ count = tab_count record, association
38
+ words = tab_words association, namespace, count
39
+ words = tag.span words, class: 'recourse-tab-word' if icon || count
35
40
 
36
- words = [icon && tag.i(class: "bi bi-#{icon}"), tab_name(record, association, namespace)]
37
-
38
- safe_join words.compact, ' '
41
+ safe_join [icon && tag.i(class: "bi bi-#{icon}"), count, words].compact, ' '
39
42
  end
40
43
 
41
- # `8 ZIPs` where the record keeps a count read off the record itself, no
42
- # query, like the column and the bare `ZIPs` where it keeps none. A namespace
43
- # the routes drew leads either of those, so two nestings of one model read
44
- # apart: `10 visited places` beside `4 booked places`, `Visited places` beside
45
- # `Booked places`. Whatever leads earns the downcase, and a title that leads
46
- # keeps its capital — which is what puts these beside Show and Edit.
47
- def tab_name(record, association, namespace)
48
- count = tab_count record, association
49
- lead = [count, namespace_words(namespace)].compact_blank.join ' '
50
- title = Recourse.model_title association.klass, count: count, lower: lead.present?
44
+ # `ZIPs` where the record keeps no count, and `places` after the `8` where it
45
+ # does read off the record itself, no query, like the column. A namespace the
46
+ # routes drew leads either, so two nestings of one model read apart: `10 visited
47
+ # places` beside `4 booked places`, `Visited places` beside `Booked places`.
48
+ # Whatever leads earns the downcase, and a title that leads keeps its capital —
49
+ # which is what puts these beside Show and Edit.
50
+ def tab_words(association, namespace, count)
51
+ lead = namespace_words namespace
52
+ lead = lead.upcase_first unless count
53
+ title = Recourse.model_title association.klass, count: count, lower: count || lead.present?
51
54
 
52
- [lead.presence&.upcase_first, title].compact.join ' '
55
+ [lead.presence, title].compact.join ' '
53
56
  end
54
57
 
55
58
  def tab_count(record, association)
@@ -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.3.0'
3
+ VERSION = '4.4.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.3.0
4
+ version: 4.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob