cafe_car 0.1.0 → 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.
Files changed (240) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +682 -8
  3. data/Rakefile +21 -0
  4. data/app/assets/fonts/Lexend.css +7 -0
  5. data/app/assets/fonts/Lexend.ttf +0 -0
  6. data/app/assets/images/noise.svg +16 -0
  7. data/app/assets/stylesheets/actiontext.css +31 -0
  8. data/app/assets/stylesheets/application.css +1 -0
  9. data/app/assets/stylesheets/cafe_car/code/base16-dark.css +89 -0
  10. data/app/assets/stylesheets/cafe_car/code/base16-light.css +90 -0
  11. data/app/assets/stylesheets/cafe_car/pagination.css +5 -0
  12. data/app/assets/stylesheets/cafe_car/themes/cool.css +32 -0
  13. data/app/assets/stylesheets/cafe_car/themes/cool2.css +31 -0
  14. data/app/assets/stylesheets/cafe_car/themes/defaults.css +60 -0
  15. data/app/assets/stylesheets/cafe_car/themes/warm-dark.css +29 -0
  16. data/app/assets/stylesheets/cafe_car/themes/warm.css +24 -0
  17. data/app/assets/stylesheets/cafe_car/tooltips.css +20 -0
  18. data/app/assets/stylesheets/cafe_car/trix.css +56 -0
  19. data/app/assets/stylesheets/cafe_car/utility.css +63 -0
  20. data/app/assets/stylesheets/cafe_car.css +96 -0
  21. data/app/assets/stylesheets/iconoir.css +22 -0
  22. data/app/assets/stylesheets/ui/Alert.css +25 -0
  23. data/app/assets/stylesheets/ui/Article.css +11 -0
  24. data/app/assets/stylesheets/ui/Button.css +42 -0
  25. data/app/assets/stylesheets/ui/Card.css +74 -0
  26. data/app/assets/stylesheets/ui/Chat.css +33 -0
  27. data/app/assets/stylesheets/ui/Close.css +11 -0
  28. data/app/assets/stylesheets/ui/Code.css +4 -0
  29. data/app/assets/stylesheets/ui/Controls.css +16 -0
  30. data/app/assets/stylesheets/ui/Error.css +3 -0
  31. data/app/assets/stylesheets/ui/Example.css +45 -0
  32. data/app/assets/stylesheets/ui/Field.css +31 -0
  33. data/app/assets/stylesheets/ui/Grid.css +6 -0
  34. data/app/assets/stylesheets/ui/Group.css +16 -0
  35. data/app/assets/stylesheets/ui/Icon.css +27 -0
  36. data/app/assets/stylesheets/ui/Image.css +14 -0
  37. data/app/assets/stylesheets/ui/InfoCircle.css +11 -0
  38. data/app/assets/stylesheets/ui/Input.css +36 -0
  39. data/app/assets/stylesheets/ui/Layout.css +100 -0
  40. data/app/assets/stylesheets/ui/Menu.css +38 -0
  41. data/app/assets/stylesheets/ui/Modal.css +26 -0
  42. data/app/assets/stylesheets/ui/Navigation.css +37 -0
  43. data/app/assets/stylesheets/ui/Page.css +105 -0
  44. data/app/assets/stylesheets/ui/Row.css +9 -0
  45. data/app/assets/stylesheets/ui/Table.css +101 -0
  46. data/app/assets/stylesheets/ui/components.css +24 -0
  47. data/app/controllers/cafe_car/application_controller.rb +9 -0
  48. data/app/controllers/cafe_car/examples_controller.rb +22 -0
  49. data/app/controllers/cafe_car/sessions_controller.rb +30 -0
  50. data/app/controllers/concerns/cafe_car/authentication.rb +61 -0
  51. data/app/javascript/application.js +5 -0
  52. data/app/javascript/cafe_car.js +174 -0
  53. data/app/models/cafe_car/session.rb +18 -0
  54. data/app/policies/cafe_car/application_policy.rb +42 -0
  55. data/app/policies/cafe_car/session_policy.rb +19 -0
  56. data/app/presenters/cafe_car/action_text/rich_text_presenter.rb +7 -0
  57. data/app/presenters/cafe_car/active_record/base_presenter.rb +6 -0
  58. data/app/presenters/cafe_car/active_record/relation_presenter.rb +17 -0
  59. data/app/presenters/cafe_car/active_storage/attached/one_presenter.rb +9 -0
  60. data/app/presenters/cafe_car/active_storage/attachment_presenter.rb +18 -0
  61. data/app/presenters/cafe_car/basic_object_presenter.rb +5 -0
  62. data/app/presenters/cafe_car/code_presenter.rb +18 -0
  63. data/app/presenters/cafe_car/currency_presenter.rb +5 -0
  64. data/app/presenters/cafe_car/date_and_time/compatibility_presenter.rb +6 -0
  65. data/app/presenters/cafe_car/date_presenter.rb +5 -0
  66. data/app/presenters/cafe_car/date_time_presenter.rb +11 -0
  67. data/app/presenters/cafe_car/enumerable_presenter.rb +13 -0
  68. data/app/presenters/cafe_car/false_class_presenter.rb +5 -0
  69. data/app/presenters/cafe_car/hash_presenter.rb +6 -0
  70. data/app/presenters/cafe_car/nil_class_presenter.rb +13 -0
  71. data/app/presenters/cafe_car/presenter.rb +157 -0
  72. data/app/presenters/cafe_car/range_presenter.rb +16 -0
  73. data/app/presenters/cafe_car/record_presenter.rb +5 -0
  74. data/app/presenters/cafe_car/string_presenter.rb +20 -0
  75. data/app/presenters/cafe_car/symbol_presenter.rb +5 -0
  76. data/app/presenters/cafe_car/true_class_presenter.rb +5 -0
  77. data/app/ui/cafe_car/ui/button.rb +9 -0
  78. data/app/ui/cafe_car/ui/card.rb +18 -0
  79. data/app/ui/cafe_car/ui/field.rb +11 -0
  80. data/app/ui/cafe_car/ui/grid.rb +30 -0
  81. data/app/ui/cafe_car/ui/layout.rb +7 -0
  82. data/app/ui/cafe_car/ui/page.rb +14 -0
  83. data/app/views/application/_actions.html.haml +1 -0
  84. data/app/views/application/_alerts.html.haml +2 -0
  85. data/app/views/application/_body.html.haml +7 -0
  86. data/app/views/application/_controls.html.haml +12 -0
  87. data/app/views/application/_debug.html.haml +18 -0
  88. data/app/views/application/_empty.html.haml +1 -0
  89. data/app/views/application/_errors.html.haml +4 -0
  90. data/app/views/application/_field.html.haml +5 -0
  91. data/app/views/application/_fields.html.haml +1 -0
  92. data/app/views/application/_filters.html.haml +8 -0
  93. data/app/views/application/_form.html.haml +6 -0
  94. data/app/views/application/_grid.html.haml +3 -0
  95. data/app/views/application/_grid_item.html.haml +1 -0
  96. data/app/views/application/_head.html.haml +17 -0
  97. data/app/views/application/_index.html.haml +8 -0
  98. data/app/views/application/_index_actions.html.haml +7 -0
  99. data/app/views/application/_navigation.html.haml +9 -0
  100. data/app/views/application/_navigation_links.html.haml +5 -0
  101. data/app/views/application/_notes.html.haml +10 -0
  102. data/app/views/application/_popup.html.haml +7 -0
  103. data/app/views/application/_show.html.haml +9 -0
  104. data/app/views/application/_submit.html.haml +1 -0
  105. data/app/views/application/_table.html.haml +6 -0
  106. data/app/views/cafe_car/application/create.turbo_stream.haml +2 -0
  107. data/app/views/cafe_car/application/destroy.turbo_stream.haml +1 -0
  108. data/app/views/cafe_car/application/edit.html.haml +18 -0
  109. data/app/views/cafe_car/application/edit.turbo_stream.haml +7 -0
  110. data/app/views/cafe_car/application/index.html.haml +15 -0
  111. data/app/views/cafe_car/application/new.html.haml +8 -0
  112. data/app/views/cafe_car/application/new.turbo_stream.haml +8 -0
  113. data/app/views/cafe_car/application/show.html.haml +36 -0
  114. data/app/views/cafe_car/application/update.turbo_stream.haml +2 -0
  115. data/app/views/cafe_car/examples/_example.html.haml +12 -0
  116. data/app/views/cafe_car/examples/_index.html.haml +16 -0
  117. data/app/views/cafe_car/examples/_navigation_links.html.haml +2 -0
  118. data/app/views/cafe_car/examples/ui/_alert.html.haml +4 -0
  119. data/app/views/cafe_car/examples/ui/_button.html.haml +3 -0
  120. data/app/views/cafe_car/examples/ui/_card.html.haml +6 -0
  121. data/app/views/cafe_car/examples/ui/_chat.html.haml +3 -0
  122. data/app/views/cafe_car/examples/ui/_controls.html.haml +3 -0
  123. data/app/views/cafe_car/examples/ui/_error.html.haml +1 -0
  124. data/app/views/cafe_car/examples/ui/_field.html.haml +9 -0
  125. data/app/views/cafe_car/examples/ui/_grid.html.haml +11 -0
  126. data/app/views/cafe_car/examples/ui/_group.html.haml +21 -0
  127. data/app/views/cafe_car/examples/ui/_info_circle.html.haml +1 -0
  128. data/app/views/cafe_car/examples/ui/_menu.html.haml +5 -0
  129. data/app/views/cafe_car/examples/ui/_modal.html.haml +4 -0
  130. data/app/views/cafe_car/examples/ui/_navigation.html.haml +4 -0
  131. data/app/views/cafe_car/examples/ui/_page.html.haml +4 -0
  132. data/app/views/cafe_car/examples/ui/_table.html.haml +13 -0
  133. data/app/views/cafe_car/layouts/mailer.html.haml +8 -0
  134. data/app/views/cafe_car/layouts/mailer.text.erb +1 -0
  135. data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  136. data/app/views/layouts/application.html.haml +4 -0
  137. data/app/views/layouts/mailer.html.erb +13 -0
  138. data/app/views/layouts/mailer.text.erb +1 -0
  139. data/app/views/notes/_fields.html.haml +1 -0
  140. data/app/views/passwords_mailer/reset.html.haml +5 -0
  141. data/app/views/passwords_mailer/reset.text.erb +4 -0
  142. data/app/views/ui/_card.html.haml +8 -0
  143. data/app/views/ui/_field.html.haml +1 -0
  144. data/app/views/ui/_modal_close.html.haml +1 -0
  145. data/app/views/ui/_page.html.haml +7 -0
  146. data/config/brakeman.ignore +77 -0
  147. data/config/importmap.rb +12 -0
  148. data/config/locales/en.yml +63 -0
  149. data/config/routes.rb +9 -0
  150. data/db/migrate/20251005220017_create_slugs.rb +13 -0
  151. data/lib/cafe_car/active_record.rb +21 -0
  152. data/lib/cafe_car/application_responder.rb +17 -0
  153. data/lib/cafe_car/attributes.rb +23 -0
  154. data/lib/cafe_car/auto_resolver.rb +49 -0
  155. data/lib/cafe_car/caching.rb +20 -0
  156. data/lib/cafe_car/component.rb +155 -0
  157. data/lib/cafe_car/context.rb +17 -0
  158. data/lib/cafe_car/controller/filtering.rb +30 -0
  159. data/lib/cafe_car/controller.rb +218 -0
  160. data/lib/cafe_car/core_ext/array.rb +24 -0
  161. data/lib/cafe_car/core_ext/hash.rb +15 -0
  162. data/lib/cafe_car/core_ext/module.rb +15 -0
  163. data/lib/cafe_car/core_ext.rb +5 -0
  164. data/lib/cafe_car/current.rb +9 -0
  165. data/lib/cafe_car/engine.rb +107 -0
  166. data/lib/cafe_car/field_builder.rb +44 -0
  167. data/lib/cafe_car/field_info.rb +144 -0
  168. data/lib/cafe_car/fields.rb +21 -0
  169. data/lib/cafe_car/filter/field_builder.rb +4 -0
  170. data/lib/cafe_car/filter/field_info.rb +22 -0
  171. data/lib/cafe_car/filter/form_builder.rb +21 -0
  172. data/lib/cafe_car/filter.rb +5 -0
  173. data/lib/cafe_car/filter_builder.rb +20 -0
  174. data/lib/cafe_car/form_builder.rb +105 -0
  175. data/lib/cafe_car/generators.rb +30 -0
  176. data/lib/cafe_car/helpers.rb +178 -0
  177. data/lib/cafe_car/href_builder.rb +97 -0
  178. data/lib/cafe_car/informable.rb +9 -0
  179. data/lib/cafe_car/input_builder.rb +25 -0
  180. data/lib/cafe_car/inputs/association_builder.rb +6 -0
  181. data/lib/cafe_car/inputs/base_input.rb +19 -0
  182. data/lib/cafe_car/inputs/belongs_to_builder.rb +6 -0
  183. data/lib/cafe_car/inputs/password_input.rb +7 -0
  184. data/lib/cafe_car/inputs/string_input.rb +7 -0
  185. data/lib/cafe_car/link_builder.rb +65 -0
  186. data/lib/cafe_car/model.rb +23 -0
  187. data/lib/cafe_car/model_info.rb +24 -0
  188. data/lib/cafe_car/name_patch.rb +17 -0
  189. data/lib/cafe_car/navigation.rb +76 -0
  190. data/lib/cafe_car/option_helpers.rb +53 -0
  191. data/lib/cafe_car/param_parser.rb +45 -0
  192. data/lib/cafe_car/pluralization.rb +15 -0
  193. data/lib/cafe_car/policy.rb +77 -0
  194. data/lib/cafe_car/proc_helpers.rb +13 -0
  195. data/lib/cafe_car/query_builder.rb +186 -0
  196. data/lib/cafe_car/queryable.rb +29 -0
  197. data/lib/cafe_car/resolver.rb +27 -0
  198. data/lib/cafe_car/routing.rb +17 -0
  199. data/lib/cafe_car/table/body_builder.rb +12 -0
  200. data/lib/cafe_car/table/builder.rb +52 -0
  201. data/lib/cafe_car/table/foot_builder.rb +14 -0
  202. data/lib/cafe_car/table/head_builder.rb +26 -0
  203. data/lib/cafe_car/table/label_builder.rb +48 -0
  204. data/lib/cafe_car/table/objects_builder.rb +8 -0
  205. data/lib/cafe_car/table/row_builder.rb +39 -0
  206. data/lib/cafe_car/table_builder.rb +13 -0
  207. data/lib/cafe_car/turbo_tag_builder.rb +7 -0
  208. data/lib/cafe_car/ui.rb +11 -0
  209. data/lib/cafe_car/version.rb +1 -1
  210. data/lib/cafe_car/visitors.rb +21 -0
  211. data/lib/cafe_car.rb +25 -168
  212. data/lib/generators/cafe_car/controller/USAGE +11 -0
  213. data/lib/generators/cafe_car/controller/controller_generator.rb +26 -0
  214. data/lib/generators/cafe_car/controller/templates/controller.rb.tt +5 -0
  215. data/lib/generators/cafe_car/install/USAGE +8 -0
  216. data/lib/generators/cafe_car/install/install_generator.rb +46 -0
  217. data/lib/generators/cafe_car/install/templates/application_policy.rb.tt +7 -0
  218. data/lib/generators/cafe_car/notes/USAGE +12 -0
  219. data/lib/generators/cafe_car/notes/notes_generator.rb +13 -0
  220. data/lib/generators/cafe_car/notes/templates/create_notes.rb.tt +12 -0
  221. data/lib/generators/cafe_car/notes/templates/notable.rb.tt +7 -0
  222. data/lib/generators/cafe_car/notes/templates/note.rb.tt +6 -0
  223. data/lib/generators/cafe_car/policy/USAGE +8 -0
  224. data/lib/generators/cafe_car/policy/policy_generator.rb +39 -0
  225. data/lib/generators/cafe_car/policy/templates/policy.rb.tt +20 -0
  226. data/lib/generators/cafe_car/resource/USAGE +13 -0
  227. data/lib/generators/cafe_car/resource/resource_generator.rb +32 -0
  228. data/lib/generators/cafe_car/sessions/USAGE +17 -0
  229. data/lib/generators/cafe_car/sessions/sessions_generator.rb +29 -0
  230. data/lib/generators/cafe_car/sessions/templates/create_sessions.rb.tt +12 -0
  231. data/lib/tasks/holdco_tasks.rake +532 -0
  232. data/lib/tasks/templates/tasks_header.md +37 -0
  233. metadata +444 -21
  234. data/app/views/cafe_car/application/_fields.html.erb +0 -7
  235. data/app/views/cafe_car/application/_filters.html.erb +0 -0
  236. data/app/views/cafe_car/application/_form.html.erb +0 -22
  237. data/lib/cafe_car/railtie.rb +0 -4
  238. /data/app/views/{cafe_car/application/_actions.html.erb → application/_aside.html.haml} +0 -0
  239. /data/app/views/{cafe_car/application/_aside.html.erb → application/_footer.html.haml} +0 -0
  240. /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: 4e445b1f35396aa6ae7810eaab23e0d5b57bfb4ad6ea8fed5eb723586dbf1fa7
