inline_forms 8.1.61 → 8.1.65

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: 1c2157f464ca5f6e0a7f42dfbf783dfdbcd606942f8c40be8385479237889794
4
- data.tar.gz: aa502e01d242a09fc6989b5f7f865c9fbbde33a98592273116daf07d96a8821d
3
+ metadata.gz: acc7bbb91a0ebefa61a500e74814588ffeed1ac4bb9c4da36098b09d47c8483e
4
+ data.tar.gz: 448a2bd05b8ef8cf69d4a4268c29cfaa7c8955089f4b57675632334e5b2ef42c
5
5
  SHA512:
6
- metadata.gz: 9ca4f57a7ae8c58b68b63103553b6de3f59f187b9fa9cef64fdaa9e5dcdfb494a69dfbce24f3207b0c6198c08dd03d79de84d6c542283115959edd3b7518e776
7
- data.tar.gz: 651bf78a377bebbdb9fbf1a17f6ad995e00c5de0b399c90377f2680db8d816d592ebf394a8d49a963a244798a890e04c9a09554486767e7c59487e2cbe3da69c
6
+ metadata.gz: ead6649bf5d9c969bc93b4f937f2fe11e97be176184788ff884e94932471baf893408711a3556ef274f4a3eea2be0ffd3401e52f03848b69c3bf2a89ca454880
7
+ data.tar.gz: 1f74c4a96abc1dfcb0215e6f465636e80d0512a3f9a4ab590a33ab3764d0d7d9f3377e39576cfcac2f9ad79b2890de33b7cd8ffd3bee4ba59d406a0f96d9dd6f
data/CHANGELOG.md CHANGED
@@ -4,6 +4,74 @@ All notable changes to this project are documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [8.1.65] - 2026-10-31
8
+
9
+ Fixes from a review of 8.1.60 to 8.1.64.
10
+
11
+ ### Fixed
12
+
13
+ - **Dropdowns honour a `reorder` in `inline_forms_list`.** Since 8.1.60 rule 3 added the scope's order to a `default_scope` order, so a scope that reorders lost: Scouting's lookups with `inline_forms_list_by_name` would have sorted by name and lost their pinned entries. Rule 3 now uses the scope's order as it stands: it follows a `default_scope` order, unless the scope reorders.
14
+ - **A list order on a joined table no longer breaks the dropdown.** Since 8.1.60 rule 3 dropped the scope's joins and kept its ORDER BY, which was invalid SQL. Association joins now come along as LEFT joins, so the order works and no record is hidden. A string join stays as written, so it can still hide records.
15
+ - **`pdf_link` needs no model method.** `pdf_link_edit` called the attribute, so a new form with a `pdf_link` row raised `NoMethodError` unless the model had a stub. The example app's `def report` is gone.
16
+ - **`pdf_link` works under a sub-path.** Both links now start with the app's `request.script_name`.
17
+
18
+ ### Tests
19
+
20
+ - `dropdown_test`: `default_scope` with a list scope, a `reorder`, and a list order on a joined table (`dropdown` and `check_list`).
21
+ - `legacy_elements_test`: `pdf_link` in a new form without a model method, and under a sub-path.
22
+ - Example app: `example_app_seed_rollback_test` runs the seed migration's `down`.
23
+
24
+ ### Documentation
25
+
26
+ - `CLAUDE.md`: inline_forms is released with `rake release:all`.
27
+ - The 8.1.59 changelog date is the release date, 2026-10-25.
28
+
29
+ ### Lockstep
30
+
31
+ - validation_hints 8.1.65, inline_forms_installer 8.1.65, inline_forms_schema_edit 8.1.65.
32
+
33
+ ## [8.1.64] - 2026-10-31
34
+
35
+ ### Changed
36
+
37
+ - **`pdf_link`'s labels are translated.** `inline_forms.files.preview` and `inline_forms.files.download_pdf`, in English ("preview | download pdf") and Dutch ("voorbeeld | pdf downloaden"), with the English texts as defaults for other locales. An app in Dutch now shows the Dutch labels: a test that finds the link by its text needs the new text.
38
+
39
+ ### Tests
40
+
41
+ - `locale_test`: `pdf_link` in Dutch.
42
+
43
+ ### Lockstep
44
+
45
+ - validation_hints 8.1.64, inline_forms_installer 8.1.64, inline_forms_schema_edit 8.1.64.
46
+
47
+ ## [8.1.63] - 2026-10-31
48
+
49
+ ### Changed
50
+
51
+ - **`pdf_link` reads "preview | download pdf".** "preview pdf" read as one label. The second link's text is now `download pdf` and a ` | ` separates the links; hrefs, classes, `data-turbo` and `target` are unchanged.
52
+
53
+ ### Tests
54
+
55
+ - `legacy_elements_test` and `example_app_showcase_pdf_link_test`: the link texts and the separator.
56
+
57
+ ### Lockstep
58
+
59
+ - validation_hints 8.1.63, inline_forms_installer 8.1.63, inline_forms_schema_edit 8.1.63.
60
+
61
+ ## [8.1.62] - 2026-10-30
62
+
63
+ ### Added
64
+
65
+ - **The example app has a `pdf_link`.** `FormElementShowcase` gets a `report` row, with `ShowcaseReportsController` behind `/report/:id` (preview) and `/report/:id.pdf` (a sample PDF, as a download).
66
+
67
+ ### Tests
68
+
69
+ - `example_app_showcase_pdf_link_test`: the links, the new form, the preview, the download and guests.
70
+
71
+ ### Lockstep
72
+
73
+ - validation_hints 8.1.62, inline_forms_installer 8.1.62, inline_forms_schema_edit 8.1.62.
74
+
7
75
  ## [8.1.61] - 2026-10-30
