elaine_crud 0.1.3 → 0.1.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b331198ebb4dcf4ce1e5d3718d65fb36e2fac98ef24c219f46206fc9cb450309
4
- data.tar.gz: 690b1dd2f306cf3a6156dd35095471c283dd9674fe35f3895effec18d48cc047
3
+ metadata.gz: 6790f924718c05bea415357a3ace073990743e5e8c0014ea5380d674adbbc4f7
4
+ data.tar.gz: d1b352c734dbd526047a6503cdaed192dfa30daa05c3f92577f84f302d44e970
5
5
  SHA512:
6
- metadata.gz: 40b0b2d357e3bd0b87360fe7f0ea59613fb7dab409534b7843181caa15e726681ff2d1fd0d42161765be50b79bb8ac1ba7aeab03d19eaf89288e858865abacdf
7
- data.tar.gz: dbd100c7c1dcea367e12522c14fabb94f68b6ca7a19c6f42945da72752f475c0e2e6a8684611c33253ad1edcbb31d8e20bf31ff233b3cc912c1105bf08df3fc1
6
+ metadata.gz: 1cdcb91a0a27cbb97a1d9db7cab2588263933a99d46f81423a18c447c9bd58b6bf0d478d81a53403f0f31249768fcfdd383207542dc23a7d29f239600cfd9dcc
7
+ data.tar.gz: '085b69b219b226dd5d2c1cbced974603c5da07833113f5eca33f6311d9f3febf10d4e91c0c592f751efd9bb61c23239a1ef03b23ad213dd0a6cc15893faccaa5'
data/README.md CHANGED
@@ -1,14 +1,26 @@
1
1
  # ElaineCrud
2
2
 
3
- A Rails engine for rapidly generating CRUD interfaces for ActiveRecord models with minimal configuration.
3
+ A Rails engine for rapidly generating CRUD interfaces for ActiveRecord models with minimal configuration. This controller will create you the following CRUD interface:
4
+ ```ruby
5
+ class TaskController < ElaineCrud::BaseController
6
+ layout 'application'
7
+
8
+ model Task # Your ActiveRecord model
9
+ permit_params :title, :description, :priority, :completed, :due_date
10
+ end
11
+ ```
12
+ ![Image](docs/screenshots/screenshot1.png)
4
13
 
5
14
  ## Features
6
15
 
7
- - **Zero Configuration**: Works out of the box with any ActiveRecord model
8
- - **Minimal Code**: Just specify model and permitted params
9
- - **Modern UI**: Clean, responsive interface with TailwindCSS
10
- - **Extensible**: Override any view or behavior in your host app
11
- - **Rails Conventions**: Follows standard Rails patterns
16
+ - **Zero Configuration**: Works out of the box with any ActiveRecord model
17
+ - **Rails Conventions**: Follows standard Rails patterns
18
+ - **Minimal Code**: Just specify model and permitted params
19
+ - **Search & Filter**: Built-in search across text fields
20
+ - **Sortable Columns**: Click column headers to sort
21
+ - **Pagination**: Automatic pagination with configurable page size
22
+ - **Export**: Download data as CSV, Excel, or JSON
23
+ - **Extensible**: Override any view or behavior in your host app
12
24
 
13
25
  ## Installation
14
26
 
@@ -25,7 +37,43 @@ bundle install
25
37
 
26
38
  ## Quick Start
27
39
 
