better_controller 0.1.0

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 (40) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +95 -0
  5. data/CHANGELOG.md +18 -0
  6. data/CODE_OF_CONDUCT.md +132 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +624 -0
  9. data/Rakefile +12 -0
  10. data/examples/api_controller.rb +31 -0
  11. data/examples/application_controller.rb +51 -0
  12. data/examples/products_controller.rb +25 -0
  13. data/examples/user_serializer.rb +15 -0
  14. data/examples/user_service.rb +31 -0
  15. data/examples/users_controller.rb +86 -0
  16. data/lib/better_controller/action_helpers.rb +76 -0
  17. data/lib/better_controller/base.rb +61 -0
  18. data/lib/better_controller/configuration.rb +69 -0
  19. data/lib/better_controller/logging.rb +101 -0
  20. data/lib/better_controller/method_not_overridden_error.rb +13 -0
  21. data/lib/better_controller/pagination.rb +44 -0
  22. data/lib/better_controller/parameter_validation.rb +86 -0
  23. data/lib/better_controller/params_helpers.rb +109 -0
  24. data/lib/better_controller/railtie.rb +18 -0
  25. data/lib/better_controller/resources_controller.rb +263 -0
  26. data/lib/better_controller/response_helpers.rb +74 -0
  27. data/lib/better_controller/serializer.rb +85 -0
  28. data/lib/better_controller/service.rb +94 -0
  29. data/lib/better_controller/version.rb +5 -0
  30. data/lib/better_controller.rb +55 -0
  31. data/lib/generators/better_controller/controller_generator.rb +65 -0
  32. data/lib/generators/better_controller/install_generator.rb +22 -0
  33. data/lib/generators/better_controller/templates/README +87 -0
  34. data/lib/generators/better_controller/templates/controller.rb +78 -0
  35. data/lib/generators/better_controller/templates/initializer.rb +31 -0
  36. data/lib/generators/better_controller/templates/serializer.rb +32 -0
  37. data/lib/generators/better_controller/templates/service.rb +57 -0
  38. data/lib/tasks/better_controller_tasks.rake +61 -0
  39. data/sig/better_controller.rbs +4 -0
  40. metadata +226 -0
