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.
Files changed (219) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +566 -7
  3. data/Rakefile +13 -0
  4. data/app/assets/images/noise.svg +16 -0
  5. data/app/assets/stylesheets/actiontext.css +31 -0
  6. data/app/assets/stylesheets/application.css +1 -0
  7. data/app/assets/stylesheets/cafe_car/code/base16-dark.css +89 -0
  8. data/app/assets/stylesheets/cafe_car/code/base16-light.css +90 -0
  9. data/app/assets/stylesheets/cafe_car/pagination.css +5 -0
  10. data/app/assets/stylesheets/cafe_car/themes/cool.css +32 -0
  11. data/app/assets/stylesheets/cafe_car/themes/cool2.css +31 -0
  12. data/app/assets/stylesheets/cafe_car/themes/defaults.css +61 -0
  13. data/app/assets/stylesheets/cafe_car/themes/warm-dark.css +29 -0
  14. data/app/assets/stylesheets/cafe_car/themes/warm.css +24 -0
  15. data/app/assets/stylesheets/cafe_car/trix.css +56 -0
  16. data/app/assets/stylesheets/cafe_car/utility.css +64 -0
  17. data/app/assets/stylesheets/cafe_car.css +85 -0
  18. data/app/assets/stylesheets/iconoir.css +22 -0
  19. data/app/assets/stylesheets/ui/Alert.css +24 -0
  20. data/app/assets/stylesheets/ui/Article.css +11 -0
  21. data/app/assets/stylesheets/ui/Button.css +42 -0
  22. data/app/assets/stylesheets/ui/Card.css +70 -0
  23. data/app/assets/stylesheets/ui/Code.css +4 -0
  24. data/app/assets/stylesheets/ui/Controls.css +16 -0
  25. data/app/assets/stylesheets/ui/Error.css +3 -0
  26. data/app/assets/stylesheets/ui/Example.css +45 -0
  27. data/app/assets/stylesheets/ui/Field.css +31 -0
  28. data/app/assets/stylesheets/ui/Grid.css +4 -0
  29. data/app/assets/stylesheets/ui/Group.css +16 -0
  30. data/app/assets/stylesheets/ui/Icon.css +27 -0
  31. data/app/assets/stylesheets/ui/Image.css +14 -0
  32. data/app/assets/stylesheets/ui/InfoCircle.css +11 -0
  33. data/app/assets/stylesheets/ui/Input.css +36 -0
  34. data/app/assets/stylesheets/ui/Layout.css +93 -0
  35. data/app/assets/stylesheets/ui/Menu.css +38 -0
  36. data/app/assets/stylesheets/ui/Modal.css +33 -0
  37. data/app/assets/stylesheets/ui/Navigation.css +29 -0
  38. data/app/assets/stylesheets/ui/Page.css +66 -0
  39. data/app/assets/stylesheets/ui/Row.css +9 -0
  40. data/app/assets/stylesheets/ui/Table.css +130 -0
  41. data/app/assets/stylesheets/ui/components.css +22 -0
  42. data/app/controllers/cafe_car/application_controller.rb +9 -0
  43. data/app/controllers/cafe_car/examples_controller.rb +22 -0
  44. data/app/javascript/application.js +5 -0
  45. data/app/javascript/cafe_car.js +169 -0
  46. data/app/models/cafe_car/slug.rb +3 -0
  47. data/app/policies/cafe_car/application_policy.rb +42 -0
  48. data/app/presenters/cafe_car/action_text/rich_text_presenter.rb +7 -0
  49. data/app/presenters/cafe_car/active_record/base_presenter.rb +6 -0
  50. data/app/presenters/cafe_car/active_record/relation_presenter.rb +17 -0
  51. data/app/presenters/cafe_car/active_storage/attached/one_presenter.rb +9 -0
  52. data/app/presenters/cafe_car/active_storage/attachment_presenter.rb +17 -0
  53. data/app/presenters/cafe_car/basic_object_presenter.rb +5 -0
  54. data/app/presenters/cafe_car/currency_presenter.rb +5 -0
  55. data/app/presenters/cafe_car/date_and_time/compatibility_presenter.rb +6 -0
  56. data/app/presenters/cafe_car/date_presenter.rb +4 -0
  57. data/app/presenters/cafe_car/date_time_presenter.rb +11 -0
  58. data/app/presenters/cafe_car/enumerable_presenter.rb +13 -0
  59. data/app/presenters/cafe_car/false_class_presenter.rb +5 -0
  60. data/app/presenters/cafe_car/hash_presenter.rb +11 -0
  61. data/app/presenters/cafe_car/nil_class_presenter.rb +13 -0
  62. data/app/presenters/cafe_car/presenter.rb +147 -0
  63. data/app/presenters/cafe_car/range_presenter.rb +16 -0
  64. data/app/presenters/cafe_car/record_presenter.rb +5 -0
  65. data/app/presenters/cafe_car/string_presenter.rb +20 -0
  66. data/app/presenters/cafe_car/symbol_presenter.rb +5 -0
  67. data/app/presenters/cafe_car/true_class_presenter.rb +5 -0
  68. data/app/ui/cafe_car/ui/button.rb +8 -0
  69. data/app/ui/cafe_car/ui/field.rb +11 -0
  70. data/app/ui/cafe_car/ui/page.rb +10 -0
  71. data/app/views/application/_actions.html.haml +1 -0
  72. data/app/views/application/_alerts.html.haml +2 -0
  73. data/app/views/application/_body.html.haml +6 -0
  74. data/app/views/application/_controls.html.haml +11 -0
  75. data/app/views/application/_debug.html.haml +11 -0
  76. data/app/views/application/_empty.html.haml +1 -0
  77. data/app/views/application/_errors.html.haml +8 -0
  78. data/app/views/application/_field.html.haml +5 -0
  79. data/app/views/application/_fields.html.haml +1 -0
  80. data/app/views/application/_filters.html.haml +8 -0
  81. data/app/views/application/_form.html.haml +6 -0
  82. data/app/views/application/_grid.html.haml +3 -0
  83. data/app/views/application/_grid_item.html.haml +1 -0
  84. data/app/views/application/_head.html.haml +16 -0
  85. data/app/views/application/_index.html.haml +4 -0
  86. data/app/views/application/_index_actions.html.haml +7 -0
  87. data/app/views/application/_navigation.html.haml +2 -0
  88. data/app/views/application/_navigation_links.html.haml +5 -0
  89. data/app/views/application/_notes.html.haml +9 -0
  90. data/app/views/application/_show.html.haml +9 -0
  91. data/app/views/application/_submit.html.haml +1 -0
  92. data/app/views/application/_table.html.haml +6 -0
  93. data/app/views/cafe_car/application/create.turbo_stream.haml +2 -0
  94. data/app/views/cafe_car/application/destroy.turbo_stream.haml +1 -0
  95. data/app/views/cafe_car/application/edit.html.haml +18 -0
  96. data/app/views/cafe_car/application/edit.turbo_stream.haml +9 -0
  97. data/app/views/cafe_car/application/index.html.haml +12 -0
  98. data/app/views/cafe_car/application/new.html.haml +8 -0
  99. data/app/views/cafe_car/application/new.turbo_stream.haml +9 -0
  100. data/app/views/cafe_car/application/show.html.haml +36 -0
  101. data/app/views/cafe_car/application/update.turbo_stream.haml +2 -0
  102. data/app/views/cafe_car/examples/_example.html.haml +12 -0
  103. data/app/views/cafe_car/examples/_index.html.haml +16 -0
  104. data/app/views/cafe_car/examples/_navigation_links.html.haml +2 -0
  105. data/app/views/cafe_car/examples/ui/_alert.html.haml +4 -0
  106. data/app/views/cafe_car/examples/ui/_button.html.haml +3 -0
  107. data/app/views/cafe_car/examples/ui/_card.html.haml +6 -0
  108. data/app/views/cafe_car/examples/ui/_controls.html.haml +3 -0
  109. data/app/views/cafe_car/examples/ui/_error.html.haml +1 -0
  110. data/app/views/cafe_car/examples/ui/_field.html.haml +9 -0
  111. data/app/views/cafe_car/examples/ui/_grid.html.haml +11 -0
  112. data/app/views/cafe_car/examples/ui/_group.html.haml +21 -0
  113. data/app/views/cafe_car/examples/ui/_info_circle.html.haml +1 -0
  114. data/app/views/cafe_car/examples/ui/_menu.html.haml +5 -0
  115. data/app/views/cafe_car/examples/ui/_modal.html.haml +4 -0
  116. data/app/views/cafe_car/examples/ui/_navigation.html.haml +4 -0
  117. data/app/views/cafe_car/examples/ui/_page.html.haml +4 -0
  118. data/app/views/cafe_car/examples/ui/_table.html.haml +13 -0
  119. data/app/views/cafe_car/layouts/mailer.html.haml +8 -0
  120. data/app/views/cafe_car/layouts/mailer.text.erb +1 -0
  121. data/app/views/layouts/action_text/contents/_content.html.erb +3 -0
  122. data/app/views/layouts/application.html.haml +4 -0
  123. data/app/views/layouts/mailer.html.erb +13 -0
  124. data/app/views/layouts/mailer.text.erb +1 -0
  125. data/app/views/notes/_fields.html.haml +1 -0
  126. data/app/views/ui/_card.html.haml +13 -0
  127. data/app/views/ui/_field.html.haml +7 -0
  128. data/app/views/ui/_grid.html.haml +17 -0
  129. data/app/views/ui/_layout_menu.html.haml +2 -0
  130. data/app/views/ui/_modal_close.html.haml +2 -0
  131. data/app/views/ui/_page.html.haml +13 -0
  132. data/config/brakeman.ignore +77 -0
  133. data/config/importmap.rb +12 -0
  134. data/config/locales/en.yml +55 -0
  135. data/config/routes.rb +5 -0
  136. data/db/migrate/20251005220017_create_slugs.rb +13 -0
  137. data/lib/cafe_car/active_record.rb +21 -0
  138. data/lib/cafe_car/application_responder.rb +11 -0
  139. data/lib/cafe_car/attributes.rb +23 -0
  140. data/lib/cafe_car/auto_resolver.rb +49 -0
  141. data/lib/cafe_car/caching.rb +20 -0
  142. data/lib/cafe_car/component.rb +92 -0
  143. data/lib/cafe_car/context.rb +16 -0
  144. data/lib/cafe_car/controller/filtering.rb +22 -0
  145. data/lib/cafe_car/controller.rb +179 -0
  146. data/lib/cafe_car/core_ext/array.rb +11 -0
  147. data/lib/cafe_car/core_ext.rb +7 -0
  148. data/lib/cafe_car/current.rb +6 -0
  149. data/lib/cafe_car/engine.rb +100 -0
  150. data/lib/cafe_car/field_builder.rb +44 -0
  151. data/lib/cafe_car/field_info.rb +142 -0
  152. data/lib/cafe_car/fields.rb +14 -0
  153. data/lib/cafe_car/filter/field_builder.rb +4 -0
  154. data/lib/cafe_car/filter/field_info.rb +22 -0
  155. data/lib/cafe_car/filter/form_builder.rb +21 -0
  156. data/lib/cafe_car/filter.rb +5 -0
  157. data/lib/cafe_car/filter_builder.rb +20 -0
  158. data/lib/cafe_car/form_builder.rb +105 -0
  159. data/lib/cafe_car/generators.rb +30 -0
  160. data/lib/cafe_car/helpers.rb +151 -0
  161. data/lib/cafe_car/href_builder.rb +71 -0
  162. data/lib/cafe_car/informable.rb +9 -0
  163. data/lib/cafe_car/input_builder.rb +25 -0
  164. data/lib/cafe_car/inputs/association_builder.rb +6 -0
  165. data/lib/cafe_car/inputs/base_input.rb +19 -0
  166. data/lib/cafe_car/inputs/belongs_to_builder.rb +6 -0
  167. data/lib/cafe_car/inputs/password_input.rb +7 -0
  168. data/lib/cafe_car/inputs/string_input.rb +7 -0
  169. data/lib/cafe_car/link_builder.rb +62 -0
  170. data/lib/cafe_car/model.rb +23 -0
  171. data/lib/cafe_car/model_info.rb +24 -0
  172. data/lib/cafe_car/name_patch.rb +17 -0
  173. data/lib/cafe_car/navigation.rb +76 -0
  174. data/lib/cafe_car/option_helpers.rb +47 -0
  175. data/lib/cafe_car/param_parser.rb +41 -0
  176. data/lib/cafe_car/pluralization.rb +15 -0
  177. data/lib/cafe_car/policy.rb +77 -0
  178. data/lib/cafe_car/proc_helpers.rb +13 -0
  179. data/lib/cafe_car/query_builder.rb +186 -0
  180. data/lib/cafe_car/queryable.rb +29 -0
  181. data/lib/cafe_car/resolver.rb +23 -0
  182. data/lib/cafe_car/routing.rb +17 -0
  183. data/lib/cafe_car/table/body_builder.rb +12 -0
  184. data/lib/cafe_car/table/builder.rb +51 -0
  185. data/lib/cafe_car/table/foot_builder.rb +14 -0
  186. data/lib/cafe_car/table/head_builder.rb +26 -0
  187. data/lib/cafe_car/table/label_builder.rb +48 -0
  188. data/lib/cafe_car/table/objects_builder.rb +8 -0
  189. data/lib/cafe_car/table/row_builder.rb +41 -0
  190. data/lib/cafe_car/table_builder.rb +13 -0
  191. data/lib/cafe_car/turbo_tag_builder.rb +7 -0
  192. data/lib/cafe_car/ui.rb +9 -0
  193. data/lib/cafe_car/version.rb +1 -1
  194. data/lib/cafe_car/visitors.rb +21 -0
  195. data/lib/cafe_car.rb +9 -177
  196. data/lib/generators/cafe_car/controller/USAGE +11 -0
  197. data/lib/generators/cafe_car/controller/controller_generator.rb +26 -0
  198. data/lib/generators/cafe_car/controller/templates/controller.rb.tt +5 -0
  199. data/lib/generators/cafe_car/install/USAGE +8 -0
  200. data/lib/generators/cafe_car/install/install_generator.rb +47 -0
  201. data/lib/generators/cafe_car/install/templates/application_policy.rb.tt +7 -0
  202. data/lib/generators/cafe_car/notes/USAGE +12 -0
  203. data/lib/generators/cafe_car/notes/notes_generator.rb +13 -0
  204. data/lib/generators/cafe_car/notes/templates/create_notes.rb.tt +12 -0
  205. data/lib/generators/cafe_car/notes/templates/notable.rb.tt +7 -0
  206. data/lib/generators/cafe_car/notes/templates/note.rb.tt +6 -0
  207. data/lib/generators/cafe_car/policy/USAGE +8 -0
  208. data/lib/generators/cafe_car/policy/policy_generator.rb +39 -0
  209. data/lib/generators/cafe_car/policy/templates/policy.rb.tt +20 -0
  210. data/lib/generators/cafe_car/resource/USAGE +13 -0
  211. data/lib/generators/cafe_car/resource/resource_generator.rb +32 -0
  212. metadata +410 -15
  213. data/app/views/cafe_car/application/_fields.html.erb +0 -7
  214. data/app/views/cafe_car/application/_filters.html.erb +0 -0
  215. data/app/views/cafe_car/application/_form.html.erb +0 -22
  216. data/lib/cafe_car/railtie.rb +0 -4
  217. /data/app/views/{cafe_car/application/_actions.html.erb → application/_aside.html.haml} +0 -0
  218. /data/app/views/{cafe_car/application/_aside.html.erb → application/_footer.html.haml} +0 -0
  219. /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: f8d0f3cafde61c20c26c71051639e723c59aa9a9c1e6d3e8ad33b3a7d97aec8d
4
+ data.tar.gz: 315d84333c1c9a7bfe90b4ea7e58911cc5c3da3012f54ce942075f12863098ef
5
5
  SHA512:
6
- metadata.gz: 52a1deab336f310203bb1e31779ed76a2da3a9616e84fdf6b7a7b3962b503f3d903d623a1c44143203c81a44a680d56a2d0a844b57f70f93dde727232538abd1
7
- data.tar.gz: 5a3f0a0113086d9826d8952093af024f9c51091f261e7ab78b98de9796a1b69647fc436979479f4b68773d2350f5c3451853b03c6faf8c6cba45b47f71239e28
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
- ## Usage
5
- How to use my plugin.
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
- $ bundle
505
+ $ rails generate cafe_car:policy Product
17
506
  ```
18
507
 
19
- Or install it yourself as:
508
+ ### Notes Generator
509
+
510
+ Add polymorphic audit trail notes to your app:
511
+
20
512
  ```bash
21
- $ gem install cafe_car
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
- Contribution directions go here.
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);