b4um_generators 0.2.16 → 0.3.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: 3872ce0dc22b0119d636611c3948c941da818ed3c8ee65301da95c0570ba677d
4
- data.tar.gz: 77a2d8228dddbb5852325b81c1e1cc65482dc429912629884cf7c580438dac4e
3
+ metadata.gz: 167af7d5ad10e01abf428bf058e8d0ebc1b85e7791e43882dd1c53966d1ef89f
4
+ data.tar.gz: 715da7ed28265ec0f4451684dd35d375c749fbb8eb7f06b915a13e8bbadea26b
5
5
  SHA512:
6
- metadata.gz: 1e44802b32dc4e9ee22c7bd2b4507a34a692777ca0a940b904f0403223b0d36b7bbd9c403579a13cd2fe9409d76f86fd21cc487022d13b4abd7de0ef600f4e6a
7
- data.tar.gz: 1a20c4ba742c086c788c5426085724d3bea2a3a01e30229ea1dc0f190b6ad4d2819a5c5609e326dd95fdfc4b70beb728260ded64a26416d0725322144e7f6b1b
6
+ metadata.gz: 2478f3db2a6f42ca3ca83158f86d9313676b5263c07b324f69d786b587ed603ab5af6a2319d123e7524e4cab508530431a161c18ed3e5e1deea50cd581f90a4f
7
+ data.tar.gz: 7c874c56a5057bbb5110b683ae3cdfbfd84c4471f5f40cc96e2f53fb1ca32843df64f8f5dde7ba3614274cad19e3fa33e5e563c217b9d46394f4e5dbbbdb9c16
data/README.de.md CHANGED
@@ -732,6 +732,86 @@ Die Suche bietet:
732
732
  - Kein zusätzliches Such-Gem erforderlich
733
733
  - Sichere wiederholte Ausführung des Generators
734
734
 
735
+ ## Sortable
736
+
737
+ Füge einer bestehenden Ressource eine dauerhaft gespeicherte Sortierreihenfolge hinzu:
738
+
739
+ ```bash
740
+ bin/rails generate b4um:sortable Product
741
+ bin/rails db:migrate
742
+ ```
743
+
744
+ Der Generator fügt eine `position`-Spalte hinzu und weist bestehenden Datensätzen beim Ausführen der generierten
745
+ Migration automatisch Positionen zu.
746
+
747
+ Neue Datensätze werden automatisch am Ende der Liste eingefügt.
748
+
749
+ Standardmäßig wird die bestehende Index-Seite zur sortierbaren Seite:
750
+
751
+ ```text
752
+ /products
753
+ ```
754
+
755
+ Das bestehende Index-Layout wird auf das b4um-Sortable-Layout umgestellt. Die gespeicherte Position wird außerdem als
756
+ Standardsortierung der Ressource verwendet.
757
+
758
+ Sortable bietet:
759
+
760
+ - Dauerhaft in der Datenbank gespeicherte ganzzahlige Positionen
761
+ - Automatische Positionierung bestehender Datensätze während der Migration
762
+ - Automatische Positionierung neuer Datensätze am Ende
763
+ - Drag-and-Drop-Sortierung mit Stimulus
764
+ - Direkte Änderung der Position über ein Zahlenfeld
765
+ - Automatisches Verschieben der betroffenen Datensätze
766
+ - Lückenlose Positionen nach dem Verschieben oder Löschen von Datensätzen
767
+ - Serverseitige Sortierung innerhalb einer Datenbanktransaktion
768
+ - CSRF-geschützte Update-Anfragen
769
+ - Integration mit b4um Search und Pagination
770
+ - Kein zusätzliches Sortier-Gem erforderlich
771
+ - Sichere wiederholte Ausführung des Generators
772
+
773
+ ### Separate Sortierseite
774
+
775
+ Mit `--path` kann eine separate Sortierseite erstellt werden:
776
+
777
+ ```bash
778
+ bin/rails generate b4um:sortable Product --path=sortierung
779
+ bin/rails db:migrate
780
+ ```
781
+
782
+ Dadurch wird die sortierbare Seite genau unter folgender URL erstellt:
783
+
784
+ ```text
785
+ /sortierung
786
+ ```
787
+
788
+ während der normale Ressourcen-Index weiterhin unter
789
+
790
+ ```text
791
+ /products
792
+ ```
793
+
794
+ verfügbar bleibt.
795
+
796
+ Das bestehende Index-Layout, beispielsweise Bento oder Table, bleibt unverändert. Es verwendet weiterhin die
797
+ gespeicherte `position`-Sortierung.
798
+
799
+ Die separate Sortierseite verwendet den bestehenden Controller der Ressource. Es wird kein zusätzlicher Controller und
800
+ kein Namespace erzeugt.
801
+
802
+ Der mit `--path` angegebene Wert bestimmt direkt die URL der separaten Sortierseite. Zum Beispiel:
803
+
804
+ ```bash
805
+ bin/rails generate b4um:sortable Product --path=produktuebersicht
806
+ bin/rails generate b4um:sortable Product --path=verwaltung
807
+ bin/rails generate b4um:sortable Product --path=admin
808
+ ```
809
+
810
+ Dadurch entstehen entsprechend `/produktuebersicht`, `/verwaltung` und `/admin`.
811
+
812
+ Eine separate Sortierseite ermöglicht außerdem, nur die `sort`-Action durch die Authentifizierung zu schützen, während
813
+ der normale Ressourcen-Index öffentlich bleiben kann.
814
+
735
815
  ## Kommentare
