backstage 0.1.13 → 0.1.15

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: 9de1bd6823d300ceef1f45b44ff97b635b6497ed1e58df6d814ec5ce45efc56e
4
- data.tar.gz: d2863d1096ece91857ab403a4fa874f015f4065b5d9922553eaccc09d2245061
3
+ metadata.gz: da02200425a97de115bbd7a4ff85a2cc907ab221647c3c9d22bb105ce690ff99
4
+ data.tar.gz: 4f53000b38b952e825cb5e82211162a7ce2149834e4efc671cf560595226593b
5
5
  SHA512:
6
- metadata.gz: f2799b494c57a749cc0c29595121fda2a90bf5c99704138bf70597fe14b1480c96c49984528b29f95fee0b1714ce0376e351be31057ea67b0fd6fb261675cb5d
7
- data.tar.gz: d95bbf7d40f238529b320ed0194f8c4fd6ecfc7799630e1274145423d869f1e36aeeb581a9abeb8a17761bc85464ca4618f705ad1e776ddf91f3cdb9bb1dcbef
6
+ metadata.gz: 83f557e7644fa1e07487d3995854645f71454fee792023e9a344f24639fe222cef1d9c8a6bb130e8673dac4f7c4a942df5bb52a7ce06b0628c0a74461e3b1a59
7
+ data.tar.gz: dfd0f21995ca2a17e92016e86e6c975633b35b54c40a198e9edd8964116080a574055bd8480aceb589d5391739d87ee51cbdb65fcc600c636b3b8e1d4c31894c
data/CHANGELOG.md CHANGED
@@ -7,6 +7,25 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.1.15] — 2026-05-27
11
+
12
+ ### Added
13
+
14
+ - `c.nested :assoc, fields: [...]` DSL renders existing `has_many` records as an inline editable table using `accepts_nested_attributes_for`
15
+ - Nested rows include a destroy button (×) and a `_destroy` hidden field; clicking hides the row and marks it for deletion on save
16
+ - A blank add row is always rendered at the bottom of the nested table for creating new records
17
+ - `image_url` fields on index and dashboard tables now render as `<img>` tags instead of raw URLs
18
+ - `belongs_to` fields now render correctly on dashboard tables (was missing)
19
+ - Shared `backstage/shared/_cell` partial extracted for consistent field rendering across index and dashboard views
20
+
21
+ ## [0.1.14] — 2026-05-26
22
+
23
+ ### Fixed
24
+
25
+ - `ActionsController` no longer falls back to `Backstage::ResourcesController` when no custom controller exists — raises `NotImplementedError` instead of silently dispatching inherited CRUD actions
26
+ - Custom action dispatch now uses `method_defined?(name, false)` so only actions defined directly on the host subclass are callable; inherited methods (`index`, `create`, `update`, `destroy`, etc.) are blocked
27
+ - Gemspec `homepage` corrected to point to the right repository
28
+
10
29
  ## [0.1.13] — 2026-05-26
11
30
 
12
31
  ### Added
@@ -8,14 +8,19 @@ module Backstage
8
8
  end
9
9
 
10
10
  resource_name = params[:resource].classify.pluralize
11
- controller_class = "Backstage::#{resource_name}Controller".safe_constantize ||
12
- Backstage::ResourcesController
11
+ controller_class = "Backstage::#{resource_name}Controller".safe_constantize
13
12
  action_name = params[:action_name]
14
13
 
15
- unless controller_class.method_defined?(action_name)
14
+ if controller_class.nil? || controller_class == Backstage::ResourcesController
15
+ raise NotImplementedError,
16
+ "Backstage: no custom controller found for #{resource_name}. " \
17
+ "Define Backstage::#{resource_name}Controller."
18
+ end
19
+
20
+ unless controller_class.method_defined?(action_name.to_sym, false)
16
21
  raise NotImplementedError,
17
22
  "Backstage: no action '#{action_name}' on #{controller_class}. " \
