brut 0.0.13 → 0.0.21

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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +4 -6
  3. data/brut.gemspec +1 -3
  4. data/lib/brut/back_end/seed_data.rb +19 -2
  5. data/lib/brut/back_end/sidekiq/middlewares/server.rb +2 -1
  6. data/lib/brut/back_end/sidekiq/middlewares.rb +2 -1
  7. data/lib/brut/back_end/sidekiq.rb +2 -1
  8. data/lib/brut/back_end/validator.rb +5 -1
  9. data/lib/brut/back_end.rb +9 -0
  10. data/lib/brut/cli/apps/scaffold.rb +16 -24
  11. data/lib/brut/cli.rb +4 -3
  12. data/lib/brut/factory_bot.rb +0 -5
  13. data/lib/brut/framework/app.rb +70 -5
  14. data/lib/brut/framework/config.rb +9 -46
  15. data/lib/brut/framework/container.rb +3 -2
  16. data/lib/brut/framework/errors.rb +12 -4
  17. data/lib/brut/framework/mcp.rb +63 -2
  18. data/lib/brut/framework/project_environment.rb +6 -2
  19. data/lib/brut/framework.rb +1 -1
  20. data/lib/brut/front_end/asset_path_resolver.rb +15 -0
  21. data/lib/brut/front_end/component.rb +101 -246
  22. data/lib/brut/front_end/components/constraint_violations.rb +10 -10
  23. data/lib/brut/front_end/components/form_tag.rb +17 -29
  24. data/lib/brut/front_end/components/i18n_translations.rb +12 -13
  25. data/lib/brut/front_end/components/input.rb +0 -1
  26. data/lib/brut/front_end/components/inputs/csrf_token.rb +3 -3
  27. data/lib/brut/front_end/components/inputs/radio_button.rb +6 -6
  28. data/lib/brut/front_end/components/inputs/select.rb +13 -20
  29. data/lib/brut/front_end/components/inputs/text_field.rb +18 -33
  30. data/lib/brut/front_end/components/inputs/textarea.rb +11 -26
  31. data/lib/brut/front_end/components/locale_detection.rb +2 -2
  32. data/lib/brut/front_end/components/page_identifier.rb +3 -5
  33. data/lib/brut/front_end/components/{time.rb → time_tag.rb} +14 -11
  34. data/lib/brut/front_end/components/traceparent.rb +5 -6
  35. data/lib/brut/front_end/http_method.rb +4 -0
  36. data/lib/brut/front_end/inline_svg_locator.rb +21 -0
  37. data/lib/brut/front_end/layout.rb +19 -0
  38. data/lib/brut/front_end/page.rb +52 -40
  39. data/lib/brut/front_end/request_context.rb +13 -0
  40. data/lib/brut/front_end/routing.rb +8 -3
  41. data/lib/brut/front_end.rb +32 -0
  42. data/lib/brut/i18n/base_methods.rb +51 -11
  43. data/lib/brut/i18n/for_back_end.rb +8 -0
  44. data/lib/brut/i18n/for_cli.rb +5 -1
  45. data/lib/brut/i18n/for_html.rb +9 -1
  46. data/lib/brut/i18n/http_accept_language.rb +47 -0
  47. data/lib/brut/i18n.rb +1 -0
  48. data/lib/brut/instrumentation/open_telemetry.rb +25 -0
  49. data/lib/brut/instrumentation.rb +3 -5
  50. data/lib/brut/sinatra_helpers.rb +13 -7
  51. data/lib/brut/spec_support/component_support.rb +27 -13
  52. data/lib/brut/spec_support/e2e_support.rb +4 -0
  53. data/lib/brut/spec_support/general_support.rb +3 -0
  54. data/lib/brut/spec_support/handler_support.rb +6 -1
  55. data/lib/brut/spec_support/matcher.rb +1 -0
  56. data/lib/brut/spec_support/matchers/be_page_for.rb +1 -0
  57. data/lib/brut/spec_support/matchers/have_html_attribute.rb +1 -0
  58. data/lib/brut/spec_support/matchers/have_i18n_string.rb +3 -1
  59. data/lib/brut/spec_support/matchers/have_link_to.rb +1 -0
  60. data/lib/brut/spec_support/matchers/have_redirected_to.rb +1 -0
  61. data/lib/brut/spec_support/matchers/have_rendered.rb +1 -0
  62. data/lib/brut/spec_support/matchers/have_returned_rack_response.rb +44 -0
  63. data/lib/brut/spec_support/rspec_setup.rb +1 -0
  64. data/lib/brut/spec_support.rb +5 -4
  65. data/lib/brut/version.rb +1 -1
  66. data/lib/brut.rb +7 -50
  67. metadata +14 -49
  68. data/doc-src/architecture.md +0 -102
  69. data/doc-src/assets.md +0 -98
  70. data/doc-src/forms.md +0 -214
  71. data/doc-src/handlers.md +0 -83
  72. data/doc-src/javascript.md +0 -265
  73. data/doc-src/keyword-injection.md +0 -183
  74. data/doc-src/pages.md +0 -210
  75. data/doc-src/route-hooks.md +0 -59
  76. data/lib/brut/front_end/template.rb +0 -47
  77. data/lib/brut/front_end/templates/block_filter.rb +0 -61
  78. data/lib/brut/front_end/templates/erb_engine.rb +0 -26
  79. data/lib/brut/front_end/templates/erb_parser.rb +0 -84
  80. data/lib/brut/front_end/templates/escapable_filter.rb +0 -20
  81. data/lib/brut/front_end/templates/html_safe_string.rb +0 -68
  82. data/lib/brut/front_end/templates/locator.rb +0 -60