28
- ### 1. Ensure Your Application Has a Layout
40
+ ### 1. Ensure you have ActiveRecord model representing your data.
41
+
42
+ ```bash
43
+ bin/rails generate model Task title:string description:text priority:integer completed:boolean due_date:date
44
+ bin/rails db:migrate
45
+ ```
46
+
47
+ ### 2. Create a Controller for your ActiveRecord Model
48
+
49
+ This controller handles one ActiveRecord model and presents a CRUD interface to the user. It derives from the ElaineCrud::BaseController, which provides good defaults for how the CRUD view should operate. The controller requires a working layout (see step 4).
50
+
51
+ The main configuration is setting the ActiveRecord model using the `model Task` keyword, where Task is an ActiveRecord model. You also need to explicitly specify which model fields are permitted using the `permit_params` command.
52
+
53
+ The following example controller provides a fully working CRUD view for the ActiveRecord with pagination, sorting, filtering and exporting. You can customise the behaviour, which is explained in more details later.
54
+
55
+ ```ruby
56
+ class TaskController < ElaineCrud::BaseController
57
+ layout 'application' # Use your app's layout (wraps ElaineCrud's content)
58
+
59
+ model Task
60
+ permit_params :title, :description, :priority, :completed, :due_date
61
+ end
62
+ ```
63
+
64
+ ### 3. Add Routes
65
+
66
+ You need to add the created Controller to the Rails routes.rb. In this example you simply declare `resources :tasks` where `:tasks` maps to the TaskController.
67
+
68
+ ```ruby
69
+ # config/routes.rb
70
+ Rails.application.routes.draw do
71
+ resources :tasks
72
+ root "tasks#index"
73
+ end
74
+ ```
75
+
76
+ ### 4. Ensure Your Application Has a Layout
29
77
 
30
78
  ElaineCrud is a **content-only engine** - it provides CRUD views but relies on your application to provide the HTML structure (layout, navigation, styling).
31
79
 