4
- data.tar.gz: d914a80b54cda7f0579303a909a38093eae32dc1836d19f91a200fffb3775eee
3
+ metadata.gz: fdfdd44f9f4f564e6fe25b17a694b0dd6dce990efdc30990d0cef474a98cc718
4
+ data.tar.gz: e483b2317f3a39b3b7c57b9c44bd20e3e766376f4889551bdc8ed199b8e6db52
5
5
  SHA512:
6
- metadata.gz: 52a1deab336f310203bb1e31779ed76a2da3a9616e84fdf6b7a7b3962b503f3d903d623a1c44143203c81a44a680d56a2d0a844b57f70f93dde727232538abd1
7
- data.tar.gz: 5a3f0a0113086d9826d8952093af024f9c51091f261e7ab78b98de9796a1b69647fc436979479f4b68773d2350f5c3451853b03c6faf8c6cba45b47f71239e28
6
+ metadata.gz: daba4f7613586a3ff4f74e952800d3738965afafb81fbf1bfe82eec4b3ae5363c99d7aa879c1d3be438ff1ecdbf18d37d071307072487a1d2fb434dbfa3a6b62
7
+ data.tar.gz: '051968fadd32602c6b8884443200f030aeae9bcecb731c602161b854f0222e8197b65d568eadce9e1c45175cc66b64fab899d74f5df367df856f7c39e29bc8eb'
data/README.md CHANGED
@@ -1,10 +1,39 @@
1
1
  # CafeCar