736
816
 
737
817
  Füge einem bestehenden Modell polymorphe Kommentare hinzu:
@@ -1135,7 +1215,7 @@ wird.
1135
1215
 
1136
1216
  ## Version
1137
1217
 
1138
- Aktuelle Version: `0.2.16`
1218
+ Aktuelle Version: `0.3.0`
1139
1219
 
1140
1220
  ## Autor
1141
1221
 
data/README.md CHANGED
@@ -725,6 +725,81 @@ Search provides:
725
725
  - No additional search gem
726
726
  - Safe repeated generator runs
727
727
 
728
+ ## Sortable
729
+
730
+ Add persistent sortable positioning to an existing resource:
731
+
732
+ ```bash
733
+ bin/rails generate b4um:sortable Product
734
+ bin/rails db:migrate
735
+ ```
736
+
737
+ The generator adds a `position` column and assigns positions to existing records when the generated migration is run.
738
+
739
+ New records are automatically added at the end of the list.
740
+
741
+ By default, the existing resource index becomes the sortable page:
742
+
743
+ ```text
744
+ /products
745
+ ```
746
+
747
+ The existing index layout is switched to the b4um sortable layout. The stored position is also used as the default
748
+ ordering for the resource.
749
+
750
+ Sortable provides:
751
+
752
+ - Persistent integer positions stored in the database
753
+ - Automatic positioning of existing records during migration
754
+ - Automatic positioning of new records at the end
755
+ - Drag-and-drop sorting with Stimulus
756
+ - Direct position changes using a number field
757
+ - Automatic shifting of affected records
758
+ - Contiguous positions after moving or deleting records
759
+ - Server-side sorting inside a database transaction
760
+ - CSRF-protected update requests
761
+ - Integration with b4um Search and Pagination
762
+ - No additional sorting gem
763
+ - Safe repeated generator runs
764
+
765
+ ### Separate Sortable Page
766
+
767
+ A separate sortable page can be created with `--path`:
768
+
769
+ ```bash
770
+ bin/rails generate b4um:sortable Product --path=sortierung
771
+ bin/rails db:migrate
772
+ ```
773
+
774
+ This creates the sortable page at exactly:
775
+
776
+ ```text
777
+ /sortierung
778
+ ```
779
+
780
+ while the regular resource index remains available at:
781
+
782
+ ```text
783
+ /products
784
+ ```
785
+
786
+ The existing index layout, such as Bento or Table, remains unchanged. It still uses the stored `position` ordering.
787
+
788
+ The separate sortable page uses the existing resource controller. No additional controller or namespace is generated.
789
+
790
+ The value passed to `--path` directly defines the URL of the separate sortable page. For example:
791
+
792
+ ```bash
793
+ bin/rails generate b4um:sortable Product --path=produktuebersicht
794
+ bin/rails generate b4um:sortable Product --path=verwaltung
795
+ bin/rails generate b4um:sortable Product --path=admin
796
+ ```
797
+
798
+ These create `/produktuebersicht`, `/verwaltung`, and `/admin` respectively.
799
+
800
+ Using a separate sortable page also makes it possible to protect only the `sort` action with authentication while
801
+ keeping the regular resource index public.
802
+
728
803
  ## Comments
729
804
 
730
805
  Add polymorphic comments to an existing model:
@@ -1124,7 +1199,7 @@ The gem can be tested locally by referencing the repository with `path:` from a
1124
1199
 
1125
1200
  ## Version
1126
1201
 
1127
- Current version: `0.2.16`
1202
+ Current version: `0.3.0`
1128
1203
 
1129
1204
  ## Author
1130
1205
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module B4umGenerators
4
- VERSION = "0.2.16"
4
+ VERSION = "0.3.0"
5
5
  end
@@ -943,6 +943,72 @@ module B4um
943
943
  - No additional search gem required
944
944
  - Safe repeated generator runs
945
945
 
946
+ SORTABLE
947
+
948
+ bin/rails generate b4um:sortable MODEL
949
+
950
+ Adds persistent sortable positioning to an existing resource.
951
+
952
+ Example:
953
+
954
+ bin/rails generate b4um:sortable Product
955
+ bin/rails db:migrate
956
+
957
+ Existing records receive positions automatically when the
958
+ generated migration is run.
959
+
960
+ New records are automatically added at the end of the list.
961
+
962
+ By default, the existing index page becomes the sortable page:
963
+
964
+ /products
965
+
966
+ The existing index layout is switched to the b4um sortable
967
+ layout. The stored position is also used as the default
968
+ ordering for the resource.
969
+
970
+ A separate sortable page can be created with --path:
971
+
972
+ bin/rails generate b4um:sortable Product --path=sortierung
973
+ bin/rails db:migrate
974
+
975
+ This creates the sortable page at exactly:
976
+
977
+ /sortierung
978
+
979
+ The existing resource index remains unchanged:
980
+
981
+ /products
982
+
983
+ The separate page uses the existing resource controller.
984
+ No additional controller or namespace is generated.
985
+
986
+ Other examples:
987
+
988
+ --path=produktuebersicht
989
+ --path=verwaltung
990
+ --path=admin
991
+
992
+ The value passed to --path defines the URL of the separate
993
+ sortable page.
994
+
995
+ Sortable features:
996
+
997
+ - Persistent integer positions stored in the database
998
+ - Existing records receive positions during migration
999
+ - New records are automatically placed at the end
1000
+ - Drag-and-drop sorting with Stimulus
1001
+ - Positions can also be changed directly with a number field
1002
+ - Moving a record automatically shifts the affected records
1003
+ - Positions remain contiguous after moving or deleting records
1004
+ - Server-side sorting inside a database transaction
1005
+ - CSRF-protected update requests
1006
+ - No additional sorting gem required
1007
+ - Works with b4um Search and Pagination
1008
+ - Stored ordering is reflected by the regular resource index
1009
+ - Optional separate sortable page with --path
1010
+ - Safe repeated generator runs
1011
+
946
1012
 
947
1013
  COMMENTS
948
1014
 
@@ -1149,6 +1149,7 @@ module B4um
1149
1149
  )
1150
1150
 
1151
1151
  @field = field
1152
+ @field_type = field_type(field)
1152
1153
 
1153
1154
  template(
1154
1155
  template_name,
@@ -1156,6 +1157,7 @@ module B4um
1156
1157
  )
1157
1158
  ensure
1158
1159
  @field = nil
1160
+ @field_type = nil
1159
1161
  end
1160
1162
 
1161
1163
  def display_template_for(field)
@@ -11,8 +11,9 @@
11
11
  >
12
12
  <strong class="resource-label"><%= @field.humanize %>:</strong>
13
13
 
14
- <span class="resource-value">
15
- <%%= <%= singular_table_name %>.<%= @field %>.presence || "—" %>
14
+ <span class="resource-value"><% if @field_type == :decimal %>
15
+ <%%= <%= singular_table_name %>.<%= @field %>.present? ? number_with_precision(<%= singular_table_name %>.<%= @field %>, precision: 2, delimiter: ".", separator: ",") : "—" %><% else %>
16
+ <%%= <%= singular_table_name %>.<%= @field %>.presence || "—" %><% end %>
16
17
  </span>
17
18
  </div>
18
19
  <%% end %>