8
76
 
9
77
  ### Fixed
@@ -40,7 +108,7 @@ Fixes from a review of 8.1.58 and 8.1.59.
40
108
 
41
109
  - validation_hints 8.1.60, inline_forms_installer 8.1.60, inline_forms_schema_edit 8.1.60.
42
110
 
43
- ## [8.1.59] - 2026-10-22
111
+ ## [8.1.59] - 2026-10-25
44
112
 
45
113
  ### Fixed
46
114
 
@@ -8,12 +8,14 @@ module InlineForms
8
8
  # 2. the table has a `dropdown_order` column (ascending), so a lookup can
9
9
  # put '---' / 'onbekend' first and process phases in process order;
10
10
  # 3. the ORDER BY of the model's `inline_forms_list` scope (its list order;
11
- # only the order: a where/joins in that scope does not hide records here);
11
+ # only the order: a where in that scope does not hide records here, and
12
+ # its joins come along as LEFT joins, for an order on a joined table);
12
13
  # 4. database order (a default_scope order included).
13
14
  #
14
15
  # 1 and 2 replace any default_scope order, put NULLs last (a record nobody
15
- # numbered yet goes to the end) and break ties on the primary key. 3 adds to
16
- # a default_scope order, as merging the scope did.
16
+ # numbered yet goes to the end) and break ties on the primary key. 3 keeps a
17
+ # default_scope order after it, unless the scope reorders: the scope's own
18
+ # order_values already say which.
17
19
  #
18
20
  # The macro is on every Active Record model (engine initializer), so hosts
19
21
  # need no ApplicationRecord change.
@@ -53,15 +55,26 @@ module InlineForms
53
55
  keys = [ table[column].public_send(direction).nulls_last ]
54
56
  keys << table[klass.primary_key].asc if klass.primary_key && klass.primary_key != column
55
57
  relation.reorder(*keys)
56
- elsif (list_order = list_order_values(klass)).any?
57
- relation.order(*list_order)
58
+ elsif (list = list_scope(klass))&.order_values&.any?
59
+ with_list_joins(relation, list).reorder(*list.order_values)
58
60
  else
59
61
  relation
60
62
  end
61
63
  end
62
64
 
63
- def self.list_order_values(klass)
64
- klass.respond_to?(:inline_forms_list) ? klass.inline_forms_list.order_values : []
65
+ def self.list_scope(klass)
66
+ klass.inline_forms_list if klass.respond_to?(:inline_forms_list)
67
+ end
68
+
69
+ # An association join becomes a LEFT join, so a record without the
70
+ # association stays in the list. A string join can't: it stays as written.
71
+ def self.with_list_joins(relation, list)
72
+ named, raw = list.joins_values.partition { |join| join.is_a?(Symbol) || join.is_a?(Hash) }
73
+ named = (named | list.left_outer_joins_values | list.eager_load_values) - relation.joins_values
74
+ raw -= relation.joins_values
75
+ relation = relation.left_outer_joins(*named) if named.any?
76
+ relation = relation.joins(*raw) if raw.any?
77
+ relation
65
78
  end
66
79
  end
67
80
  end
@@ -8,10 +8,17 @@ module InlineForms
8
8
  def pdf_link_show(object, attribute)