2
- Short description and motivation.
3
2
 
4
- ## Usage
5
- How to use my plugin.
3
+ [![CI](https://img.shields.io/github/actions/workflow/status/craft-concept/cafe_car/ci.yml?branch=main&label=CI)](https://github.com/craft-concept/cafe_car/actions/workflows/ci.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/cafe_car)](https://rubygems.org/gems/cafe_car)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green)](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.
12
+
13
+ **Perfect for**: Admin panels, internal tools, and rapid prototyping.
14
+
15
+ ## Features
16
+
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
23
+ - 📊 **Smart presenters** - Automatic type-aware display of your data
24
+ - 🔍 **Advanced filtering** - Range queries, comparison operators, and
25
+ association filters
26
+ - 📄 **Pagination & sorting** - Kaminari integration with sortable columns
27
+ - ⚡ **Hotwire ready** - Turbo Streams support out of the box
28
+ - 📝 **Intelligent forms** - Auto-generated forms with smart field detection
29
+
30
+ ## Prerequisites
31
+
32
+ - Ruby 3.3+ (developed and tested against 3.3.5)
33
+ - Rails 8.0+ (developed and tested against Rails 8.1)
6
34
 
7
35
  ## Installation
36
+
8
37
  Add this line to your application's Gemfile:
9
38
 
10
39
  ```ruby
@@ -12,17 +41,662 @@ gem "cafe_car"
12
41
  ```
13
42
 
14
43
  And then execute:
44
+
45
+ ```bash
46
+ $ bundle install
47
+ ```
48
+
49
+ Run the installer to set up CafeCar in your application:
50
+
51
+ ```bash
52
+ $ rails generate cafe_car:install
53
+ ```
54
+
55
+ This will:
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)
60
+ - Mount the CafeCar engine at `/` under the `:admin` namespace
61
+ - Create `app/policies/application_policy.rb`
62
+ - Add `CafeCar::Controller` to your `ApplicationController`
63
+ - Set up JavaScript imports for CafeCar, Trix, and ActionText
64
+
65
+ ## Getting Started
66
+
67
+ ### Quick Start: Generate a Complete Resource
68
+
69
+ The fastest way to get started is to generate a complete resource (model +
70
+ controller + policy):
71
+
72
+ ```bash
73
+ $ rails generate cafe_car:resource Product name:string price:decimal description:text
74
+ ```
75
+
76
+ This creates:
77
+
78
+ - Migration and model (`app/models/product.rb`)
79
+ - Controller with CRUD actions (`app/controllers/products_controller.rb`)
80
+ - Policy with permission methods (`app/policies/product_policy.rb`)
81
+
82
+ Run migrations and start your server:
83
+
84
+ ```bash
85
+ $ rails db:migrate
86
+ $ rails server
87
+ ```
88
+
89
+ Navigate to `/products` and you'll see a fully functional CRUD interface!
90
+
91
+ ### Manual Setup
92
+
93
+ You can also add CafeCar to existing resources:
94
+
95
+ #### 1. Add to Controller
96
+
97
+ ```ruby
98
+ class ProductsController < ApplicationController
99
+ cafe_car
100
+ end
101
+ ```
102
+
103
+ That single line provides:
104
+
105
+ - All 7 RESTful actions (index, show, new, create, edit, update, destroy)
106
+ - Automatic authorization via Pundit
107
+ - Filtering and sorting
108
+ - JSON/HTML/Turbo Stream responses
109
+ - Smart parameter handling
110
+
111
+ #### 2. Create a Policy
112
+
113
+ ```ruby
114
+ # app/policies/product_policy.rb
115
+ class ProductPolicy < ApplicationPolicy
116
+ def index? = user.present?
117
+ def show? = user.present?
118
+ def create? = user.admin?
119
+ def update? = user.admin?
120
+ def destroy? = user.admin?
121
+
122
+ def permitted_attributes
123
+ [:name, :price, :description, :category_id]
124
+ end
125
+ end
126
+ ```
127
+
128
+ The policy controls both authorization and which attributes can be edited.
129
+
130
+ ## Core Components
131
+
132
+ ### Controllers
133
+
134
+ The `CafeCar::Controller` module provides automatic CRUD functionality with the
135
+ `cafe_car` class method.
136
+
137
+ ```ruby
138
+ class Admin::ClientsController < ApplicationController
139
+ cafe_car
140
+ end
141
+ ```
142
+
143
+ **What you get:**
144
+
145
+ - **RESTful actions**: `index`, `show`, `new`, `edit`, `create`, `update`,
146
+ `destroy`
147
+ - **Authorization**: Automatic `authorize!` before each action
148
+ - **Smart defaults**: Model detection from controller name
149
+ - **Callbacks**: Lifecycle hooks for `render`, `update`, `create`, `destroy`
150
+ - **Responders**: JSON, HTML, and Turbo Stream responses
151
+
152
+ **Limiting actions:**
153
+
154
+ ```ruby
155
+ cafe_car only: [:index, :show]
156
+ # or
157
+ cafe_car except: [:destroy]
158
+ ```
159
+
160
+ **Custom model:**
161
+
162
+ ```ruby
163
+ class Admin::ClientsController < ApplicationController
164
+ model Company # Use Company model instead of Client
165
+ cafe_car
166
+ end
167
+ ```
168
+
169
+ **Callbacks:**
170
+
171
+ ```ruby
172
+ class ProductsController < ApplicationController
173
+ cafe_car
174
+
175
+ set_callback :create, :after do |controller|
176
+ NotificationMailer.product_created(controller.object).deliver_later
177
+ end
178
+ end
179
+ ```
180
+
181
+ ### Policies
182
+
183
+ CafeCar extends Pundit with attribute-level permissions and auto-detection of
184
+ displayable fields.
185
+
186
+ ```ruby
187
+ class ClientPolicy < ApplicationPolicy
188
+ def index? = admin?
189
+ def show? = admin?
190
+ def create? = admin?
191
+ def update? = admin?
192
+ def destroy? = update?
193
+
194
+ def permitted_attributes
195
+ [:name, :owner_id, :email, :phone]
196
+ end
197
+
198
+ class Scope < Scope
199
+ def resolve
200
+ admin? ? scope.all : scope.where(owner: user)
201
+ end
202
+ end
203
+ end
204
+ ```
205
+
206
+ **Key methods:**
207
+
208
+ - `permitted_attributes` - Attributes that can be edited via forms
209
+ - `displayable_attributes` - Attributes shown in views (auto-detected from
210
+ columns + associations)
211
+ - `displayable_associations` - Associations that can be displayed
212
+ - `filtered_attribute?(attr)` - Check if attribute should be hidden (uses Rails
213
+ parameter filters)
214
+
215
+ **Scope pattern:**
216
+
217
+ The `Scope` class filters collections based on user permissions:
218
+
219
+ ```ruby
220
+ class Scope < Scope
221
+ def resolve
222
+ admin? ? scope.all : scope.where(owner: user)
223
+ end
224
+ end
225
+ ```
226
+
227
+ ### Presenters
228
+
229
+ Presenters convert model objects into view-ready representations with automatic
230
+ type detection.
231
+
232
+ **Automatic usage** (in views):
233
+
234
+ ```erb
235
+ <%= present(@product) %>
236
+ ```
237
+
238
+ This automatically:
239
+
240
+ 1. Finds the appropriate presenter for the object type
241
+ 2. Checks policy permissions
242
+ 3. Renders displayable attributes
243
+ 4. Uses type-specific formatting
244
+
245
+ **Custom presenters:**
246
+
247
+ ```ruby
248
+ # app/presenters/product_presenter.rb
249
+ class ProductPresenter < CafeCar::Presenter
250
+ show :name
251
+ show :price
252
+ show :description
253
+ show :category
254
+ show :created_at
255
+
256
+ # Custom display method
257
+ def preview
258
+ "#{name} - #{format_currency(price)}"
259
+ end
260
+
261
+ private
262
+
263
+ def format_currency(amount)
264
+ "$#{amount}"
265
+ end
266
+ end
267
+ ```
268
+
269
+ **Built-in presenters:**
270
+
271
+ - `RecordPresenter` - ActiveRecord models
272
+ - `DatePresenter`, `DateTimePresenter` - Dates and times
273
+ - `CurrencyPresenter` - Money values
274
+ - `RangePresenter` - Range objects
275
+ - `ActiveStorage::AttachmentPresenter` - File attachments
276
+ - `ActionText::RichTextPresenter` - Rich text content
277
+ - `EnumerablePresenter`, `HashPresenter` - Collections
278
+ - `NilClassPresenter` - Handles nil values gracefully
279
+
280
+ **Presenter methods:**
281
+
282
+ ```ruby
283
+ presenter = present(@product)
284
+ presenter.show(:name) # Display single attribute
285
+ presenter.attributes # All displayable attributes
286
+ presenter.associations # All displayable associations
287
+ presenter.to_html # Render to HTML
288
+ ```
289
+
290
+ ### UI Components
291
+
292
+ CafeCar provides a flexible component system for building interfaces.
293
+
294
+ **Basic usage:**
295
+
296
+ ```ruby
297
+ # In views or helpers
298
+ ui.Card do
299
+ ui.Field label: "Name" do
300
+ @product.name
301
+ end
302
+ end
303
+ ```
304
+
305
+ **Available components:**
306
+
307
+ - `Page` - Page container with title and actions
308
+ - `Grid`, `Row` - Layout containers
309
+ - `Card` - Content cards
310
+ - `Table` - Data tables
311
+ - `Field` - Form fields with labels
312
+ - `Button` - Action buttons
313
+ - `Modal` - Modal dialogs
314
+ - `Alert` - Flash messages
315
+ - `Menu`, `Navigation` - Navigation elements
316
+
317
+ **Component options:**
318
+
319
+ ```ruby
320
+ ui.Button "Save", class: "primary", type: "submit"
321
+ ui.Field label: "Email", required: true, hint: "We'll never share this"
322
+ ui.Card title: "Details", collapsed: false
323
+ ```
324
+
325
+ **Custom components:**
326
+
327
+ Create partials in `app/views/cafe_car/ui/`:
328
+
329
+ ```haml
330
+ -# app/views/cafe_car/ui/_badge.html.haml
331
+ %span.badge{ class: ui.classname }
332
+ = yield
333
+ ```
334
+
335
+ Use it:
336
+
337
+ ```ruby
338
+ ui.Badge class: "success" do
339
+ "Active"
340
+ end
341
+ ```
342
+
343
+ ### Forms
344
+
345
+ CafeCar provides an enhanced form builder with smart field detection.
346
+
347
+ **Basic forms:**
348
+
349
+ ```erb
350
+ <%= form_with model: @product do |f| %>
351
+ <%= f.input :name %>
352
+ <%= f.input :price %>
353
+ <%= f.input :description, as: :text %>
354
+ <%= f.association :category %>
355
+ <%= f.submit %>
356
+ <% end %>
357
+ ```
358
+
359
+ **Smart field types:**
360
+
361
+ The form builder automatically detects field types:
362
+
363
+ - Password fields (columns named `password`, `password_confirmation`)
364
+ - File attachments (ActiveStorage `has_one_attached`, `has_many_attached`)
365
+ - Rich text (ActionText `has_rich_text`)
366
+ - Associations (belongs_to, has_many)
367
+ - Polymorphic associations
368
+ - Dates, datetimes, booleans, etc.
369
+
370
+ **Custom field rendering:**
371
+
372
+ ```erb
373
+ <%= form_with model: @product do |f| %>
374
+ <%= f.field(:price).label %>
375
+ <%= f.field(:price).input class: "currency" %>
376
+ <%= f.field(:price).hint "In USD" %>
377
+ <%= f.field(:price).error %>
378
+ <% end %>
379
+ ```
380
+
381
+ **Association select:**
382
+
383
+ ```erb
384
+ <%= f.association :category %>
385
+ ```
386
+
387
+ Automatically creates a select dropdown with all categories.
388
+
389
+ ### Filtering & Sorting
390
+
391
+ CafeCar provides advanced filtering with minimal configuration.
392
+
393
+ **URL-based filtering:**
394
+
395
+ ```
396
+ /products?name=Widget&price.min=10&price.max=50&created_at=2024-01-01..2024-12-31
397
+ ```
398
+
399
+ **Filter operators:**
400
+
401
+ - **Range queries**: `created_at=2024..2025-01-01`
402
+ - **Comparisons**: `price.min=10`, `price.max=50`
403
+ - **Greater than**: `price.gt=10` or `price=>10`
404
+ - **Less than**: `price.lt=50` or `price=<50`
405
+ - **Equals**: `status=active` or `status.eq=active`
406
+ - **Arrays**: `tags=red,blue,green`
407
+
408
+ **Sorting:**
409
+
410
+ ```
411
+ /products?sort=name # Ascending
412
+ /products?sort=-price # Descending (note the minus)
413
+ /products?sort=category,-price # Multiple columns
414
+ ```
415
+
416
+ **In models:**
417
+
418
+ ```ruby
419
+ class Product < ApplicationRecord
420
+ include CafeCar::Model # Auto-included via engine
421
+ end
422
+ ```
423
+
424
+ The model gets:
425
+
426
+ - `sorted(*keys)` - Parse and apply sort parameters
427
+ - `normalize_sort_key(key)` - Internal helper that converts a sort key to Arel
428
+ order format
429
+
430
+ **Custom filters in controllers:**
431
+
432
+ ```ruby
433
+ class ProductsController < ApplicationController
434
+ cafe_car
435
+
436
+ private
437
+
438
+ def find_objects
439
+ @objects = model.where(active: true)
440
+ .query(filter_params)
441
+ .sorted(sort_params)
442
+ .page(page_params)
443
+ end
444
+ end
445
+ ```
446
+
447
+ ## Advanced Usage
448
+
449
+ ### Customizing Views
450
+
451
+ Override default views by creating templates in your application:
452
+
453
+ ```
454
+ app/views/
455
+ products/
456
+ index.html.haml # Override index view
457
+ show.html.haml # Override show view
458
+ _form.html.haml # Override form partial
459
+ ```
460
+
461
+ CafeCar's default views are in `app/views/cafe_car/application/` and serve as
462
+ templates.
463
+
464
+ ### Custom Responders
465
+
466
+ ```ruby
467
+ class ProductsController < ApplicationController
468
+ cafe_car
469
+
470
+ private
471
+
472
+ def create
473
+ super
474
+ respond_with object, location: custom_path
475
+ end
476
+ end
477
+ ```
478
+
479
+ ### Authorization Helpers
480
+
481
+ In controllers:
482
+
483
+ ```ruby
484
+ authorize! # Authorize current action
485
+ policy(object).update? # Check specific permission
486
+ policy(object).permitted_attributes # Get editable attributes
487
+ ```
488
+
489
+ In views:
490
+
491
+ ```erb
492
+ <% if policy(@product).update? %>
493
+ <%= link_to "Edit", edit_product_path(@product) %>
494
+ <% end %>
495
+ ```
496
+
497
+ ### Current Context
498
+
499
+ Access current request context anywhere:
500
+
501
+ ```ruby
502
+ CafeCar::Current.user # Current user
503
+ CafeCar::Current.request_id # Request ID
504
+ CafeCar::Current.user_agent # User agent string
505
+ CafeCar::Current.ip_address # IP address
506
+ ```
507
+
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.
580
+
581
+ ## Generators
582
+
583
+ ### Resource Generator
584
+
585
+ Generate a complete resource (model + controller + policy):
586
+
587
+ ```bash
588
+ $ rails generate cafe_car:resource Product name:string price:decimal
589
+ ```
590
+
591
+ ### Controller Generator
592
+
593
+ Generate just a controller:
594
+
595
+ ```bash
596
+ $ rails generate cafe_car:controller Products
597
+ ```
598
+
599
+ ### Policy Generator
600
+
601
+ Generate just a policy:
602
+
603
+ ```bash
604
+ $ rails generate cafe_car:policy Product
605
+ ```
606
+
607
+ ### Notes Generator
608
+
609
+ Add polymorphic audit trail notes to your app:
610
+
15
611
  ```bash
16
- $ bundle
612
+ $ rails generate cafe_car:notes
17
613
  ```
18
614
 
19
- Or install it yourself as:
615
+ Creates:
616
+
617
+ - Migration for notes table
618
+ - `Note` model
619
+ - `Notable` concern for trackable models
620
+
621
+ ### Sessions Generator
622
+
623
+ Enable opt-in login/logout (see [Sessions & Authentication](#sessions--authentication)):
624
+
20
625
  ```bash
21
- $ gem install cafe_car
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
+
632
+ ## Configuration
633
+
634
+ ### Custom Form Builder
635
+
636
+ ```ruby
637
+ # config/initializers/cafe_car.rb
638
+ module CafeCar
639
+ class FormBuilder < ActionView::Helpers::FormBuilder
640
+ # Your customizations
641
+ end
642
+ end
643
+ ```
644
+
645
+ ### Custom Presenter
646
+
647
+ ```ruby
648
+ # app/presenters/application_presenter.rb
649
+ class ApplicationPresenter < CafeCar::Presenter
650
+ # Application-wide presenter customizations
651
+ end
652
+
653
+ # app/presenters/product_presenter.rb
654
+ class ProductPresenter < ApplicationPresenter
655
+ show :name
656
+ show :price
657
+ end
658
+ ```
659
+
660
+ ### Custom Policy
661
+
662
+ ```ruby
663
+ # app/policies/application_policy.rb
664
+ class ApplicationPolicy < CafeCar::ApplicationPolicy
665
+ def admin?
666
+ user&.admin?
667
+ end
668
+ end
669
+ ```
670
+
671
+ ## Testing
672
+
673
+ CafeCar integrates with standard Rails testing tools:
674
+
675
+ ```ruby
676
+ # test/controllers/products_controller_test.rb
677
+ class ProductsControllerTest < ActionDispatch::IntegrationTest
678
+ test "index displays products" do
679
+ get products_url
680
+ assert_response :success
681
+ end
682
+
683
+ test "create with valid attributes" do
684
+ assert_difference "Product.count", 1 do
685
+ post products_url, params: { product: { name: "Widget" } }
686
+ end
687
+ assert_redirected_to product_path(Product.last)
688
+ end
689
+ end
22
690
  ```
23
691
 
24
692
  ## Contributing
25
- Contribution directions go here.
693
+
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).
26
698
 
27
699
  ## License
28
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
700
+
701
+ The gem is available as open source under the terms of the
702
+ [MIT License](https://opensource.org/licenses/MIT).