cafe_car 0.1.0 → 0.1.1
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 +566 -7
- data/Rakefile +13 -0
- data/app/assets/images/noise.svg +16 -0
- data/app/assets/stylesheets/actiontext.css +31 -0
- data/app/assets/stylesheets/application.css +1 -0
- data/app/assets/stylesheets/cafe_car/code/base16-dark.css +89 -0
- data/app/assets/stylesheets/cafe_car/code/base16-light.css +90 -0
- data/app/assets/stylesheets/cafe_car/pagination.css +5 -0
- data/app/assets/stylesheets/cafe_car/themes/cool.css +32 -0
- data/app/assets/stylesheets/cafe_car/themes/cool2.css +31 -0
- data/app/assets/stylesheets/cafe_car/themes/defaults.css +61 -0
- data/app/assets/stylesheets/cafe_car/themes/warm-dark.css +29 -0
- data/app/assets/stylesheets/cafe_car/themes/warm.css +24 -0
- data/app/assets/stylesheets/cafe_car/trix.css +56 -0
- data/app/assets/stylesheets/cafe_car/utility.css +64 -0
- data/app/assets/stylesheets/cafe_car.css +85 -0
- data/app/assets/stylesheets/iconoir.css +22 -0
- data/app/assets/stylesheets/ui/Alert.css +24 -0
- data/app/assets/stylesheets/ui/Article.css +11 -0
- data/app/assets/stylesheets/ui/Button.css +42 -0
- data/app/assets/stylesheets/ui/Card.css +70 -0
- data/app/assets/stylesheets/ui/Code.css +4 -0
- data/app/assets/stylesheets/ui/Controls.css +16 -0
- data/app/assets/stylesheets/ui/Error.css +3 -0
- data/app/assets/stylesheets/ui/Example.css +45 -0
- data/app/assets/stylesheets/ui/Field.css +31 -0
- data/app/assets/stylesheets/ui/Grid.css +4 -0
- data/app/assets/stylesheets/ui/Group.css +16 -0
- data/app/assets/stylesheets/ui/Icon.css +27 -0
- data/app/assets/stylesheets/ui/Image.css +14 -0
- data/app/assets/stylesheets/ui/InfoCircle.css +11 -0
- data/app/assets/stylesheets/ui/Input.css +36 -0
- data/app/assets/stylesheets/ui/Layout.css +93 -0
- data/app/assets/stylesheets/ui/Menu.css +38 -0
- data/app/assets/stylesheets/ui/Modal.css +33 -0
- data/app/assets/stylesheets/ui/Navigation.css +29 -0
- data/app/assets/stylesheets/ui/Page.css +66 -0
- data/app/assets/stylesheets/ui/Row.css +9 -0
- data/app/assets/stylesheets/ui/Table.css +130 -0
- data/app/assets/stylesheets/ui/components.css +22 -0
- data/app/controllers/cafe_car/application_controller.rb +9 -0
- data/app/controllers/cafe_car/examples_controller.rb +22 -0
- data/app/javascript/application.js +5 -0
- data/app/javascript/cafe_car.js +169 -0
- data/app/models/cafe_car/slug.rb +3 -0
- data/app/policies/cafe_car/application_policy.rb +42 -0
- data/app/presenters/cafe_car/action_text/rich_text_presenter.rb +7 -0
- data/app/presenters/cafe_car/active_record/base_presenter.rb +6 -0
- data/app/presenters/cafe_car/active_record/relation_presenter.rb +17 -0
- data/app/presenters/cafe_car/active_storage/attached/one_presenter.rb +9 -0
- data/app/presenters/cafe_car/active_storage/attachment_presenter.rb +17 -0
- data/app/presenters/cafe_car/basic_object_presenter.rb +5 -0
- data/app/presenters/cafe_car/currency_presenter.rb +5 -0
- data/app/presenters/cafe_car/date_and_time/compatibility_presenter.rb +6 -0
- data/app/presenters/cafe_car/date_presenter.rb +4 -0
- data/app/presenters/cafe_car/date_time_presenter.rb +11 -0
- data/app/presenters/cafe_car/enumerable_presenter.rb +13 -0
- data/app/presenters/cafe_car/false_class_presenter.rb +5 -0
- data/app/presenters/cafe_car/hash_presenter.rb +11 -0
- data/app/presenters/cafe_car/nil_class_presenter.rb +13 -0
- data/app/presenters/cafe_car/presenter.rb +147 -0
- data/app/presenters/cafe_car/range_presenter.rb +16 -0
- data/app/presenters/cafe_car/record_presenter.rb +5 -0
- data/app/presenters/cafe_car/string_presenter.rb +20 -0
- data/app/presenters/cafe_car/symbol_presenter.rb +5 -0
- data/app/presenters/cafe_car/true_class_presenter.rb +5 -0
- data/app/ui/cafe_car/ui/button.rb +8 -0
- data/app/ui/cafe_car/ui/field.rb +11 -0
- data/app/ui/cafe_car/ui/page.rb +10 -0
- data/app/views/application/_actions.html.haml +1 -0
- data/app/views/application/_alerts.html.haml +2 -0
- data/app/views/application/_body.html.haml +6 -0
- data/app/views/application/_controls.html.haml +11 -0
- data/app/views/application/_debug.html.haml +11 -0
- data/app/views/application/_empty.html.haml +1 -0
- data/app/views/application/_errors.html.haml +8 -0
- data/app/views/application/_field.html.haml +5 -0
- data/app/views/application/_fields.html.haml +1 -0
- data/app/views/application/_filters.html.haml +8 -0
- data/app/views/application/_form.html.haml +6 -0
- data/app/views/application/_grid.html.haml +3 -0
- data/app/views/application/_grid_item.html.haml +1 -0
- data/app/views/application/_head.html.haml +16 -0
- data/app/views/application/_index.html.haml +4 -0
- data/app/views/application/_index_actions.html.haml +7 -0
- data/app/views/application/_navigation.html.haml +2 -0
- data/app/views/application/_navigation_links.html.haml +5 -0
- data/app/views/application/_notes.html.haml +9 -0
- data/app/views/application/_show.html.haml +9 -0
- data/app/views/application/_submit.html.haml +1 -0
- data/app/views/application/_table.html.haml +6 -0
- data/app/views/cafe_car/application/create.turbo_stream.haml +2 -0
- data/app/views/cafe_car/application/destroy.turbo_stream.haml +1 -0
- data/app/views/cafe_car/application/edit.html.haml +18 -0
- data/app/views/cafe_car/application/edit.turbo_stream.haml +9 -0
- data/app/views/cafe_car/application/index.html.haml +12 -0
- data/app/views/cafe_car/application/new.html.haml +8 -0
- data/app/views/cafe_car/application/new.turbo_stream.haml +9 -0
- data/app/views/cafe_car/application/show.html.haml +36 -0
- data/app/views/cafe_car/application/update.turbo_stream.haml +2 -0
- data/app/views/cafe_car/examples/_example.html.haml +12 -0
- data/app/views/cafe_car/examples/_index.html.haml +16 -0
- data/app/views/cafe_car/examples/_navigation_links.html.haml +2 -0
- data/app/views/cafe_car/examples/ui/_alert.html.haml +4 -0
- data/app/views/cafe_car/examples/ui/_button.html.haml +3 -0
- data/app/views/cafe_car/examples/ui/_card.html.haml +6 -0
- data/app/views/cafe_car/examples/ui/_controls.html.haml +3 -0
- data/app/views/cafe_car/examples/ui/_error.html.haml +1 -0
- data/app/views/cafe_car/examples/ui/_field.html.haml +9 -0
- data/app/views/cafe_car/examples/ui/_grid.html.haml +11 -0
- data/app/views/cafe_car/examples/ui/_group.html.haml +21 -0
- data/app/views/cafe_car/examples/ui/_info_circle.html.haml +1 -0
- data/app/views/cafe_car/examples/ui/_menu.html.haml +5 -0
- data/app/views/cafe_car/examples/ui/_modal.html.haml +4 -0
- data/app/views/cafe_car/examples/ui/_navigation.html.haml +4 -0
- data/app/views/cafe_car/examples/ui/_page.html.haml +4 -0
- data/app/views/cafe_car/examples/ui/_table.html.haml +13 -0
- data/app/views/cafe_car/layouts/mailer.html.haml +8 -0
- data/app/views/cafe_car/layouts/mailer.text.erb +1 -0
- data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
- data/app/views/layouts/application.html.haml +4 -0
- data/app/views/layouts/mailer.html.erb +13 -0
- data/app/views/layouts/mailer.text.erb +1 -0
- data/app/views/notes/_fields.html.haml +1 -0
- data/app/views/ui/_card.html.haml +13 -0
- data/app/views/ui/_field.html.haml +7 -0
- data/app/views/ui/_grid.html.haml +17 -0
- data/app/views/ui/_layout_menu.html.haml +2 -0
- data/app/views/ui/_modal_close.html.haml +2 -0
- data/app/views/ui/_page.html.haml +13 -0
- data/config/brakeman.ignore +77 -0
- data/config/importmap.rb +12 -0
- data/config/locales/en.yml +55 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20251005220017_create_slugs.rb +13 -0
- data/lib/cafe_car/active_record.rb +21 -0
- data/lib/cafe_car/application_responder.rb +11 -0
- data/lib/cafe_car/attributes.rb +23 -0
- data/lib/cafe_car/auto_resolver.rb +49 -0
- data/lib/cafe_car/caching.rb +20 -0
- data/lib/cafe_car/component.rb +92 -0
- data/lib/cafe_car/context.rb +16 -0
- data/lib/cafe_car/controller/filtering.rb +22 -0
- data/lib/cafe_car/controller.rb +179 -0
- data/lib/cafe_car/core_ext/array.rb +11 -0
- data/lib/cafe_car/core_ext.rb +7 -0
- data/lib/cafe_car/current.rb +6 -0
- data/lib/cafe_car/engine.rb +100 -0
- data/lib/cafe_car/field_builder.rb +44 -0
- data/lib/cafe_car/field_info.rb +142 -0
- data/lib/cafe_car/fields.rb +14 -0
- data/lib/cafe_car/filter/field_builder.rb +4 -0
- data/lib/cafe_car/filter/field_info.rb +22 -0
- data/lib/cafe_car/filter/form_builder.rb +21 -0
- data/lib/cafe_car/filter.rb +5 -0
- data/lib/cafe_car/filter_builder.rb +20 -0
- data/lib/cafe_car/form_builder.rb +105 -0
- data/lib/cafe_car/generators.rb +30 -0
- data/lib/cafe_car/helpers.rb +151 -0
- data/lib/cafe_car/href_builder.rb +71 -0
- data/lib/cafe_car/informable.rb +9 -0
- data/lib/cafe_car/input_builder.rb +25 -0
- data/lib/cafe_car/inputs/association_builder.rb +6 -0
- data/lib/cafe_car/inputs/base_input.rb +19 -0
- data/lib/cafe_car/inputs/belongs_to_builder.rb +6 -0
- data/lib/cafe_car/inputs/password_input.rb +7 -0
- data/lib/cafe_car/inputs/string_input.rb +7 -0
- data/lib/cafe_car/link_builder.rb +62 -0
- data/lib/cafe_car/model.rb +23 -0
- data/lib/cafe_car/model_info.rb +24 -0
- data/lib/cafe_car/name_patch.rb +17 -0
- data/lib/cafe_car/navigation.rb +76 -0
- data/lib/cafe_car/option_helpers.rb +47 -0
- data/lib/cafe_car/param_parser.rb +41 -0
- data/lib/cafe_car/pluralization.rb +15 -0
- data/lib/cafe_car/policy.rb +77 -0
- data/lib/cafe_car/proc_helpers.rb +13 -0
- data/lib/cafe_car/query_builder.rb +186 -0
- data/lib/cafe_car/queryable.rb +29 -0
- data/lib/cafe_car/resolver.rb +23 -0
- data/lib/cafe_car/routing.rb +17 -0
- data/lib/cafe_car/table/body_builder.rb +12 -0
- data/lib/cafe_car/table/builder.rb +51 -0
- data/lib/cafe_car/table/foot_builder.rb +14 -0
- data/lib/cafe_car/table/head_builder.rb +26 -0
- data/lib/cafe_car/table/label_builder.rb +48 -0
- data/lib/cafe_car/table/objects_builder.rb +8 -0
- data/lib/cafe_car/table/row_builder.rb +41 -0
- data/lib/cafe_car/table_builder.rb +13 -0
- data/lib/cafe_car/turbo_tag_builder.rb +7 -0
- data/lib/cafe_car/ui.rb +9 -0
- data/lib/cafe_car/version.rb +1 -1
- data/lib/cafe_car/visitors.rb +21 -0
- data/lib/cafe_car.rb +9 -177
- data/lib/generators/cafe_car/controller/USAGE +11 -0
- data/lib/generators/cafe_car/controller/controller_generator.rb +26 -0
- data/lib/generators/cafe_car/controller/templates/controller.rb.tt +5 -0
- data/lib/generators/cafe_car/install/USAGE +8 -0
- data/lib/generators/cafe_car/install/install_generator.rb +47 -0
- data/lib/generators/cafe_car/install/templates/application_policy.rb.tt +7 -0
- data/lib/generators/cafe_car/notes/USAGE +12 -0
- data/lib/generators/cafe_car/notes/notes_generator.rb +13 -0
- data/lib/generators/cafe_car/notes/templates/create_notes.rb.tt +12 -0
- data/lib/generators/cafe_car/notes/templates/notable.rb.tt +7 -0
- data/lib/generators/cafe_car/notes/templates/note.rb.tt +6 -0
- data/lib/generators/cafe_car/policy/USAGE +8 -0
- data/lib/generators/cafe_car/policy/policy_generator.rb +39 -0
- data/lib/generators/cafe_car/policy/templates/policy.rb.tt +20 -0
- data/lib/generators/cafe_car/resource/USAGE +13 -0
- data/lib/generators/cafe_car/resource/resource_generator.rb +32 -0
- metadata +410 -15
- data/app/views/cafe_car/application/_fields.html.erb +0 -7
- data/app/views/cafe_car/application/_filters.html.erb +0 -0
- data/app/views/cafe_car/application/_form.html.erb +0 -22
- data/lib/cafe_car/railtie.rb +0 -4
- /data/app/views/{cafe_car/application/_actions.html.erb → application/_aside.html.haml} +0 -0
- /data/app/views/{cafe_car/application/_aside.html.erb → application/_footer.html.haml} +0 -0
- /data/app/views/cafe_car/{application/_extra_fields.html.erb → examples/_index_actions.html.haml} +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f8d0f3cafde61c20c26c71051639e723c59aa9a9c1e6d3e8ad33b3a7d97aec8d
|
|
4
|
+
data.tar.gz: 315d84333c1c9a7bfe90b4ea7e58911cc5c3da3012f54ce942075f12863098ef
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 390d873729793c6dd8ec53988a0d6fe68eefa31bebd5df7999a7faf1ab16d46d50cd14f35afe6d671e56c774d0a2366526fbe9f4f631139a85e076e1da789e46
|
|
7
|
+
data.tar.gz: 4dc107cbc55e13033fbb5b7b9e55c50672ffa4ebe6eb8de23c6a523af0026ff4465207519e1bd250eb1958d50b379a70c4e308cd2ae8cbf6cb86b4a071aa6c3e
|
data/README.md
CHANGED
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
# CafeCar
|
|
2
|
-
Short description and motivation.
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
CafeCar is a Rails engine that extends the MVC "view" layer to provide automatic CRUD UI generation with sensible defaults. Its philosophy is rooted in the idea that Rails should render _something_ that represents the CRUD operations of your models by default. These defaults can then be expanded or overridden on either an application-wide or model-specific basis.
|
|
4
|
+
|
|
5
|
+
**Perfect for**: Admin panels, internal tools, and rapid prototyping.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🚀 **Auto-generated CRUD interfaces** - One line of code generates complete index, show, new, edit views
|
|
10
|
+
- 🎨 **Component-based UI system** - Flexible, composable components for building interfaces
|
|
11
|
+
- 🔐 **Built-in authorization** - Pundit integration for attribute-level permissions
|
|
12
|
+
- 📊 **Smart presenters** - Automatic type-aware display of your data
|
|
13
|
+
- 🔍 **Advanced filtering** - Range queries, comparison operators, and association filters
|
|
14
|
+
- 📄 **Pagination & sorting** - Kaminari integration with sortable columns
|
|
15
|
+
- ⚡ **Hotwire ready** - Turbo Streams support out of the box
|
|
16
|
+
- 📝 **Intelligent forms** - Auto-generated forms with smart field detection
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
- Rails 7.0+
|
|
21
|
+
- Ruby 3.0+
|
|
6
22
|
|
|
7
23
|
## Installation
|
|
24
|
+
|
|
8
25
|
Add this line to your application's Gemfile:
|
|
9
26
|
|
|
10
27
|
```ruby
|
|
@@ -12,17 +29,559 @@ gem "cafe_car"
|
|
|
12
29
|
```
|
|
13
30
|
|
|
14
31
|
And then execute:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
$ bundle install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Run the installer to set up CafeCar in your application:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
$ rails generate cafe_car:install
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This will:
|
|
44
|
+
- Add required dependencies (bcrypt, paper_trail, factory_bot_rails, faker, rouge)
|
|
45
|
+
- Mount the CafeCar engine at `/` under the `:admin` namespace
|
|
46
|
+
- Create `app/policies/application_policy.rb`
|
|
47
|
+
- Add `CafeCar::Controller` to your `ApplicationController`
|
|
48
|
+
- Set up JavaScript imports for Trix and ActionText
|
|
49
|
+
|
|
50
|
+
## Getting Started
|
|
51
|
+
|
|
52
|
+
### Quick Start: Generate a Complete Resource
|
|
53
|
+
|
|
54
|
+
The fastest way to get started is to generate a complete resource (model + controller + policy):
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
$ rails generate cafe_car:resource Product name:string price:decimal description:text
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
This creates:
|
|
61
|
+
- Migration and model (`app/models/product.rb`)
|
|
62
|
+
- Controller with CRUD actions (`app/controllers/products_controller.rb`)
|
|
63
|
+
- Policy with permission methods (`app/policies/product_policy.rb`)
|
|
64
|
+
|
|
65
|
+
Run migrations and start your server:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
$ rails db:migrate
|
|
69
|
+
$ rails server
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Navigate to `/products` and you'll see a fully functional CRUD interface!
|
|
73
|
+
|
|
74
|
+
### Manual Setup
|
|
75
|
+
|
|
76
|
+
You can also add CafeCar to existing resources:
|
|
77
|
+
|
|
78
|
+
#### 1. Add to Controller
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
class ProductsController < ApplicationController
|
|
82
|
+
recline_in_the_cafe_car
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
That single line provides:
|
|
87
|
+
- All 7 RESTful actions (index, show, new, create, edit, update, destroy)
|
|
88
|
+
- Automatic authorization via Pundit
|
|
89
|
+
- Filtering and sorting
|
|
90
|
+
- JSON/HTML/Turbo Stream responses
|
|
91
|
+
- Smart parameter handling
|
|
92
|
+
|
|
93
|
+
#### 2. Create a Policy
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
# app/policies/product_policy.rb
|
|
97
|
+
class ProductPolicy < ApplicationPolicy
|
|
98
|
+
def index? = user.present?
|
|
99
|
+
def show? = user.present?
|
|
100
|
+
def create? = user.admin?
|
|
101
|
+
def update? = user.admin?
|
|
102
|
+
def destroy? = user.admin?
|
|
103
|
+
|
|
104
|
+
def permitted_attributes
|
|
105
|
+
[:name, :price, :description, :category_id]
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The policy controls both authorization and which attributes can be edited.
|
|
111
|
+
|
|
112
|
+
## Core Components
|
|
113
|
+
|
|
114
|
+
### Controllers
|
|
115
|
+
|
|
116
|
+
The `CafeCar::Controller` module provides automatic CRUD functionality with the `recline_in_the_cafe_car` class method.
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
class Admin::ClientsController < ApplicationController
|
|
120
|
+
recline_in_the_cafe_car
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**What you get:**
|
|
125
|
+
|
|
126
|
+
- **RESTful actions**: `index`, `show`, `new`, `edit`, `create`, `update`, `destroy`
|
|
127
|
+
- **Authorization**: Automatic `authorize!` before each action
|
|
128
|
+
- **Smart defaults**: Model detection from controller name
|
|
129
|
+
- **Callbacks**: Lifecycle hooks for `render`, `update`, `create`, `destroy`
|
|
130
|
+
- **Responders**: JSON, HTML, and Turbo Stream responses
|
|
131
|
+
|
|
132
|
+
**Limiting actions:**
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
recline_in_the_cafe_car only: [:index, :show]
|
|
136
|
+
# or
|
|
137
|
+
recline_in_the_cafe_car except: [:destroy]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Custom model:**
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
class Admin::ClientsController < ApplicationController
|
|
144
|
+
model Company # Use Company model instead of Client
|
|
145
|
+
recline_in_the_cafe_car
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Callbacks:**
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
152
|
+
class ProductsController < ApplicationController
|
|
153
|
+
recline_in_the_cafe_car
|
|
154
|
+
|
|
155
|
+
set_callback :create, :after do |controller|
|
|
156
|
+
NotificationMailer.product_created(controller.object).deliver_later
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Policies
|
|
162
|
+
|
|
163
|
+
CafeCar extends Pundit with attribute-level permissions and auto-detection of displayable fields.
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
class ClientPolicy < ApplicationPolicy
|
|
167
|
+
def index? = admin?
|
|
168
|
+
def show? = admin?
|
|
169
|
+
def create? = admin?
|
|
170
|
+
def update? = admin?
|
|
171
|
+
def destroy? = update?
|
|
172
|
+
|
|
173
|
+
def permitted_attributes
|
|
174
|
+
[:name, :owner_id, :email, :phone]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
class Scope < Scope
|
|
178
|
+
def resolve
|
|
179
|
+
admin? ? scope.all : scope.where(owner: user)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Key methods:**
|
|
186
|
+
|
|
187
|
+
- `permitted_attributes` - Attributes that can be edited via forms
|
|
188
|
+
- `displayable_attributes` - Attributes shown in views (auto-detected from columns + associations)
|
|
189
|
+
- `displayable_associations` - Associations that can be displayed
|
|
190
|
+
- `filtered_attribute?(attr)` - Check if attribute should be hidden (uses Rails parameter filters)
|
|
191
|
+
|
|
192
|
+
**Scope pattern:**
|
|
193
|
+
|
|
194
|
+
The `Scope` class filters collections based on user permissions:
|
|
195
|
+
|
|
196
|
+
```ruby
|
|
197
|
+
class Scope < Scope
|
|
198
|
+
def resolve
|
|
199
|
+
admin? ? scope.all : scope.where(owner: user)
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Presenters
|
|
205
|
+
|
|
206
|
+
Presenters convert model objects into view-ready representations with automatic type detection.
|
|
207
|
+
|
|
208
|
+
**Automatic usage** (in views):
|
|
209
|
+
|
|
210
|
+
```erb
|
|
211
|
+
<%= present(@product) %>
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
This automatically:
|
|
215
|
+
1. Finds the appropriate presenter for the object type
|
|
216
|
+
2. Checks policy permissions
|
|
217
|
+
3. Renders displayable attributes
|
|
218
|
+
4. Uses type-specific formatting
|
|
219
|
+
|
|
220
|
+
**Custom presenters:**
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
# app/presenters/product_presenter.rb
|
|
224
|
+
class ProductPresenter < CafeCar::Presenter
|
|
225
|
+
show :name
|
|
226
|
+
show :price
|
|
227
|
+
show :description
|
|
228
|
+
show :category
|
|
229
|
+
show :created_at
|
|
230
|
+
|
|
231
|
+
# Custom display method
|
|
232
|
+
def preview
|
|
233
|
+
"#{name} - #{format_currency(price)}"
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
private
|
|
237
|
+
|
|
238
|
+
def format_currency(amount)
|
|
239
|
+
"$#{amount}"
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Built-in presenters:**
|
|
245
|
+
|
|
246
|
+
- `RecordPresenter` - ActiveRecord models
|
|
247
|
+
- `DatePresenter`, `DateTimePresenter` - Dates and times
|
|
248
|
+
- `CurrencyPresenter` - Money values
|
|
249
|
+
- `RangePresenter` - Range objects
|
|
250
|
+
- `ActiveStorage::AttachmentPresenter` - File attachments
|
|
251
|
+
- `ActionText::RichTextPresenter` - Rich text content
|
|
252
|
+
- `EnumerablePresenter`, `HashPresenter` - Collections
|
|
253
|
+
- `NilClassPresenter` - Handles nil values gracefully
|
|
254
|
+
|
|
255
|
+
**Presenter methods:**
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
presenter = present(@product)
|
|
259
|
+
presenter.show(:name) # Display single attribute
|
|
260
|
+
presenter.attributes # All displayable attributes
|
|
261
|
+
presenter.associations # All displayable associations
|
|
262
|
+
presenter.to_html # Render to HTML
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### UI Components
|
|
266
|
+
|
|
267
|
+
CafeCar provides a flexible component system for building interfaces.
|
|
268
|
+
|
|
269
|
+
**Basic usage:**
|
|
270
|
+
|
|
271
|
+
```ruby
|
|
272
|
+
# In views or helpers
|
|
273
|
+
ui.Card do
|
|
274
|
+
ui.Field label: "Name" do
|
|
275
|
+
@product.name
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Available components:**
|
|
281
|
+
|
|
282
|
+
- `Page` - Page container with title and actions
|
|
283
|
+
- `Grid`, `Row` - Layout containers
|
|
284
|
+
- `Card` - Content cards
|
|
285
|
+
- `Table` - Data tables
|
|
286
|
+
- `Field` - Form fields with labels
|
|
287
|
+
- `Button` - Action buttons
|
|
288
|
+
- `Modal` - Modal dialogs
|
|
289
|
+
- `Alert` - Flash messages
|
|
290
|
+
- `Menu`, `Navigation` - Navigation elements
|
|
291
|
+
|
|
292
|
+
**Component options:**
|
|
293
|
+
|
|
294
|
+
```ruby
|
|
295
|
+
ui.Button "Save", class: "primary", type: "submit"
|
|
296
|
+
ui.Field label: "Email", required: true, hint: "We'll never share this"
|
|
297
|
+
ui.Card title: "Details", collapsed: false
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**Custom components:**
|
|
301
|
+
|
|
302
|
+
Create partials in `app/views/cafe_car/ui/`:
|
|
303
|
+
|
|
304
|
+
```haml
|
|
305
|
+
-# app/views/cafe_car/ui/_badge.html.haml
|
|
306
|
+
%span.badge{ class: ui.classname }
|
|
307
|
+
= yield
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
Use it:
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
ui.Badge class: "success" do
|
|
314
|
+
"Active"
|
|
315
|
+
end
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Forms
|
|
319
|
+
|
|
320
|
+
CafeCar provides an enhanced form builder with smart field detection.
|
|
321
|
+
|
|
322
|
+
**Basic forms:**
|
|
323
|
+
|
|
324
|
+
```erb
|
|
325
|
+
<%= form_with model: @product do |f| %>
|
|
326
|
+
<%= f.input :name %>
|
|
327
|
+
<%= f.input :price %>
|
|
328
|
+
<%= f.input :description, as: :text %>
|
|
329
|
+
<%= f.association :category %>
|
|
330
|
+
<%= f.submit %>
|
|
331
|
+
<% end %>
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Smart field types:**
|
|
335
|
+
|
|
336
|
+
The form builder automatically detects field types:
|
|
337
|
+
|
|
338
|
+
- Password fields (columns named `password`, `password_confirmation`)
|
|
339
|
+
- File attachments (ActiveStorage `has_one_attached`, `has_many_attached`)
|
|
340
|
+
- Rich text (ActionText `has_rich_text`)
|
|
341
|
+
- Associations (belongs_to, has_many)
|
|
342
|
+
- Polymorphic associations
|
|
343
|
+
- Dates, datetimes, booleans, etc.
|
|
344
|
+
|
|
345
|
+
**Custom field rendering:**
|
|
346
|
+
|
|
347
|
+
```erb
|
|
348
|
+
<%= form_with model: @product do |f| %>
|
|
349
|
+
<%= f.field(:price).label %>
|
|
350
|
+
<%= f.field(:price).input class: "currency" %>
|
|
351
|
+
<%= f.field(:price).hint "In USD" %>
|
|
352
|
+
<%= f.field(:price).errors %>
|
|
353
|
+
<% end %>
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**Association select:**
|
|
357
|
+
|
|
358
|
+
```erb
|
|
359
|
+
<%= f.association :category %>
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
Automatically creates a select dropdown with all categories.
|
|
363
|
+
|
|
364
|
+
### Filtering & Sorting
|
|
365
|
+
|
|
366
|
+
CafeCar provides advanced filtering with minimal configuration.
|
|
367
|
+
|
|
368
|
+
**URL-based filtering:**
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
/products?name=Widget&price.min=10&price.max=50&created_at=2024-01-01..2024-12-31
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Filter operators:**
|
|
375
|
+
|
|
376
|
+
- **Range queries**: `created_at=2024..2025-01-01`
|
|
377
|
+
- **Comparisons**: `price.min=10`, `price.max=50`
|
|
378
|
+
- **Greater than**: `price.gt=10` or `price=>10`
|
|
379
|
+
- **Less than**: `price.lt=50` or `price=<50`
|
|
380
|
+
- **Equals**: `status=active` or `status.eq=active`
|
|
381
|
+
- **Arrays**: `tags=red,blue,green`
|
|
382
|
+
|
|
383
|
+
**Sorting:**
|
|
384
|
+
|
|
385
|
+
```
|
|
386
|
+
/products?sort=name # Ascending
|
|
387
|
+
/products?sort=-price # Descending (note the minus)
|
|
388
|
+
/products?sort=category,-price # Multiple columns
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
**In models:**
|
|
392
|
+
|
|
393
|
+
```ruby
|
|
394
|
+
class Product < ApplicationRecord
|
|
395
|
+
include CafeCar::Model # Auto-included via engine
|
|
396
|
+
end
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
The model gets:
|
|
400
|
+
- `sorted(*keys)` - Parse and apply sort parameters
|
|
401
|
+
- `normalized_sort_key()` - Convert sort keys to Arel format
|
|
402
|
+
|
|
403
|
+
**Custom filters in controllers:**
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
class ProductsController < ApplicationController
|
|
407
|
+
recline_in_the_cafe_car
|
|
408
|
+
|
|
409
|
+
private
|
|
410
|
+
|
|
411
|
+
def find_objects
|
|
412
|
+
@objects = model.where(active: true)
|
|
413
|
+
.query(filter_params)
|
|
414
|
+
.sorted(sort_params)
|
|
415
|
+
.page(page_params)
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Advanced Usage
|
|
421
|
+
|
|
422
|
+
### Customizing Views
|
|
423
|
+
|
|
424
|
+
Override default views by creating templates in your application:
|
|
425
|
+
|
|
426
|
+
```
|
|
427
|
+
app/views/
|
|
428
|
+
products/
|
|
429
|
+
index.html.haml # Override index view
|
|
430
|
+
show.html.haml # Override show view
|
|
431
|
+
_form.html.haml # Override form partial
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
CafeCar's default views are in `app/views/cafe_car/application/` and serve as templates.
|
|
435
|
+
|
|
436
|
+
### Custom Responders
|
|
437
|
+
|
|
438
|
+
```ruby
|
|
439
|
+
class ProductsController < ApplicationController
|
|
440
|
+
recline_in_the_cafe_car
|
|
441
|
+
|
|
442
|
+
private
|
|
443
|
+
|
|
444
|
+
def create
|
|
445
|
+
super
|
|
446
|
+
respond_with object, location: custom_path
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### Authorization Helpers
|
|
452
|
+
|
|
453
|
+
In controllers:
|
|
454
|
+
|
|
455
|
+
```ruby
|
|
456
|
+
authorize! # Authorize current action
|
|
457
|
+
policy(object).update? # Check specific permission
|
|
458
|
+
policy(object).permitted_attributes # Get editable attributes
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
In views:
|
|
462
|
+
|
|
463
|
+
```erb
|
|
464
|
+
<% if policy(@product).update? %>
|
|
465
|
+
<%= link_to "Edit", edit_product_path(@product) %>
|
|
466
|
+
<% end %>
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### Current Context
|
|
470
|
+
|
|
471
|
+
Access current request context anywhere:
|
|
472
|
+
|
|
473
|
+
```ruby
|
|
474
|
+
CafeCar::Current.user # Current user
|
|
475
|
+
CafeCar::Current.request_id # Request ID
|
|
476
|
+
CafeCar::Current.user_agent # User agent string
|
|
477
|
+
CafeCar::Current.ip_address # IP address
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Set in controllers via `set_current_attributes` (automatically called by `recline_in_the_cafe_car`).
|
|
481
|
+
|
|
482
|
+
## Generators
|
|
483
|
+
|
|
484
|
+
### Resource Generator
|
|
485
|
+
|
|
486
|
+
Generate a complete resource (model + controller + policy):
|
|
487
|
+
|
|
488
|
+
```bash
|
|
489
|
+
$ rails generate cafe_car:resource Product name:string price:decimal
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Controller Generator
|
|
493
|
+
|
|
494
|
+
Generate just a controller:
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
$ rails generate cafe_car:controller Products
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Policy Generator
|
|
501
|
+
|
|
502
|
+
Generate just a policy:
|
|
503
|
+
|
|
15
504
|
```bash
|
|
16
|
-
$
|
|
505
|
+
$ rails generate cafe_car:policy Product
|
|
17
506
|
```
|
|
18
507
|
|
|
19
|
-
|
|
508
|
+
### Notes Generator
|
|
509
|
+
|
|
510
|
+
Add polymorphic audit trail notes to your app:
|
|
511
|
+
|
|
20
512
|
```bash
|
|
21
|
-
$
|
|
513
|
+
$ rails generate cafe_car:notes
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
Creates:
|
|
517
|
+
- Migration for notes table
|
|
518
|
+
- `Note` model
|
|
519
|
+
- `Notable` concern for trackable models
|
|
520
|
+
|
|
521
|
+
## Configuration
|
|
522
|
+
|
|
523
|
+
### Custom Form Builder
|
|
524
|
+
|
|
525
|
+
```ruby
|
|
526
|
+
# config/initializers/cafe_car.rb
|
|
527
|
+
module CafeCar
|
|
528
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
|
529
|
+
# Your customizations
|
|
530
|
+
end
|
|
531
|
+
end
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
### Custom Presenter
|
|
535
|
+
|
|
536
|
+
```ruby
|
|
537
|
+
# app/presenters/application_presenter.rb
|
|
538
|
+
class ApplicationPresenter < CafeCar::Presenter
|
|
539
|
+
# Application-wide presenter customizations
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# app/presenters/product_presenter.rb
|
|
543
|
+
class ProductPresenter < ApplicationPresenter
|
|
544
|
+
show :name
|
|
545
|
+
show :price
|
|
546
|
+
end
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
### Custom Policy
|
|
550
|
+
|
|
551
|
+
```ruby
|
|
552
|
+
# app/policies/application_policy.rb
|
|
553
|
+
class ApplicationPolicy < CafeCar::ApplicationPolicy
|
|
554
|
+
def admin?
|
|
555
|
+
user&.admin?
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
```
|
|
559
|
+
|
|
560
|
+
## Testing
|
|
561
|
+
|
|
562
|
+
CafeCar integrates with standard Rails testing tools:
|
|
563
|
+
|
|
564
|
+
```ruby
|
|
565
|
+
# test/controllers/products_controller_test.rb
|
|
566
|
+
class ProductsControllerTest < ActionDispatch::IntegrationTest
|
|
567
|
+
test "index displays products" do
|
|
568
|
+
get products_url
|
|
569
|
+
assert_response :success
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
test "create with valid attributes" do
|
|
573
|
+
assert_difference "Product.count", 1 do
|
|
574
|
+
post products_url, params: { product: { name: "Widget" } }
|
|
575
|
+
end
|
|
576
|
+
assert_redirected_to product_path(Product.last)
|
|
577
|
+
end
|
|
578
|
+
end
|
|
22
579
|
```
|
|
23
580
|
|
|
24
581
|
## Contributing
|
|
25
|
-
|
|
582
|
+
|
|
583
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
26
584
|
|
|
27
585
|
## License
|
|
586
|
+
|
|
28
587
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
require "bundler/setup"
|
|
2
2
|
|
|
3
|
+
APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
|
|
4
|
+
load "rails/tasks/engine.rake"
|
|
5
|
+
|
|
3
6
|
require "bundler/gem_tasks"
|
|
7
|
+
require "rubocop/rake_task"
|
|
8
|
+
|
|
9
|
+
RuboCop::RakeTask.new
|
|
10
|
+
|
|
11
|
+
task :brakeman do
|
|
12
|
+
require "brakeman"
|
|
13
|
+
Brakeman.run app_path: ".", print_report: true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
task default: %i[rubocop test brakeman]
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!-- <svg viewBox='0 0 275 275' xmlns='http://www.w3.org/2000/svg'>
|
|
2
|
+
<filter id='noiseFilter'>
|
|
3
|
+
<feTurbulence type='fractalNoise' baseFrequency='1.8' numOctaves='3' stitchTiles='stitch' />
|
|
4
|
+
</filter>
|
|
5
|
+
|
|
6
|
+
<rect width='100%' height='100%' filter='url(#noiseFilter)' />
|
|
7
|
+
</svg> -->
|
|
8
|
+
<svg width="100%" height="100%" xmlns='http://www.w3.org/2000/svg'>
|
|
9
|
+
<filter id='noiseFilter'>
|
|
10
|
+
<feTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='5' />
|
|
11
|
+
<!-- <feColorMatrix type="matrix" values="0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0 0.5 0 0 0 0.5 0" /> -->
|
|
12
|
+
<feColorMatrix type="saturate" values=".1" />
|
|
13
|
+
</filter>
|
|
14
|
+
|
|
15
|
+
<rect width='100%' height='100%' filter='url(#noiseFilter)' />
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and
|
|
3
|
+
* the trix-editor content (whether displayed or under editing). Feel free to incorporate this
|
|
4
|
+
* inclusion directly in any other asset bundle and remove this file.
|
|
5
|
+
*
|
|
6
|
+
*= require trix
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* We need to override trix.css’s image gallery styles to accommodate the
|
|
11
|
+
* <action-text-attachment> element we wrap around attachments. Otherwise,
|
|
12
|
+
* images in galleries will be squished by the max-width: 33%; rule.
|
|
13
|
+
*/
|
|
14
|
+
.trix-content .attachment-gallery > action-text-attachment,
|
|
15
|
+
.trix-content .attachment-gallery > .attachment {
|
|
16
|
+
flex: 1 0 33%;
|
|
17
|
+
padding: 0 0.5em;
|
|
18
|
+
max-width: 33%;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment,
|
|
22
|
+
.trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment,
|
|
23
|
+
.trix-content .attachment-gallery.attachment-gallery--4 > .attachment {
|
|
24
|
+
flex-basis: 50%;
|
|
25
|
+
max-width: 50%;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.trix-content action-text-attachment .attachment {
|
|
29
|
+
padding: 0 !important;
|
|
30
|
+
max-width: 100% !important;
|
|
31
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import url(cafe_car.css);
|