9
9
  # the attribute is the action
10
10
  # turbo:false so the links leave the field's turbo-frame: the preview has no matching frame ("Content missing") and the PDF is not HTML; the preview gets its own tab so the record stays open
11
- "#{link_to 'preview', "/#{attribute}/#{object.id}", :class => "pdf_preview", :target => "_blank", :data => { :turbo => false }} #{link_to 'pdf', "/#{attribute}/#{object.id}.pdf", :class => "pdf_open", :data => { :turbo => false }}".html_safe
11
+ # script_name so the links work in an app deployed under a sub-path
12
+ path = "#{request.script_name}/#{attribute}/#{object.id}"
13
+ safe_join([
14
+ link_to(t("inline_forms.files.preview", :default => "preview"), path, :class => "pdf_preview", :target => "_blank", :data => { :turbo => false }),
15
+ link_to(t("inline_forms.files.download_pdf", :default => "download pdf"), "#{path}.pdf", :class => "pdf_open", :data => { :turbo => false })
16
+ ], " | ")
12
17
  end
13
18
 
14
19
  def pdf_link_edit(object, attribute)
20
+ # the attribute is an action: a model needs no method for it
21
+ return unless object.respond_to?(attribute)
15
22
  o = object.send(attribute)
16
23
  o = o.to_s + " (" + distance_of_time_in_words_to_now(o) + ")" if o.is_a?(Time)
17
24
  o = o._presentation if o.respond_to?(:_presentation)
@@ -1,5 +1,5 @@
1
1
  # -*- encoding : utf-8 -*-
2
2
 
3
3
  module InlineForms
4
- VERSION = "8.1.61"
4
+ VERSION = "8.1.65"
5
5
  end
@@ -49,6 +49,8 @@ en:
49
49
  destroy: destroy
50
50
  files:
51
51
  download: download
52
+ download_pdf: download pdf
53
+ preview: preview
52
54
  replace: replace file
53
55
  remove: remove file
54
56
  restore: restore
@@ -49,6 +49,8 @@ nl:
49
49
  destroy: verwijderd
50
50
  files:
51
51
  download: downloaden
52
+ download_pdf: pdf downloaden
53
+ preview: voorbeeld
52
54
  replace: bestand vervangen
53
55
  remove: bestand verwijderen
54
56
  restore: terugzetten
@@ -15,6 +15,7 @@ class DropdownTest < InlineFormsIntegrationTestCase
15
15
  klass.singleton_class.remove_method(:inline_forms_list) if klass.singleton_class.method_defined?(:inline_forms_list, false)
16
16
  end
17
17
  Phase.remove_method(:disabled_for_dropdown?) if Phase.method_defined?(:disabled_for_dropdown?, false)
18
+ Kind.default_scopes = []
18
19
  end
19
20
 
20
21
  def view
@@ -90,6 +91,45 @@ class DropdownTest < InlineFormsIntegrationTestCase
90
91
  assert_equal %w[Alpha Hidden Zeta], html.scan(/<option value="([^"]*)">/).flatten
91
92
  end
92
93
 
94
+ test "a reorder in inline_forms_list replaces a default_scope order" do
95
+ Kind.send(:default_scope) { order(:name) }
96
+ %w[Alpha Otro Zeta].each { |name| Kind.create!(name: name) }
97
+ Kind.define_singleton_method(:inline_forms_list) { reorder(Arel::Nodes::Descending.new(arel_table[:name].eq("Otro")), :name) }
98
+
99
+ assert_equal %w[Otro Alpha Zeta], InlineForms::DropdownOrder.apply(Kind.all).map(&:name)
100
+ end
101
+
102
+ test "the list order follows a default_scope order, without repeating it" do
103
+ Kind.send(:default_scope) { order(:name) }
104
+ Kind.define_singleton_method(:inline_forms_list) { order(:id) }
105
+
106
+ sql = InlineForms::DropdownOrder.apply(Kind.all).to_sql
107
+
108
+ assert_equal 1, sql.scan('"kinds"."name"').size, sql
109
+ assert_operator sql.index('"kinds"."name"'), :<, sql.index('"kinds"."id"')
110
+ end
111
+
112
+ test "an inline_forms_list order on a joined table hides no records" do
113
+ Part.create!(name: "Axle", phase: Phase.create!(name: "z"))
114
+ Part.create!(name: "Bolt", phase: Phase.create!(name: "a"))
115
+ Part.create!(name: "Free")
116
+ Part.define_singleton_method(:inline_forms_list) { joins(:phase).order("phases.name DESC") }
117
+
118
+ assert_equal %w[Axle Bolt Free], InlineForms::DropdownOrder.apply(Part.all).map(&:name)
119
+ end
120
+
121
+ test "check_list keeps a joining inline_forms_list filter and its order" do
122
+ machine = Machine.create!(name: "Press")
123
+ Part.create!(name: "Axle", phase: Phase.create!(name: "z"))
124
+ Part.create!(name: "Bolt", phase: Phase.create!(name: "a"))
125
+ Part.create!(name: "Hidden")
126
+ Part.define_singleton_method(:inline_forms_list) { joins(:phase).order("phases.name") }
127
+
128
+ html = view.check_list_edit(machine, :parts)
129
+
130
+ assert_equal %w[Bolt Axle], html.scan(%r{<label for=[^>]*>([^<]*)</label>}).flatten.reject(&:empty?)
131
+ end
132
+
93
133
  test "disabled_for_dropdown? and the options come from one query" do