@@ -0,0 +1,158 @@
1
+ /* -------------------------------------------------------
2
+ Sortable
3
+ ------------------------------------------------------- */
4
+
5
+ .b4um-sortable {
6
+ display: grid;
7
+ gap: 0.75rem;
8
+ }
9
+
10
+ .b4um-sortable__item {
11
+ display: grid;
12
+ grid-template-columns: auto minmax(0, 1fr) auto;
13
+ align-items: center;
14
+ gap: 1.25rem;
15
+
16
+ padding: 1rem 1.25rem;
17
+
18
+ background: var(--b4um-surface);
19
+ border: 1px solid var(--b4um-border-light);
20
+ border-radius: var(--b4um-radius);
21
+
22
+ transition:
23
+ border-color 0.2s ease,
24
+ box-shadow 0.2s ease,
25
+ opacity 0.2s ease;
26
+ }
27
+
28
+ .b4um-sortable__item:hover {
29
+ border-color: var(--b4um-border-hover);
30
+ }
31
+
32
+ .b4um-sortable__item--dragging {
33
+ opacity: 0.5;
34
+ box-shadow: 0 8px 24px var(--b4um-primary-shadow-soft);
35
+ }
36
+
37
+ /* -------------------------------------------------------
38
+ Controls
39
+ ------------------------------------------------------- */
40
+
41
+ .b4um-sortable__controls {
42
+ display: flex;
43
+ align-items: center;
44
+ gap: 0.5rem;
45
+ }
46
+
47
+ .b4um-sortable__position {
48
+ box-sizing: border-box;
49
+
50
+ width: 4rem;
51
+ min-width: 4rem;
52
+ height: 2.75rem;
53
+
54
+ margin: 0;
55
+ padding: 0.5rem;
56
+
57
+ background: var(--b4um-surface);
58
+ color: var(--b4um-text);
59
+
60
+ border: 1px solid var(--b4um-border);
61
+ border-radius: 10px;
62
+
63
+ font: inherit;
64
+ text-align: center;
65
+ }
66
+
67
+ .b4um-sortable__position:hover {
68
+ border-color: var(--b4um-border-hover);
69
+ }
70
+
71
+ .b4um-sortable__position:focus {
72
+ border-color: var(--b4um-primary);
73
+ outline: 3px solid var(--b4um-primary-focus);
74
+ outline-offset: 1px;
75
+ }
76
+
77
+ /* -------------------------------------------------------
78
+ Drag handle
79
+ ------------------------------------------------------- */
80
+
81
+ .b4um-sortable__handle {
82
+ display: inline-flex;
83
+ align-items: center;
84
+ justify-content: center;
85
+
86
+ width: 2.75rem;
87
+ height: 2.75rem;
88
+
89
+ margin: 0;
90
+ padding: 0;
91
+
92
+ background: transparent;
93
+ color: var(--b4um-text-secondary);
94
+
95
+ border: 0;
96
+ border-radius: 10px;
97
+
98
+ font: inherit;
99
+ font-size: 1.2rem;
100
+ line-height: 1;
101
+
102
+ cursor: grab;
103
+
104
+ transition:
105
+ background 0.2s ease,
106
+ color 0.2s ease;
107
+ }
108
+
109
+ .b4um-sortable__handle:hover {
110
+ background: var(--b4um-primary-soft);
111
+ color: var(--b4um-primary);
112
+ }
113
+
114
+ .b4um-sortable__handle:active {
115
+ cursor: grabbing;
116
+ }
117
+
118
+ /* -------------------------------------------------------
119
+ Content
120
+ ------------------------------------------------------- */
121
+
122
+ .b4um-sortable__content {
123
+ min-width: 0;
124
+ }
125
+
126
+ .b4um-sortable__content .resource {
127
+ margin: 0;
128
+ }
129
+
130
+ /* -------------------------------------------------------
131
+ Actions
132
+ ------------------------------------------------------- */
133
+
134
+ .b4um-sortable__actions {
135
+ display: flex;
136
+ align-items: center;
137
+ justify-content: flex-end;
138
+ }
139
+
140
+ .b4um-sortable__actions .button {
141
+ margin: 0;
142
+ white-space: nowrap;
143
+ }
144
+
145
+ /* -------------------------------------------------------
146
+ Responsive
147
+ ------------------------------------------------------- */
148
+
149
+ @media (max-width: 48rem) {
150
+ .b4um-sortable__item {
151
+ grid-template-columns: 1fr;
152
+ gap: 0.75rem;
153
+ }
154
+
155
+ .b4um-sortable__actions {
156
+ justify-content: flex-start;
157
+ }
158
+ }
@@ -22,6 +22,7 @@
22
22
  @import 'b4um/hero.css';
