cafe_car 0.1.1 → 0.1.2
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/README.md +155 -40
- data/Rakefile +9 -1
- data/app/assets/fonts/Lexend.css +7 -0
- data/app/assets/fonts/Lexend.ttf +0 -0
- data/app/assets/stylesheets/cafe_car/themes/defaults.css +58 -59
- data/app/assets/stylesheets/cafe_car/tooltips.css +20 -0
- data/app/assets/stylesheets/cafe_car/utility.css +1 -2
- data/app/assets/stylesheets/cafe_car.css +17 -6
- data/app/assets/stylesheets/ui/Alert.css +2 -1
- data/app/assets/stylesheets/ui/Button.css +6 -6
- data/app/assets/stylesheets/ui/Card.css +7 -3
- data/app/assets/stylesheets/ui/Chat.css +33 -0
- data/app/assets/stylesheets/ui/Close.css +11 -0
- data/app/assets/stylesheets/ui/Grid.css +2 -0
- data/app/assets/stylesheets/ui/Icon.css +3 -3
- data/app/assets/stylesheets/ui/Layout.css +20 -13
- data/app/assets/stylesheets/ui/Modal.css +5 -12
- data/app/assets/stylesheets/ui/Navigation.css +13 -5
- data/app/assets/stylesheets/ui/Page.css +42 -3
- data/app/assets/stylesheets/ui/Table.css +27 -56
- data/app/assets/stylesheets/ui/components.css +2 -0
- data/app/controllers/cafe_car/examples_controller.rb +1 -1
- data/app/controllers/cafe_car/sessions_controller.rb +30 -0
- data/app/controllers/concerns/cafe_car/authentication.rb +61 -0
- data/app/javascript/cafe_car.js +16 -11
- data/app/models/cafe_car/session.rb +18 -0
- data/app/policies/cafe_car/session_policy.rb +19 -0
- data/app/presenters/cafe_car/active_storage/attachment_presenter.rb +5 -4
- data/app/presenters/cafe_car/code_presenter.rb +18 -0
- data/app/presenters/cafe_car/date_presenter.rb +1 -0
- data/app/presenters/cafe_car/date_time_presenter.rb +2 -2
- data/app/presenters/cafe_car/enumerable_presenter.rb +1 -1
- data/app/presenters/cafe_car/hash_presenter.rb +3 -8
- data/app/presenters/cafe_car/presenter.rb +22 -12
- data/app/presenters/cafe_car/string_presenter.rb +2 -2
- data/app/ui/cafe_car/ui/button.rb +2 -1
- data/app/ui/cafe_car/ui/card.rb +18 -0
- data/app/ui/cafe_car/ui/grid.rb +30 -0
- data/app/ui/cafe_car/ui/layout.rb +7 -0
- data/app/ui/cafe_car/ui/page.rb +5 -1
- data/app/views/application/_body.html.haml +2 -1
- data/app/views/application/_controls.html.haml +1 -0
- data/app/views/application/_debug.html.haml +9 -2
- data/app/views/application/_errors.html.haml +4 -8
- data/app/views/application/_grid.html.haml +1 -1
- data/app/views/application/_grid_item.html.haml +1 -1
- data/app/views/application/_head.html.haml +1 -0
- data/app/views/application/_index.html.haml +6 -2
- data/app/views/application/_index_actions.html.haml +3 -3
- data/app/views/application/_navigation.html.haml +7 -0
- data/app/views/application/_navigation_links.html.haml +1 -1
- data/app/views/application/_notes.html.haml +1 -0
- data/app/views/application/_popup.html.haml +7 -0
- data/app/views/cafe_car/application/edit.html.haml +1 -1
- data/app/views/cafe_car/application/edit.turbo_stream.haml +3 -5
- data/app/views/cafe_car/application/index.html.haml +3 -0
- data/app/views/cafe_car/application/new.turbo_stream.haml +5 -6
- data/app/views/cafe_car/application/show.html.haml +2 -2
- data/app/views/cafe_car/examples/ui/_chat.html.haml +3 -0
- data/app/views/cafe_car/examples/ui/_info_circle.html.haml +1 -1
- data/app/views/cafe_car/examples/ui/_modal.html.haml +1 -1
- data/app/views/passwords_mailer/reset.html.haml +5 -0
- data/app/views/passwords_mailer/reset.text.erb +4 -0
- data/app/views/ui/_card.html.haml +6 -11
- data/app/views/ui/_field.html.haml +1 -7
- data/app/views/ui/_modal_close.html.haml +1 -2
- data/app/views/ui/_page.html.haml +6 -12
- data/config/brakeman.ignore +3 -3
- data/config/locales/en.yml +10 -2
- data/config/routes.rb +5 -1
- data/db/migrate/20251005220017_create_slugs.rb +2 -2
- data/lib/cafe_car/active_record.rb +1 -1
- data/lib/cafe_car/application_responder.rb +6 -0
- data/lib/cafe_car/attributes.rb +1 -1
- data/lib/cafe_car/auto_resolver.rb +1 -1
- data/lib/cafe_car/component.rb +102 -39
- data/lib/cafe_car/context.rb +5 -4
- data/lib/cafe_car/controller/filtering.rb +9 -1
- data/lib/cafe_car/controller.rb +52 -13
- data/lib/cafe_car/core_ext/array.rb +13 -0
- data/lib/cafe_car/core_ext/hash.rb +15 -0
- data/lib/cafe_car/core_ext/module.rb +15 -0
- data/lib/cafe_car/core_ext.rb +0 -2
- data/lib/cafe_car/current.rb +4 -1
- data/lib/cafe_car/engine.rb +9 -2
- data/lib/cafe_car/field_builder.rb +1 -1
- data/lib/cafe_car/field_info.rb +14 -12
- data/lib/cafe_car/fields.rb +7 -0
- data/lib/cafe_car/filter/field_info.rb +1 -1
- data/lib/cafe_car/filter/form_builder.rb +2 -2
- data/lib/cafe_car/filter_builder.rb +1 -1
- data/lib/cafe_car/form_builder.rb +1 -1
- data/lib/cafe_car/generators.rb +1 -1
- data/lib/cafe_car/helpers.rb +37 -10
- data/lib/cafe_car/href_builder.rb +35 -9
- data/lib/cafe_car/input_builder.rb +1 -1
- data/lib/cafe_car/link_builder.rb +14 -11
- data/lib/cafe_car/model.rb +2 -2
- data/lib/cafe_car/navigation.rb +10 -10
- data/lib/cafe_car/option_helpers.rb +11 -5
- data/lib/cafe_car/param_parser.rb +10 -6
- data/lib/cafe_car/policy.rb +2 -2
- data/lib/cafe_car/query_builder.rb +3 -3
- data/lib/cafe_car/resolver.rb +5 -1
- data/lib/cafe_car/routing.rb +1 -1
- data/lib/cafe_car/table/builder.rb +3 -2
- data/lib/cafe_car/table/head_builder.rb +2 -2
- data/lib/cafe_car/table/label_builder.rb +1 -1
- data/lib/cafe_car/table/row_builder.rb +5 -7
- data/lib/cafe_car/table_builder.rb +3 -3
- data/lib/cafe_car/ui.rb +2 -0
- data/lib/cafe_car/version.rb +1 -1
- data/lib/cafe_car/visitors.rb +2 -2
- data/lib/cafe_car.rb +25 -0
- data/lib/generators/cafe_car/controller/templates/controller.rb.tt +1 -1
- data/lib/generators/cafe_car/install/install_generator.rb +0 -1
- data/lib/generators/cafe_car/sessions/USAGE +17 -0
- data/lib/generators/cafe_car/sessions/sessions_generator.rb +29 -0
- data/lib/generators/cafe_car/sessions/templates/create_sessions.rb.tt +12 -0
- data/lib/tasks/holdco_tasks.rake +532 -0
- data/lib/tasks/templates/tasks_header.md +37 -0
- metadata +76 -48
- data/app/models/cafe_car/slug.rb +0 -3
- data/app/views/ui/_grid.html.haml +0 -17
- data/app/views/ui/_layout_menu.html.haml +0 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fdfdd44f9f4f564e6fe25b17a694b0dd6dce990efdc30990d0cef474a98cc718
|
|
4
|
+
data.tar.gz: e483b2317f3a39b3b7c57b9c44bd20e3e766376f4889551bdc8ed199b8e6db52
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: daba4f7613586a3ff4f74e952800d3738965afafb81fbf1bfe82eec4b3ae5363c99d7aa879c1d3be438ff1ecdbf18d37d071307072487a1d2fb434dbfa3a6b62
|
|
7
|
+
data.tar.gz: '051968fadd32602c6b8884443200f030aeae9bcecb731c602161b854f0222e8197b65d568eadce9e1c45175cc66b64fab899d74f5df367df856f7c39e29bc8eb'
|
data/README.md
CHANGED
|
@@ -1,24 +1,36 @@
|
|
|
1
1
|
# CafeCar
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/craft-concept/cafe_car/actions/workflows/ci.yml)
|
|
4
|
+
[](https://rubygems.org/gems/cafe_car)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
CafeCar is a Rails engine that extends the MVC "view" layer to provide automatic
|
|
8
|
+
CRUD UI generation with sensible defaults. Its philosophy is rooted in the idea
|
|
9
|
+
that Rails should render _something_ that represents the CRUD operations of your
|
|
10
|
+
models by default. These defaults can then be expanded or overridden on either
|
|
11
|
+
an application-wide or model-specific basis.
|
|
4
12
|
|
|
5
13
|
**Perfect for**: Admin panels, internal tools, and rapid prototyping.
|
|
6
14
|
|
|
7
15
|
## Features
|
|
8
16
|
|
|
9
|
-
- 🚀 **Auto-generated CRUD interfaces** - One line of code generates complete
|
|
10
|
-
|
|
11
|
-
-
|
|
17
|
+
- 🚀 **Auto-generated CRUD interfaces** - One line of code generates complete
|
|
18
|
+
index, show, new, edit views
|
|
19
|
+
- 🎨 **Component-based UI system** - Flexible, composable components for
|
|
20
|
+
building interfaces
|
|
21
|
+
- 🔐 **Built-in authorization** - Pundit integration for attribute-level
|
|
22
|
+
permissions
|
|
12
23
|
- 📊 **Smart presenters** - Automatic type-aware display of your data
|
|
13
|
-
- 🔍 **Advanced filtering** - Range queries, comparison operators, and
|
|
24
|
+
- 🔍 **Advanced filtering** - Range queries, comparison operators, and
|
|
25
|
+
association filters
|
|
14
26
|
- 📄 **Pagination & sorting** - Kaminari integration with sortable columns
|
|
15
27
|
- ⚡ **Hotwire ready** - Turbo Streams support out of the box
|
|
16
28
|
- 📝 **Intelligent forms** - Auto-generated forms with smart field detection
|
|
17
29
|
|
|
18
30
|
## Prerequisites
|
|
19
31
|
|
|
20
|
-
-
|
|
21
|
-
-
|
|
32
|
+
- Ruby 3.3+ (developed and tested against 3.3.5)
|
|
33
|
+
- Rails 8.0+ (developed and tested against Rails 8.1)
|
|
22
34
|
|
|
23
35
|
## Installation
|
|
24
36
|
|
|
@@ -41,23 +53,28 @@ $ rails generate cafe_car:install
|
|
|
41
53
|
```
|
|
42
54
|
|
|
43
55
|
This will:
|
|
44
|
-
|
|
56
|
+
|
|
57
|
+
- Add required gems (cnc, bcrypt, paper_trail, factory_bot_rails, faker, rouge)
|
|
58
|
+
plus development tools (hotwire-livereload, better_errors, binding_of_caller,
|
|
59
|
+
chrome_devtools_rails, i18n-debug)
|
|
45
60
|
- Mount the CafeCar engine at `/` under the `:admin` namespace
|
|
46
61
|
- Create `app/policies/application_policy.rb`
|
|
47
62
|
- Add `CafeCar::Controller` to your `ApplicationController`
|
|
48
|
-
- Set up JavaScript imports for Trix and ActionText
|
|
63
|
+
- Set up JavaScript imports for CafeCar, Trix, and ActionText
|
|
49
64
|
|
|
50
65
|
## Getting Started
|
|
51
66
|
|
|
52
67
|
### Quick Start: Generate a Complete Resource
|
|
53
68
|
|
|
54
|
-
The fastest way to get started is to generate a complete resource (model +
|
|
69
|
+
The fastest way to get started is to generate a complete resource (model +
|
|
70
|
+
controller + policy):
|
|
55
71
|
|
|
56
72
|
```bash
|
|
57
73
|
$ rails generate cafe_car:resource Product name:string price:decimal description:text
|
|
58
74
|
```
|
|
59
75
|
|
|
60
76
|
This creates:
|
|
77
|
+
|
|
61
78
|
- Migration and model (`app/models/product.rb`)
|
|
62
79
|
- Controller with CRUD actions (`app/controllers/products_controller.rb`)
|
|
63
80
|
- Policy with permission methods (`app/policies/product_policy.rb`)
|
|
@@ -79,11 +96,12 @@ You can also add CafeCar to existing resources:
|
|
|
79
96
|
|
|
80
97
|
```ruby
|
|
81
98
|
class ProductsController < ApplicationController
|
|
82
|
-
|
|
99
|
+
cafe_car
|
|
83
100
|
end
|
|
84
101
|
```
|
|
85
102
|
|
|
86
103
|
That single line provides:
|
|
104
|
+
|
|
87
105
|
- All 7 RESTful actions (index, show, new, create, edit, update, destroy)
|
|
88
106
|
- Automatic authorization via Pundit
|
|
89
107
|
- Filtering and sorting
|
|
@@ -113,17 +131,19 @@ The policy controls both authorization and which attributes can be edited.
|
|
|
113
131
|
|
|
114
132
|
### Controllers
|
|
115
133
|
|
|
116
|
-
The `CafeCar::Controller` module provides automatic CRUD functionality with the
|
|
134
|
+
The `CafeCar::Controller` module provides automatic CRUD functionality with the
|
|
135
|
+
`cafe_car` class method.
|
|
117
136
|
|
|
118
137
|
```ruby
|
|
119
138
|
class Admin::ClientsController < ApplicationController
|
|
120
|
-
|
|
139
|
+
cafe_car
|
|
121
140
|
end
|
|
122
141
|
```
|
|
123
142
|
|
|
124
143
|
**What you get:**
|
|
125
144
|
|
|
126
|
-
- **RESTful actions**: `index`, `show`, `new`, `edit`, `create`, `update`,
|
|
145
|
+
- **RESTful actions**: `index`, `show`, `new`, `edit`, `create`, `update`,
|
|
146
|
+
`destroy`
|
|
127
147
|
- **Authorization**: Automatic `authorize!` before each action
|
|
128
148
|
- **Smart defaults**: Model detection from controller name
|
|
129
149
|
- **Callbacks**: Lifecycle hooks for `render`, `update`, `create`, `destroy`
|
|
@@ -132,9 +152,9 @@ end
|
|
|
132
152
|
**Limiting actions:**
|
|
133
153
|
|
|
134
154
|
```ruby
|
|
135
|
-
|
|
155
|
+
cafe_car only: [:index, :show]
|
|
136
156
|
# or
|
|
137
|
-
|
|
157
|
+
cafe_car except: [:destroy]
|
|
138
158
|
```
|
|
139
159
|
|
|
140
160
|
**Custom model:**
|
|
@@ -142,7 +162,7 @@ recline_in_the_cafe_car except: [:destroy]
|
|
|
142
162
|
```ruby
|
|
143
163
|
class Admin::ClientsController < ApplicationController
|
|
144
164
|
model Company # Use Company model instead of Client
|
|
145
|
-
|
|
165
|
+
cafe_car
|
|
146
166
|
end
|
|
147
167
|
```
|
|
148
168
|
|
|
@@ -150,8 +170,8 @@ end
|
|
|
150
170
|
|
|
151
171
|
```ruby
|
|
152
172
|
class ProductsController < ApplicationController
|
|
153
|
-
|
|
154
|
-
|
|
173
|
+
cafe_car
|
|
174
|
+
|
|
155
175
|
set_callback :create, :after do |controller|
|
|
156
176
|
NotificationMailer.product_created(controller.object).deliver_later
|
|
157
177
|
end
|
|
@@ -160,7 +180,8 @@ end
|
|
|
160
180
|
|
|
161
181
|
### Policies
|
|
162
182
|
|
|
163
|
-
CafeCar extends Pundit with attribute-level permissions and auto-detection of
|
|
183
|
+
CafeCar extends Pundit with attribute-level permissions and auto-detection of
|
|
184
|
+
displayable fields.
|
|
164
185
|
|
|
165
186
|
```ruby
|
|
166
187
|
class ClientPolicy < ApplicationPolicy
|
|
@@ -185,9 +206,11 @@ end
|
|
|
185
206
|
**Key methods:**
|
|
186
207
|
|
|
187
208
|
- `permitted_attributes` - Attributes that can be edited via forms
|
|
188
|
-
- `displayable_attributes` - Attributes shown in views (auto-detected from
|
|
209
|
+
- `displayable_attributes` - Attributes shown in views (auto-detected from
|
|
210
|
+
columns + associations)
|
|
189
211
|
- `displayable_associations` - Associations that can be displayed
|
|
190
|
-
- `filtered_attribute?(attr)` - Check if attribute should be hidden (uses Rails
|
|
212
|
+
- `filtered_attribute?(attr)` - Check if attribute should be hidden (uses Rails
|
|
213
|
+
parameter filters)
|
|
191
214
|
|
|
192
215
|
**Scope pattern:**
|
|
193
216
|
|
|
@@ -203,7 +226,8 @@ end
|
|
|
203
226
|
|
|
204
227
|
### Presenters
|
|
205
228
|
|
|
206
|
-
Presenters convert model objects into view-ready representations with automatic
|
|
229
|
+
Presenters convert model objects into view-ready representations with automatic
|
|
230
|
+
type detection.
|
|
207
231
|
|
|
208
232
|
**Automatic usage** (in views):
|
|
209
233
|
|
|
@@ -212,6 +236,7 @@ Presenters convert model objects into view-ready representations with automatic
|
|
|
212
236
|
```
|
|
213
237
|
|
|
214
238
|
This automatically:
|
|
239
|
+
|
|
215
240
|
1. Finds the appropriate presenter for the object type
|
|
216
241
|
2. Checks policy permissions
|
|
217
242
|
3. Renders displayable attributes
|
|
@@ -227,14 +252,14 @@ class ProductPresenter < CafeCar::Presenter
|
|
|
227
252
|
show :description
|
|
228
253
|
show :category
|
|
229
254
|
show :created_at
|
|
230
|
-
|
|
255
|
+
|
|
231
256
|
# Custom display method
|
|
232
257
|
def preview
|
|
233
258
|
"#{name} - #{format_currency(price)}"
|
|
234
259
|
end
|
|
235
|
-
|
|
260
|
+
|
|
236
261
|
private
|
|
237
|
-
|
|
262
|
+
|
|
238
263
|
def format_currency(amount)
|
|
239
264
|
"$#{amount}"
|
|
240
265
|
end
|
|
@@ -349,7 +374,7 @@ The form builder automatically detects field types:
|
|
|
349
374
|
<%= f.field(:price).label %>
|
|
350
375
|
<%= f.field(:price).input class: "currency" %>
|
|
351
376
|
<%= f.field(:price).hint "In USD" %>
|
|
352
|
-
<%= f.field(:price).
|
|
377
|
+
<%= f.field(:price).error %>
|
|
353
378
|
<% end %>
|
|
354
379
|
```
|
|
355
380
|
|
|
@@ -397,17 +422,19 @@ end
|
|
|
397
422
|
```
|
|
398
423
|
|
|
399
424
|
The model gets:
|
|
425
|
+
|
|
400
426
|
- `sorted(*keys)` - Parse and apply sort parameters
|
|
401
|
-
- `
|
|
427
|
+
- `normalize_sort_key(key)` - Internal helper that converts a sort key to Arel
|
|
428
|
+
order format
|
|
402
429
|
|
|
403
430
|
**Custom filters in controllers:**
|
|
404
431
|
|
|
405
432
|
```ruby
|
|
406
433
|
class ProductsController < ApplicationController
|
|
407
|
-
|
|
408
|
-
|
|
434
|
+
cafe_car
|
|
435
|
+
|
|
409
436
|
private
|
|
410
|
-
|
|
437
|
+
|
|
411
438
|
def find_objects
|
|
412
439
|
@objects = model.where(active: true)
|
|
413
440
|
.query(filter_params)
|
|
@@ -431,16 +458,17 @@ app/views/
|
|
|
431
458
|
_form.html.haml # Override form partial
|
|
432
459
|
```
|
|
433
460
|
|
|
434
|
-
CafeCar's default views are in `app/views/cafe_car/application/` and serve as
|
|
461
|
+
CafeCar's default views are in `app/views/cafe_car/application/` and serve as
|
|
462
|
+
templates.
|
|
435
463
|
|
|
436
464
|
### Custom Responders
|
|
437
465
|
|
|
438
466
|
```ruby
|
|
439
467
|
class ProductsController < ApplicationController
|
|
440
|
-
|
|
441
|
-
|
|
468
|
+
cafe_car
|
|
469
|
+
|
|
442
470
|
private
|
|
443
|
-
|
|
471
|
+
|
|
444
472
|
def create
|
|
445
473
|
super
|
|
446
474
|
respond_with object, location: custom_path
|
|
@@ -477,7 +505,78 @@ CafeCar::Current.user_agent # User agent string
|
|
|
477
505
|
CafeCar::Current.ip_address # IP address
|
|
478
506
|
```
|
|
479
507
|
|
|
480
|
-
Set in controllers via `set_current_attributes` (automatically called by
|
|
508
|
+
Set in controllers via `set_current_attributes` (automatically called by
|
|
509
|
+
`cafe_car`).
|
|
510
|
+
|
|
511
|
+
## Sessions & Authentication
|
|
512
|
+
|
|
513
|
+
Sessions are **opt-in**. CafeCar works for plain CRUD with no login at all: when
|
|
514
|
+
a policy denies access and no sessions infrastructure is present, the request
|
|
515
|
+
gets a plain **403 Forbidden** instead of redirecting to a login page that
|
|
516
|
+
doesn't exist. Authorization (Pundit policies) is always on; *authentication*
|
|
517
|
+
(knowing who the user is) is the part you turn on when you want it.
|
|
518
|
+
|
|
519
|
+
### Enabling sessions
|
|
520
|
+
|
|
521
|
+
1. **Run the generator** to add the `sessions` table:
|
|
522
|
+
|
|
523
|
+
```bash
|
|
524
|
+
$ rails generate cafe_car:sessions
|
|
525
|
+
$ rails db:migrate
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
The `CafeCar::Session` model and `SessionPolicy` ship with the engine, so the
|
|
529
|
+
generator only creates the migration (columns: `user`, `ip_address`,
|
|
530
|
+
`user_agent`).
|
|
531
|
+
|
|
532
|
+
2. **Expose the routes.** Mounting the engine already provides them. To expose
|
|
533
|
+
login at the top level without mounting, add to `config/routes.rb`:
|
|
534
|
+
|
|
535
|
+
```ruby
|
|
536
|
+
resource :session, only: %i[new create destroy], controller: "cafe_car/sessions"
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
This gives you `new_session_path` (login form) and `session_path` (create via
|
|
540
|
+
`POST`, log out via `DELETE`).
|
|
541
|
+
|
|
542
|
+
3. **Prepare your user model.** It needs `has_secure_password` and an `email`:
|
|
543
|
+
|
|
544
|
+
```ruby
|
|
545
|
+
class User < ApplicationRecord
|
|
546
|
+
has_secure_password
|
|
547
|
+
has_many :sessions, dependent: :destroy, class_name: "CafeCar::Session"
|
|
548
|
+
end
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
4. **Different user model name?** Set it in an initializer (resolved lazily):
|
|
552
|
+
|
|
553
|
+
```ruby
|
|
554
|
+
# config/initializers/cafe_car.rb
|
|
555
|
+
CafeCar.user_class_name = "Account"
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Once sessions are available, an authorization failure for a signed-out visitor
|
|
559
|
+
redirects to the login form (remembering where they were headed) instead of
|
|
560
|
+
returning 403.
|
|
561
|
+
|
|
562
|
+
### Helpers
|
|
563
|
+
|
|
564
|
+
These are available in controllers and views:
|
|
565
|
+
|
|
566
|
+
- `authenticated?` - truthy when someone is logged in
|
|
567
|
+
- `current_user` - the logged-in user (or `nil`)
|
|
568
|
+
- `current_session` - the current `CafeCar::Session`
|
|
569
|
+
|
|
570
|
+
```erb
|
|
571
|
+
<% if authenticated? %>
|
|
572
|
+
Signed in as <%= current_user.email %>
|
|
573
|
+
<% else %>
|
|
574
|
+
<%= link_to "Log in", new_session_path %>
|
|
575
|
+
<% end %>
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
Logging in (`POST /session` with `session[:email]`/`session[:password]`) sets a
|
|
579
|
+
signed, http-only cookie; logging out (`DELETE /session`) clears it.
|
|
481
580
|
|
|
482
581
|
## Generators
|
|
483
582
|
|
|
@@ -514,10 +613,22 @@ $ rails generate cafe_car:notes
|
|
|
514
613
|
```
|
|
515
614
|
|
|
516
615
|
Creates:
|
|
616
|
+
|
|
517
617
|
- Migration for notes table
|
|
518
618
|
- `Note` model
|
|
519
619
|
- `Notable` concern for trackable models
|
|
520
620
|
|
|
621
|
+
### Sessions Generator
|
|
622
|
+
|
|
623
|
+
Enable opt-in login/logout (see [Sessions & Authentication](#sessions--authentication)):
|
|
624
|
+
|
|
625
|
+
```bash
|
|
626
|
+
$ rails generate cafe_car:sessions
|
|
627
|
+
```
|
|
628
|
+
|
|
629
|
+
Creates the `sessions` table migration. The `CafeCar::Session` model and
|
|
630
|
+
`SessionPolicy` already ship with the engine.
|
|
631
|
+
|
|
521
632
|
## Configuration
|
|
522
633
|
|
|
523
634
|
### Custom Form Builder
|
|
@@ -539,7 +650,7 @@ class ApplicationPresenter < CafeCar::Presenter
|
|
|
539
650
|
# Application-wide presenter customizations
|
|
540
651
|
end
|
|
541
652
|
|
|
542
|
-
# app/presenters/product_presenter.rb
|
|
653
|
+
# app/presenters/product_presenter.rb
|
|
543
654
|
class ProductPresenter < ApplicationPresenter
|
|
544
655
|
show :name
|
|
545
656
|
show :price
|
|
@@ -568,7 +679,7 @@ class ProductsControllerTest < ActionDispatch::IntegrationTest
|
|
|
568
679
|
get products_url
|
|
569
680
|
assert_response :success
|
|
570
681
|
end
|
|
571
|
-
|
|
682
|
+
|
|
572
683
|
test "create with valid attributes" do
|
|
573
684
|
assert_difference "Product.count", 1 do
|
|
574
685
|
post products_url, params: { product: { name: "Widget" } }
|
|
@@ -580,8 +691,12 @@ end
|
|
|
580
691
|
|
|
581
692
|
## Contributing
|
|
582
693
|
|
|
583
|
-
Contributions are welcome! Please
|
|
694
|
+
Contributions are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) for
|
|
695
|
+
development setup, how to run the tests (`bundle exec rake`), and PR expectations.
|
|
696
|
+
By participating you agree to the [Code of Conduct](CODE_OF_CONDUCT.md). To report a
|
|
697
|
+
security issue, see [SECURITY.md](SECURITY.md).
|
|
584
698
|
|
|
585
699
|
## License
|
|
586
700
|
|
|
587
|
-
The gem is available as open source under the terms of the
|
|
701
|
+
The gem is available as open source under the terms of the
|
|
702
|
+
[MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
|
@@ -10,7 +10,15 @@ RuboCop::RakeTask.new
|
|
|
10
10
|
|
|
11
11
|
task :brakeman do
|
|
12
12
|
require "brakeman"
|
|
13
|
-
Brakeman.run app_path: ".", print_report: true
|
|
13
|
+
Brakeman.run app_path: ".", print_report: true, pager: false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
task :test do
|
|
17
|
+
Rails::TestUnit::Runner.run_from_rake("test")
|
|
14
18
|
end
|
|
15
19
|
|
|
16
20
|
task default: %i[rubocop test brakeman]
|
|
21
|
+
|
|
22
|
+
# Holdco backlog machinery (tasks:new, tasks:index, tasks:claim, tasks:done, rake task).
|
|
23
|
+
# Loads every .rake under lib/tasks/ so the gem's own tasks and holdco_tasks both run.
|
|
24
|
+
Dir.glob(File.expand_path("lib/tasks/*.rake", __dir__)).sort.each { |f| load f }
|
|
Binary file
|
|
@@ -1,61 +1,60 @@
|
|
|
1
1
|
:root {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
--duration: 300ms;
|
|
2
|
+
color-scheme: light dark;
|
|
3
|
+
accent-color: var(--accent);
|
|
4
|
+
scroll-behavior: smooth;
|
|
5
|
+
|
|
6
|
+
--blue: blue;
|
|
7
|
+
--green: green;
|
|
8
|
+
--orange: orange;
|
|
9
|
+
--red: red;
|
|
10
|
+
|
|
11
|
+
--white: white;
|
|
12
|
+
--gray: #eee;
|
|
13
|
+
--black: black;
|
|
14
|
+
|
|
15
|
+
--accent: var(--blue);
|
|
16
|
+
--primary: var(--accent);
|
|
17
|
+
--secondary: var(--accent);
|
|
18
|
+
--tertiary: var(--accent);
|
|
19
|
+
|
|
20
|
+
--success: var(--green);
|
|
21
|
+
--warning: var(--orange);
|
|
22
|
+
--danger: var(--red);
|
|
23
|
+
--error: var(--red);
|
|
24
|
+
--info: var(--blue);
|
|
25
|
+
|
|
26
|
+
--link: var(--accent);
|
|
27
|
+
--card: var(--white);
|
|
28
|
+
--button: var(--gray);
|
|
29
|
+
--menu: var(--card);
|
|
30
|
+
|
|
31
|
+
--shadow: rgb(50 50 93 / 0.25);
|
|
32
|
+
--backdrop: rgb(0 0 0 / 0.5);
|
|
33
|
+
|
|
34
|
+
--hover: rgb(50 50 93 / 0.03);
|
|
35
|
+
--outline: var(--accent);
|
|
36
|
+
--invalid: color-mix(in lab, var(--error) 3%, var(--input));
|
|
37
|
+
--selection: color-mix(in lab, var(--accent) 25%, transparent);
|
|
38
|
+
|
|
39
|
+
--font-family: ui-sans-serif, -apple-system, menu, sans-serif;
|
|
40
|
+
--font: 16px var(--font-family);
|
|
41
|
+
|
|
42
|
+
--box-shadow: var(--shadow) 0 2px 5px -1px, var(--shadow) 0 1px 3px -1px;
|
|
43
|
+
--inset-shadow: inset var(--shadow) 0 1px 3px -1px;
|
|
44
|
+
|
|
45
|
+
--gap: 1rem;
|
|
46
|
+
--indent: 0;
|
|
47
|
+
--half-gap: calc(var(--gap) / 2);
|
|
48
|
+
--third-gap: calc(var(--gap) / 3);
|
|
49
|
+
--gap2: calc(var(--gap) * 2);
|
|
50
|
+
--radius: 12px;
|
|
51
|
+
--menu-radius: calc(var(--radius) / 2);
|
|
52
|
+
--control-radius: calc(var(--radius) / 3);
|
|
53
|
+
|
|
54
|
+
--max-width: 720px;
|
|
55
|
+
--page-width: 1024px;
|
|
56
|
+
--aside-width: 20vw;
|
|
57
|
+
--modal-width: 720px;
|
|
58
|
+
|
|
59
|
+
--duration: 100ms;
|
|
61
60
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
[data-tip]:not(:has([data-tip]:hover)):hover {
|
|
2
|
+
&::before {
|
|
3
|
+
content: attr(data-tip, attr(title));
|
|
4
|
+
position: fixed;
|
|
5
|
+
display: block;
|
|
6
|
+
|
|
7
|
+
position-anchor: auto;
|
|
8
|
+
position-area: top;
|
|
9
|
+
position-try-fallbacks: flip-block;
|
|
10
|
+
|
|
11
|
+
z-index: 99;
|
|
12
|
+
background: var(--tooltip, rgb(0 0 0 / 0.8));
|
|
13
|
+
font-size: 0.8rem;
|
|
14
|
+
color: var(--white, white);
|
|
15
|
+
padding: 0.25rem 0.5rem;
|
|
16
|
+
max-width: 80%;
|
|
17
|
+
white-space: wrap;
|
|
18
|
+
border-radius: var(--control-radius);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -7,8 +7,10 @@
|
|
|
7
7
|
@import url(cafe_car/trix.css);
|
|
8
8
|
|
|
9
9
|
@import url(cafe_car/pagination.css);
|
|
10
|
+
@import url(cafe_car/tooltips.css);
|
|
10
11
|
@import url(cafe_car/utility.css);
|
|
11
12
|
@import url(ui/components.css);
|
|
13
|
+
@import url(Lexend.css);
|
|
12
14
|
@import url(cafe_car/themes/warm.css);
|
|
13
15
|
@import url(cafe_car/themes/warm-dark.css) (prefers-color-scheme: dark);
|
|
14
16
|
|
|
@@ -16,7 +18,7 @@
|
|
|
16
18
|
@import url(cafe_car/code/base16-dark.css) (prefers-color-scheme: dark);
|
|
17
19
|
|
|
18
20
|
:root {
|
|
19
|
-
--
|
|
21
|
+
--font-family: Lexend, ui-sans-serif, -apple-system, menu, sans-serif;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
:focus {
|
|
@@ -33,7 +35,7 @@ a {
|
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
&:active {
|
|
36
|
-
filter: brightness(.95);
|
|
38
|
+
filter: brightness(0.95);
|
|
37
39
|
}
|
|
38
40
|
}
|
|
39
41
|
|
|
@@ -53,20 +55,29 @@ body {
|
|
|
53
55
|
background-blend-mode: soft-light; */
|
|
54
56
|
font: var(--font);
|
|
55
57
|
margin: 0;
|
|
56
|
-
min-height:
|
|
58
|
+
min-height: 100dvh;
|
|
59
|
+
display: flex;
|
|
60
|
+
flex-flow: column;
|
|
61
|
+
|
|
62
|
+
> * {
|
|
63
|
+
flex: 1;
|
|
64
|
+
}
|
|
57
65
|
}
|
|
58
66
|
|
|
59
|
-
h1,
|
|
67
|
+
h1,
|
|
68
|
+
h2,
|
|
69
|
+
h4,
|
|
70
|
+
h5,
|
|
71
|
+
h6,
|
|
72
|
+
p {
|
|
60
73
|
margin-left: var(--indent);
|
|
61
74
|
}
|
|
62
75
|
|
|
63
76
|
time {
|
|
64
77
|
text-decoration: dotted underline;
|
|
65
78
|
text-decoration-color: var(--dim);
|
|
66
|
-
cursor: default;
|
|
67
79
|
}
|
|
68
80
|
|
|
69
|
-
|
|
70
81
|
html[data-turbo-visit-direction="forward"]::view-transition-group(root),
|
|
71
82
|
html[data-turbo-visit-direction="back"]::view-transition-group(root) {
|
|
72
83
|
animation: none;
|