data/README.md ADDED
@@ -0,0 +1,624 @@
1
+ # BetterController 🎮
2
+
3
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4
+ [![Ruby Style Guide](https://img.shields.io/badge/code_style-rubocop-brightgreen.svg)](https://github.com/rubocop/rubocop)
5
+
6
+ > 🚀 A powerful Ruby gem for building standardized, maintainable, and feature-rich Rails controllers
7
+
8
+ BetterController simplifies the process of building RESTful controllers in your Rails applications. It provides a structured approach to define controllers, services, and serializers, making your development process more maintainable and efficient.
9
+
10
+ ## ✨ Key Features
11
+
12
+ - 🏗️ **Standardized Controller Structure**: Define RESTful controllers with minimal code
13
+ - 🛠️ **Service Layer**: Separate business logic from controllers
14
+ - 📦 **Serialization**: Standardized JSON serialization for your resources
15
+ - 📄 **Pagination**: Built-in pagination support for collections
16
+ - 🔍 **Parameter Validation**: Robust parameter validation and type casting
17
+ - 🔄 **Response Handling**: Consistent JSON responses with standardized structure
18
+ - 🚨 **Error Handling**: Comprehensive error handling with custom error classes
19
+ - 📝 **Logging**: Enhanced logging capabilities
20
+ - 🧩 **Generators**: Rails generators for controllers, services, and serializers
21
+
22
+ ## Why BetterController? 🤔
23
+
24
+ - 🏗️ **Standardized Approach**: Consistent controller structure across your application
25
+ - 🧩 **Modular Design**:
26
+ - Separate controller, service, and serializer components
27
+ - Reusable modules
28
+ - Configurable behavior
29
+ - 🔄 **Flexible Implementation**:
30
+ - Override default behavior when needed
31
+ - Customize actions and responses
32
+ - Extend with your own functionality
33
+ - ✅ **Robust Error Handling**:
34
+ - Standardized error responses
35
+ - Detailed error logging
36
+ - Custom error classes
37
+ - 📊 **Enhanced Responses**:
38
+ - Consistent JSON structure
39
+ - Pagination metadata
40
+ - Custom response formatting
41
+
42
+ ## Installation
43
+
44
+ Add the gem to your Gemfile:
45
+
46
+ ```ruby
47
+ gem 'better_controller'
48
+ ```
49
+
50
+ Then run:
51
+
52
+ ```bash
53
+ bundle install
54
+ ```
55
+
56
+ Or install the gem manually:
57
+
58
+ ```bash
59
+ gem install better_controller
60
+ ```
61
+
62
+ ## Configuration
63
+
64
+ In a Rails application, you can create an initializer by running:
65
+
66
+ ```bash
67
+ rails generate better_controller:install
68
+ ```
69
+
70
+ This command creates the file `config/initializers/better_controller.rb` with a default configuration. An example configuration is:
71
+
72
+ ```ruby
73
+ BetterController.configure do |config|
74
+ # Pagination configuration
75
+ config[:pagination] = {
76
+ enabled: true,
77
+ per_page: 25
78
+ }
79
+
80
+ # Serialization configuration
81
+ config[:serialization] = {
82
+ include_root: false,
83
+ camelize_keys: true
84
+ }
85
+
86
+ # Error handling configuration
87
+ config[:error_handling] = {
88
+ log_errors: true,
89
+ detailed_errors: true
90
+ }
91
+ end
92
+ ```
93
+
94
+ ## Generators
95
+
96
+ BetterController provides several generators to help you quickly scaffold your application:
97
+
98
+ ### Install Generator
99
+
100
+ Creates an initializer with default configuration:
101
+
102
+ ```bash
103
+ rails generate better_controller:install
104
+ ```
105
+
106
+ ### Controller Generator
107
+
108
+ Generates a controller with optional service and serializer:
109
+
110
+ ```bash
111
+ rails generate better_controller:controller Users index show create update destroy
112
+ ```
113
+
114
+ Options:
115
+ - `--skip-service`: Skip generating a service class
116
+ - `--skip-serializer`: Skip generating a serializer class
117
+ - `--model=MODEL_NAME`: Specify a custom model name (defaults to singular of controller name)
118
+
119
+ This will create:
120
+ - `app/controllers/users_controller.rb`
121
+ - `app/services/user_service.rb` (unless `--skip-service` is specified)
122
+ - `app/serializers/user_serializer.rb` (unless `--skip-serializer` is specified)
123
+
124
+ ## Basic Usage
125
+
126
+ ### Controller Setup
127
+
128
+ Include BetterController in your ApplicationController:
129
+
130
+ ```ruby
131
+ class ApplicationController < ActionController::Base
132
+ include BetterController
133
+ end
134
+ ```
135
+
136
+ ### Creating a ResourcesController
137
+
138
+ Create a controller that inherits from the ResourcesController:
139
+
140
+ ```ruby
141
+ class UsersController < BetterController::ResourcesController
142
+ # Controller-specific configuration
143
+ def resource_class
144
+ User
145
+ end
146
+
147
+ def resource_params
148
+ params.require(:user).permit(:name, :email, :role)
149
+ end
150
+
151
+ def resource_creator
152
+ UserService.create(resource_params)
153
+ end
154
+
155
+ def resource_updater
156
+ UserService.update(@resource, resource_params)
157
+ end
158
+
159
+ def resource_destroyer
160
+ UserService.destroy(@resource)
161
+ end
162
+
163
+ def index_serializer
164
+ UserSerializer
165
+ end
166
+
167
+ def show_serializer
168
+ UserSerializer
169
+ end
170
+
171
+ def create_serializer
172
+ UserSerializer
173
+ end
174
+
175
+ def update_serializer
176
+ UserSerializer
177
+ end
178
+
179
+ def destroy_serializer
180
+ UserSerializer
181
+ end
182
+
183
+ def create_message
184
+ 'User created successfully'
185
+ end
186
+
187
+ def update_message
188
+ 'User updated successfully'
189
+ end
190
+
191
+ def destroy_message
192
+ 'User deleted successfully'
193
+ end
194
+ end
195
+ ```
196
+
197
+ ### Service Layer
198
+
199
+ Create a service class to handle business logic:
200
+
201
+ ```ruby
202
+ class UserService
203
+ def self.create(params)
204
+ user = User.new(params)
205
+ user.save
206
+ user
207
+ end
208
+
209
+ def self.update(user, params)
210
+ user.update(params)
211
+ user
212
+ end
213
+
214
+ def self.destroy(user)
215
+ user.destroy
216
+ user
217
+ end
218
+ end
219
+ ```
220
+
221
+ ### Serializer
222
+
223
+ Create a serializer to format your responses:
224
+
225
+ ```ruby
226
+ class UserSerializer
227
+ def self.serialize(user, options = {})
228
+ {
229
+ id: user.id,
230
+ name: user.name,
231
+ email: user.email,
232
+ role: user.role,
233
+ created_at: user.created_at,
234
+ updated_at: user.updated_at
235
+ }
236
+ end
237
+ end
238
+ ```
239
+
240
+ ## Core Features
241
+
242
+ ### ResourcesController
243
+
244
+ The `ResourcesController` provides a standardized implementation of RESTful actions:
245
+
246
+ ```ruby
247
+ # Available actions
248
+ index # GET /resources
249
+ show # GET /resources/:id
250
+ create # POST /resources
251
+ update # PUT/PATCH /resources/:id
252
+ destroy # DELETE /resources/:id
253
+ ```
254
+
255
+ ### Response Handling
256
+
257
+ BetterController provides standardized methods for handling responses:
258
+
259
+ ```ruby
260
+ # Success response
261
+ respond_with_success(data, options = {})
262
+
263
+ # Error response
264
+ respond_with_error(errors, options = {})
265
+ ```
266
+
267
+ Example response format:
268
+
269
+ ```json
270
+ {
271
+ "data": { ... },
272
+ "message": "Operation completed successfully",
273
+ "meta": { ... }
274
+ }
275
+ ```
276
+
277
+ ### Pagination
278
+
279
+ The `Pagination` module provides pagination functionality for ActiveRecord collections:
280
+
281
+ ```ruby
282
+ def index
283
+ execute_action do
284
+ collection = paginate(resource_collection_resolver)
285
+ data = serialize_collection(collection, index_serializer)
286
+ respond_with_success(data, options: { meta: meta })
287
+ end
288
+ end
289
+ ```
290
+
291
+ Pagination metadata is included in the response:
292
+
293
+ ```json
294
+ {
295
+ "data": [ ... ],
296
+ "meta": {
297
+ "pagination": {
298
+ "total_count": 100,
299
+ "total_pages": 4,
300
+ "current_page": 1,
301
+ "per_page": 25
302
+ }
303
+ }
304
+ }
305
+ ```
306
+
307
+ ### Error Handling
308
+
309
+ BetterController provides comprehensive error handling:
310
+
311
+ ```ruby
312
+ begin
313
+ # Your code here
314
+ rescue ActiveRecord::RecordNotFound => e
315
+ respond_with_error(e.message, status: :not_found)
316
+ rescue ActionController::ParameterMissing => e
317
+ respond_with_error(e.message, status: :bad_request)
318
+ rescue StandardError => e
319
+ respond_with_error(e.message, status: :internal_server_error)
320
+ end
321
+ ```
322
+
323
+ Error response format:
324
+
325
+ ```json
326
+ {
327
+ "errors": {
328
+ "base": ["Resource not found"]
329
+ },
330
+ "message": "An error occurred",
331
+ "status": 404
332
+ }
333
+ ```
334
+
335
+ end
336
+ ```
337
+
338
+ ## Advanced Customization
339
+
340
+ ### Overriding Default Behavior
341
+
342
+ You can override any of the default methods in your controller to customize behavior:
343
+
344
+ ```ruby
345
+ class UsersController < BetterController::ResourcesController
346
+ # Override the default index action
347
+ def index
348
+ execute_action do
349
+ @users = User.where(active: true)
350
+ data = serialize_collection(@users, index_serializer)
351
+ respond_with_success(data, options: { meta: { active_count: @users.count } })
352
+ end
353
+ end
354
+
355
+ # Override the resource finder method
356
+ def resource_finder
357
+ User.includes(:posts, :comments).find(params[:id])
358
+ end
359
+
360
+ # Add custom actions
361
+ def activate
362
+ execute_action do
363
+ @resource = resource_finder
364
+ @resource.update(active: true)
365
+ data = serialize_resource(@resource, show_serializer)
366
+ respond_with_success(data, options: { message: 'User activated successfully' })
367
+ end
368
+ end
369
+ end
370
+ ```
371
+
372
+ ### Custom Serialization
373
+
374
+ You can implement custom serialization logic:
375
+
376
+ ```ruby
377
+ class UserSerializer
378
+ def self.serialize(user, options = {})
379
+ serialized = {
380
+ id: user.id,
381
+ name: user.name,
382
+ email: user.email
383
+ }
384
+
385
+ # Add additional fields based on options
386
+ if options[:include_details]
387
+ serialized.merge!({
388
+ role: user.role,
389
+ last_login: user.last_login,
390
+ created_at: user.created_at
391
+ })
392
+ end
393
+
394
+ serialized
395
+ end
396
+
397
+ def self.serialize_collection(users, options = {})
398
+ users.map { |user| serialize(user, options) }
399
+ end
400
+ end
401
+ ```
402
+
403
+ ### Custom Error Handling
404
+
405
+ Implement custom error handling in your controllers:
406
+
407
+ ```ruby
408
+ class ApplicationController < ActionController::Base
409
+ include BetterController
410
+
411
+ rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found
412
+ rescue_from ActionController::ParameterMissing, with: :handle_parameter_missing
413
+ rescue_from CustomError::AuthorizationError, with: :handle_authorization_error
414
+
415
+ private
416
+
417
+ def handle_not_found(exception)
418
+ respond_with_error(exception.message, status: :not_found, options: { code: 'NOT_FOUND' })
419
+ end
420
+
421
+ def handle_parameter_missing(exception)
422
+ respond_with_error("Required parameter missing: #{exception.param}", status: :bad_request)
423
+ end
424
+
425
+ def handle_authorization_error(exception)
426
+ respond_with_error('You are not authorized to perform this action', status: :forbidden)
427
+ end
428
+ end
429
+ ```
430
+
431
+ ## Testing
432
+
433
+ BetterController includes RSpec tests to ensure functionality. Run the tests with:
434
+
435
+ ```bash
436
+ bundle exec rspec
437
+ ```
438
+
439
+ Example test for a controller:
440
+
441
+ ```ruby
442
+ RSpec.describe UsersController, type: :controller do
443
+ describe '#index' do
444
+ it 'returns a collection of users' do
445
+ get :index
446
+ expect(response).to have_http_status(:ok)
447
+ expect(JSON.parse(response.body)['data']).to be_an(Array)
448
+ end
449
+ end
450
+
451
+ describe '#create' do
452
+ it 'creates a new user' do
453
+ post :create, params: { user: { name: 'John Doe', email: 'john@example.com' } }
454
+ expect(response).to have_http_status(:created)
455
+ expect(JSON.parse(response.body)['message']).to eq('User created successfully')
456
+ end
457
+ end
458
+ end
459
+ ```
460
+
461
+ ## Contributing
462
+
463
+ 1. Fork the repository
464
+ 2. Create your feature branch (`git checkout -b feature/my-new-feature`)
465
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
466
+ 4. Push to the branch (`git push origin feature/my-new-feature`)
467
+ 5. Create a new Pull Request
468
+
469
+ ## License
470
+
471
+ This project is licensed under the MIT License - see the LICENSE file for details.
472
+
473
+ ## Advanced Features
474
+
475
+ ### Pagination
476
+
477
+ The `Pagination` module provides pagination functionality for ActiveRecord collections:
478
+
479
+ ```ruby
480
+ def index
481
+ execute_action do
482
+ collection = User.all
483
+ @users = paginate(collection, page: params[:page], per_page: 20)
484
+ respond_with_success(@users)
485
+ end
486
+ end
487
+ ```
488
+
489
+ ### Parameter Helpers
490
+
491
+ The `ParamsHelpers` module provides enhanced parameter handling:
492
+
493
+ ```ruby
494
+ # Get a typed parameter
495
+ user_id = param(:user_id, type: :integer)
496
+
497
+ # Get a boolean parameter
498
+ active = boolean_param(:active, default: true)
499
+
500
+ # Get a date parameter
501
+ start_date = date_param(:start_date)
502
+
503
+ # Get a JSON parameter
504
+ data = json_param(:data)
505
+ ```
506
+
507
+ ### Logging
508
+
509
+ The `Logging` module provides enhanced logging capabilities:
510
+
511
+ ```ruby
512
+ # Log at different levels
513
+ log_info("Processing request")
514
+ log_debug("Debug information")
515
+ log_warn("Warning message")
516
+ log_error("Error occurred")
517
+
518
+ # Log with tags
519
+ log_info("User created", { user_id: user.id, email: user.email })
520
+
521
+ # Log exceptions
522
+ begin
523
+ # Some code that might raise an exception
524
+ rescue => e
525
+ log_exception(e, { controller: self.class.name, action: action_name })
526
+ end
527
+ ```
528
+
529
+ ## Generators
530
+
531
+ ### Controller Generator
532
+
533
+ Generate a controller with BetterController:
534
+
535
+ ```bash
536
+ rails generate better_controller:controller Users index show create update destroy
537
+ ```
538
+
539
+ This will create:
540
+ - A UsersController with the specified actions
541
+ - A UserService for handling business logic
542
+ - A UserSerializer for serializing responses
543
+
544
+ ## Example Implementation
545
+
546
+ ### Controller
547
+
548
+ ```ruby
549
+ class UsersController < ApplicationController
550
+ include BetterController::ResourcesController
551
+
552
+ # GET /users
553
+ def index
554
+ execute_action do
555
+ @resource_collection = resource_collection_resolver
556
+ data = serialize_resource(@resource_collection, index_serializer)
557
+ respond_with_success(data, options: { meta: meta })
558
+ end
559
+ end
560
+
561
+ # GET /users/:id
562
+ def show
563
+ execute_action do
564
+ @resource = resource_resolver
565
+ data = serialize_resource(@resource, show_serializer)
566
+ respond_with_success(data)
567
+ end
568
+ end
569
+
570
+ # POST /users
571
+ def create
572
+ execute_action do
573
+ @resource = resource_service.create(resource_params)
574
+ data = serialize_resource(@resource, create_serializer)
575
+ respond_with_success(data, status: :created)
576
+ end
577
+ end
578
+
579
+ # PATCH/PUT /users/:id
580
+ def update
581
+ execute_action do
582
+ @resource = resource_resolver
583
+ resource_service.update(@resource, resource_params)
584
+ data = serialize_resource(@resource, update_serializer)
585
+ respond_with_success(data)
586
+ end
587
+ end
588
+
589
+ # DELETE /users/:id
590
+ def destroy
591
+ execute_action do
592
+ @resource = resource_resolver
593
+ resource_service.destroy(@resource)
594
+ respond_with_success(nil, status: :no_content)
595
+ end
596
+ end
597
+
598
+ protected
599
+
600
+ def resource_service_class
601
+ UserService
602
+ end
603
+
604
+ def resource_params_root_key
605
+ :user
606
+ end
607
+
608
+ def resource_serializer
609
+ UserSerializer
610
+ end
611
+ end
612
+ ```
613
+
614
+ ## Contributing 🤝
615
+
616
+ 1. Fork the repository
617
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
618
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
619
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
620
+ 5. Open a Pull Request
621
+
622
+ ## License 📄
623
+
624
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require 'rubocop/rake_task'
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example of an API controller using BetterController
4
+ class ApiController < ActionController::API
5
+ include BetterController
6
+
7
+ # Global error handling for API controllers
8
+ rescue_from ActiveRecord::RecordNotFound do |exception|
9
+ respond_with_error(exception, status: :not_found)
10
+ end
11
+
12
+ rescue_from ActiveRecord::RecordInvalid do |exception|
13
+ respond_with_error(exception, status: :unprocessable_entity)
14
+ end
15
+
16
+ # Helper method for authentication
17
+ def authenticate_api_user!
18
+ token = request.headers['Authorization']&.split&.last
19
+
20
+ return if valid_token?(token)
21
+
22
+ respond_with_error('Unauthorized access', status: :unauthorized)
23
+ end
24
+
25
+ private
26
+
27
+ def valid_token?(token)
28
+ # Implementation of token validation logic
29
+ token.present? && ApiToken.exists?(token: token)
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ApplicationController < ActionController::Base
4
+ include BetterController
5
+
6
+ # Handle common exceptions with custom responses
7
+ rescue_from ActiveRecord::RecordNotFound, with: :handle_not_found
8
+ rescue_from ActiveRecord::RecordInvalid, with: :handle_validation_error
9
+ rescue_from ActionController::ParameterMissing, with: :handle_parameter_missing
10
+
11
+ protected
12
+
13
+ # Handle record not found errors
14
+ def handle_not_found(exception)
15
+ log_exception(exception)
16
+ respond_with_error(
17
+ message: 'Resource not found',
18
+ details: exception.message,
19
+ status: :not_found
20
+ )
21
+ end
22
+
23
+ # Handle validation errors
24
+ def handle_validation_error(exception)
25
+ log_exception(exception)
26
+ respond_with_error(
27
+ message: 'Validation failed',
28
+ details: exception.record.errors.full_messages,
29
+ status: :unprocessable_entity
30
+ )
31
+ end
32
+
33
+ # Handle missing parameters
34
+ def handle_parameter_missing(exception)
35
+ log_exception(exception)
36
+ respond_with_error(
37
+ message: 'Missing parameter',
38
+ details: exception.message,
39
+ status: :bad_request
40
+ )
41
+ end
42
+
43
+ # Add custom metadata to responses
44
+ def meta
45
+ {
46
+ app_version: '1.0.0',
47
+ api_version: 'v1',
48
+ timestamp: Time.current,
49
+ }
50
+ end
51
+ end