18
- "Define it in a Backstage::#{resource_name}Controller subclass."
23
+ "Define it in Backstage::#{resource_name}Controller."
19
24
  end
20
25
 
21
26
  status, headers, body = controller_class.action(action_name).call(request.env)
@@ -100,7 +100,7 @@ module Backstage
100
100
  [{field.name => []}]
101
101
  elsif field.nested?
102
102
  writable = field.nested_fields - field.nested_readonly_fields
103
- [{"#{field.name}_attributes": [:id, *writable]}]
103
+ [{"#{field.name}_attributes": [:id, :_destroy, *writable]}]
104
104
  else
105
105
  [field.name]
106
106
  end
@@ -13,13 +13,7 @@
13
13
  <% @records.each do |record| %>
14
14
  <tr id="<%= @resource_config.model_name_param %>_<%= record.id %>_row">
15
15
  <% @resource_config.index_fields.each do |field| %>
16
- <td>
17
- <% if field.enum? %>
18
- <%= record.public_send(field.name).to_s.humanize %>
19
- <% else %>
20
- <%= record.public_send(field.name) %>
21
- <% end %>
22
- </td>
16
+ <td><%= render "backstage/shared/cell", field: field, record: record %></td>
23
17
  <% end %>
24
18
  <td><%= link_to "Edit", edit_resource_path(resource: @resource_config.model_name_param, id: record.id) %></td>
25
19
  </tr>
@@ -1,30 +1,39 @@
1
1
  <% records = record.public_send(field.name) %>
2
- <% if records.any? %>
3
- <table>
4
- <thead>
2
+ <table>
3
+ <thead>
4
+ <tr>
5
+ <% field.nested_fields.each do |col| %>
6
+ <th><%= col.to_s.humanize %></th>
7
+ <% end %>
8
+ <th></th>
9
+ </tr>
10
+ </thead>
11
+ <tbody>
12
+ <%= f.fields_for field.name do |nf| %>
5
13
  <tr>
14
+ <%= nf.hidden_field :id %>
15
+ <%= nf.hidden_field :_destroy, value: "0", data: {nested_destroy_flag: true} %>
6
16
  <% field.nested_fields.each do |col| %>
7
- <th><%= col.to_s.humanize %></th>
17
+ <td>
18
+ <% if field.nested_readonly_fields.include?(col) %>
19
+ <%= nf.object.public_send(col) %>
20
+ <% else %>
21
+ <%= nf.text_field col %>
22
+ <% end %>
23
+ </td>
8
24
  <% end %>
25
+ <td>
26
+ <button type="button" data-nested-destroy aria-label="Remove">&times;</button>
27
+ </td>
9
28
  </tr>
10
- </thead>
11
- <tbody>
12
- <%= f.fields_for field.name do |nf| %>
13
- <%= nf.hidden_field :id %>
14
- <tr>
15
- <% field.nested_fields.each do |col| %>
16
- <td>
17
- <% if field.nested_readonly_fields.include?(col) %>
18
- <%= nf.object.public_send(col) %>
19
- <% else %>
20
- <%= nf.text_field col %>
21
- <% end %>
22
- </td>
23
- <% end %>
24
- </tr>
25
- <% end %>
26
- </tbody>
27
- </table>
28
- <% else %>
29
- <em>None</em>
30
- <% end %>
29
+ <% end %>
30
+ <%= f.fields_for field.name, records.klass.new, child_index: records.size do |nf| %>
31
+ <tr data-nested-new-row>
32
+ <% field.nested_fields.reject { |col| field.nested_readonly_fields.include?(col) }.each do |col| %>
33
+ <td><%= nf.text_field col %></td>
34
+ <% end %>
35
+ <td></td>
36
+ </tr>
37
+ <% end %>
38
+ </tbody>
39
+ </table>
@@ -32,20 +32,7 @@
32
32
  <% @records.each do |record| %>
33
33
  <tr>