23
23
  @import 'b4um/cards.css';
24
24
  @import 'b4um/tables.css';
25
+ @import 'b4um/sortable.css';
25
26
  @import 'b4um/pagination.css';
26
27
  @import 'b4um/badges.css';
27
28
  @import 'b4um/callouts.css';
@@ -108,20 +108,33 @@ module B4um
108
108
  /
109
109
  @#{Regexp.escape(plural_table_name)}\s*=\s*
110
110
  b4um_search\(\s*
111
- #{Regexp.escape(class_name)}\.all,\s*
111
+ (?<collection>
112
+ #{Regexp.escape(class_name)}
113
+ \.(?:all|order\(\s*:position\s*,\s*:id\s*\))
114
+ )\s*,\s*
112
115
  params\[:q\]\s*
113
116
  \)
114
117
  /x
115
118
  end
116
119
 
117
120
  def paginate_existing_search
121
+ controller_content = File.read(controller_path)
122
+ match = controller_content.match(search_pattern)
123
+
124
+ unless match
125
+ raise Thor::Error,
126
+ "Could not find the search collection in app/controllers/#{plural_table_name}_controller.rb"
127
+ end
128
+
129
+ collection = match[:collection]
130
+
118
131
  gsub_file(
119
132
  controller_path,
120
133
  search_pattern,
121
134
  [
122
135
  "@#{plural_table_name}, @pagination = b4um_paginate(",
123
136
  " b4um_search(",
124
- " #{class_name}.all,",
137
+ " #{collection},",
125
138
  " params[:q]",
126
139
  " ),",
127
140
  " per_page: #{options[:per_page]}",
@@ -131,19 +144,33 @@ module B4um
131
144
  end
132
145
 
133
146
  def paginate_plain_collection(controller_content)
134
- old_index = "@#{plural_table_name} = #{class_name}.all"
147
+ plain_collection = "#{class_name}.all"
148
+ sortable_collection = "#{class_name}.order(:position, :id)"
135
149
 
136
- unless controller_content.include?(old_index)
150
+ collection =
151
+ if controller_content.include?(
152
+ "@#{plural_table_name} = #{sortable_collection}"
153
+ )
154
+ sortable_collection
155
+ elsif controller_content.include?(
156
+ "@#{plural_table_name} = #{plain_collection}"
157
+ )
158
+ plain_collection
159
+ end
160
+
161
+ unless collection
137
162
  raise Thor::Error,
138
163
  "Could not find the index collection in app/controllers/#{plural_table_name}_controller.rb"
139
164
  end
140
165
 
166
+ old_index = "@#{plural_table_name} = #{collection}"
167
+
141
168
  gsub_file(
142
169
  controller_path,
143
170
  old_index,
144
171
  [
145
172
  "@#{plural_table_name}, @pagination = b4um_paginate(",
146
- " #{class_name}.all,",
173
+ " #{collection},",
147
174
  " per_page: #{options[:per_page]}",
148
175
  " )"
149
176
  ].join("\n")
@@ -92,19 +92,33 @@ module B4um
92
92
  return
93
93
  end
94
94
 
95
- old_index = "@#{file_name.pluralize} = #{class_name}.all"
95
+ plain_collection = "#{class_name}.all"
96
+ sortable_collection = "#{class_name}.order(:position, :id)"
96
97
 
97
- unless controller_content.include?(old_index)
98
+ collection =
99
+ if controller_content.include?(
100
+ "@#{file_name.pluralize} = #{sortable_collection}"
101
+ )
102
+ sortable_collection
103
+ elsif controller_content.include?(
104
+ "@#{file_name.pluralize} = #{plain_collection}"
105
+ )
106
+ plain_collection
107
+ end
108
+
109
+ unless collection
98
110
  raise Thor::Error,
99
111
  "Could not find the index collection in #{controller_path}"
100
112
  end
101
113
 
102
- search_code = <<~RUBY.chomp
103
- @#{file_name.pluralize} = b4um_search(
104
- #{class_name}.all,
105
- params[:q]
106
- )
107
- RUBY
114
+ old_index = "@#{file_name.pluralize} = #{collection}"
115
+
116
+ search_code = [
117
+ "@#{file_name.pluralize} = b4um_search(",
118
+ " #{collection},",
119
+ " params[:q]",
120
+ " )"
121
+ ].join("\n")
108
122
 
109
123
  gsub_file(
110
124
  controller_path,