data/lib/brut.rb CHANGED
@@ -1,60 +1,17 @@
1
1
  require_relative "brut/framework"
2
2
 
3
- # Brut is a way to make web apps with Ruby. It focuses on web standards, object-orientation, and other fundamentals. Brut seeks to
4
- # minimize abstractions where possible.
3
+ # Brut is a way to make web apps with Ruby. It focuses on web standards, object-orientation, and other
4
+ # fundamentals. Brut seeks to minimize abstractions where possible.
5
5
  #
6
- # Brut encourages the use of the browser's technology and encourages you to build a web app based on good practices that are set up by
7
- # default. Brut may not look easy, but it aims to be simple. It attempts to minimize dependencies and complexity, while leveraging
6
+ # Brut encourages the use of the browser's technology and encourages you to build a web app based
7
+ # on good practices that are set up by default. Brut may not look easy, but it aims to be simple.
8
+ # It attempts to minimize dependencies and complexity, while leveraging
8
9
  # common tested Ruby libraries related to web development.
9
10
  #
10
11
  # Have fun!
11
12
  module Brut
12
- # In Brut, the _front end_ is considered anything that interacts directly with a web browser or HTTP. This includes rendering HTML,
13
- # managing JavaScript and CSS, and processing form submissions. It contrasts to {Brut::BackEnd}, which handles the business logic
14
- # and database.
15
- #
16
- # You {Brut::App} defines pages, forms, and actions. A page is backed by a subclass of {Brut::FrontEnd::Page}, which provides
17
- # dynamic data for rendering. A page can reference {Brut::FrontEnd::Component} subclasses to allow functional decomposition of front
18
- # end logic and markup, as well as re-use. Both pages and components have ERB files that describe the HTML to be rendered.
19
- #
20
- # A {Brut::FrontEnd::Form} subclass defines a form that a browser will submit to your app. That
21
- # submission is processed by a {Brut::FrontEnd::Handler} subclass. Handlers can also respond to other HTTP requests.
22
- #
23
- # In addition to responding to requests, you can subclass {Brut::FrontEnd::RouteHook} or {Brut::FrontEnd::Middleware} to perform
24
- # further manipulation of the request.
25
- #
26
- # The entire front-end is based on Rack, so you should be able to achieve anything you need to.
27
- module FrontEnd
28
- autoload(:AssetMetadata, "brut/front_end/asset_metadata")
29
- autoload(:Component, "brut/front_end/component")
30
- autoload(:Components, "brut/front_end/component")
31
- autoload(:Download, "brut/front_end/download")
32
- autoload(:Flash, "brut/front_end/flash")
33
- autoload(:Form, "brut/front_end/form")
34
- autoload(:Handler, "brut/front_end/handler")
35
- autoload(:Handlers, "brut/front_end/handler")
36
- autoload(:HandlingResults, "brut/front_end/handling_results")
37
- autoload(:HttpMethod, "brut/front_end/http_method")
38
- autoload(:HttpStatus, "brut/front_end/http_status")
39
- autoload(:GenericResponse, "brut/front_end/generic_response")
40
- autoload(:Middleware, "brut/front_end/middleware")
41
- autoload(:Middlewares, "brut/front_end/middleware")
42
- autoload(:Page, "brut/front_end/page")
43
- autoload(:Pages, "brut/front_end/page")
44
- autoload(:RequestContext, "brut/front_end/request_context")
45
- autoload(:RouteHook, "brut/front_end/route_hook")
46
- autoload(:RouteHooks, "brut/front_end/route_hook")
47
- autoload(:Routing, "brut/front_end/routing")
48
- autoload(:Session, "brut/front_end/session")
49
- end
50
- # The _back end_ of a Brut app is where your app's business logic and database are managed. While the bulk of your Brut app's code
51
- # will be in the back end, Brut is far less prescriptive about how to manage that than it is the front end.
52
- module BackEnd
53
- autoload(:Validators, "brut/back_end/validator")
54
- autoload(:Sidekiq, "brut/back_end/sidekiq")
55
- # Do not put SeedData here - it must be loaded only when needed
56
- end
57
- # I18n is where internationalization and localization support lives.
13
+ autoload(:FrontEnd, "brut/front_end")
14
+ autoload(:BackEnd, "brut/back_end")
58
15
  autoload(:I18n, "brut/i18n")
