inline_forms 8.1.64 → 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: d65f5d0ad495091211688c0db04b4466e240ea578b1349523e97a1ce66d3cd00
4
- data.tar.gz: 9aca4b16c7481547715c240283ff8bac4de6d1d19caa57e3bc239aa51ed2899c
3
+ metadata.gz: acc7bbb91a0ebefa61a500e74814588ffeed1ac4bb9c4da36098b09d47c8483e
4
+ data.tar.gz: 448a2bd05b8ef8cf69d4a4268c29cfaa7c8955089f4b57675632334e5b2ef42c
5
5
  SHA512:
6
- metadata.gz: 05afb1bc303bfdda7506735a166f1f69f0ee5f8a79efa2279a4d928e308c69047037bfd00383a260a3e547f5540bdd7f36d8aff476da0c54c9c7b92df21a39b2
7
- data.tar.gz: c059604b46b459595cc34fb574704a1e0203aab6eb40083825df0763c0e9ae48489765515529efa30561f0b2bed44ec39944566750b7e6fc14b7da2c4d209033
6
+ metadata.gz: ead6649bf5d9c969bc93b4f937f2fe11e97be176184788ff884e94932471baf893408711a3556ef274f4a3eea2be0ffd3401e52f03848b69c3bf2a89ca454880
7
+ data.tar.gz: 1f74c4a96abc1dfcb0215e6f465636e80d0512a3f9a4ab590a33ab3764d0d7d9f3377e39576cfcac2f9ad79b2890de33b7cd8ffd3bee4ba59d406a0f96d9dd6f
data/CHANGELOG.md CHANGED
@@ -4,6 +4,32 @@ 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
+
7
33
  ## [8.1.64] - 2026-10-31
8
34
 
9
35
  ### Changed
@@ -82,7 +108,7 @@ Fixes from a review of 8.1.58 and 8.1.59.
82
108
 
83
109
  - validation_hints 8.1.60, inline_forms_installer 8.1.60, inline_forms_schema_edit 8.1.60.
84
110
 
85
- ## [8.1.59] - 2026-10-22
111
+ ## [8.1.59] - 2026-10-25
86
112
 
87
113
  ### Fixed
88
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 t("inline_forms.files.preview", :default => "preview"), "/#{attribute}/#{object.id}", :class => "pdf_preview", :target => "_blank", :data => { :turbo => false }} | #{link_to t("inline_forms.files.download_pdf", :default => "download 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.64"
4
+ VERSION = "8.1.65"
5
5
  end
@@ -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" }
@@ -62,6 +62,23 @@ class LegacyElementsTest < InlineFormsIntegrationTestCase
62
62
  assert_match %r{>preview</a> \| <a[^>]*>download pdf</a>}, response.body
63
63
  end
64
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
+
65
82
  test "pdf_link links leave the field's turbo frame" do
66
83
  frame = "widget_#{@widget.id}"
67
84
  get widget_path(@widget, update: frame), headers: frame_headers(frame)
@@ -123,11 +123,11 @@ class LocaleTest < InlineFormsIntegrationTestCase
123
123
  end
124
124
 
125
125
  test "pdf_link reads Dutch" do
126
+ original_default = I18n.default_locale
126
127
  widget = Widget.create!(name: "Rapport")
127
128
  frame = "widget_#{widget.id}"
128
129
 
129
130
  # Not strictly: the dummy's dropdown values have no nl texts.
130
- original_default = I18n.default_locale
131
131
  I18n.with_locale(:nl) do
132
132
  I18n.default_locale = :nl
133
133
  get widget_path(widget, update: frame), headers: frame_headers(frame)
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.64
4
+ version: 8.1.65
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ace Suares