trek 2.1.0 → 2.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 +4 -4
- data/.slim-lint.yml +0 -3
- data/AGENTS.md +8 -1
- data/CHANGELOG.md +75 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +20 -3
- data/Makefile +11 -9
- data/README.md +1 -1
- data/app/assets/icons/trek/audit.svg +1 -0
- data/app/assets/icons/trek/chevron-down.svg +1 -0
- data/app/assets/icons/trek/chevron-up.svg +1 -0
- data/app/assets/icons/trek/external.svg +1 -0
- data/app/assets/icons/trek/print.svg +1 -0
- data/app/assets/icons/trek/theme-auto.svg +1 -0
- data/app/assets/icons/trek/theme-dark.svg +1 -0
- data/app/assets/icons/trek/theme-light.svg +1 -0
- data/app/assets/stylesheets/trek/base.css +13 -0
- data/app/components/trek/button_component/button_component.css +9 -0
- data/app/components/trek/button_component.rb +13 -1
- data/app/components/trek/dashboard/analytics_component/analytics_component.html.slim +2 -0
- data/app/components/trek/dashboard/analytics_component.rb +1 -0
- data/app/components/trek/dashboard/chart_component/chart_component.css +192 -0
- data/app/components/trek/dashboard/chart_component/chart_component.html.slim +6 -0
- data/app/components/trek/dashboard/chart_component/chart_component.js +1 -0
- data/app/components/trek/dashboard/chart_component.rb +27 -0
- data/app/components/trek/dashboard/tile_component/tile_component.css +40 -0
- data/app/components/trek/dashboard/tile_component/tile_component.html.slim +14 -4
- data/app/components/trek/dashboard/tile_component.rb +20 -2
- data/app/components/trek/dialog_component/dialog_component.html.slim +6 -2
- data/app/components/trek/dialog_component/dialog_component.js +62 -2
- data/app/components/trek/dropdown_component/dropdown_component.css +66 -0
- data/app/components/trek/dropdown_component/dropdown_component.html.slim +31 -0
- data/app/components/trek/dropdown_component/dropdown_component.js +114 -0
- data/app/components/trek/dropdown_component.rb +29 -0
- data/app/components/trek/form/actions_component/actions_component.css +6 -7
- data/app/components/trek/form/content_editor_component/content_editor_component.html.slim +10 -12
- data/app/components/trek/form/content_editor_component/content_editor_component.js +4 -0
- data/app/components/trek/form/group_component/group_component.css +34 -0
- data/app/components/trek/header_component/header_component.css +10 -4
- data/app/components/trek/header_component/header_component.html.slim +17 -10
- data/app/components/trek/header_component/header_component.js +29 -5
- data/app/components/trek/header_component.rb +20 -3
- data/app/components/trek/layout_component/layout_component.css +20 -0
- data/app/components/trek/layout_component/layout_component.html.slim +16 -0
- data/app/components/trek/layout_component/layout_component.js +35 -6
- data/app/components/trek/menu_component/menu_component.css +6 -0
- data/app/components/trek/menu_component.rb +29 -4
- data/app/components/trek/toaster/toast_component.rb +1 -1
- data/app/controllers/concerns/trek/authorization.rb +12 -0
- data/app/formatters/prosemirror_to_html/global_id_to_links_formatter.rb +3 -1
- data/app/helpers/trek/page_path_helper.rb +4 -3
- data/app/helpers/trek/theme_helper.rb +16 -1
- data/app/models/concerns/trek/phonable.rb +15 -3
- data/app/models/concerns/trek/sectionable.rb +5 -0
- data/app/models/concerns/trek/users/validations.rb +1 -1
- data/app/models/prosemirror.rb +5 -2
- data/app/uploaders/audio_uploader.rb +11 -0
- data/app/uploaders/record_image_uploader.rb +3 -0
- data/app/views/trek/panels/images/_form.html.slim +1 -1
- data/app/views/trek/panels/images/create.turbo_stream.slim +3 -3
- data/app/views/trek/panels/images/new.html.slim +1 -1
- data/app/views/trek/panels/links/_form.html.slim +1 -1
- data/app/views/trek/panels/links/create.turbo_stream.slim +3 -3
- data/app/views/trek/panels/links/new.html.slim +1 -1
- data/app/views/trek/panels/prompts/_form.html.slim +1 -1
- data/app/views/trek/panels/prompts/create.turbo_stream.slim +3 -3
- data/app/views/trek/panels/prompts/new.html.slim +1 -1
- data/config/locales/trek.en.yml +3 -2
- data/config/locales/trek.fr.yml +1 -0
- data/docs/customize.md +18 -2
- data/docs/deploy.md +7 -2
- data/docs/getting-started.md +1 -1
- data/docs/index.md +1 -1
- data/docs/reference/components.md +15 -0
- data/docs/reference/concerns/phonable.md +27 -1
- data/docs/reference/environment-variables.md +2 -0
- data/docs/reference/forms.md +2 -0
- data/docs/reference/generators/install.md +4 -1
- data/docs/reference/icons.md +1 -1
- data/docs/reference/models/tag.md +1 -1
- data/docs/reference/models/user.md +4 -2
- data/docs/reference/uploaders.md +31 -6
- data/docs/what-is-trek.md +1 -1
- data/lib/generators/trek/install/admin/routes_generator.rb +7 -1
- data/lib/generators/trek/install/appsignal_generator.rb +102 -0
- data/lib/generators/trek/install/model_specs_generator.rb +64 -0
- data/lib/generators/trek/install/postmark_generator.rb +3 -0
- data/lib/generators/trek/install/ruby_generator.rb +1 -1
- data/lib/generators/trek/install/scalingo_generator.rb +1 -1
- data/lib/generators/trek/install/uploaders_generator.rb +2 -0
- data/lib/generators/trek/install_generator.rb +5 -0
- data/lib/generators/trek/taxonomies_generator.rb +7 -0
- data/lib/generators/trek/templates/.gitlab-ci.yml +1 -1
- data/lib/generators/trek/templates/config/appsignal.rb.tt +25 -0
- data/lib/generators/trek/templates/controllers/admin/uploads_controller.rb +4 -0
- data/lib/generators/trek/templates/controllers/admin/users_controller.rb +4 -1
- data/lib/generators/trek/templates/controllers/concerns/appsignal_context.rb +37 -0
- data/lib/generators/trek/templates/db/seeds/setup/fragments.rb +3 -0
- data/lib/generators/trek/templates/db/seeds/setup/users.rb +6 -0
- data/lib/generators/trek/templates/db/seeds/test/cases/page_tree.rb +5 -0
- data/lib/generators/trek/templates/initializers/appsignal_context.rb +5 -0
- data/lib/generators/trek/templates/locales/scaffold/model.en.yml.tt +1 -1
- data/lib/generators/trek/templates/migrations/fragments.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/menu_nodes.rb.erb +0 -1
- data/lib/generators/trek/templates/migrations/tag_categories.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/taggings.rb.erb +1 -1
- data/lib/generators/trek/templates/migrations/users.rb.erb +2 -1
- data/lib/generators/trek/templates/models/tag.rb +6 -1
- data/lib/generators/trek/templates/policies/admin/tag_policy.rb +1 -1
- data/lib/generators/trek/templates/policies/external_link_policy.rb +1 -1
- data/lib/generators/trek/templates/spec/fixtures/files/image.png +0 -0
- data/lib/generators/trek/templates/spec/fixtures/files/sound.mp3 +0 -0
- data/lib/generators/trek/templates/spec/models/page_spec.rb +24 -0
- data/lib/generators/trek/templates/spec/models/user_spec.rb +76 -0
- data/lib/generators/trek/templates/spec/policies/admin/external_link_policy_spec.rb +17 -0
- data/lib/generators/trek/templates/spec/policies/admin/fragment_policy_spec.rb +33 -0
- data/lib/generators/trek/templates/spec/policies/admin/page_policy_spec.rb +39 -0
- data/lib/generators/trek/templates/spec/policies/admin/preview_policy_spec.rb +9 -0
- data/lib/generators/trek/templates/spec/policies/admin/tag_category_policy_spec.rb +31 -0
- data/lib/generators/trek/templates/spec/policies/admin/tag_policy_spec.rb +31 -0
- data/lib/generators/trek/templates/spec/policies/admin/user_policy_spec.rb +58 -0
- data/lib/generators/trek/templates/spec/requests/admin/fragments_spec.rb +50 -0
- data/lib/generators/trek/templates/spec/requests/admin/pages_spec.rb +47 -0
- data/lib/generators/trek/templates/spec/requests/admin/uploads_spec.rb +22 -0
- data/lib/generators/trek/templates/spec/requests/admin/user_password_resets_spec.rb +71 -0
- data/lib/generators/trek/templates/spec/requests/admin/user_sessions_spec.rb +45 -0
- data/lib/generators/trek/templates/spec/requests/admin/users_spec.rb +99 -0
- data/lib/generators/trek/templates/views/admin/user_password_resets/edit.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin/user_password_resets/new.html.slim +1 -1
- data/lib/generators/trek/templates/views/admin/user_sessions/new.html.slim +2 -2
- data/lib/generators/trek/templates/views/admin/users/theme.turbo_stream.slim +2 -0
- data/lib/generators/trek/upgrade/v2_generator.rb +3 -0
- data/lib/tasks/trek/attachments.rake +18 -0
- data/lib/trek/cached_attachments.rb +53 -0
- data/lib/trek/saved_attachments_reload.rb +44 -0
- data/lib/trek/version.rb +1 -1
- data/lib/trek.rb +3 -0
- data/package.json +1 -1
- data/postcss.config.js +36 -0
- data/trek.gemspec +5 -0
- metadata +76 -3
- data/app/assets/icons/trek/theme.svg +0 -11
data/app/models/prosemirror.rb
CHANGED
|
@@ -10,9 +10,12 @@ class Prosemirror
|
|
|
10
10
|
def format_content(parsed_content)
|
|
11
11
|
return if parsed_content.blank?
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
# GlobalIDs must be resolved to URLs before rendering: the renderer's
|
|
14
|
+
# sanitizer strips href attributes with unknown protocols, so a gid://
|
|
15
|
+
# link would lose its href entirely.
|
|
16
|
+
content = ProsemirrorToHtml::GlobalIdToLinksFormatter.new(parsed_content.to_json).to_s
|
|
17
|
+
content = renderer.render(JSON.parse(content))
|
|
14
18
|
content = ProsemirrorToHtml::RemoveEmptyParagraphsFormatter.new(content).to_s
|
|
15
|
-
content = ProsemirrorToHtml::GlobalIdToLinksFormatter.new(content).to_s
|
|
16
19
|
# rubocop:disable Rails/OutputSafety
|
|
17
20
|
content.html_safe
|
|
18
21
|
# rubocop:enable Rails/OutputSafety
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
class AudioUploader < Shrine
|
|
2
|
+
plugin :determine_mime_type
|
|
3
|
+
plugin :validation_helpers
|
|
4
|
+
# The admin's sound field accepts files up to 200 MB (`Trek::Form::SoundFieldComponent`), above
|
|
5
|
+
# the 20 MB the Shrine initializer allows images.
|
|
6
|
+
plugin :upload_endpoint, url: true, max_size: 200.megabytes
|
|
7
|
+
|
|
8
|
+
Attacher.validate do
|
|
9
|
+
validate_mime_type %w[audio/mpeg audio/mp4 audio/aac audio/ogg audio/wav audio/x-wav audio/webm audio/flac]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -1,3 +1,6 @@
|
|
|
1
1
|
class RecordImageUploader < ImageUploader
|
|
2
2
|
plugin :activerecord
|
|
3
|
+
# Keeps a file attached during a save that reloads the record (closure_tree reorders siblings
|
|
4
|
+
# that way) pending, so it is still promoted out of the cache storage on commit.
|
|
5
|
+
plugin Trek::SavedAttachmentsReload
|
|
3
6
|
end
|
|
@@ -24,7 +24,7 @@ h1.c-trek--layout-panel-title = t(".title")
|
|
|
24
24
|
= render Trek::Form::ActionsComponent.new do |c|
|
|
25
25
|
ruby:
|
|
26
26
|
c.with_button(
|
|
27
|
-
data: { action: "trek--layout#
|
|
27
|
+
data: { action: "trek--layout#closeStackedPanel" },
|
|
28
28
|
text: t("admin.actions.cancel"),
|
|
29
29
|
)
|
|
30
30
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
- if @object.valid?
|
|
2
|
-
= turbo_stream.append("trek--layout-panel")
|
|
3
|
-
div(data-trek--layout-target="
|
|
2
|
+
= turbo_stream.append("trek--layout-stacked-panel")
|
|
3
|
+
div(data-trek--layout-target="stackedPanelCloser")
|
|
4
4
|
|
|
5
5
|
= turbo_stream.append(@object.turbo_frame_id)
|
|
6
6
|
div(
|
|
@@ -15,5 +15,5 @@
|
|
|
15
15
|
text=@object.text
|
|
16
16
|
)
|
|
17
17
|
- else
|
|
18
|
-
= turbo_stream.update("trek--layout-panel")
|
|
18
|
+
= turbo_stream.update("trek--layout-stacked-panel")
|
|
19
19
|
= render "form"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
= turbo_frame_tag("trek--layout-panel")
|
|
1
|
+
= turbo_frame_tag("trek--layout-stacked-panel")
|
|
2
2
|
= render "form"
|
|
@@ -28,7 +28,7 @@ h1.c-trek--layout-panel-title = t(".title")
|
|
|
28
28
|
= render Trek::Form::ActionsComponent.new do |c|
|
|
29
29
|
ruby:
|
|
30
30
|
c.with_button(
|
|
31
|
-
data: { action: "trek--layout#
|
|
31
|
+
data: { action: "trek--layout#closeStackedPanel" },
|
|
32
32
|
text: t("admin.actions.cancel"),
|
|
33
33
|
)
|
|
34
34
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
- if @object.valid?
|
|
2
|
-
= turbo_stream.append("trek--layout-panel")
|
|
3
|
-
div(data-trek--layout-target="
|
|
2
|
+
= turbo_stream.append("trek--layout-stacked-panel")
|
|
3
|
+
div(data-trek--layout-target="stackedPanelCloser")
|
|
4
4
|
|
|
5
5
|
= turbo_stream.append(@object.turbo_frame_id)
|
|
6
6
|
div(
|
|
@@ -8,5 +8,5 @@
|
|
|
8
8
|
data-params=@object.to_json
|
|
9
9
|
)
|
|
10
10
|
- else
|
|
11
|
-
= turbo_stream.update("trek--layout-panel")
|
|
11
|
+
= turbo_stream.update("trek--layout-stacked-panel")
|
|
12
12
|
= render "form"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
= turbo_frame_tag("trek--layout-panel")
|
|
1
|
+
= turbo_frame_tag("trek--layout-stacked-panel")
|
|
2
2
|
= render "form"
|
|
@@ -26,7 +26,7 @@ h1.c-trek--layout-panel-title = t(".title")
|
|
|
26
26
|
= render Trek::Form::ActionsComponent.new do |c|
|
|
27
27
|
ruby:
|
|
28
28
|
c.with_button(
|
|
29
|
-
data: { action: "trek--layout#
|
|
29
|
+
data: { action: "trek--layout#closeStackedPanel" },
|
|
30
30
|
text: t("admin.actions.cancel"),
|
|
31
31
|
)
|
|
32
32
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
- if @object.valid?
|
|
2
|
-
= turbo_stream.append("trek--layout-panel")
|
|
3
|
-
div(data-trek--layout-target="
|
|
2
|
+
= turbo_stream.append("trek--layout-stacked-panel")
|
|
3
|
+
div(data-trek--layout-target="stackedPanelCloser")
|
|
4
4
|
|
|
5
5
|
= turbo_stream.append(@object.turbo_frame_id)
|
|
6
6
|
div(
|
|
@@ -14,5 +14,5 @@
|
|
|
14
14
|
title=@object.title
|
|
15
15
|
)
|
|
16
16
|
- else
|
|
17
|
-
= turbo_stream.update("trek--layout-panel")
|
|
17
|
+
= turbo_stream.update("trek--layout-stacked-panel")
|
|
18
18
|
= render "form"
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
= turbo_frame_tag("trek--layout-panel")
|
|
1
|
+
= turbo_frame_tag("trek--layout-stacked-panel")
|
|
2
2
|
= render "form"
|
data/config/locales/trek.en.yml
CHANGED
|
@@ -69,8 +69,8 @@ en:
|
|
|
69
69
|
self: the same tab
|
|
70
70
|
prompt:
|
|
71
71
|
targets:
|
|
72
|
-
blank:
|
|
73
|
-
self:
|
|
72
|
+
blank: a new tab
|
|
73
|
+
self: the same tab
|
|
74
74
|
contents: Contents
|
|
75
75
|
dashboard: Dashboard
|
|
76
76
|
dashboards:
|
|
@@ -264,3 +264,4 @@ en:
|
|
|
264
264
|
notices:
|
|
265
265
|
authentication_needed: Please log in
|
|
266
266
|
rate_limited: Too many attempts, please try again later
|
|
267
|
+
unauthorized: You are not allowed to do that
|
data/config/locales/trek.fr.yml
CHANGED
data/docs/customize.md
CHANGED
|
@@ -27,7 +27,21 @@ Replace Trek's favicon with your own:
|
|
|
27
27
|
Trek's UI components accept options where it matters:
|
|
28
28
|
|
|
29
29
|
- **`Trek::Form::ActionsComponent`** — pick an `aspect` and add your own `classnames` to restyle form action bars.
|
|
30
|
-
- **`Trek::DialogComponent`** — set the `title` and the buttons of modal dialogs.
|
|
30
|
+
- **`Trek::DialogComponent`** — set the `title` and the buttons of modal dialogs. The confirm dialog wired to `data-turbo-confirm` can also be customized per action from the confirming element's data attributes, with the delete wording as fallback:
|
|
31
|
+
|
|
32
|
+
```slim
|
|
33
|
+
= render Trek::ButtonComponent.new(
|
|
34
|
+
text: "Run the prize draw",
|
|
35
|
+
to: admin_event_draw_path(event), method: :post,
|
|
36
|
+
data: {
|
|
37
|
+
turbo_confirm: "Run the prize draw now? Winners will be picked and emails sent.",
|
|
38
|
+
turbo_confirm_title: "Prize draw",
|
|
39
|
+
turbo_confirm_button: "Run the draw",
|
|
40
|
+
turbo_confirm_color: "grass",
|
|
41
|
+
},
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
31
45
|
- **`Trek::Form::ContentEditorComponent`** — toggle the `nodes`, `blocks` and `floating` options to slim down the editor where full rich text isn't needed:
|
|
32
46
|
|
|
33
47
|
```ruby
|
|
@@ -62,9 +76,11 @@ Each scaffolded resource gets its own policy in `app/policies/admin`. Adjust the
|
|
|
62
76
|
|
|
63
77
|
Trek's admin supports light, dark and auto themes, selectable per user. The design tokens are based on [Radix colors](https://www.radix-ui.com/colors); override the CSS custom properties in your own stylesheet to adapt the palette.
|
|
64
78
|
|
|
79
|
+
The auto theme follows the system setting (`prefers-color-scheme`). The `autoDarkTheme` plugin in the generated `postcss.config.js` copies every rule targeting `.dark-theme` into a `@media (prefers-color-scheme: dark)` block, with `.auto-theme` in its place, so dark overrides in your own stylesheets (`.dark-theme { --accent-9: … }`, `.dark-theme .card { … }`) apply to the auto theme too.
|
|
80
|
+
|
|
65
81
|
## Dependencies configuration
|
|
66
82
|
|
|
67
|
-
The installer configures Shrine, Postmark,
|
|
83
|
+
The installer configures Shrine, Postmark, ActionPolicy, Mobility and friends with sensible defaults — through initializers generated **in your app**. You can edit them at any time:
|
|
68
84
|
|
|
69
85
|
- `config/initializers/shrine.rb` — storage backends, upload limits
|
|
70
86
|
- `config/credentials.yml.enc` — Postmark API token (`EDITOR="code --wait" bin/rails credentials:edit`)
|
data/docs/deploy.md
CHANGED
|
@@ -20,14 +20,19 @@ Generate the Scalingo files (an `Aptfile` and a `Procfile` with the web process
|
|
|
20
20
|
rails g trek:install:scalingo
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Then configure the Scalingo app's environment, pinning the buildpack to a release
|
|
23
|
+
Then configure the Scalingo app's environment, pinning the buildpack to a release through its archive:
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
scalingo --app <app> env-set BUILDPACK_URL=https://github.com/etaminstudio/trek-buildpack
|
|
26
|
+
scalingo --app <app> env-set BUILDPACK_URL=https://github.com/etaminstudio/trek-buildpack/archive/refs/tags/v0.1.0.tar.gz
|
|
27
27
|
scalingo --app <app> env-set 'DATABASE_URL=$SCALINGO_POSTGRESQL_URL'
|
|
28
28
|
scalingo --app <app> env-set RAILS_MASTER_KEY=$(cat config/master.key)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
> Pin through the archive, not a `#tag` suffix: Scalingo reads `#ref` on `BUILDPACK_URL` as a **branch**, so
|
|
32
|
+
> `…/trek-buildpack#v0.1.0` asks for a branch named `v0.1.0`, which does not exist, and the build fails with
|
|
33
|
+
> `fail to download and extract the custom buildpack: Invalid status code: 404 Not Found`. The `.tar.gz` form above is
|
|
34
|
+
> downloaded as is and stays pinned to the release.
|
|
35
|
+
|
|
31
36
|
Make sure `config/database.yml` reads `DATABASE_URL` in production — the section `rails new` generates targets a local socket with a password variable instead:
|
|
32
37
|
|
|
33
38
|
```yml
|
data/docs/getting-started.md
CHANGED
|
@@ -68,7 +68,7 @@ The installer will:
|
|
|
68
68
|
- create the `Fragment` model and include Trek's concerns
|
|
69
69
|
- create the relevant policies
|
|
70
70
|
- generate and run the relevant migrations
|
|
71
|
-
- set up authentication (
|
|
71
|
+
- set up authentication (`has_secure_password` with database-backed sessions), authorization (ActionPolicy), uploads (Shrine), emails (Postmark), i18n, linting, CI and more
|
|
72
72
|
|
|
73
73
|
### Configure Postmark (optional)
|
|
74
74
|
|
data/docs/index.md
CHANGED
|
@@ -32,7 +32,7 @@ features:
|
|
|
32
32
|
details: Content and admin UI translations powered by Mobility and i18n-tasks, with optional automatic translation via DeepL.
|
|
33
33
|
- icon: 🔐
|
|
34
34
|
title: Roles & policies built in
|
|
35
|
-
details:
|
|
35
|
+
details: Authentication on Rails' own has_secure_password and ActionPolicy authorization with admin, editor, user, reader and guest roles, enforced across every panel.
|
|
36
36
|
- icon: 🧩
|
|
37
37
|
title: Hotwire-native UI
|
|
38
38
|
details: ViewComponents, Stimulus and Turbo under the hood, with light & dark themes, Radix colors and a customizable design system.
|
|
@@ -22,6 +22,21 @@ The main building blocks, all in the `Trek::` namespace:
|
|
|
22
22
|
| `HeadingComponent` | headings |
|
|
23
23
|
| `GateComponent` | authorization-based visibility gate |
|
|
24
24
|
| `IconComponent` | SVG [icon](/reference/icons) renderer |
|
|
25
|
+
| `Dashboard::SectionComponent` / `Dashboard::TileComponent` | dashboard sections of shortcut tiles |
|
|
26
|
+
|
|
27
|
+
## Dashboard tiles
|
|
28
|
+
|
|
29
|
+
A tile is a link by default. With `method:`, it issues another HTTP method through Turbo (as a `data-turbo-method` link), so a tile can create a record in one click rather than open a form; `data:` adds attributes to the link, such as a confirmation:
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
s.with_tile(
|
|
33
|
+
text: "New weekly draft",
|
|
34
|
+
icon: "trek/add",
|
|
35
|
+
href: [:draft_current_week, :admin, :newsletters],
|
|
36
|
+
method: :post,
|
|
37
|
+
data: { turbo_confirm: "Create this week's draft?" }
|
|
38
|
+
)
|
|
39
|
+
```
|
|
25
40
|
|
|
26
41
|
## Form components
|
|
27
42
|
|
|
@@ -13,11 +13,37 @@ end
|
|
|
13
13
|
|
|
14
14
|
For each declared attribute:
|
|
15
15
|
|
|
16
|
-
- a validation (`phone: { possible: true, allow_blank: true }
|
|
16
|
+
- a validation (`phone: { possible: true, allow_blank: true }`, see [Strictness](#strictness))
|
|
17
17
|
- normalization to E.164 international format on save
|
|
18
18
|
- `formatted_{attr}` — national format (`"01 23 45 67 89"`)
|
|
19
19
|
- `{attr}_with_country_code` — E.164 (`"+33123456789"`)
|
|
20
20
|
|
|
21
|
+
## Strictness
|
|
22
|
+
|
|
23
|
+
`strictness:` picks how hard Phonelib looks at a number. It applies to every attribute of the call.
|
|
24
|
+
|
|
25
|
+
| value | what is accepted |
|
|
26
|
+
| --- | --- |
|
|
27
|
+
| `:possible` (default) | the digits **could** form a number for the country — right length and shape |
|
|
28
|
+
| `:valid` | the digits fall in a range the regulator has actually **assigned** |
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
class Organization < ApplicationRecord
|
|
32
|
+
include Trek::Phonable
|
|
33
|
+
phonable_attributes :phone, strictness: :valid
|
|
34
|
+
end
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The gap between the two is worth knowing before leaving the default in place. With
|
|
38
|
+
`Phonelib.default_country = "FR"`, `"0012345678"` is *possible*: under `:possible` it is accepted and
|
|
39
|
+
normalized to `"+12345678"` — the stray leading zero read as an international prefix, the number
|
|
40
|
+
silently rehomed to country code +1, and the original input gone, since normalization happens on
|
|
41
|
+
save. Under `:valid` the same input is rejected and the person is asked to correct it.
|
|
42
|
+
|
|
43
|
+
`:possible` is the right default for a number stored as a way to reach someone, where a false
|
|
44
|
+
rejection is worse than a number that never gets dialled. Choose `:valid` when the application
|
|
45
|
+
intends to actually call or text what it collects.
|
|
46
|
+
|
|
21
47
|
## Usage
|
|
22
48
|
|
|
23
49
|
```ruby
|
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
| --- | --- |
|
|
5
5
|
| `ADMIN_EMAIL` / `ADMIN_PASSWORD` | credentials used by [`trek:admin:user`](/reference/generators/admin-user) |
|
|
6
6
|
| `POSTMARK_API_TOKEN` | Postmark email delivery (auto-injected into credentials by the [installer](/reference/generators/install)) |
|
|
7
|
+
| `APPSIGNAL_PUSH_API_KEY` | AppSignal error tracking and performance monitoring (auto-injected into credentials by the [installer](/reference/generators/install)) |
|
|
8
|
+
| `APPSIGNAL_APP_ENV` | environment name reported to AppSignal (optional, defaults to `RAILS_ENV`) |
|
|
7
9
|
| `DEEPL_AUTH_KEY` | automatic locale translation in the [scaffold generator](/reference/generators/scaffold) |
|
|
8
10
|
| `FEATURE_STORAGE` | `local` (development) or `s3` (production) |
|
|
9
11
|
| `S3_BUCKET`, `S3_REGION`, `S3_ENDPOINT`, `S3_ACCESS_KEY_ID`, `S3_SECRET_ACCESS_KEY`, `S3_BASE_URL` | S3 storage configuration for [uploads](/reference/uploaders) |
|
data/docs/reference/forms.md
CHANGED
|
@@ -13,6 +13,8 @@ Trek ships `Trek::FormBuilder`, an extension of [`ViewComponent::Form`](https://
|
|
|
13
13
|
|
|
14
14
|
Each helper renders the matching component from the [`Trek::Form` namespace](/reference/components#form-components).
|
|
15
15
|
|
|
16
|
+
Standard helpers are styled too: a plain `f.file_field` inside an `f.group` gets its native `::file-selector-button` styled like a narrow `Trek::ButtonComponent` (following the current `data-accent-color`), keeping the browser's built-in "file chosen" feedback with no JS — handy for one-off uploads like a spreadsheet import.
|
|
17
|
+
|
|
16
18
|
```slim
|
|
17
19
|
= form_with model: [:admin, @book] do |f|
|
|
18
20
|
= f.group :title
|
|
@@ -21,7 +21,7 @@ The installer orchestrates dedicated sub-generators, all under the `trek:install
|
|
|
21
21
|
|
|
22
22
|
| Area | What gets set up |
|
|
23
23
|
| --- | --- |
|
|
24
|
-
| Authentication |
|
|
24
|
+
| Authentication | bcrypt and `has_secure_password` on `User`, the `UserSession` model (one per signed-in browser, found through a signed `session_id` cookie), the `Trek::Authentication` controller concern, login/logout & password reset panels |
|
|
25
25
|
| Authorization | ActionPolicy and the base policies |
|
|
26
26
|
| Models | `User`, `Page`, `PagePath`, `PageVersion`, `Fragment`, `MenuNode`, `ExternalLink`, `Current` — with migrations |
|
|
27
27
|
| Attachments | Shrine configuration and the uploaders |
|
|
@@ -32,6 +32,8 @@ The installer orchestrates dedicated sub-generators, all under the `trek:install
|
|
|
32
32
|
| Tooling | esbuild, PostCSS, EditorConfig, Procfiles, Makefile, health check endpoint |
|
|
33
33
|
| Linting | Standard, ESLint, Stylelint, Prettier, Slim Lint, Lefthook git hooks |
|
|
34
34
|
| Security | Brakeman, bundler-audit, ruby_audit |
|
|
35
|
+
| Monitoring | AppSignal error tracking and performance monitoring — `config/appsignal.rb`, the push API key in the credentials, and the `AppsignalContext` concern tagging transactions per area and user |
|
|
36
|
+
| Specs | RSpec, and specs for the models Trek installs (`trek:install:model_specs`): `spec/models/page_spec.rb` and `spec/models/user_spec.rb`, with their test data as [Oaken](https://github.com/kaspth/oaken) seed scripts — defaults in `db/seeds/setup/`, scenarios in `db/seeds/test/cases/` loaded with `seed "cases/…"`. They check Trek's behaviour in your app — slugs and paths, user validations and roles — so a customization that breaks it fails your suite; edit or remove the ones you deliberately change. Trek's own suite runs the same files |
|
|
35
37
|
| N+1 detection | Prosopite (development/test), scanning every request — see [Controllers › N+1 detection](/reference/controllers#n-1-detection) |
|
|
36
38
|
| CI | GitLab CI configuration |
|
|
37
39
|
| Deployment | Scalingo files (`trek:install:scalingo`, run on demand) — see [Deploying to Scalingo](/deploy) |
|
|
@@ -41,4 +43,5 @@ The installer orchestrates dedicated sub-generators, all under the `trek:install
|
|
|
41
43
|
Define these **before** running the installer to have them injected automatically:
|
|
42
44
|
|
|
43
45
|
- `POSTMARK_API_TOKEN` — injected into the Rails credentials for email delivery
|
|
46
|
+
- `APPSIGNAL_PUSH_API_KEY` — injected into the Rails credentials for error tracking and performance monitoring
|
|
44
47
|
- `DEEPL_AUTH_KEY` — enables automatic locale translation in the [scaffold generator](/reference/generators/scaffold)
|
data/docs/reference/icons.md
CHANGED
|
@@ -6,6 +6,6 @@ Trek bundles ~40 SVG icons, rendered with `Trek::IconComponent`:
|
|
|
6
6
|
render Trek::IconComponent.new(:search)
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
Available keys include: `add`, `arrow-bar-left`, `arrow-bar-right`, `bold`, `check`, `chevron-left`, `chevron-right`, `chevron-double`, `close`, `cursor`, `danger`, `download`, `eraser`, `eye`, `grid`, `hr`, `italic`, `layout`, `link`, `list`, `logout`, `paragraph`, `pic`, `profile`, `quotation`, `rating`, `redo`, `undo`, `search`…
|
|
9
|
+
Available keys include: `add`, `arrow-bar-left`, `arrow-bar-right`, `bold`, `check`, `chevron-left`, `chevron-right`, `chevron-double`, `close`, `cursor`, `danger`, `download`, `eraser`, `external`, `eye`, `grid`, `hr`, `italic`, `layout`, `link`, `list`, `logout`, `paragraph`, `pic`, `profile`, `quotation`, `rating`, `redo`, `undo`, `search`…
|
|
10
10
|
|
|
11
11
|
[MenuNode](/reference/models/menu-node) records can reference an icon via their `icon_key` to display it in the admin sidebar.
|
|
@@ -12,7 +12,7 @@ The taxonomy models, installed by the [taxonomies generator](/reference/generato
|
|
|
12
12
|
| `position` | integer | ordering within the category |
|
|
13
13
|
| `taggings_count` | integer | counter cache |
|
|
14
14
|
|
|
15
|
-
- `belongs_to :category`, `has_many :taggings`, `
|
|
15
|
+
- `belongs_to :category`, `has_many :taggings`, and `taggables`, the tagged records of any model
|
|
16
16
|
- Scopes: `ordered`, `in_category(key)`, `in_categories(keys)`
|
|
17
17
|
|
|
18
18
|
## TagCategory
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# User
|
|
2
2
|
|
|
3
|
-
The host application's `User` model, into which Trek injects authentication (
|
|
3
|
+
The host application's `User` model, into which Trek injects authentication (`has_secure_password` and database-backed sessions), roles, theme & locale preferences and the invitation workflow.
|
|
4
4
|
|
|
5
5
|
## Columns added by Trek
|
|
6
6
|
|
|
@@ -10,8 +10,10 @@ The host application's `User` model, into which Trek injects authentication (Sor
|
|
|
10
10
|
| `role` | integer | enum, default `guest` |
|
|
11
11
|
| `theme` | integer | enum, default `auto` |
|
|
12
12
|
| `locale` | string | admin UI language |
|
|
13
|
+
| `password_digest` | string | `has_secure_password` (bcrypt), required |
|
|
14
|
+
| `last_activity_at` | datetime | updated after each signed-in admin request |
|
|
13
15
|
|
|
14
|
-
|
|
16
|
+
When the app has no `User` model yet, Trek creates the `users` table with its `email` column. Signed-in browsers are `UserSession` records (`user_agent`, `ip_address`) in a `user_sessions` table, found through a signed `session_id` cookie. Password reset (15 minutes) and invitation (1 week) links carry tokens from `generates_token_for`, so no column stores them.
|
|
15
17
|
|
|
16
18
|
## Roles
|
|
17
19
|
|
data/docs/reference/uploaders.md
CHANGED
|
@@ -5,15 +5,40 @@ File uploads are handled by [Shrine](https://shrinerb.com), with Uppy on the fro
|
|
|
5
5
|
- `ImageUploader` — base image uploader
|
|
6
6
|
- `RecordImageUploader` — attach an image to any record: `include RecordImageUploader::Attachment(:image)`
|
|
7
7
|
- `ModelImageUploader` — model-level image uploader (used by content editor images)
|
|
8
|
+
- `AudioUploader` — sounds for `f.sound_field` (MP3, AAC/M4A, Ogg, WAV, WebM, FLAC): `include AudioUploader::Attachment(:audio)` on a record with an `audio_data` jsonb column
|
|
8
9
|
|
|
9
10
|
## Storage
|
|
10
11
|
|
|
11
12
|
Configured per environment in the generated Shrine initializer (`config/initializers/shrine.rb`):
|
|
12
13
|
|
|
13
|
-
| Environment | Storage
|
|
14
|
-
|
|
|
15
|
-
| production
|
|
16
|
-
| development | local filesystem (`public/uploads/`)
|
|
17
|
-
| test
|
|
14
|
+
| Environment | Storage |
|
|
15
|
+
| ----------- | --------------------------------------------------------------------- |
|
|
16
|
+
| production | S3 (via the [`S3_*` ENV variables](/reference/environment-variables)) |
|
|
17
|
+
| development | local filesystem (`public/uploads/`) |
|
|
18
|
+
| test | in-memory |
|
|
18
19
|
|
|
19
|
-
Image processing uses libvips, blurhash placeholders are generated for progressive loading, and direct uploads go through the Shrine `upload_endpoint` (20 MB max).
|
|
20
|
+
Image processing uses libvips, blurhash placeholders are generated for progressive loading, and direct uploads go through the Shrine `upload_endpoint`: `POST /admin/upload/image` (20 MB max) and `POST /admin/upload/audio` (200 MB max), handled by the generated `Admin::UploadsController`. The endpoint only checks the size; each uploader validates the file type when it is attached to a record.
|
|
21
|
+
|
|
22
|
+
## Attachments and records reloaded mid-save
|
|
23
|
+
|
|
24
|
+
`RecordImageUploader` loads `Trek::SavedAttachmentsReload` on top of Shrine's `activerecord` plugin:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
class RecordImageUploader < ImageUploader
|
|
28
|
+
plugin :activerecord
|
|
29
|
+
plugin Trek::SavedAttachmentsReload
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Shrine resets every attacher when a record is reloaded, which is right for an explicit reload but loses a pending attachment when something reloads the record _during_ the save — closure_tree does exactly that in its `after_save` callback when it reorders the siblings of a numerically ordered tree, as the generated `Page` is. The file would then never be promoted out of the `cache` storage. The plugin keeps, across such a reload, the attachers whose file was saved with the record; an attachment assigned but not saved is still discarded.
|
|
34
|
+
|
|
35
|
+
## Files left in the cache storage
|
|
36
|
+
|
|
37
|
+
An uploaded file first lands in the temporary `cache` storage, and is promoted to the permanent `store` after the record is committed. A record whose promotion was skipped keeps pointing to the cache, and its file breaks as soon as the cache is cleaned up. To find those records, for every model and attachment, and promote the ones whose file still exists:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
DRY_RUN=1 bin/rails trek:attachments:promote_cached # only lists them
|
|
41
|
+
bin/rails trek:attachments:promote_cached
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Each line gives the status (`promoted`, `promotable` on a dry run, `missing` when the cached file is gone, `changed` when the record changed meanwhile), the record and attachment, and the file id. Missing files have to be uploaded again.
|
data/docs/what-is-trek.md
CHANGED
|
@@ -10,7 +10,7 @@ Trek ships as a Rails engine (a gem plus an NPM package) that integrates into yo
|
|
|
10
10
|
- **Pages** — hierarchical, translatable pages with version history, SEO metadata and stable, redirect-aware URL paths.
|
|
11
11
|
- **Fragments** — reusable chunks of content (a footer text, a hero tagline…) editable from the admin and usable anywhere in your views.
|
|
12
12
|
- **A rich text editor** — based on [Tiptap](https://tiptap.dev), with image uploads (Uppy), link insertion and content stored as structured ProseMirror JSON.
|
|
13
|
-
- **Authentication & authorization** — [
|
|
13
|
+
- **Authentication & authorization** — sign in built on Rails' [`has_secure_password`](https://api.rubyonrails.org/classes/ActiveModel/SecurePassword/ClassMethods.html) with database-backed sessions, password resets, invitations, and [ActionPolicy](https://actionpolicy.evilmartians.io) policies with `admin`, `editor`, `user`, `reader` and `guest` roles.
|
|
14
14
|
- **Internationalization** — content translations with [Mobility](https://github.com/shioyama/mobility), locale management with [i18n-tasks](https://github.com/glebm/i18n-tasks), and optional automatic translation through DeepL.
|
|
15
15
|
- **File uploads** — [Shrine](https://shrinerb.com)-backed attachments with S3 or local storage, image processing via libvips, and blurhash placeholders.
|
|
16
16
|
- **Generators** — an installer that sets up everything above, and a scaffold generator that creates a full admin panel for any model in one command.
|
|
@@ -14,7 +14,13 @@ module Trek
|
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
def upload_image
|
|
17
|
-
|
|
17
|
+
# The endpoint `Trek::Form::ImageFieldComponent` uploads to: /admin/upload/image
|
|
18
|
+
route 'post "upload/image", to: "uploads#image"', namespace: :admin
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def upload_audio
|
|
22
|
+
# The endpoint `Trek::Form::SoundFieldComponent` uploads to: /admin/upload/audio
|
|
23
|
+
route 'post "upload/audio", to: "uploads#audio"', namespace: :admin
|
|
18
24
|
end
|
|
19
25
|
|
|
20
26
|
def lookbook
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
require "rails/generators/actions"
|
|
2
|
+
require "generators/trek/helpers"
|
|
3
|
+
|
|
4
|
+
module Trek
|
|
5
|
+
module Generators
|
|
6
|
+
module Install
|
|
7
|
+
class AppsignalGenerator < Rails::Generators::Base
|
|
8
|
+
include Trek::Generators::Helpers
|
|
9
|
+
|
|
10
|
+
desc "Install AppSignal (error tracking and performance monitoring)."
|
|
11
|
+
|
|
12
|
+
source_root File.expand_path("../templates", __dir__)
|
|
13
|
+
|
|
14
|
+
def add_gem
|
|
15
|
+
return if in_gemfile?("appsignal")
|
|
16
|
+
|
|
17
|
+
gem "appsignal", comment: "Error tracking and performance monitoring [https://appsignal.com]"
|
|
18
|
+
bundle_install
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Like Postmark's API token: the secret lives in the Rails credentials, not in
|
|
22
|
+
# config/appsignal.rb (where `appsignal install` would write it in plain text).
|
|
23
|
+
def inject_appsignal_to_credentials
|
|
24
|
+
return if credentials_encrypted_file.read&.include?("appsignal:")
|
|
25
|
+
|
|
26
|
+
inject_into_credentials(
|
|
27
|
+
<<~YAML
|
|
28
|
+
appsignal:
|
|
29
|
+
push_api_key: #{ENV.fetch("APPSIGNAL_PUSH_API_KEY", "")}
|
|
30
|
+
YAML
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def create_config
|
|
35
|
+
template "config/appsignal.rb", "config/appsignal.rb"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def add_app_env_to_env_example
|
|
39
|
+
return unless File.exist?(".env.example")
|
|
40
|
+
return if File.read(".env.example").include?("APPSIGNAL_APP_ENV")
|
|
41
|
+
|
|
42
|
+
append_to_file ".env.example", "APPSIGNAL_APP_ENV=\n"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# A broken initializer or a missing credential raises before Rails can report
|
|
46
|
+
# anything — the error reporter does not exist yet — so the boot is wrapped.
|
|
47
|
+
def report_boot_errors
|
|
48
|
+
return unless File.exist?("config.ru")
|
|
49
|
+
return if File.read("config.ru").include?("Appsignal.send_error")
|
|
50
|
+
|
|
51
|
+
gsub_file "config.ru",
|
|
52
|
+
%r{^require_relative ["']config/environment["']\n},
|
|
53
|
+
boot_error_reporting
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def create_context_concern
|
|
57
|
+
copy_file "controllers/concerns/appsignal_context.rb",
|
|
58
|
+
"app/controllers/concerns/appsignal_context.rb"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def include_context_in_application_controller
|
|
62
|
+
return if File.read("app/controllers/application_controller.rb").include?("AppsignalContext")
|
|
63
|
+
|
|
64
|
+
inject_into_class "app/controllers/application_controller.rb",
|
|
65
|
+
"ApplicationController",
|
|
66
|
+
" include AppsignalContext\n"
|
|
67
|
+
run "rubocop --autocorrect app/controllers/application_controller.rb", abort_on_failure: true
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def include_context_in_trek_controllers
|
|
71
|
+
copy_file "initializers/appsignal_context.rb",
|
|
72
|
+
"config/initializers/appsignal_context.rb"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def final_instructions
|
|
76
|
+
say ""
|
|
77
|
+
say "AppSignal is configured for production. Check the setup with:"
|
|
78
|
+
say ""
|
|
79
|
+
say " bundle exec appsignal diagnose"
|
|
80
|
+
say ""
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
private
|
|
84
|
+
|
|
85
|
+
def boot_error_reporting
|
|
86
|
+
<<~RUBY
|
|
87
|
+
begin
|
|
88
|
+
require_relative "config/environment"
|
|
89
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
90
|
+
# Errors raised while booting (a broken initializer, a missing credential)
|
|
91
|
+
# happen before the Rails error reporter exists — report them directly.
|
|
92
|
+
# The gem itself is loaded by Bundler.require, so a failure earlier than
|
|
93
|
+
# that (config/boot.rb, the Gemfile) has no Appsignal to report with.
|
|
94
|
+
Appsignal.send_error(error) if defined?(Appsignal)
|
|
95
|
+
raise
|
|
96
|
+
end
|
|
97
|
+
RUBY
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require "rails/generators/actions"
|
|
2
|
+
require "generators/trek/helpers"
|
|
3
|
+
|
|
4
|
+
module Trek
|
|
5
|
+
module Generators
|
|
6
|
+
module Install
|
|
7
|
+
class ModelSpecsGenerator < Rails::Generators::Base
|
|
8
|
+
include Trek::Generators::Helpers
|
|
9
|
+
|
|
10
|
+
desc "Add specs for the models Trek installs (Page, User…), with their Oaken seeds."
|
|
11
|
+
|
|
12
|
+
source_root File.expand_path("../templates", __dir__)
|
|
13
|
+
|
|
14
|
+
# In every group: `db/seeds.rb` calls Oaken, and `db:seed` runs in every environment.
|
|
15
|
+
def add_gem
|
|
16
|
+
return if in_gemfile?("oaken")
|
|
17
|
+
|
|
18
|
+
gem "oaken", "~> 1.0"
|
|
19
|
+
bundle_install
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def load_oaken_in_seeds
|
|
23
|
+
seeds = File.join(destination_root, "db/seeds.rb")
|
|
24
|
+
return if File.exist?(seeds) && File.read(seeds).include?("Oaken.seed")
|
|
25
|
+
|
|
26
|
+
create_file "db/seeds.rb" unless File.exist?(seeds)
|
|
27
|
+
append_to_file "db/seeds.rb", <<~RUBY
|
|
28
|
+
|
|
29
|
+
# Oaken (https://github.com/kaspth/oaken) loads db/seeds/setup (defaults and helpers) first.
|
|
30
|
+
# Pass directories to seed them too, e.g. `Oaken.seed :data`; specs load their own cases with
|
|
31
|
+
# `seed "cases/…"`.
|
|
32
|
+
Oaken.seed
|
|
33
|
+
RUBY
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def load_oaken_in_specs
|
|
37
|
+
rails_helper = File.join(destination_root, "spec/rails_helper.rb")
|
|
38
|
+
return unless File.exist?(rails_helper)
|
|
39
|
+
return if File.read(rails_helper).include?("oaken/rspec_setup")
|
|
40
|
+
|
|
41
|
+
# After rspec-rails, which Oaken's setup configures (transactional examples, seeding the
|
|
42
|
+
# database before the suite).
|
|
43
|
+
inject_into_file "spec/rails_helper.rb",
|
|
44
|
+
"require \"oaken/rspec_setup\"\n",
|
|
45
|
+
after: %r{^require ["']rspec/rails["'].*\n}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Every file under the templates' db/seeds, spec/models and spec/requests is installed, so a new
|
|
49
|
+
# template needs no change here.
|
|
50
|
+
def copy_seeds
|
|
51
|
+
directory "db/seeds", "db/seeds"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def copy_specs
|
|
55
|
+
directory "spec/models", "spec/models"
|
|
56
|
+
directory "spec/requests", "spec/requests"
|
|
57
|
+
directory "spec/fixtures", "spec/fixtures"
|
|
58
|
+
# The tag policies' specs come with them, from `trek:taxonomies`.
|
|
59
|
+
directory "spec/policies", "spec/policies", exclude_pattern: /tag(_category)?_policy_spec/
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|