59
16
  autoload(:Instrumentation,"brut/instrumentation")
60
17
  autoload(:SinatraHelpers, "brut/sinatra_helpers")
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brut
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.13
4
+ version: 0.0.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - David Bryant Copeland
@@ -80,7 +80,7 @@ dependencies:
80
80
  - !ruby/object:Gem::Version
81
81
  version: '0'
82
82
  - !ruby/object:Gem::Dependency
83
- name: prism
83
+ name: phlex
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - ">="
@@ -94,7 +94,7 @@ dependencies:
94
94
  - !ruby/object:Gem::Version
95
95
  version: '0'
96
96
  - !ruby/object:Gem::Dependency
97
- name: rack-protection
97
+ name: prism
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - ">="
@@ -108,7 +108,7 @@ dependencies:
108
108
  - !ruby/object:Gem::Version
109
109
  version: '0'
110
110
  - !ruby/object:Gem::Dependency
111
- name: rackup
111
+ name: rack-protection
112
112
  requirement: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - ">="
@@ -122,7 +122,7 @@ dependencies:
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
124
  - !ruby/object:Gem::Dependency
125
- name: rexml
125
+ name: rackup
126
126
  requirement: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - ">="
@@ -177,34 +177,6 @@ dependencies:
177
177
  - - ">="
178
178
  - !ruby/object:Gem::Version
179
179
  version: '0'
180
- - !ruby/object:Gem::Dependency
181
- name: temple
182
- requirement: !ruby/object:Gem::Requirement
183
- requirements:
184
- - - ">="
185
- - !ruby/object:Gem::Version
186
- version: '0'
187
- type: :runtime
188
- prerelease: false
189
- version_requirements: !ruby/object:Gem::Requirement
190
- requirements:
191
- - - ">="
192
- - !ruby/object:Gem::Version
193
- version: '0'
194
- - !ruby/object:Gem::Dependency
195
- name: tilt
196
- requirement: !ruby/object:Gem::Requirement
197
- requirements:
198
- - - ">="
199
- - !ruby/object:Gem::Version
200
- version: '0'
201
- type: :runtime
202
- prerelease: false
203
- version_requirements: !ruby/object:Gem::Requirement
204
- requirements:
205
- - - ">="
206
- - !ruby/object:Gem::Version
207
- version: '0'
208
180
  - !ruby/object:Gem::Dependency
209
181
  name: tzinfo
210
182
  requirement: !ruby/object:Gem::Requirement
@@ -434,14 +406,6 @@ files:
434
406
  - bin/rake
435
407
  - bin/setup
436
408
  - brut.gemspec
437
- - doc-src/architecture.md
438
- - doc-src/assets.md
439
- - doc-src/forms.md
440
- - doc-src/handlers.md
441
- - doc-src/javascript.md
442
- - doc-src/keyword-injection.md
443
- - doc-src/pages.md
444
- - doc-src/route-hooks.md
445
409
  - docker-compose.dx.yml
446
410
  - docs-todo.md
447
411
  - dx/build
@@ -454,6 +418,7 @@ files:
454
418
  - dx/start
455
419
  - dx/stop
456
420
  - lib/brut.rb
421
+ - lib/brut/back_end.rb
457
422
  - lib/brut/back_end/seed_data.rb
458
423
  - lib/brut/back_end/sidekiq.rb
459
424
  - lib/brut/back_end/sidekiq/middlewares.rb
@@ -491,7 +456,9 @@ files:
491
456
  - lib/brut/framework/mcp.rb
492
457
  - lib/brut/framework/patch_semantic_logger.rb
493
458
  - lib/brut/framework/project_environment.rb
459
+ - lib/brut/front_end.rb
494
460
  - lib/brut/front_end/asset_metadata.rb
461
+ - lib/brut/front_end/asset_path_resolver.rb
495
462
  - lib/brut/front_end/component.rb
496
463
  - lib/brut/front_end/components/constraint_violations.rb
497
464
  - lib/brut/front_end/components/form_tag.rb
@@ -504,7 +471,7 @@ files:
504
471
  - lib/brut/front_end/components/inputs/textarea.rb
505
472
  - lib/brut/front_end/components/locale_detection.rb
506
473
  - lib/brut/front_end/components/page_identifier.rb
507
- - lib/brut/front_end/components/time.rb
474
+ - lib/brut/front_end/components/time_tag.rb
508
475
  - lib/brut/front_end/components/traceparent.rb
509
476
  - lib/brut/front_end/download.rb
510
477
  - lib/brut/front_end/flash.rb
@@ -527,6 +494,8 @@ files:
527
494
  - lib/brut/front_end/handling_results.rb
528
495
  - lib/brut/front_end/http_method.rb
