inline_forms 8.1.58 → 8.1.59
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 +25 -0
- data/lib/generators/templates/application_record.rb +9 -0
- data/lib/inline_forms/dropdown_order.rb +61 -0
- data/lib/inline_forms/form_elements/check_list_helper.rb +1 -1
- data/lib/inline_forms/form_elements/dropdown_helper.rb +1 -1
- data/lib/inline_forms/form_elements/dropdown_with_other_helper.rb +1 -0
- data/lib/inline_forms/version.rb +1 -1
- data/lib/inline_forms.rb +9 -0
- data/test/dummy/app/models/part.rb +1 -0
- data/test/dummy/app/models/phase.rb +9 -0
- data/test/integration/dropdown_test.rb +94 -7
- data/test/integration_test_helper.rb +9 -0
- metadata +4 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 122426ec8aafea8e48c9693153e155891b9f97cb73a40983a0bdcf21051acc26
|
|
4
|
+
data.tar.gz: bbf2a4dacbfa14d9cf84cab79de9356f5623542e28029aa18f000fe25c21b432
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 043a09c0faaedf4d13ecc8ea20cf6b772709fc20237c7a6e87da188a2392045fab619a7b589a2966ae7f9fa49bc27e5f1caa37778b86605842595ccd74c4f56d
|
|
7
|
+
data.tar.gz: eaa601424c02655fdec7721a81f5f48af2674d58077e2325f2c1ccab49375d60f1dea794891a86dd7d85e7e3c3b2cebb8fad26fedaa2cded1fe2326f65fd9a27
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,31 @@ All notable changes to this project are documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [8.1.59] - 2026-10-22
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Dropdowns follow the model's order again, with one rule for every table-backed list.** 8.1.58 made `dropdown` sort its options alphabetically by presentation. That overrode the order lookups depend on, such as '---' / 'onbekend' first and process phases in process order. Before that, from 2017 to 8.1.57, the sort was discarded and dropdowns showed database order. Until 2015 they used the model's `order_by_clause`. `dropdown_with_other` used database order, and `check_list` used `inline_forms_list`. All three now use `InlineForms::DropdownOrder`; the first match wins:
|
|
12
|
+
1. the model declares **`inline_forms_dropdown_order :column`** (or `:column, :desc`);
|
|
13
|
+
2. the table has a **`dropdown_order`** column, used ascending: give '---' 0 and the phases 10, 20, 30;
|
|
14
|
+
3. the model's `inline_forms_list` scope (its list order; unchanged for `check_list`);
|
|
15
|
+
4. database order, including a `default_scope` order.
|
|
16
|
+
|
|
17
|
+
Rules 1 and 2 replace any `default_scope` order, put NULLs last (a record nobody has numbered yet goes to the end) and break ties on `id`.
|
|
18
|
+
- The macro is on every Active Record model (engine initializer `inline_forms.dropdown_order`), so hosts need no `ApplicationRecord` change. An unknown direction raises `ArgumentError` when the class loads; an unknown column raises when the dropdown renders.
|
|
19
|
+
- **Hosts:** lookups that declare nothing keep their `inline_forms_list` order. StProject's lookups declare `order(:name, :id)`, so they stay alphabetical; Scouting's `default_scope { order(name: :asc) }` still applies. A lookup that needs a fixed order gets a `dropdown_order` integer column, or `inline_forms_dropdown_order`. Papiamentu's `def self.order_by_clause` is still not read; use the macro instead.
|
|
20
|
+
- The installer's `ApplicationRecord` template documents the rule.
|
|
21
|
+
- **Example app: a `Phase` lookup shows the rule.** `inline_forms create … --example` generates `Phase` (`name`, `dropdown_order`), gives Apartment a `phase` dropdown below `owner`, and seeds `---` 0, helprequest 10, decision 20, admittance 30, created in neither order. On an apartment the dropdown reads ---, helprequest, decision, admittance, while `/phases` lists them alphabetically (`_list_order:name`). The seeded apartments get helprequest, decision and ---.
|
|
22
|
+
|
|
23
|
+
### Tests
|
|
24
|
+
|
|
25
|
+
- `dropdown_test`: each rule, desc, declared column over `dropdown_order`, NULLs last, `id` tie-break, `dropdown_with_other` and `check_list`, bad direction or column. The dummy app gains a `Phase` lookup with a `dropdown_order` column (`Part belongs_to :phase`).
|
|
26
|
+
- Example app: `example_app_phase_dropdown_test` checks the apartment's phase dropdown order, that choosing a phase saves it, and that `/phases` keeps its alphabetical list order.
|
|
27
|
+
|
|
28
|
+
### Lockstep
|
|
29
|
+
|
|
30
|
+
- validation_hints 8.1.59, inline_forms_installer 8.1.59, inline_forms_schema_edit 8.1.59.
|
|
31
|
+
|
|
7
32
|
## [8.1.58] - 2026-10-21
|
|
8
33
|
|
|
9
34
|
Three gem issues found in the StProject review (2026-10-04). The fourth it listed, the CSS `@import` of `http://fonts.googleapis.com`, was already fixed in 8.1.22 (self-hosted Open Sans). Also the first step of the Sass `@import` → `@use` move.
|
|
@@ -26,6 +26,15 @@ class ApplicationRecord < ActiveRecord::Base
|
|
|
26
26
|
# `unscope`/`reorder` cleanly.
|
|
27
27
|
scope :inline_forms_list, -> { all }
|
|
28
28
|
|
|
29
|
+
# Dropdowns, dropdown_with_other and check_list offer a model's records in
|
|
30
|
+
# this order (first match wins; InlineForms::DropdownOrder):
|
|
31
|
+
# 1. inline_forms_dropdown_order :col # or :col, :desc
|
|
32
|
+
# 2. a `dropdown_order` integer column, ascending (e.g. '---' 0,
|
|
33
|
+
# 'helprequest' 10, 'decision' 20, 'admittance' 30)
|
|
34
|
+
# 3. inline_forms_list above
|
|
35
|
+
# 4. database order
|
|
36
|
+
# 1 and 2 put NULLs last and break ties on id.
|
|
37
|
+
|
|
29
38
|
# Wrapper for @model.human_attribute_name -> Model.human_attribute_name
|
|
30
39
|
def human_attribute_name(*args)
|
|
31
40
|
self.class.human_attribute_name(*args)
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module InlineForms
|
|
4
|
+
# The order in which a table-backed form element (dropdown,
|
|
5
|
+
# dropdown_with_other, check_list) offers its records. First match wins:
|
|
6
|
+
#
|
|
7
|
+
# 1. the model declares `inline_forms_dropdown_order :column, :asc|:desc`;
|
|
8
|
+
# 2. the table has a `dropdown_order` column (ascending), so a lookup can
|
|
9
|
+
# put '---' / 'onbekend' first and process phases in process order;
|
|
10
|
+
# 3. the model's `inline_forms_list` scope (its list order);
|
|
11
|
+
# 4. database order (a default_scope order included).
|
|
12
|
+
#
|
|
13
|
+
# 1 and 2 replace any default_scope order, put NULLs last (a record nobody
|
|
14
|
+
# numbered yet goes to the end) and break ties on the primary key.
|
|
15
|
+
#
|
|
16
|
+
# The macro is on every Active Record model (engine initializer), so hosts
|
|
17
|
+
# need no ApplicationRecord change.
|
|
18
|
+
module DropdownOrder
|
|
19
|
+
COLUMN = "dropdown_order"
|
|
20
|
+
DIRECTIONS = %i[asc desc].freeze
|
|
21
|
+
|
|
22
|
+
module ClassMethods
|
|
23
|
+
def inline_forms_dropdown_order(column, direction = :asc)
|
|
24
|
+
direction = direction.to_s.downcase.to_sym
|
|
25
|
+
unless DIRECTIONS.include?(direction)
|
|
26
|
+
raise ArgumentError, "inline_forms_dropdown_order: direction must be :asc or :desc, got #{direction.inspect}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
@inline_forms_dropdown_order = [ column.to_s, direction ].freeze
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# [column, direction] declared on this class or an ancestor, or nil.
|
|
33
|
+
def inline_forms_dropdown_order_spec
|
|
34
|
+
return @inline_forms_dropdown_order if defined?(@inline_forms_dropdown_order)
|
|
35
|
+
|
|
36
|
+
superclass.inline_forms_dropdown_order_spec if superclass.respond_to?(:inline_forms_dropdown_order_spec)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.apply(relation)
|
|
41
|
+
klass = relation.klass
|
|
42
|
+
column, direction = klass.inline_forms_dropdown_order_spec if klass.respond_to?(:inline_forms_dropdown_order_spec)
|
|
43
|
+
column, direction = COLUMN, :asc if column.nil? && klass.column_names.include?(COLUMN)
|
|
44
|
+
|
|
45
|
+
if column
|
|
46
|
+
unless klass.column_names.include?(column)
|
|
47
|
+
raise ArgumentError, "#{klass.name}.inline_forms_dropdown_order: #{klass.table_name} has no column #{column.inspect}"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
table = klass.arel_table
|
|
51
|
+
keys = [ table[column].public_send(direction).nulls_last ]
|
|
52
|
+
keys << table[klass.primary_key].asc if klass.primary_key && klass.primary_key != column
|
|
53
|
+
relation.reorder(*keys)
|
|
54
|
+
elsif klass.respond_to?(:inline_forms_list)
|
|
55
|
+
relation.merge(klass.inline_forms_list)
|
|
56
|
+
else
|
|
57
|
+
relation
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -21,7 +21,7 @@ module InlineForms
|
|
|
21
21
|
object.send(attribute).build if object.send(attribute).empty?
|
|
22
22
|
klass = object.send(attribute).first.class
|
|
23
23
|
values = cancan_enabled? ? klass.accessible_by(current_ability) : klass.all
|
|
24
|
-
values =
|
|
24
|
+
values = InlineForms::DropdownOrder.apply(values)
|
|
25
25
|
out = ''
|
|
26
26
|
values.each do | item |
|
|
27
27
|
out << "<div class='row #{cycle('odd', 'even')}'>"
|
|
@@ -29,7 +29,7 @@ module InlineForms
|
|
|
29
29
|
if klass.method_defined? :disabled_for_dropdown?
|
|
30
30
|
options_disabled = values.map{|v| v.id if v.disabled_for_dropdown?}.compact
|
|
31
31
|
end
|
|
32
|
-
values = values
|
|
32
|
+
values = InlineForms::DropdownOrder.apply(values)
|
|
33
33
|
# the leading underscore is to avoid name conflicts, like 'email' and 'email_type' will result in 'email' and 'email[email_type_id]' in the form!
|
|
34
34
|
collection_select( ('_' + object.class.to_s.underscore).to_sym,
|
|
35
35
|
attribute.to_s.foreign_key.to_sym,
|
data/lib/inline_forms/version.rb
CHANGED
data/lib/inline_forms.rb
CHANGED
|
@@ -10,6 +10,7 @@ require "inline_forms/schema_apply"
|
|
|
10
10
|
require "inline_forms/schema_label"
|
|
11
11
|
require "inline_forms/form_element_from_callee"
|
|
12
12
|
require "inline_forms/searchable"
|
|
13
|
+
require "inline_forms/dropdown_order"
|
|
13
14
|
require "inline_forms/archived_form_elements"
|
|
14
15
|
require "inline_forms/form_element_registry"
|
|
15
16
|
require "inline_forms/form_elements"
|
|
@@ -359,6 +360,14 @@ module InlineForms
|
|
|
359
360
|
end
|
|
360
361
|
end
|
|
361
362
|
|
|
363
|
+
# `inline_forms_dropdown_order` on every model (InlineForms::DropdownOrder),
|
|
364
|
+
# so hosts need no ApplicationRecord change to declare a dropdown order.
|
|
365
|
+
initializer "inline_forms.dropdown_order" do
|
|
366
|
+
ActiveSupport.on_load(:active_record) do
|
|
367
|
+
extend InlineForms::DropdownOrder::ClassMethods
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
362
371
|
initializer "inline_forms.assets.precompile" do |app|
|
|
363
372
|
# Skip when the host has no Sprockets-style pipeline (e.g. the test/dummy
|
|
364
373
|
# harness); generated apps always bundle sprockets-rails.
|
|
@@ -2,16 +2,103 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative "../integration_test_helper"
|
|
4
4
|
|
|
5
|
-
#
|
|
6
|
-
#
|
|
5
|
+
# Order of the records table-backed form elements offer
|
|
6
|
+
# (InlineForms::DropdownOrder): the model's inline_forms_dropdown_order, else
|
|
7
|
+
# a dropdown_order column, else inline_forms_list, else database order.
|
|
7
8
|
class DropdownTest < InlineFormsIntegrationTestCase
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
teardown do
|
|
10
|
+
[ Phase, Machine, Kind, Part ].each do |klass|
|
|
11
|
+
klass.remove_instance_variable(:@inline_forms_dropdown_order) if klass.instance_variable_defined?(:@inline_forms_dropdown_order)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
11
14
|
|
|
15
|
+
def view
|
|
12
16
|
get machines_path
|
|
13
|
-
|
|
17
|
+
controller.view_context
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def option_texts(html)
|
|
21
|
+
html.scan(%r{<option(?: [^>]*)?>([^<]*)</option>}).flatten
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_phases
|
|
25
|
+
Phase.create!(name: "admittance", dropdown_order: 30)
|
|
26
|
+
Phase.create!(name: "nieuw")
|
|
27
|
+
Phase.create!(name: "decision", dropdown_order: 20)
|
|
28
|
+
Phase.create!(name: "---", dropdown_order: 0)
|
|
29
|
+
Phase.create!(name: "helprequest", dropdown_order: 10)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test "a dropdown_order column orders the dropdown, NULLs last" do
|
|
33
|
+
create_phases
|
|
34
|
+
|
|
35
|
+
html = view.dropdown_edit(Part.create!(name: "Gear"), :phase)
|
|
36
|
+
|
|
37
|
+
assert_equal [ "---", "helprequest", "decision", "admittance", "nieuw" ], option_texts(html)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
test "the model's declared direction applies to the dropdown_order column" do
|
|
41
|
+
create_phases
|
|
42
|
+
Phase.inline_forms_dropdown_order :dropdown_order, :desc
|
|
43
|
+
|
|
44
|
+
html = view.dropdown_edit(Part.create!(name: "Gear"), :phase)
|
|
45
|
+
|
|
46
|
+
assert_equal [ "admittance", "decision", "helprequest", "---", "nieuw" ], option_texts(html)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
test "a declared column wins over the dropdown_order column" do
|
|
50
|
+
create_phases
|
|
51
|
+
Phase.inline_forms_dropdown_order :name, :desc
|
|
52
|
+
|
|
53
|
+
html = view.dropdown_edit(Part.create!(name: "Gear"), :phase)
|
|
54
|
+
|
|
55
|
+
assert_equal [ "nieuw", "helprequest", "decision", "admittance", "---" ], option_texts(html)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
test "without a declaration or column, the list order applies" do
|
|
59
|
+
%w[Zeta Alpha Mu].each { |name| Machine.create!(name: name) } # inline_forms_list: order(:name, :id)
|
|
60
|
+
|
|
61
|
+
html = view.dropdown_edit(Part.create!(name: "Gear"), :machine)
|
|
62
|
+
|
|
63
|
+
assert_equal %w[Alpha Mu Zeta], option_texts(html)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
test "ties on the declared column break on id" do
|
|
67
|
+
first = Phase.create!(name: "same", dropdown_order: 5)
|
|
68
|
+
second = Phase.create!(name: "same", dropdown_order: 5)
|
|
69
|
+
|
|
70
|
+
assert_equal [ first.id, second.id ], InlineForms::DropdownOrder.apply(Phase.all).map(&:id)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
test "without anything declared, records come in database order" do
|
|
74
|
+
%w[Zeta Alpha].each { |name| Kind.create!(name: name) } # inline_forms_list: all
|
|
75
|
+
|
|
76
|
+
assert_equal %w[Zeta Alpha], InlineForms::DropdownOrder.apply(Kind.all).map(&:name)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
test "dropdown_with_other offers its datalist in the dropdown order" do
|
|
80
|
+
%w[Zeta Alpha Mu].each { |name| Kind.create!(name: name) }
|
|
81
|
+
Kind.inline_forms_dropdown_order :name
|
|
82
|
+
|
|
83
|
+
html = view.dropdown_with_other_edit(Widget.create!(name: "W"), :kind)
|
|
84
|
+
|
|
85
|
+
assert_equal %w[Alpha Mu Zeta], html.scan(/<option value="([^"]*)">/).flatten
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
test "check_list lists its records in the dropdown order" do
|
|
89
|
+
machine = Machine.create!(name: "Press")
|
|
90
|
+
%w[Bolt Nut Axle].each { |name| Part.create!(name: name) }
|
|
91
|
+
Part.inline_forms_dropdown_order :name, :desc
|
|
92
|
+
|
|
93
|
+
html = view.check_list_edit(machine, :parts)
|
|
94
|
+
|
|
95
|
+
assert_equal %w[Nut Bolt Axle], html.scan(%r{<label for=[^>]*>([^<]*)</label>}).flatten.reject(&:empty?)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
test "an unknown direction or column is refused" do
|
|
99
|
+
assert_raises(ArgumentError) { Phase.inline_forms_dropdown_order :name, :up }
|
|
14
100
|
|
|
15
|
-
|
|
101
|
+
Phase.inline_forms_dropdown_order :position
|
|
102
|
+
assert_raises(ArgumentError) { InlineForms::DropdownOrder.apply(Phase.all).to_a }
|
|
16
103
|
end
|
|
17
104
|
end
|
|
@@ -42,9 +42,18 @@ ActiveRecord::Schema.define do
|
|
|
42
42
|
t.timestamps
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
# Lookup with a dropdown_order column (InlineForms::DropdownOrder);
|
|
46
|
+
# Part belongs_to it for the dropdown tests.
|
|
47
|
+
create_table :phases, force: true do |t|
|
|
48
|
+
t.string :name
|
|
49
|
+
t.integer :dropdown_order
|
|
50
|
+
t.timestamps
|
|
51
|
+
end
|
|
52
|
+
|
|
45
53
|
create_table :parts, force: true do |t|
|
|
46
54
|
t.string :name
|
|
47
55
|
t.integer :machine_id
|
|
56
|
+
t.integer :phase_id
|
|
48
57
|
t.timestamps
|
|
49
58
|
end
|
|
50
59
|
add_index :parts, :machine_id
|
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.
|
|
4
|
+
version: 8.1.59
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ace Suares
|
|
@@ -223,6 +223,7 @@ files:
|
|
|
223
223
|
- lib/inline_forms.rb
|
|
224
224
|
- lib/inline_forms/archived_form_elements.rb
|
|
225
225
|
- lib/inline_forms/attribute_list.rb
|
|
226
|
+
- lib/inline_forms/dropdown_order.rb
|
|
226
227
|
- lib/inline_forms/form_element_from_callee.rb
|
|
227
228
|
- lib/inline_forms/form_element_registry.rb
|
|
228
229
|
- lib/inline_forms/form_elements.rb
|
|
@@ -299,6 +300,7 @@ files:
|
|
|
299
300
|
- test/dummy/app/models/kind.rb
|
|
300
301
|
- test/dummy/app/models/machine.rb
|
|
301
302
|
- test/dummy/app/models/part.rb
|
|
303
|
+
- test/dummy/app/models/phase.rb
|
|
302
304
|
- test/dummy/app/models/widget.rb
|
|
303
305
|
- test/dummy/app/views/_gizmos_search.html.erb
|
|
304
306
|
- test/dummy/app/views/stats/show.html.erb
|
|
@@ -388,6 +390,7 @@ test_files:
|
|
|
388
390
|
- test/dummy/app/models/kind.rb
|
|
389
391
|
- test/dummy/app/models/machine.rb
|
|
390
392
|
- test/dummy/app/models/part.rb
|
|
393
|
+
- test/dummy/app/models/phase.rb
|
|
391
394
|
- test/dummy/app/models/widget.rb
|
|
392
395
|
- test/dummy/app/views/_gizmos_search.html.erb
|
|
393
396
|
- test/dummy/app/views/stats/show.html.erb
|