34
34
  <% @resource_config.index_fields.each do |field| %>
35
- <td>
36
- <% if field.enum? %>
37
- <%= record.public_send(field.name).to_s.humanize %>
38
- <% elsif field.belongs_to? %>
39
- <% related = record.public_send(field.association.name)
40
- col = field.association.display_column
41
- label = related&.public_send(col) %>
42
- <% if related %>
43
- <%= link_to label, edit_resource_path(resource: related.class.model_name.plural, id: related.id) %>
44
- <% end %>
45
- <% else %>
46
- <%= record.public_send(field.name) %>
47
- <% end %>
48
- </td>
35
+ <td><%= render "backstage/shared/cell", field: field, record: record %></td>
49
36
  <% end %>
50
37
  <td><%= link_to "Edit", edit_resource_path(resource: params[:resource], id: record.id) %></td>
51
38
  </tr>
@@ -0,0 +1,17 @@
1
+ <% if field.image_url? %>
2
+ <% url = record.public_send(field.name) %>
3
+ <% if url.present? %>
4
+ <%= image_tag url, style: "max-height:60px;" %>
5
+ <% end %>
6
+ <% elsif field.enum? %>
7
+ <%= record.public_send(field.name).to_s.humanize %>
8
+ <% elsif field.belongs_to? %>
9
+ <% related = record.public_send(field.association.name)
10
+ col = field.association.display_column
11
+ label = related&.public_send(col) %>
12
+ <% if related %>
13
+ <%= link_to label, edit_resource_path(resource: related.class.model_name.plural, id: related.id) %>
14
+ <% end %>
15
+ <% else %>
16
+ <%= record.public_send(field.name) %>
17
+ <% end %>
@@ -26,6 +26,13 @@
26
26
  })
27
27
  })
28
28
  })
29
+ document.querySelectorAll("[data-nested-destroy]").forEach(function(btn) {
30
+ btn.addEventListener("click", function() {
31
+ var row = btn.closest("tr")
32
+ row.querySelector("[data-nested-destroy-flag]").value = "1"
33
+ row.hidden = true
34
+ })
35
+ })
29
36
  })
30
37
  </script>
31
38
  </head>
@@ -36,6 +36,10 @@ module Backstage
36
36
  type == :nested
37
37
  end
38
38
 
39
+ def image_url?
40
+ type == :image_url
41
+ end
42
+
39
43
  def nested_fields
40
44
  options[:nested_fields] || []
41
45
  end
@@ -1,3 +1,3 @@
1
1
  module Backstage
2
- VERSION = "0.1.13"
2
+ VERSION = "0.1.15"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backstage
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.13
4
+ version: 0.1.15
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gareth James
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-26 00:00:00.000000000 Z
11
+ date: 2026-05-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -26,7 +26,6 @@ dependencies:
26
26
  version: '8.0'
27
27
  description:
28
28
  email:
29
- - g.claude@bemused.org
30
29
  executables: []
31
30
  extensions: []
32
31
  extra_rdoc_files: []
@@ -62,6 +61,7 @@ files:
62
61
  - app/views/backstage/resources/edit.html.erb
63
62
  - app/views/backstage/resources/index.html.erb
64
63
  - app/views/backstage/resources/new.html.erb
64
+ - app/views/backstage/shared/_cell.html.erb
65
65
  - app/views/backstage/shared/_pagination.html.erb
66
66
  - app/views/layouts/backstage/backstage.html.erb
67
67
  - config/routes.rb
@@ -80,7 +80,7 @@ files:
80
80
  - lib/generators/backstage/install/templates/SKILL.md
81
81
  - lib/generators/backstage/install/templates/backstage.rb
82
82
  - lib/generators/backstage/install/templates/backstage.yml
83
- homepage: https://github.com/gjtorikian/backstage
83
+ homepage: https://github.com/garethfr/backstage
84
84
  licenses:
85
85
  - MIT
86
86
  metadata: {}