529
496
  - lib/brut/front_end/http_status.rb
497
+ - lib/brut/front_end/inline_svg_locator.rb
498
+ - lib/brut/front_end/layout.rb
530
499
  - lib/brut/front_end/layouts/_internal.html.erb
531
500
  - lib/brut/front_end/middleware.rb
532
501
  - lib/brut/front_end/middlewares/annotate_brut_owned_paths.rb
@@ -545,15 +514,9 @@ files:
545
514
  - lib/brut/front_end/route_hooks/setup_request_context.rb
546
515
  - lib/brut/front_end/routing.rb
547
516
  - lib/brut/front_end/session.rb
548
- - lib/brut/front_end/template.rb
549
- - lib/brut/front_end/templates/block_filter.rb
550
- - lib/brut/front_end/templates/erb_engine.rb
551
- - lib/brut/front_end/templates/erb_parser.rb
552
- - lib/brut/front_end/templates/escapable_filter.rb
553
- - lib/brut/front_end/templates/html_safe_string.rb
554
- - lib/brut/front_end/templates/locator.rb
555
517
  - lib/brut/i18n.rb
556
518
  - lib/brut/i18n/base_methods.rb
519
+ - lib/brut/i18n/for_back_end.rb
557
520
  - lib/brut/i18n/for_cli.rb
558
521
  - lib/brut/i18n/for_html.rb
559
522
  - lib/brut/i18n/http_accept_language.rb
@@ -565,6 +528,7 @@ files:
565
528
  - lib/brut/spec_support.rb
566
529
  - lib/brut/spec_support/clock_support.rb
567
530
  - lib/brut/spec_support/component_support.rb
531
+ - lib/brut/spec_support/e2e_support.rb
568
532
  - lib/brut/spec_support/e2e_test_server.rb
569
533
  - lib/brut/spec_support/enhanced_node.rb
570
534
  - lib/brut/spec_support/flash_support.rb
@@ -581,6 +545,7 @@ files:
581
545
  - lib/brut/spec_support/matchers/have_redirected_to.rb
582
546
  - lib/brut/spec_support/matchers/have_rendered.rb
583
547
  - lib/brut/spec_support/matchers/have_returned_http_status.rb
548
+ - lib/brut/spec_support/matchers/have_returned_rack_response.rb
584
549
  - lib/brut/spec_support/rspec_setup.rb
585
550
  - lib/brut/spec_support/session_support.rb
586
551
  - lib/brut/version.rb