@@ -35,256 +83,272 @@ Your Rails app should have a layout file (typically `app/views/layouts/applicati
35
83
  - JavaScript imports (including Turbo)
36
84
  - Navigation/header/footer (optional, your choice)
37
85
 
38
- Most Rails apps with TailwindCSS already have this. If not, ensure you have:
86
+ Here's an example layout file, which contains the critical stylesheet_link_tag for elaine_crud:
39
87
 
40
88
  ```erb
41
- <!-- app/views/layouts/application.html.erb -->
42
89
  <!DOCTYPE html>
43
90
  <html>
44
91
  <head>
45
- <title>Your App</title>
92
+ <title><%= content_for(:title) || "Taskmanager" %></title>
93
+ <meta name="viewport" content="width=device-width,initial-scale=1">
46
94
  <%= csrf_meta_tags %>
47
95
  <%= csp_meta_tag %>
48
- <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
96
+
97
+ <%= stylesheet_link_tag "elaine_crud", "data-turbo-track": "reload" %>
98
+ <%= stylesheet_link_tag :app, "data-turbo-track": "reload" %>
49
99
  <%= javascript_importmap_tags %>
50
100
  </head>
51
- <body>
52
- <%= yield %>
101
+
102
+ <body class="bg-gray-100">
103
+ <main class="w-full px-4 py-6">
104
+ <%= yield %>
105
+ </main>
53
106
  </body>
54
107
  </html>
55
108
  ```
56
109
 
57
- ### 2. Create a Controller
110
+ ### 5. Start Your Server
58
111
 
59
- Specify which layout ElaineCrud should use with the `layout` directive:
112
+ ```bash
113
+ bin/dev
114
+ ```
60
115
 
61
- ```ruby
62
- class PeopleController < ElaineCrud::BaseController
63
- layout 'application' # Use your app's layout (wraps ElaineCrud's content)
116
+ ## Usage
64
117
 
65
- model Person
66
- permit_params :name, :email, :phone, :active
67
- end
68
- ```
118
+ ### Extending the default Controller
69
119
 
70
- **Important**: The `layout 'application'` line tells ElaineCrud to render its CRUD views inside your application's layout. Without this, you'll see unstyled content with no HTML structure.
120
+ As described earlier, this example controller uses a lot of default from the `ElaineCrud::BaseController`.
71
121
 
72
- ### 3. Add Routes
122
+ One of the first things user usually wants to customise is how a certain ActiveRecord property is rendered to the user.
73
123
 
74
124
  ```ruby
75
- # config/routes.rb
76
- resources :people
77
- ```
125
+ class TaskController < ElaineCrud::BaseController
126
+ layout 'application' # Use your app's layout (wraps ElaineCrud's content)
78
127
 
79
- ### 4. Add Stylesheet (Choose One Approach)
128
+ model Task
129
+ permit_params :title, :description, :priority, :completed, :due_date
80
130
 
81
- ElaineCrud offers two ways to handle styling:
131
+ # Configures the ActiveRecord field :title
132
+ field :title do
82
133
 
83
- #### Option A: Use Precompiled CSS (Easiest, Zero Config)
134
+ # Human readable name for the field, shown in the column header.
135
+ title "Task title"
84
136
 
85
- Add the precompiled stylesheet to your layout:
137
+ # Description is shown in the edit form
138
+ description "Write a short title for your task, so that it's easily understandable"
86
139
 
87
- ```erb
88
- <!-- app/views/layouts/application.html.erb -->
89
- <!DOCTYPE html>
90
- <html>
91
- <head>
92
- <title>Your App</title>
93
- <%= stylesheet_link_tag "elaine_crud", "data-turbo-track": "reload" %>
94
- <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %>
95
- </head>
96
- <body>
97
- <%= yield %>
98
- </body>
99
- </html>
100
- ```
140
+ # Custom rendering. `value` is the raw value of the field and `record` is the entire ActiveRecord
141
+ display_as { |value, record| "<b>#{value}</b>" if value.present? }
101
142
 
102
- **Pros:** No Tailwind configuration needed, works immediately
103
- **Cons:** Cannot customize Tailwind theme
104
-
105
- #### Option B: Scan ElaineCrud Sources (For Customization)
106
-
107
- If you want to customize the Tailwind theme, add ElaineCrud's views to your `tailwind.config.js`:
108
-
109
- ```javascript
110
- const path = require('path')
111
- const execSync = require('child_process').execSync
112
-
113
- // Get the gem path from bundler
114
- const gemPath = execSync('bundle show elaine_crud', { encoding: 'utf-8' }).trim()
115
-
116
- module.exports = {
117
- content: [
118
- './app/views/**/*.html.erb',
119
- './app/helpers/**/*.rb',
120
- './app/assets/stylesheets/**/*.css',
121
- './app/javascript/**/*.js',
122
- // Add ElaineCrud gem views and helpers
123
- path.join(gemPath, 'app/views/**/*.html.erb'),
124
- path.join(gemPath, 'app/helpers/**/*.rb')
125
- ],
126
- theme: {
127
- extend: {
128
- // Your custom theme here
129
- },
130
- },
131
- }
132
- ```
143
+ # By default text columns are searchable but not filterable. You can set a field to be filterable
144
+ # so that it shows in Advanced Filters section, but then it's no longer searchable
145
+ filterable true
133
146
 
134
- **Pros:** Full theme customization
135
- **Cons:** Requires Tailwind CSS build setup
147
+ # By default text columns are searchable and other columns are not
148
+ searchable false
149
+ end
136
150
 
137
- ### 5. Restart Your Server
151
+ field :priority do
152
+ # Gives a html dropdown for available options
153
+ options [1,2,3,4,5]
154
+ end
138
155
 
139
- ```bash
140
- rails server
141
- ```
156
+ # `read_only` can be used prevent field from being edited
157
+ field :modified_at do
158
+ read_only true
159
+ end
142
160
 
143
- Navigate to `/people` and you'll have a fully functional CRUD interface!
161
+ # We can also hide a field completely
162
+ field :created_at do
163
+ hidden true
164
+ end
144
165
 
145
- ## Usage
166
+ end
167
+ ```
146
168
 
147
- ### Basic Controller
169
+ ### Virtual fields
148
170
 
149
- The minimal controller setup:
171
+ It is possible to create a virtual column which is not backed by any actual field in the ActiveRecord, but instead its contents is calculated based on other fields. For example if your Order has `amount` and `price_per_unit` fields, you could have a `total_cost` virtual field:
150
172
 
151
173
  ```ruby
152
- class ArticlesController < ElaineCrud::BaseController
153
- layout 'application' # Host app controls layout (header/footer/styling)
154
-
155
- model Article
156
- permit_params :title, :content, :published
174
+ class OrderController < ElaineCrud::BaseController
175
+ layout 'application' # Use your app's layout (wraps ElaineCrud's content)
176
+
177
+ model Order
178
+ permit_params :product, :amount, :price_per_unit
179
+
180
+ # The field `total_cost` simply does not exists in the ActiveRecord model
181
+ field :total_cost do
182
+ readonly true
183
+ display_as { |value,record|
184
+ # The `value` parameter will be nil as it does not exists.
185
+ record.amount * record.price_per_unit
186
+ }
187
+ end
157
188
  end
158
189
  ```
159
190
 
160
- ### DSL Reference
191
+ ### Relations
192
+
193
+ ElaineCrud supports ActiveRecord relations automatically, if they are configured in the underlying ActiveRecords. Lets assume these models:
194
+ ```
195
+ bin/rails generate model Department name:string
196
+ bin/rails generate model Employee name:string email:string department:references
197
+ bin/rails db:migrate
161
198
 
162
- - `model(ModelClass)` - Specify the ActiveRecord model to manage
163
- - `permit_params(*attrs)` - Define permitted attributes for strong parameters
164
- - `columns(config)` - (Future) Configure column display options
199
+ # app/models/department.rb
200
+ class Department < ApplicationRecord
201
+ has_many :employees
202
+ end
165
203
 
166
- ### Customization
204
+ # app/models/employee.rb
205
+ class Employee < ApplicationRecord
206
+ belongs_to :department
207
+ end
208
+ ```
167
209
 
168
- #### Override Views
210
+ We can then have a simple EmployeesController and DepartmentsController
211
+ ```ruby
212
+ # app/controllers/employees_controller.rb
213
+ class EmployeesController < ElaineCrud::BaseController
214
+ layout 'application'
169
215
 
170
- Create views in your app with the same names to override engine views:
216
+ model Employee
217
+ permit_params :name, :email, :department_id
218
+ end
171
219
 
172
- ```
173
- app/views/articles/index.html.erb # Overrides engine's index view
174
- ```
220
+ # app/controllers/departments_controller.rb
221
+ class DepartmentsController < ElaineCrud::BaseController
222
+ layout 'application'
175
223
 
176
- #### Override Controller Methods
224
+ model Department
225
+ permit_params :name
177
226
 
178
- ```ruby
179
- class ArticlesController < ElaineCrud::BaseController
180
- model Article
181
- permit_params :title, :content, :published
182
-
183
- private
184
-
185
- # Custom record fetching with scoping
186
- def fetch_records
187
- Article.published.order(:title)
188
- end
189
-
190
- # Custom column selection
191
- def determine_columns
192
- %w[title published created_at]
193
- end
194
227
  end
195
228
  ```
196
229
 
197
- #### Custom Helpers
230
+ This will automatically create a relation to the Departments in the EmployeesController field like this:
231
+ ![Image](docs/screenshots/employees.png)
198
232
 
199
- Override the display helper in your application:
233
+ Also the Departments controller will have a back reference to show how many Employees are boudn to the Department.
234
+ ![Image](docs/screenshots/departments.png)
200
235
 
236
+
237
+ ### Misc options
201
238
  ```ruby
202
- # app/helpers/application_helper.rb
203
- def display_column_value(record, column)
204
- case column
205
- when 'published'
206
- record.published? ? '📘 Published' : '📝 Draft'
207
- else
208
- super # Call the engine's helper
209
- end
239
+ # app/controllers/employees_controller.rb
240
+ class EmployeesController < ElaineCrud::BaseController
241
+ layout 'application'
242
+
243
+ model Employee
244
+ permit_params :name, :email, :department_id
245
+
246
+ # Sets default sorting when the controller is opened
247
+ default_sort column: :email, direction: :asc
248
+
249
+ # Instead of editing a row in the inline editor using turbo, the Edit button
250
+ # opens /controller/#{id}/edit page
251
+ disable_turbo
252
+
253
+ # This adds a View button next to Edit and Delete buttons, which links to
254
+ # /controller/#{id}
255
+ show_view_button
256
+
257
+ # Limit how many rows the export functionality will be exporting. The default is 10000
258
+ max_export(5000)
210
259
  end
211
260
  ```
212
261
 
213
262
  ## Requirements
214
263
 
215
- - Rails 6.0+
216
- - TailwindCSS (for styling)
264
+ - Rails 7.0+
265
+ - TailwindCSS (included via precompiled CSS)
217
266
 
218
267
  ## Examples
219
268
 
220
- ### Complete Example: Managing Blog Posts
269
+ ### More detailed example with customisations
270
+
271
+ A more comprehensive example showing custom field formatting, sorting, and relationships. This example uses the more verbose block syntax with fields. Both approaches work and are valid.
221
272
 
222
273
  ```ruby
223
- # app/controllers/posts_controller.rb
224
- class PostsController < ElaineCrud::BaseController
225
- layout 'application' # Use your app's layout
226
-
227
- model Post
228
- permit_params :title, :content, :published, :category_id
229
-
230
- private
231
-
232
- def fetch_records
233
- Post.includes(:category).order(created_at: :desc)
274
+ # app/controllers/products_controller.rb
275
+ class ProductsController < ElaineCrud::BaseController
276
+ layout 'application'
277
+
278
+ model Product
279
+ permit_params :name, :description, :price, :stock_quantity, :category_id, :active
280
+
281
+ # Sort by name alphabetically by default
282
+ default_sort column: :name, direction: :asc
283
+
284
+ # Show the View button in actions column
285
+ show_view_button
286
+
287
+ # Format price as currency
288
+ field :price do |f|
289
+ f.title "Price"
290
+ f.display_as { |value, record| number_to_currency(value) if value.present? }
234
291
  end
235
- end
236
292
 
237
- # config/routes.rb
238
- resources :posts
293
+ # Custom display for stock status
294
+ field :stock_quantity do |f|
295
+ f.title "Stock"
296
+ f.display_as { |value, record|
297
+ if value.to_i > 10
298
+ content_tag(:span, "#{value} in stock", class: "text-green-600")
299
+ elsif value.to_i > 0
300
+ content_tag(:span, "Low: #{value}", class: "text-yellow-600 font-semibold")
301
+ else
302
+ content_tag(:span, "Out of stock", class: "text-red-600 font-semibold")
303
+ end
304
+ }
305
+ end
239
306
 
240
- # Navigate to /posts for instant CRUD interface
307
+ # Boolean with badge display
308
+ field :active do |f|
309
+ f.title "Status"
310
+ f.display_as { |value, record|
311
+ if value
312
+ content_tag(:span, "Active", class: "px-2 py-1 text-xs rounded bg-green-100 text-green-800")
313
+ else
314
+ content_tag(:span, "Inactive", class: "px-2 py-1 text-xs rounded bg-gray-100 text-gray-600")
315
+ end
316
+ }
317
+ end
318
+
319
+ # Foreign key - automatically renders as dropdown in forms
320
+ field :category_id do |f|
321
+ f.foreign_key model: Category, display: :name
322
+ end
323
+ end
241
324
  ```
242
325
 
243
326
  ### Example Output
244
327
 
245
328
  The generated interface includes:
246
- - **Index Page**: Responsive table with all records
329
+ - **Index Page**: Responsive grid-based table with all records
330
+ - **Inline Editing**: Click Edit to modify records in place (Turbo Frames)
331
+ - **Sortable Columns**: Click headers to sort ascending/descending
332
+ - **Search**: Filter records by text across searchable columns
333
+ - **Pagination**: Navigate through large datasets with configurable page size
334
+ - **Export**: Download as CSV, Excel (.xlsx), or JSON
247
335
  - **Smart Formatting**: Dates, booleans, and nil values formatted nicely
248
336
  - **Action Buttons**: Edit and Delete functionality
249
337
  - **Empty States**: Helpful messages when no records exist
250
- - **Modern Styling**: Clean TailwindCSS design
338
+ - **Visual Feedback**: Row highlights after saving changes
251
339
 
252
340
  ## Architecture
253
341
 
254
- ElaineCrud follows a **separation of concerns** approach:
255
-
256
- - **Engine provides**: CRUD logic, data formatting, content templates
257
- - **Host app provides**: Layout, styling, HTML structure, navigation
258
-
259
- ### Layout Control
260
-
261
- The gem doesn't impose any layout - your app controls the HTML structure:
262
-
263
- ```ruby
264
- class UsersController < ElaineCrud::BaseController
265
- layout 'admin' # Use admin layout
266
- # or layout 'public' # Use public layout
267
- # or layout false # No layout (API mode)
268
-
269
- model User
270
- permit_params :name, :email
271
- end
272
- ```
273
-
274
- This means:
275
- - ✅ **Your app controls**: Headers, footers, navigation, CSS frameworks
276
- - ✅ **Engine provides**: Table content, buttons, data formatting
277
- - ✅ **Zero view files needed**: No templates to create in your app
278
-
279
342
  See [ARCHITECTURE.md](docs/ARCHITECTURE.md) for detailed technical documentation.
280
343
 
281
344
  ## Contributing
282
345
 
283
- 1. Fork the repository
284
- 2. Create a feature branch
285
- 3. Make your changes
286
- 4. Add tests
287
- 5. Submit a pull request
346
+ 1. Open a new issue and suggest a feature
347
+ 2. Fork the repository
348
+ 3. Create a feature branch
349
+ 4. Make your changes
350
+ 5. Add tests
351
+ 6. Submit a pull request
288
352
 
289
353
  ## License
290
354
 
@@ -133,9 +133,9 @@ module ElaineCrud
133
133
  @filterable_columns = determine_filterable_columns
134
134
  @total_count = total_unfiltered_count if search_active?
135
135
 
136
- # If Turbo is disabled, always render the full edit page
136
+ # If Turbo is disabled, render standalone edit form (no table context)
137
137
  if turbo_disabled?
138
- render 'elaine_crud/base/edit'
138
+ render 'elaine_crud/base/edit_standalone'
139
139
  elsif turbo_frame_request?
140
140
  # For Turbo Frame requests, return just the edit row partial
141
141
  render partial: 'elaine_crud/base/edit_row', locals: { record: @record, columns: @columns }
@@ -0,0 +1,12 @@
1
+ <div class="container mx-auto px-4 py-8">
2
+ <div class="flex justify-between items-center mb-6">
3
+ <h1 class="text-3xl font-bold text-gray-900">Edit <%= @model_name %></h1>
4
+ <%= link_to "Back to #{@model_name.pluralize}",
5
+ url_for(action: :index),
6
+ class: "text-gray-600 hover:text-gray-900" %>
7
+ </div>
8
+
9
+ <div class="bg-white shadow-md rounded-lg p-6">
10
+ <%= render 'form', record: @record, submit_text: "Update #{@model_name}" %>
11
+ </div>
12
+ </div>
Binary file
Binary file
Binary file
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ElaineCrud
4
- VERSION = '0.1.3'
4
+ VERSION = '0.1.4'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: elaine_crud
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Garo
@@ -209,6 +209,7 @@ files:
209
209
  - app/views/elaine_crud/base/_show_details.html.erb
210
210
  - app/views/elaine_crud/base/_view_row.html.erb
211
211
  - app/views/elaine_crud/base/edit.html.erb
212
+ - app/views/elaine_crud/base/edit_standalone.html.erb
212
213
  - app/views/elaine_crud/base/index.html.erb
213
214
  - app/views/elaine_crud/base/new.html.erb
214
215
  - app/views/elaine_crud/base/new_modal.html.erb
@@ -223,6 +224,9 @@ files:
223
224
  - docs/HAS_MANY_IMPLEMENTATION.md
224
225
  - docs/LAYOUT_EXAMPLES.md
225
226
  - docs/TROUBLESHOOTING.md
227
+ - docs/screenshots/departments.png
228
+ - docs/screenshots/employees.png
229
+ - docs/screenshots/screenshot1.png
226
230
  - elaine_crud.gemspec
227
231
  - lib/elaine_crud.rb
228
232
  - lib/elaine_crud/dsl_methods.rb