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 +4 -4
- data/CHANGELOG.md +27 -1
- data/lib/inline_forms/dropdown_order.rb +20 -7
- data/lib/inline_forms/form_elements/pdf_link_helper.rb +8 -1
- data/lib/inline_forms/version.rb +1 -1
- data/test/integration/dropdown_test.rb +40 -0
- data/test/integration/legacy_elements_test.rb +17 -0
- data/test/integration/locale_test.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: acc7bbb91a0ebefa61a500e74814588ffeed1ac4bb9c4da36098b09d47c8483e
|
|
4
|
+
data.tar.gz: 448a2bd05b8ef8cf69d4a4268c29cfaa7c8955089f4b57675632334e5b2ef42c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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-
|
|
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
|
|
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
|
|
16
|
-
#
|
|
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 (
|
|
57
|
-
relation.
|
|
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.
|
|
64
|
-
klass.respond_to?(:inline_forms_list)
|
|
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
|
-
|
|
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)
|
data/lib/inline_forms/version.rb
CHANGED
|
@@ -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)
|