@@ -1,102 +0,0 @@
1
- # Brut's High Level Architecture
2
-
3
- Brut attempts to have parity with the web platform in general, and how web browsers work. For example, a web browser shows you a web
4
- page, thus to do this in Brut, you create a page class.
5
-
6
- Brut's core ethos is to captialize on fundamental knowledge you must already posses to build a web app, such as HTML, JavaScript, the
7
- Web Platform, SQL, etc.
8
-
9
- The best way to understand Brut is to break down how a request is handled.
10
-
11
- ## HTTP Requests at a High Level
12
-
13
- 1. HTTP Request is received
14
- 2. If the request's route is mapped, handle it and return the result
15
- 3. Otherwise, 404
16
-
17
- This pretty much describes every web app server in the world. Let's dig into step 2
18
-
19
- ## Pages, Forms, Actions
20
-
21
- HTML allows for exactly two ways to interact with a server: Issuing a `GET` for a resource (like a web page), or submitting a form via
22
- a `GET` or `POST`.
23
-
24
- 1. HTTP Request is received
25
- 2. If the route is a mapped page, render that page
26
- 3. If the route is a configured form, submit that form handle it
27
- 4. If the route is an asset like CSS or an image, send that.
28
- 5. Otherwise, 404
29
-
30
- A browser can use JavaScript to submit other requests, and Brut handles those, too:
31
-
32
- 1. HTTP Request is received
33
- 2. If the route is a mapped page, render that page (See {file:doc-src/pages.md})
34
- 3. If the route is a configured form, submit that form handle it (See {file:doc-src/forms.md})
35
- 4. If the route is a configured action, perform it and return the results. (See {file:doc-src/handlers.md})
36
- 5. If the route is an asset like CSS or an image, send that. (See {file:doc-src/assets.md})
37
- 6. Otherwise, 404
38
-
39
- Before we dig deeper, it's worth pointing out at this point that *Brut is not an MVC framework*, mostly because there are no
40
- controllers. *Models* in the MVC sense are instead *pages* or *components*—classes that provide all dynamic behavior and data needed
41
- to render some HTML. Brut uses ERB templates to generate HTML and you could think of this as the view, if you want.
42
-
43
- ## Back End
44
-
45
- Most of Brut is concerned with what it calls the *front end*, which is everything about receiving an HTTP request and producing the
46
- reponse. But you can almost never make a web app with no back end. You almost always need a database and a place to execute logic
47
- outside of a web browser. Brut refers to this as the *back end*.
48
-
49
- Since web back ends are less constrained to protocols, like a front end is to HTTP and other Web APIs, Brut provides a lot less for
50
- the back end and puts many fewer restrictions on it. This ends up being a good thing, since the back-end of most web apps are were
51
- most of the differentiation in behavior and logic tend to be.
52
-
53
- Brut provides access to a SQL database via the Sequel Ruby library. Brut provides some integration with Sidekiq, to allow running
54
- background jobs. Brut also provides a CLI library for creating one-off tasks (like you'd use Rake for in a Rail apps).
55
-
56
- ### SQL
57
-
58
- Almost all web apps that have a database use SQL. And Postgres is a great choice. This is the SQl database that Brut supports,
59
- though support for other databases may be added later. This is because almost all of Brut's SQL integration is via the Sequel library
60
- and it supports many databases.
61
-
62
- Brut provides some configuration for Sequel to make managing your data easier and to better-support practices that you often want
63
- to follow. For example:
64
-
65
- * All tables have a primary key of type `int` by default.
66
- * All tables have a `created_at` field that is set on row insertion.
67
- * Timestamps use `timestamp with time zone`.
68
- * You must provide a comment to document all tables.
69
- * You can have Brut manage an external unique key for each table, so you can keep your primary keys private.
70
- * `find!` is available on Sequel models, working like `find` does in Rails.
71
-
72
- See {file:doc-src/sql.md} for more details.
73
-
74
- Brut also uses Sequel's model support to provide access to your database. It is configured to namespace all models in the `DB::`
75
- namespace, so if you have a table named `widgets`, Brut will expect `DB::Widget` to be defined to access that table.
76
-
77
- The reason for this is that it is often confusing when an app conflates the concept of a database table and a domain model, it can be
78
- difficult to manage the code. If the model to access the `widgets` table were called, simply, `Widget`, then you would lose a great
79
- class name to use for modeling the widget as a domain object.
80
-
81
- ### Sidekiq
82
-
83
- Sidekiq can be added to any app without much fanfare. That said, Brut provides a few convieniences:
84
-
85
- * `bin/run-sidekiq` is provided to run Sidekiq alongside your web app
86
- * {Brut::SpecSupport::RSpecSetup} well arrange for Sidekiq to be set up in a useful way during tests:
87
- - For non-E2E tests, jobs are cleared between test runs.
88
- - During E2E tests, actual Sidekiq is used, as started by `/bin/run-sidekiq`, and jobs are cleared between tests.
89
-
90
- ### CLI / Tasks
91
-
92
- Rake is not a great tool for task automation, mostly because it exposes a cumbersome command line interface that relies on environment
93
- variables, square brackets, and commas. It's often easier to create a full-blown command line app.
94
-
95
- Brut's {Brut::CLI::App} can form the basis for any command line app or task you need for your system. It provides access to Brut
96
- internals and your app, as needed, and shares much of its startup code with your web app, ensuring parity for all code shared.
97
-
98
- Brut's CLI support also allows for an expedient definition of a subcommand-style UI that behaves like a canonical UNIX command line
99
- app, without having to write a lot of code. It wraps `OptionParser`, so if you are familiar with this library that's part of Ruby,
100
- you will be familiar with Brut's CLI API.
101
-
102
- See {file:doc-src/cli.md}.
data/doc-src/assets.md DELETED
@@ -1,98 +0,0 @@
1
- # Assets - CSS, JavaScript, Images
2
-
3
- To learn about Brut's JavaScript API support, see {file:doc-src/javascript.md}. This page is about how requests for assets are
4
- managed.
5
-
6
- At a high level, all assets are served out of the *public folder*, which is in `app/public`. Brut copies files into this folder as
7
- part of the build and development process.
8
-
9
- Currently, Brut supports JavaScript, CSS, Fonts, and Images. These are all copied and/or processed from a source location into
10
- `app/public` via {Brut::CLI::Apps::BuildAssets}:
11
-
12
- * JavaScript - From `app/src/front_end/js`, bundled to `app/public/js`.
13
- * CSS - From `app/src/front_end/css`, bundled to `app/public/css`.
14
- * Fonts - From `app/src/front_end/fonts`, bundled to `app/public/css` (yes, they are bundled to `css` as that is the only reason to have a built step for fonts - see below).
15
- * Images - From `app/src/front_end/iamges` to `app/public/images`.
16
-
17
- ## Images
18
-
19
- Images are the simplest. Images in Brut are not hashed, so they are essentially synced from `app/src/front_end/images` to
20
- `app/public/images`. Your CDN should arrange for cache invalidation.
21
-
22
- ## SVGs
23
-
24
- SVGs are treated specially. They are located in `app/src/front_end/svgs`. To use an svg, your ERB should use the
25
- {Brut::FrontEnd::Component::Helpers#svg} to inline the SVG into the page. You can put SVGs intended to be linked-to in
26
- `app/src/front_end/images`, but for SVGs to be used as icons, for example, place them in `app/src/front_end/svgs` and use the `svg`
27
- helper.
28
-
29
- ## JavaScript
30
-
31
- JavaScript is currently managed by ESBuild. No fancy options are used nor currently possible. By default, there is a single entry
32
- point for all your JavaScript, located in `app/src/front_end/javascript/index.js`. This is compiled into `app/public/js/app-«HASH».js`, for example `app/public/js/app-EAALH2IQ.js`. A sourcemap is included. Third party JS can be referenced and is assumed to be in `node_modules`.
33
-
34
- This should be sufficient for most apps, however you can use additional entry points. See {file:doc-src/javascript.md} for how to set
35
- this up. Also see "Hashing" below for how hashing works and is managed.
36
-
37
- ## CSS
38
-
39
- CSS is also managed by ESBuild. There is a single entry point located in `app/src/front_end/css/index.css`, and this is compiled into
40
- `app/public/css/styles-«HASH».css`, for example `app/public/css/styles-EAALH2IQ.css`. A sourcemap is included. Third party CSS can be
41
- referenced and is assumed to be in `node_modules`.
42
-
43
- Currently, there is no support for multiple CSS entry points - your entire app's CSS is expected to be in (or referenced by)
44
- `index.css`.
45
-
46
- To do that, Brut assumes you will use standard APIs, namely `@import`, and this is how you can bring in third party CSS as well as to
47
- manage your app's CSS in multiple files:
48
-
49
- @import "bootstrap/index.css";
50
- @import "colors.css";
51
-
52
- html { font-size: 20px; }
53
-
54
- ## Fonts
55
-
56
- ESBuild will handle fonts when CSS is built. Fonts are hashed. You should place fonts in `app/src/front_end/fonts`, however this is
57
- merely a convention. ESBuild will find your font as long as you properly use `url(...)` to reference it.
58
-
59
- To follow the convention, here is how you might write your CSS:
60
-
61
- /* index.css */
62
- @font-face {
63
- font-family: "Monaspace Xenon";
64
- src: url("../fonts/monaspace-xenon.ttf") format("truetype");
65
- font-display: swap;
66
- }
67
-
68
- ESBuild treats the relative path in `url` as relative to where the file being procssed is, thus it will expect to find
69
- `app/src/front_end/fonts/monaspace-xenon.ttf`. While it's not relevant to you where it's copied, the file will be hashed and copied
70
- to `app/public/css` and the `url(..)` will be adjusted, for example:
71
-
72
- @font-face {
73
- font-family: "Monaspace Xenon";
74
- src: url("./monaspace-xenon-VZ5IIHXZ.ttf") format("truetype");
75
- font-display: swap;
76
- }
77
-
78
- ## Hashing
79
-
80
- Hashing is on in development, testing, and production, as a way to minimize differences between the three environments. The way Brut
81
- manages this is via the file `app/config/asset_metadata.json`. This file maps the logical name of an asset to its hashed name. For
82
- example:
83
-
84
- {
85
- "asset_metadata": {
86
- ".js": {
87
- "/js/app.js": "/js/app-L6VPFHLG.js"
88
- },
89
- ".css": {
90
- "/css/styles.css": "/css/styles-PHUHEJY3.css"
91
- }
92
- }
93
- }
94
-
95
- {Brut::FrontEnd::Component::Helpers#asset_path} accepts the logical name (`/js/app.js`) and returns the actual name
96
- `/js/app-L6VPFHLG.js`). `asset_metadata.json` is managed by `bin/build-assets`.
97
-
98
- Note that the fonts are not present in this file since they are only needed by CSS, and ESBuild handles the translation there.
data/doc-src/forms.md DELETED
@@ -1,214 +0,0 @@
1
- # Forms
2
-
3
- Brut's form support is designed to have parity with the Web Platform and allow you to both generate HTML and pasrse the results of a
4
- form submission from a single source of truth. Brut's forms also allow the unification of client-side and server-side constraint
5
- violations so that you can provide client-side validations but not require JavaScript for all validations.
6
-
7
- ## High Level Overview of Forms
8
-
9
- The purpose of a form is to allow the website visitor to submit data to you. The Web Platform and web browser have deep support for
10
- this, but the core way this works is that you have a `<form>` element that contains form elements like `<input>` or `<select>` elements and, when this form is submitted, your server can access the values of each element and take whatever action is necessary.
11
-
12
- The lifecycle of this process looks like so:
13
-
14
- 1. HTML is generated, including a form with elements based on a definition you provide.
15
- 2. The visitor submits the form.
16
- 3. If there are constraint violations that the browser can detect, the form will not be submitted to your server.
17
- 4. If all constraints are satisfied (or the visitor bypasses client-side constraints), the server receives the form submission.
18
- 5. A {Brut::FrontEnd::Handler} is triggered to process that submission. The handler should re-check the client-side constraints and
19
- can perform further server-side checks.
20
- 6. The handler will decide what the website visitor will experience next (e.g. re-render the form, proceed to another page, etc).
21
-
22
- As a developer, you must write four pieces of code:
23
-
24
- * Call {Brut::SinatraHelpers::ClassMethods.form} to declare the route.
25
- * Create a subclass of {Brut::FrontEnd::Form} (whose name is determined by the route name). This class declares the inputs of your
26
- form.
27
- * Create a subclass of {Brut::FrontEnd::Handler} (whose name is determined by the route name). This class processes the form.
28
- * ERB to generate the form. The classes in {Brut::FrontEnd::Components::Inputs} have methods like {Brut::FrontEnd::Components::Inputs::TextField.for_form_input} that will generate HTML for you.
29
-
30
- ## Concepts
31
-
32
- Brut tries to create concepts that have a direct analog to the web platform.
33
-
34
- These basic concepts form the basis for Brut's form support:
35
-
36
- * *Form* is an HTML `<form>`
37
- * *Input* is an HTML `<input>`, `<select>`, `<textarea>`, etc.
38
- * *Input Name* is the name of an input, as defined by the `name` attribute.
39
- * *Submitting a form* is when the browser submits a form to the form's action. This is done with an HTTP GET or HTTP POST only. No
40
- other HTTP methods can submit a form.
41
- * *Constraint Violation* describes invalid data in an input.
42
-
43
- Building on these, Brut specifies how it manages Forms, Inputs, and Submission:
44
-
45
- * *Form Class* defines the inputs a specific form will have.
46
- * *Input Definition* defines the input for a given input name.
47
- * *Form Object* holds the values and constraint violations of all inputs.
48
- * *Handler* is a class that processes a form submission. It's `handle!` method can access the Form Object representing a submission.
49
- * *Server-Side Constraint Violation* describes invalid data that required server processing to determine.
50
-
51
- ## Basic Workflow for Handling a Form
52
-
53
- ### Define Your Form
54
-
55
- In Brut, a *form* class (or *form object*) holds metadata about the form in question. Namely, it defines all of the inputs and any
56
- client-side constraints. For example, here is a form to create a new widget, where the name must be at least 3 characters and is
57
- required, and there is an optional description:
58
-
59
- class NewWidgetForm < AppForm
60
- input :name, minlength: 3
61
- input :description, required: false
62
- end
63
-
64
- This form class provides a few features:
65
-
66
- * It can be used to generate HTML. For example, the "name" field will generate `<input type="text" name="name" required minlength="3">`
67
- * It holds the data submitted to the server, serving as an allowlist of which parameters are accepted. For example, if the browser
68
- submits "name", "description", and "price", since "price" is not an input of this form, the server will discard that value. Your code
69
- will only have access to "name" and "description"
70
- * It can validate the client-side constraints on the server. If a visitor submits the form, bypassing client-side constraint
71
- validations, and "name" is only two characters, the form object will see that the "name" field has a violation.
72
-
73
- ### Define Your Handler
74
-
75
- Handlers are like controller methods in Rails - they receive the data from a request and process it. Unlike a Rails controller, a
76
- Handler is a normal class. It implements the method `handle`. The method signature you use for `handle` determines what data will
77
- be passed into it. The return value of `handle` determines what happens after processing is complete.
78
-
79
- Suppose that creating a widget requires that the name be unique. If it's not, we re-render the page containing the form and show the
80
- user the errors. Suppose that the page in question is `/new_widget`, which would be the class `NewWidgetPage`.
81
-
82
- class NewWidgetHandler < AppHandler
83
- def handle(form:)
84
- if !form.constraint_violations?
85
- if DB::Widget[name: form.name]
86
- form.server_side_constraint_violation(input_name: :name, key: :not_unique)
87
- end
88
- end
89
-
90
- if form.constraint_violations?
91
- return NewWidgetPage.new(form:)
92
- end
93
-
94
- DB::Widget.create(name: form.name, description: form.description)
95
- redirect_to(WidgetsPage)
96
- end
97
- end
98
-
99
-
100
- {file:doc-src/pages.md Pages} provides more information about what `NewWidgetPage` and `WidgetsPage` might be or do, but the logic in
101
- the handler is, hopefully, clear. If our form is free of client-side constraint violations, we check to see if there is another
102
- widget with the name from the form. If there is, we set a server-side constraint violation.
103
-
104
- After that, if the form has any constraint violations (server-side or client-side), we return `NewWidgetPage` initialized with our
105
- existing form. This will allow that page to generate HTML that includes information about the constraint violations detected.
106
-
107
- If there aren't constraint violations, we create a widget in the database, then redirect to `WidgetsPage`. {Brut::FrontEnd::HandlingResults#redirect_to} is a convienience method for figuring out the URL for a given page.
108
-
109
- ### Generate HTML
110
-
111
- In this example, `NewWidgetPage` would be generating the HTML form. It's class might look like so:
112
-
113
- class NewWidgetPage < AppPage
114
-
115
- attr_reader :form
116
-
117
- def initialize(form: nil)
118
- @form ||= NewWidgetForm.new
119
- end
120
- end
121
-
122
- Here is the most direct way to use the form object to render HTML.
123
-
124
- <%= form_tag for: form do %>
125
- <%= component(Brut::FrontEnd::Components::TextField.for_form_input(form:, input_name: :name)) %>
126
- <%= component(Brut::FrontEnd::Components::Textarea.for_form_input(form:, input_name: :description)) %>
127
- <button>Save</button>
128
- <% end %>
129
-
130
- `for_form_input` uses the metadata in the form, along with the name of the field, to generate the appropriate HTML. This will only
131
- generate an `<input>` tag, so you have complete flexibility to style it however you like.
132
-
133
- You can also use `constraint_violations` to render any errors:
134
-
135
- <%= form_tag for: form do %>
136
-
137
- <%= component(Brut::FrontEnd::Components::TextField.for_form_input(form:, input_name: :name)) %>
138
- <%= constraint_violations(form:, input_name: :name) %>
139
-
140
- <%= component(Brut::FrontEnd::Components::Textarea.for_form_input(form:, input_name: :description)) %>
141
- <%= constraint_violations(form:, input_name: :description) %>
142
-
143
- <button>Save</button>
144
- <% end %>
145
-
146
- ## Multiple Inputs with the Same Name
147
-
148
- The HTTP spec allows for any number of inputs with the same name. All values are submitted. Rack, upon which Brut is based, further
149
- provides a way to access such duplicate names as an array, using a naming convention.
150
-
151
- Brut forms support this via `array: true` when defining an input:
152
-
153
- class NewWidgetForm < AppForm
154
- input :name, minlength: 3, array: true
155
- input :description, required: false, array: true
156
- end
157
-
158
- When you do this, a call to `for_form_input` will require an index, as will any other method you use to interact with the form, such
159
- as `server_side_constraint_violation`. When the HTML is generated, the `name=` of the `<input>` will use Rack's naming convention:
160
-
161
- <input type="text" name="name[]" ...>
162
-
163
- To access the values, you can pass an index to the generated method name, or use the `_each` form:
164
-
165
- def handle!(form:)
166
- form.name(1) # get the second value for the 'name' input
167
- form.name_each do |value,i|
168
- value # is the value of the input, empty string if omitted
169
- i # is the zero-based index
170
- end
171
- end
172
-
173
- When generating HTML, the form object will generate any number of inputs that you request:
174
-
175
- <%= form_tag for: form do %>
176
-
177
- <% 10.times do |index| %>
178
- <%= component(Brut::FrontEnd::Components::TextField.for_form_input(form:, input_name: :name, index:)) %>
179
- <%= constraint_violations(form:, input_name: :name) %>
180
- <% end %>
181
-
182
- <button>Save</button>
183
- <% end %>
184
-
185
- ## Styling and Client-Side Behavior
186
-
187
- While browsers have long-supported client-side constraint validations, there are a few complications that make them hard to use in
188
- practice. Brut provides solutions for most of these issues and allows you to unify your error reporting into a single user experien
189
- ce, regardless of where the constraint violation was identified. This does, however, require JavaScript. But, it is entirely opt-in.
190
-
191
- ### Issue: Blank Forms Match `:invalid` Pseudo-Class
192
-
193
- If an input is required, it will match the `:invalid` pseudo class if it has no value, even if a user has not interacted with the
194
- input. While Brut cannot change this behavior, it *does* allow you to have better control.
195
-
196
- If you surround your `<form>` with the `<brut-form>` custom element, that element will add `data-submitted` to the `<form>` element
197
- when submission is attempted. This means that your CSS can target something like `form[data-submitted] input:invalid` so that any
198
- styling for constraint violations will only show up if the user has attempted to submit the form.
199
-
200
- ### Issue: App-Controled Messaging for Client-Side Constraint Violations
201
-
202
- While it's not currently possible to control the browser's UI around client-side constraint violations, Brut does allow you to provide
203
- your own error messages and UX when this happens. This means you can unify your client-side and server-side messaging so it looks the
204
- same no matter what.
205
-
206
- When a field is detected to be invalid, `<brut-form>` will locate a `<brut-cv-messages>` custom element and provide it with the
207
- `ValidityState` of the input. This will create one `<brut-cv>` custom element for each constraint violation. The `<brut-cv>` custom
208
- element will use its `key=` attribute to locate the appropriate `<brut-i18n-translation>` custom element, which your server should've
209
- rendered with the appropriate error messages.
210
-
211
- This has the effect of inserting a localized message you control into the DOM wherever you want it for reporting the error to the
212
- user.
213
-
214
-