94
134
  create_phases
95
135
  Phase.define_method(:disabled_for_dropdown?) { name == "decision" }
@@ -52,6 +52,33 @@ class LegacyElementsTest < InlineFormsIntegrationTestCase
52
52
  assert_includes response.body, %(href="/report/#{@widget.id}.pdf")
53
53
  end
54
54
 
55
+ test "pdf_link reads preview | download pdf" do
56
+ frame = "widget_#{@widget.id}"
57
+ get widget_path(@widget, update: frame), headers: frame_headers(frame)
58
+
59
+ assert_response :success
60
+ assert_select %(a[href="/report/#{@widget.id}"]), text: "preview"
61
+ assert_select %(a[href="/report/#{@widget.id}.pdf"]), text: "download pdf"
62
+ assert_match %r{>preview</a> \| <a[^>]*>download pdf</a>}, response.body
63
+ end
64
+
65
+ test "pdf_link needs no model method in the new form" do
66
+ assert_not Widget.new.respond_to?(:report)
67
+
68
+ get new_widget_path(update: "widgets_list"), headers: frame_headers("widgets_list")
69
+
70
+ assert_response :success
71
+ end
72
+
73
+ test "pdf_link links keep the app's sub-path" do
74
+ frame = "widget_#{@widget.id}"
75
+ get widget_path(@widget, update: frame), headers: frame_headers(frame), env: { "SCRIPT_NAME" => "/portal" }
76
+
77
+ assert_response :success
78
+ assert_select %(a.pdf_preview[href="/portal/report/#{@widget.id}"])
79
+ assert_select %(a.pdf_open[href="/portal/report/#{@widget.id}.pdf"])
80
+ end
81
+
55
82
  test "pdf_link links leave the field's turbo frame" do
56
83
  frame = "widget_#{@widget.id}"
57
84
  get widget_path(@widget, update: frame), headers: frame_headers(frame)
@@ -18,6 +18,7 @@ class LocaleTest < InlineFormsIntegrationTestCase
18
18
  inline_forms.view.versions_rich_text inline_forms.view.version_events.create
19
19
  inline_forms.view.version_events.update inline_forms.view.version_events.destroy
20
20
  inline_forms.devise.login common.more common.logout inline_forms.view.create_failed
21
+ inline_forms.files.preview inline_forms.files.download_pdf
21
22
  ].freeze
22
23
 
23
24
  setup do
@@ -121,6 +122,23 @@ class LocaleTest < InlineFormsIntegrationTestCase
121
122
  assert_includes response.body, "old value"
122
123
  end
123
124
 
125
+ test "pdf_link reads Dutch" do
126
+ original_default = I18n.default_locale
127
+ widget = Widget.create!(name: "Rapport")
128
+ frame = "widget_#{widget.id}"
129
+
130
+ # Not strictly: the dummy's dropdown values have no nl texts.
131
+ I18n.with_locale(:nl) do
132
+ I18n.default_locale = :nl
133
+ get widget_path(widget, update: frame), headers: frame_headers(frame)
134
+ end
135
+
136
+ assert_response :success
137
+ assert_match %r{>voorbeeld</a> \| <a[^>]*>pdf downloaden</a>}, response.body
138
+ ensure
139
+ I18n.default_locale = original_default
140
+ end
141
+
124
142
  test "the devise and application layouts declare the page language" do
125
143
  get widgets_path
126
144
  I18n.with_locale(:nl) do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inline_forms
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.61
4
+ version: 8.1.65
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ace Suares