claude-agent-framework 1.0.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 (111) hide show
  1. package/README.md +128 -0
  2. package/bin/claude-framework +3 -0
  3. package/framework/agents/design-lead.md +240 -0
  4. package/framework/agents/product-owner.md +179 -0
  5. package/framework/agents/tech-lead.md +226 -0
  6. package/framework/commands/ayuda.md +127 -0
  7. package/framework/commands/a/303/261adir.md +98 -0
  8. package/framework/commands/backup.md +397 -0
  9. package/framework/commands/cambiar.md +110 -0
  10. package/framework/commands/cloud.md +457 -0
  11. package/framework/commands/code.md +142 -0
  12. package/framework/commands/debug.md +334 -0
  13. package/framework/commands/deploy.md +383 -0
  14. package/framework/commands/deshacer.md +120 -0
  15. package/framework/commands/estado.md +218 -0
  16. package/framework/commands/explica.md +227 -0
  17. package/framework/commands/feature.md +120 -0
  18. package/framework/commands/git.md +427 -0
  19. package/framework/commands/historial.md +202 -0
  20. package/framework/commands/learn.md +408 -0
  21. package/framework/commands/movil.md +245 -0
  22. package/framework/commands/nuevo.md +118 -0
  23. package/framework/commands/plan.md +134 -0
  24. package/framework/commands/prd.md +113 -0
  25. package/framework/commands/probar.md +148 -0
  26. package/framework/commands/revisar.md +208 -0
  27. package/framework/commands/seeds.md +230 -0
  28. package/framework/commands/seguridad.md +226 -0
  29. package/framework/commands/tasks.md +157 -0
  30. package/framework/skills/architecture/algorithms.md +970 -0
  31. package/framework/skills/architecture/clean-code.md +1080 -0
  32. package/framework/skills/architecture/design-patterns.md +1984 -0
  33. package/framework/skills/architecture/functional-programming.md +972 -0
  34. package/framework/skills/architecture/solid.md +991 -0
  35. package/framework/skills/cloud/cloud-aws.md +848 -0
  36. package/framework/skills/cloud/cloud-azure.md +931 -0
  37. package/framework/skills/cloud/cloud-gcp.md +848 -0
  38. package/framework/skills/cloud/message-queues.md +1229 -0
  39. package/framework/skills/core/accessibility.md +401 -0
  40. package/framework/skills/core/api.md +474 -0
  41. package/framework/skills/core/authentication.md +306 -0
  42. package/framework/skills/core/authorization.md +388 -0
  43. package/framework/skills/core/background-jobs.md +341 -0
  44. package/framework/skills/core/caching.md +473 -0
  45. package/framework/skills/core/code-review.md +341 -0
  46. package/framework/skills/core/controllers.md +290 -0
  47. package/framework/skills/core/cua.md +285 -0
  48. package/framework/skills/core/documentation.md +472 -0
  49. package/framework/skills/core/file-uploads.md +351 -0
  50. package/framework/skills/core/hotwire-native.md +296 -0
  51. package/framework/skills/core/hotwire.md +278 -0
  52. package/framework/skills/core/i18n.md +334 -0
  53. package/framework/skills/core/imports-exports.md +750 -0
  54. package/framework/skills/core/infrastructure.md +337 -0
  55. package/framework/skills/core/models.md +228 -0
  56. package/framework/skills/core/notifications.md +672 -0
  57. package/framework/skills/core/payments.md +581 -0
  58. package/framework/skills/core/performance.md +361 -0
  59. package/framework/skills/core/rails-scaffold.md +131 -0
  60. package/framework/skills/core/search.md +518 -0
  61. package/framework/skills/core/security.md +565 -0
  62. package/framework/skills/core/seeds.md +307 -0
  63. package/framework/skills/core/seo.md +542 -0
  64. package/framework/skills/core/testing.md +393 -0
  65. package/framework/skills/core/views.md +260 -0
  66. package/framework/skills/core/websockets.md +564 -0
  67. package/framework/skills/data/advanced-sql.md +1204 -0
  68. package/framework/skills/data/nosql.md +1141 -0
  69. package/framework/skills/devops/containers-advanced.md +1237 -0
  70. package/framework/skills/devops/debugging.md +834 -0
  71. package/framework/skills/devops/git-workflow.md +752 -0
  72. package/framework/skills/devops/networking.md +932 -0
  73. package/framework/skills/devops/shell-scripting.md +1132 -0
  74. package/framework/sub-agents/architecture-patterns-agent.md +1450 -0
  75. package/framework/sub-agents/cloud-agent.md +677 -0
  76. package/framework/sub-agents/data.md +504 -0
  77. package/framework/sub-agents/debugging-agent.md +554 -0
  78. package/framework/sub-agents/devops.md +483 -0
  79. package/framework/sub-agents/docs.md +176 -0
  80. package/framework/sub-agents/frontend-dev.md +349 -0
  81. package/framework/sub-agents/git-workflow-agent.md +697 -0
  82. package/framework/sub-agents/integrations.md +630 -0
  83. package/framework/sub-agents/native-dev.md +434 -0
  84. package/framework/sub-agents/qa.md +138 -0
  85. package/framework/sub-agents/rails-dev.md +375 -0
  86. package/framework/sub-agents/security.md +526 -0
  87. package/framework/sub-agents/ui.md +437 -0
  88. package/framework/sub-agents/ux.md +284 -0
  89. package/framework/templates/api-spec.md +500 -0
  90. package/framework/templates/component-spec.md +248 -0
  91. package/framework/templates/feature.json +13 -0
  92. package/framework/templates/model-spec.md +318 -0
  93. package/framework/templates/prd-template.md +80 -0
  94. package/framework/templates/task-plan.md +122 -0
  95. package/framework/templates/task-user-story.md +52 -0
  96. package/framework/templates/technical-spec.md +260 -0
  97. package/framework/templates/user-story.md +95 -0
  98. package/package.json +42 -0
  99. package/project-templates/CLAUDE.md +42 -0
  100. package/project-templates/contexts/architecture.md +25 -0
  101. package/project-templates/contexts/conventions.md +46 -0
  102. package/project-templates/contexts/design-system.md +47 -0
  103. package/project-templates/contexts/requirements.md +38 -0
  104. package/project-templates/contexts/stack.md +30 -0
  105. package/project-templates/history/active/models.md +11 -0
  106. package/project-templates/history/changelog.md +15 -0
  107. package/project-templates/workspace/.gitkeep +0 -0
  108. package/src/cli.js +52 -0
  109. package/src/init.js +104 -0
  110. package/src/status.js +75 -0
  111. package/src/update.js +88 -0
@@ -0,0 +1,1080 @@
1
+ # Skill: Clean Code Principles
2
+
3
+ ## Purpose
4
+ Write readable, maintainable, and self-documenting Ruby and Rails code following Uncle Bob's Clean Code principles.
5
+
6
+ ---
7
+
8
+ ## Naming Conventions
9
+
10
+ ### Use Intention-Revealing Names
11
+ Names should tell you why something exists, what it does, and how it's used.
12
+
13
+ ```ruby
14
+ # Bad
15
+ d = Time.now - user.created_at # elapsed time in days?
16
+ list = User.where("age > 18")
17
+ temp = calculate_something
18
+
19
+ # Good
20
+ days_since_registration = (Time.now - user.created_at).to_i / 1.day
21
+ adult_users = User.where("age >= ?", 18)
22
+ monthly_revenue = calculate_monthly_revenue
23
+ ```
24
+
25
+ ### Use Pronounceable Names
26
+ ```ruby
27
+ # Bad
28
+ genymdhms = Time.now.strftime("%Y%m%d%H%M%S")
29
+ cstmr_lst = []
30
+ modymdhms = record.updated_at
31
+
32
+ # Good
33
+ generation_timestamp = Time.now.strftime("%Y%m%d%H%M%S")
34
+ customers = []
35
+ modification_timestamp = record.updated_at
36
+ ```
37
+
38
+ ### Use Searchable Names
39
+ ```ruby
40
+ # Bad - magic numbers are not searchable
41
+ if status == 4
42
+ user.update(role: 2)
43
+ end
44
+
45
+ # Good - constants are searchable and meaningful
46
+ STATUS_APPROVED = 4
47
+ ROLE_ADMIN = 2
48
+
49
+ if status == STATUS_APPROVED
50
+ user.update(role: ROLE_ADMIN)
51
+ end
52
+
53
+ # Even better in Rails - use enums
54
+ class User < ApplicationRecord
55
+ enum status: { pending: 0, approved: 1, rejected: 2 }
56
+ enum role: { user: 0, moderator: 1, admin: 2 }
57
+ end
58
+
59
+ if user.approved?
60
+ user.admin!
61
+ end
62
+ ```
63
+
64
+ ### Naming Conventions by Type
65
+
66
+ ```ruby
67
+ # Classes/Modules: Nouns, PascalCase
68
+ class UserAccount; end
69
+ class PaymentProcessor; end
70
+ module Searchable; end
71
+
72
+ # Methods: Verbs, snake_case
73
+ def calculate_total; end
74
+ def send_notification; end
75
+ def validate_email; end
76
+
77
+ # Predicate methods: End with ?
78
+ def valid?; end
79
+ def admin?; end
80
+ def can_edit?; end
81
+
82
+ # Dangerous methods: End with !
83
+ def save!; end # Raises exception on failure
84
+ def delete!; end # Destructive operation
85
+ def normalize!; end # Mutates in place
86
+
87
+ # Variables: snake_case, descriptive
88
+ current_user = User.find(id)
89
+ total_amount = cart.items.sum(&:price)
90
+ is_authenticated = session[:user_id].present? # or: authenticated?
91
+
92
+ # Constants: SCREAMING_SNAKE_CASE
93
+ MAX_LOGIN_ATTEMPTS = 5
94
+ DEFAULT_PAGE_SIZE = 25
95
+ API_BASE_URL = "https://api.example.com"
96
+ ```
97
+
98
+ ### Avoid Mental Mapping
99
+ ```ruby
100
+ # Bad - reader must remember what a, b, c mean
101
+ def calculate(a, b, c)
102
+ a * b * (1 + c)
103
+ end
104
+
105
+ # Good - names are self-documenting
106
+ def calculate_total_with_tax(quantity, unit_price, tax_rate)
107
+ quantity * unit_price * (1 + tax_rate)
108
+ end
109
+ ```
110
+
111
+ ---
112
+
113
+ ## Functions / Methods
114
+
115
+ ### Small and Focused
116
+ Methods should do one thing, do it well, and do it only.
117
+
118
+ ```ruby
119
+ # Bad - method does too many things
120
+ def process_order(order)
121
+ # Validate
122
+ return false if order.items.empty?
123
+ return false unless order.user.active?
124
+
125
+ # Calculate totals
126
+ subtotal = order.items.sum(&:price)
127
+ tax = subtotal * 0.1
128
+ shipping = subtotal > 100 ? 0 : 10
129
+ total = subtotal + tax + shipping
130
+
131
+ # Process payment
132
+ payment_result = PaymentGateway.charge(total, order.payment_info)
133
+ return false unless payment_result.success?
134
+
135
+ # Update inventory
136
+ order.items.each do |item|
137
+ product = Product.find(item.product_id)
138
+ product.decrement!(:stock, item.quantity)
139
+ end
140
+
141
+ # Create records
142
+ order.update(
143
+ status: :paid,
144
+ total: total,
145
+ paid_at: Time.current
146
+ )
147
+
148
+ # Send notifications
149
+ OrderMailer.confirmation(order).deliver_later
150
+ AdminNotifier.new_order(order)
151
+
152
+ true
153
+ end
154
+
155
+ # Good - each method does one thing
156
+ def process_order(order)
157
+ return failure("Empty order") if order.items.empty?
158
+ return failure("Inactive user") unless order.user.active?
159
+
160
+ totals = calculate_totals(order)
161
+ payment = process_payment(order, totals[:total])
162
+
163
+ return failure(payment.error) unless payment.success?
164
+
165
+ finalize_order(order, totals, payment)
166
+ send_notifications(order)
167
+
168
+ success(order)
169
+ end
170
+
171
+ private
172
+
173
+ def calculate_totals(order)
174
+ subtotal = order.items.sum(&:price)
175
+ {
176
+ subtotal: subtotal,
177
+ tax: calculate_tax(subtotal),
178
+ shipping: calculate_shipping(subtotal),
179
+ total: subtotal + tax + shipping
180
+ }
181
+ end
182
+
183
+ def process_payment(order, amount)
184
+ PaymentGateway.charge(amount, order.payment_info)
185
+ end
186
+
187
+ def finalize_order(order, totals, payment)
188
+ reduce_inventory(order.items)
189
+ order.update(status: :paid, total: totals[:total], paid_at: Time.current)
190
+ end
191
+
192
+ def send_notifications(order)
193
+ OrderMailer.confirmation(order).deliver_later
194
+ AdminNotifier.new_order(order)
195
+ end
196
+ ```
197
+
198
+ ### Few Arguments (Ideally Zero to Two)
199
+ ```ruby
200
+ # Bad - too many arguments
201
+ def create_user(first_name, last_name, email, password, role, department, manager_id, start_date)
202
+ # ...
203
+ end
204
+
205
+ # Good - use a hash or object
206
+ def create_user(attributes)
207
+ User.create!(attributes)
208
+ end
209
+
210
+ # Or named parameters
211
+ def create_user(email:, password:, name: nil, role: :user)
212
+ User.create!(email: email, password: password, name: name, role: role)
213
+ end
214
+
215
+ # Best - use a form object or builder
216
+ class UserRegistration
217
+ include ActiveModel::Model
218
+
219
+ attr_accessor :email, :password, :name, :role
220
+
221
+ validates :email, :password, presence: true
222
+
223
+ def save
224
+ return false unless valid?
225
+ User.create!(attributes)
226
+ end
227
+ end
228
+
229
+ registration = UserRegistration.new(params[:user])
230
+ registration.save
231
+ ```
232
+
233
+ ### Avoid Flag Arguments
234
+ ```ruby
235
+ # Bad - boolean parameter
236
+ def render_page(content, include_sidebar)
237
+ if include_sidebar
238
+ render_with_sidebar(content)
239
+ else
240
+ render_without_sidebar(content)
241
+ end
242
+ end
243
+
244
+ render_page(content, true) # What does true mean?
245
+
246
+ # Good - separate methods
247
+ def render_page_with_sidebar(content)
248
+ render_with_sidebar(content)
249
+ end
250
+
251
+ def render_page_without_sidebar(content)
252
+ render_without_sidebar(content)
253
+ end
254
+
255
+ # Or use named parameters
256
+ def render_page(content, sidebar: false)
257
+ sidebar ? render_with_sidebar(content) : render_without_sidebar(content)
258
+ end
259
+
260
+ render_page(content, sidebar: true) # Clear intent
261
+ ```
262
+
263
+ ### Command Query Separation
264
+ Methods should either do something (command) or return something (query), not both.
265
+
266
+ ```ruby
267
+ # Bad - does something AND returns something
268
+ def set_and_check_name(name)
269
+ @name = name
270
+ @name.present?
271
+ end
272
+
273
+ # Good - separate command and query
274
+ def set_name(name)
275
+ @name = name
276
+ end
277
+
278
+ def name_present?
279
+ @name.present?
280
+ end
281
+
282
+ # Usage
283
+ set_name("John")
284
+ if name_present?
285
+ # ...
286
+ end
287
+ ```
288
+
289
+ ---
290
+
291
+ ## Comments
292
+
293
+ ### Code Should Be Self-Documenting
294
+ ```ruby
295
+ # Bad - comment explains what (obvious from code)
296
+ # Check if user is an adult
297
+ if user.age >= 18
298
+ # Allow access
299
+ grant_access
300
+ end
301
+
302
+ # Good - code explains itself
303
+ if user.adult?
304
+ grant_access
305
+ end
306
+
307
+ # In User model
308
+ def adult?
309
+ age >= ADULT_AGE
310
+ end
311
+ ```
312
+
313
+ ### Comment the "Why", Not the "What"
314
+ ```ruby
315
+ # Bad - explains what the code does (obvious)
316
+ # Increment counter by 1
317
+ counter += 1
318
+
319
+ # Good - explains why
320
+ # We add a buffer day to account for timezone differences
321
+ # when calculating subscription expiry
322
+ expiry_date = subscription.end_date + 1.day
323
+
324
+ # Performance optimization: batch processing to avoid memory issues
325
+ # with large datasets (see issue #1234)
326
+ User.find_each(batch_size: 1000) do |user|
327
+ process(user)
328
+ end
329
+ ```
330
+
331
+ ### Good Uses of Comments
332
+ ```ruby
333
+ # TODO: Refactor this when we upgrade to Rails 8
334
+ # FIXME: Race condition possible with concurrent updates
335
+ # HACK: Workaround for third-party API bug (ticket #XYZ)
336
+ # NOTE: This algorithm is O(n^2), acceptable for small datasets
337
+
338
+ # Legal/copyright headers (required by license)
339
+ # frozen_string_literal: true
340
+
341
+ # Public API documentation
342
+ # Calculates the compound interest for an investment.
343
+ #
344
+ # @param principal [Float] The initial investment amount
345
+ # @param rate [Float] Annual interest rate (e.g., 0.05 for 5%)
346
+ # @param years [Integer] Number of years
347
+ # @return [Float] The final amount after compound interest
348
+ #
349
+ # @example
350
+ # compound_interest(1000, 0.05, 10) #=> 1628.89
351
+ def compound_interest(principal, rate, years)
352
+ principal * (1 + rate) ** years
353
+ end
354
+
355
+ # Warning about consequences
356
+ # WARNING: This will delete all user data permanently.
357
+ # Only call this in development/test environments.
358
+ def reset_database!
359
+ raise "Not in production!" if Rails.env.production?
360
+ # ...
361
+ end
362
+ ```
363
+
364
+ ### Avoid Commented-Out Code
365
+ ```ruby
366
+ # Bad - commented code is confusing and clutters
367
+ def calculate_discount(price)
368
+ # old_discount = price * 0.1
369
+ # if customer.vip?
370
+ # old_discount *= 2
371
+ # end
372
+ # return old_discount
373
+
374
+ # new implementation
375
+ DiscountCalculator.new(price, customer).calculate
376
+ end
377
+
378
+ # Good - remove dead code, use git for history
379
+ def calculate_discount(price)
380
+ DiscountCalculator.new(price, customer).calculate
381
+ end
382
+ ```
383
+
384
+ ---
385
+
386
+ ## Formatting
387
+
388
+ ### Consistent Indentation
389
+ ```ruby
390
+ # Use 2 spaces for indentation in Ruby (standard)
391
+ class User
392
+ def full_name
393
+ "#{first_name} #{last_name}"
394
+ end
395
+
396
+ def admin?
397
+ role == :admin
398
+ end
399
+ end
400
+ ```
401
+
402
+ ### Vertical Spacing
403
+ ```ruby
404
+ class OrderService
405
+ # Group related methods together
406
+ # Separate groups with blank lines
407
+
408
+ # Public interface
409
+ def create_order(user, cart)
410
+ validate_cart(cart)
411
+ order = build_order(user, cart)
412
+ process_order(order)
413
+ order
414
+ end
415
+
416
+ def cancel_order(order)
417
+ refund_payment(order)
418
+ restore_inventory(order)
419
+ order.cancel!
420
+ end
421
+
422
+ private
423
+
424
+ # Validation helpers
425
+ def validate_cart(cart)
426
+ raise EmptyCartError if cart.empty?
427
+ end
428
+
429
+ # Order building
430
+ def build_order(user, cart)
431
+ Order.new(user: user, items: cart.items)
432
+ end
433
+
434
+ # Processing
435
+ def process_order(order)
436
+ charge_payment(order)
437
+ reserve_inventory(order)
438
+ send_confirmation(order)
439
+ end
440
+
441
+ # Payment operations
442
+ def charge_payment(order)
443
+ # ...
444
+ end
445
+
446
+ def refund_payment(order)
447
+ # ...
448
+ end
449
+
450
+ # Inventory operations
451
+ def reserve_inventory(order)
452
+ # ...
453
+ end
454
+
455
+ def restore_inventory(order)
456
+ # ...
457
+ end
458
+
459
+ # Notifications
460
+ def send_confirmation(order)
461
+ # ...
462
+ end
463
+ end
464
+ ```
465
+
466
+ ### Line Length
467
+ ```ruby
468
+ # Keep lines under 80-120 characters
469
+ # Break long method chains
470
+ User
471
+ .where(active: true)
472
+ .includes(:orders)
473
+ .order(created_at: :desc)
474
+ .limit(10)
475
+
476
+ # Break long argument lists
477
+ create_user(
478
+ email: "john@example.com",
479
+ first_name: "John",
480
+ last_name: "Doe",
481
+ role: :admin,
482
+ department: "Engineering"
483
+ )
484
+
485
+ # Break long conditionals
486
+ if user.active? &&
487
+ user.email_verified? &&
488
+ user.subscription.valid? &&
489
+ user.two_factor_enabled?
490
+ grant_premium_access
491
+ end
492
+
493
+ # Or extract to methods
494
+ if eligible_for_premium_access?(user)
495
+ grant_premium_access
496
+ end
497
+ ```
498
+
499
+ ---
500
+
501
+ ## Error Handling
502
+
503
+ ### Use Exceptions, Not Return Codes
504
+ ```ruby
505
+ # Bad - return codes
506
+ def withdraw(amount)
507
+ return -1 if amount <= 0
508
+ return -2 if amount > balance
509
+ @balance -= amount
510
+ 0 # success
511
+ end
512
+
513
+ result = account.withdraw(100)
514
+ case result
515
+ when 0 then puts "Success"
516
+ when -1 then puts "Invalid amount"
517
+ when -2 then puts "Insufficient funds"
518
+ end
519
+
520
+ # Good - exceptions
521
+ class InvalidAmountError < StandardError; end
522
+ class InsufficientFundsError < StandardError; end
523
+
524
+ def withdraw(amount)
525
+ raise InvalidAmountError, "Amount must be positive" if amount <= 0
526
+ raise InsufficientFundsError, "Balance too low" if amount > balance
527
+ @balance -= amount
528
+ end
529
+
530
+ begin
531
+ account.withdraw(100)
532
+ puts "Success"
533
+ rescue InvalidAmountError => e
534
+ puts "Error: #{e.message}"
535
+ rescue InsufficientFundsError => e
536
+ puts "Error: #{e.message}"
537
+ end
538
+ ```
539
+
540
+ ### Fail Fast
541
+ ```ruby
542
+ # Bad - deeply nested conditionals
543
+ def process_payment(order)
544
+ if order.present?
545
+ if order.items.any?
546
+ if order.user.active?
547
+ if order.payment_method.valid?
548
+ # Finally, the actual logic
549
+ charge_customer(order)
550
+ else
551
+ log_error("Invalid payment method")
552
+ end
553
+ else
554
+ log_error("Inactive user")
555
+ end
556
+ else
557
+ log_error("Empty order")
558
+ end
559
+ else
560
+ log_error("No order")
561
+ end
562
+ end
563
+
564
+ # Good - guard clauses / fail fast
565
+ def process_payment(order)
566
+ raise ArgumentError, "Order required" unless order.present?
567
+ raise EmptyOrderError unless order.items.any?
568
+ raise InactiveUserError unless order.user.active?
569
+ raise InvalidPaymentError unless order.payment_method.valid?
570
+
571
+ charge_customer(order)
572
+ end
573
+ ```
574
+
575
+ ### Define Custom Exceptions
576
+ ```ruby
577
+ # app/errors/application_error.rb
578
+ class ApplicationError < StandardError
579
+ attr_reader :code, :details
580
+
581
+ def initialize(message = nil, code: nil, details: {})
582
+ @code = code
583
+ @details = details
584
+ super(message)
585
+ end
586
+ end
587
+
588
+ # Domain-specific errors
589
+ class PaymentError < ApplicationError; end
590
+ class PaymentDeclinedError < PaymentError; end
591
+ class PaymentGatewayError < PaymentError; end
592
+
593
+ class OrderError < ApplicationError; end
594
+ class OutOfStockError < OrderError; end
595
+ class InvalidOrderError < OrderError; end
596
+
597
+ # Usage with details
598
+ raise OutOfStockError.new(
599
+ "Product not available",
600
+ code: "OUT_OF_STOCK",
601
+ details: { product_id: product.id, requested: 5, available: 2 }
602
+ )
603
+ ```
604
+
605
+ ### Handle Errors at the Right Level
606
+ ```ruby
607
+ # In Rails controller - handle and respond
608
+ class OrdersController < ApplicationController
609
+ rescue_from OrderError, with: :handle_order_error
610
+ rescue_from PaymentError, with: :handle_payment_error
611
+
612
+ def create
613
+ @order = OrderService.new.create(order_params)
614
+ redirect_to @order, notice: "Order placed!"
615
+ end
616
+
617
+ private
618
+
619
+ def handle_order_error(error)
620
+ flash.now[:alert] = error.message
621
+ render :new, status: :unprocessable_entity
622
+ end
623
+
624
+ def handle_payment_error(error)
625
+ flash.now[:alert] = "Payment failed: #{error.message}"
626
+ render :checkout, status: :payment_required
627
+ end
628
+ end
629
+
630
+ # In service - raise, don't handle
631
+ class OrderService
632
+ def create(params)
633
+ order = Order.new(params)
634
+ validate!(order)
635
+ process_payment!(order)
636
+ order.save!
637
+ order
638
+ end
639
+
640
+ private
641
+
642
+ def validate!(order)
643
+ raise InvalidOrderError, "No items" if order.items.empty?
644
+ end
645
+
646
+ def process_payment!(order)
647
+ result = PaymentGateway.charge(order.total)
648
+ raise PaymentDeclinedError, result.message unless result.success?
649
+ end
650
+ end
651
+ ```
652
+
653
+ ---
654
+
655
+ ## DRY - Don't Repeat Yourself
656
+
657
+ ### Extract Common Patterns
658
+ ```ruby
659
+ # Bad - repeated code
660
+ class UsersController < ApplicationController
661
+ def show
662
+ @user = User.find(params[:id])
663
+ authorize @user
664
+ respond_to do |format|
665
+ format.html
666
+ format.json { render json: @user }
667
+ end
668
+ rescue ActiveRecord::RecordNotFound
669
+ redirect_to users_path, alert: "User not found"
670
+ end
671
+
672
+ def edit
673
+ @user = User.find(params[:id])
674
+ authorize @user
675
+ respond_to do |format|
676
+ format.html
677
+ format.json { render json: @user }
678
+ end
679
+ rescue ActiveRecord::RecordNotFound
680
+ redirect_to users_path, alert: "User not found"
681
+ end
682
+ end
683
+
684
+ # Good - DRY with callbacks and concerns
685
+ class UsersController < ApplicationController
686
+ before_action :set_user, only: [:show, :edit, :update, :destroy]
687
+
688
+ def show
689
+ respond_with @user
690
+ end
691
+
692
+ def edit
693
+ respond_with @user
694
+ end
695
+
696
+ private
697
+
698
+ def set_user
699
+ @user = User.find(params[:id])
700
+ authorize @user
701
+ end
702
+ end
703
+
704
+ # Or with a concern for common behavior
705
+ module Findable
706
+ extend ActiveSupport::Concern
707
+
708
+ class_methods do
709
+ def set_resource(name, scope: nil)
710
+ before_action only: [:show, :edit, :update, :destroy] do
711
+ model = name.to_s.classify.constantize
712
+ query = scope ? instance_exec(&scope) : model
713
+ instance_variable_set("@#{name}", query.find(params[:id]))
714
+ end
715
+ end
716
+ end
717
+ end
718
+
719
+ class UsersController < ApplicationController
720
+ include Findable
721
+ set_resource :user
722
+ end
723
+ ```
724
+
725
+ ### But Don't Over-DRY
726
+ ```ruby
727
+ # Sometimes repetition is okay if:
728
+ # 1. The code is simple enough
729
+ # 2. The concepts are different even if similar
730
+ # 3. DRY would create unnecessary coupling
731
+
732
+ # Okay to repeat - different domains
733
+ class User < ApplicationRecord
734
+ def full_name
735
+ "#{first_name} #{last_name}"
736
+ end
737
+ end
738
+
739
+ class Author < ApplicationRecord
740
+ def full_name
741
+ "#{first_name} #{last_name}"
742
+ end
743
+ end
744
+
745
+ # These look similar but represent different concepts
746
+ # Extracting to a module might create unwanted coupling
747
+ # If full_name changes for Author (e.g., add title), User shouldn't be affected
748
+ ```
749
+
750
+ ---
751
+
752
+ ## KISS - Keep It Simple, Stupid
753
+
754
+ ### Prefer Simple Solutions
755
+ ```ruby
756
+ # Bad - over-engineered
757
+ class StringReverser
758
+ def initialize(strategy: DefaultReversalStrategy.new)
759
+ @strategy = strategy
760
+ end
761
+
762
+ def reverse(string)
763
+ @strategy.reverse(string)
764
+ end
765
+
766
+ class DefaultReversalStrategy
767
+ def reverse(string)
768
+ string.chars.reverse.join
769
+ end
770
+ end
771
+ end
772
+
773
+ reverser = StringReverser.new
774
+ reverser.reverse("hello")
775
+
776
+ # Good - simple
777
+ "hello".reverse
778
+ ```
779
+
780
+ ### Avoid Premature Optimization
781
+ ```ruby
782
+ # Bad - premature optimization
783
+ class UserSearch
784
+ def initialize
785
+ @cache = LRUCache.new(1000)
786
+ @index = BloomFilter.new
787
+ @thread_pool = ThreadPool.new(4)
788
+ end
789
+
790
+ def search(query)
791
+ return @cache.get(query) if @cache.has?(query)
792
+ # Complex parallel search...
793
+ end
794
+ end
795
+
796
+ # Good - start simple, optimize when needed
797
+ class UserSearch
798
+ def search(query)
799
+ User.where("name ILIKE ?", "%#{query}%").limit(20)
800
+ end
801
+ end
802
+
803
+ # Add complexity only when you have evidence it's needed:
804
+ # - Slow queries in production logs
805
+ # - Performance tests showing bottlenecks
806
+ # - Clear requirements for scale
807
+ ```
808
+
809
+ ---
810
+
811
+ ## YAGNI - You Aren't Gonna Need It
812
+
813
+ ### Don't Build Features You Don't Need Yet
814
+ ```ruby
815
+ # Bad - building for hypothetical future requirements
816
+ class Report
817
+ def generate(format = :pdf)
818
+ data = fetch_data
819
+
820
+ case format
821
+ when :pdf then generate_pdf(data)
822
+ when :csv then generate_csv(data)
823
+ when :excel then generate_excel(data)
824
+ when :xml then generate_xml(data) # Never requested
825
+ when :json then generate_json(data) # Never requested
826
+ when :html then generate_html(data) # Never requested
827
+ when :markdown then generate_markdown(data) # Never requested
828
+ end
829
+ end
830
+ end
831
+
832
+ # Good - build what's needed now
833
+ class Report
834
+ def generate(format = :pdf)
835
+ data = fetch_data
836
+
837
+ case format
838
+ when :pdf then generate_pdf(data)
839
+ when :csv then generate_csv(data)
840
+ else raise ArgumentError, "Unsupported format: #{format}"
841
+ end
842
+ end
843
+ end
844
+ # Add other formats when/if they're actually needed
845
+ ```
846
+
847
+ ### Avoid Over-Abstraction
848
+ ```ruby
849
+ # Bad - abstraction for one use case
850
+ class NotificationStrategyFactory
851
+ def self.create(type)
852
+ case type
853
+ when :email
854
+ EmailNotificationStrategy.new
855
+ end
856
+ # Only email exists, but we built a whole factory
857
+ end
858
+ end
859
+
860
+ # Good - just send the email
861
+ class NotificationService
862
+ def notify(user, message)
863
+ UserMailer.notification(user, message).deliver_later
864
+ end
865
+ end
866
+
867
+ # When you actually need SMS and push:
868
+ class NotificationService
869
+ def notify(user, message, via: :email)
870
+ case via
871
+ when :email then UserMailer.notification(user, message).deliver_later
872
+ when :sms then SmsClient.send(user.phone, message)
873
+ when :push then PushService.send(user.device_token, message)
874
+ end
875
+ end
876
+ end
877
+ ```
878
+
879
+ ---
880
+
881
+ ## Boy Scout Rule
882
+
883
+ ### Leave Code Better Than You Found It
884
+
885
+ ```ruby
886
+ # Before - you're fixing a bug in this method
887
+ def process_order(order)
888
+ if order.items.count > 0
889
+ total = 0
890
+ for item in order.items
891
+ total = total + item.price
892
+ end
893
+ order.total = total
894
+ order.save
895
+ UserMailer.order_confirmation(order.user, order).deliver
896
+ return true
897
+ else
898
+ return false
899
+ end
900
+ end
901
+
902
+ # After - fixed the bug AND improved the code
903
+ def process_order(order)
904
+ return false if order.items.empty?
905
+
906
+ order.update!(total: order.items.sum(&:price))
907
+ OrderMailer.confirmation(order).deliver_later
908
+
909
+ true
910
+ end
911
+
912
+ # Improvements made while fixing the bug:
913
+ # 1. Guard clause instead of wrapping if
914
+ # 2. Idiomatic Ruby (empty?, sum)
915
+ # 3. Bang method for save (fail fast)
916
+ # 4. deliver_later instead of deliver
917
+ # 5. Simplified mailer call
918
+ # 6. Removed unnecessary variable
919
+ ```
920
+
921
+ ### Small Incremental Improvements
922
+ ```ruby
923
+ # Don't try to rewrite everything at once
924
+ # Make small improvements as you touch code:
925
+
926
+ # If you see: outdated syntax, improve it
927
+ # Before
928
+ { :key => "value" }
929
+ # After
930
+ { key: "value" }
931
+
932
+ # If you see: unclear names, clarify them
933
+ # Before
934
+ def calc(x, y)
935
+ # After
936
+ def calculate_total(price, quantity)
937
+
938
+ # If you see: missing tests, add them
939
+ # Before: no tests
940
+ # After: at least cover the method you're modifying
941
+
942
+ # If you see: dead code, remove it
943
+ # Before: commented-out code from 2019
944
+ # After: delete it (git has history)
945
+ ```
946
+
947
+ ---
948
+
949
+ ## Rails-Specific Clean Code
950
+
951
+ ### Fat Models, Skinny Controllers (But Not Too Fat)
952
+ ```ruby
953
+ # Controller: Only HTTP concerns
954
+ class OrdersController < ApplicationController
955
+ def create
956
+ result = CreateOrder.call(order_params, current_user)
957
+
958
+ if result.success?
959
+ redirect_to result.order, notice: "Order placed!"
960
+ else
961
+ @order = result.order
962
+ flash.now[:alert] = result.error
963
+ render :new, status: :unprocessable_entity
964
+ end
965
+ end
966
+
967
+ private
968
+
969
+ def order_params
970
+ params.require(:order).permit(:shipping_address, items: [:product_id, :quantity])
971
+ end
972
+ end
973
+
974
+ # Service: Business logic
975
+ class CreateOrder
976
+ def self.call(params, user)
977
+ new(params, user).call
978
+ end
979
+
980
+ def initialize(params, user)
981
+ @params = params
982
+ @user = user
983
+ end
984
+
985
+ def call
986
+ order = @user.orders.build(@params)
987
+
988
+ if order.valid? && process_payment(order)
989
+ order.save!
990
+ notify(order)
991
+ Result.new(success: true, order: order)
992
+ else
993
+ Result.new(success: false, order: order, error: order.errors.full_messages.join(", "))
994
+ end
995
+ end
996
+
997
+ private
998
+
999
+ def process_payment(order)
1000
+ # ...
1001
+ end
1002
+
1003
+ def notify(order)
1004
+ # ...
1005
+ end
1006
+
1007
+ Result = Struct.new(:success, :order, :error, keyword_init: true) do
1008
+ def success?
1009
+ success
1010
+ end
1011
+ end
1012
+ end
1013
+
1014
+ # Model: Data, validations, associations, scopes
1015
+ class Order < ApplicationRecord
1016
+ belongs_to :user
1017
+ has_many :items, class_name: "OrderItem"
1018
+
1019
+ validates :shipping_address, presence: true
1020
+
1021
+ scope :recent, -> { order(created_at: :desc) }
1022
+ scope :pending, -> { where(status: :pending) }
1023
+
1024
+ def total
1025
+ items.sum(&:subtotal)
1026
+ end
1027
+ end
1028
+ ```
1029
+
1030
+ ### Use Query Objects for Complex Queries
1031
+ ```ruby
1032
+ # Instead of putting complex queries in models
1033
+ class UserSearch
1034
+ def initialize(params)
1035
+ @params = params
1036
+ end
1037
+
1038
+ def call
1039
+ scope = User.all
1040
+ scope = filter_by_status(scope)
1041
+ scope = filter_by_role(scope)
1042
+ scope = filter_by_date_range(scope)
1043
+ scope = search_by_name(scope)
1044
+ scope = apply_sorting(scope)
1045
+ scope
1046
+ end
1047
+
1048
+ private
1049
+
1050
+ def filter_by_status(scope)
1051
+ return scope unless @params[:status].present?
1052
+ scope.where(status: @params[:status])
1053
+ end
1054
+
1055
+ def filter_by_role(scope)
1056
+ return scope unless @params[:role].present?
1057
+ scope.where(role: @params[:role])
1058
+ end
1059
+
1060
+ def filter_by_date_range(scope)
1061
+ scope = scope.where("created_at >= ?", @params[:from]) if @params[:from]
1062
+ scope = scope.where("created_at <= ?", @params[:to]) if @params[:to]
1063
+ scope
1064
+ end
1065
+
1066
+ def search_by_name(scope)
1067
+ return scope unless @params[:q].present?
1068
+ scope.where("name ILIKE ?", "%#{@params[:q]}%")
1069
+ end
1070
+
1071
+ def apply_sorting(scope)
1072
+ column = @params[:sort] || :created_at
1073
+ direction = @params[:direction] || :desc
1074
+ scope.order(column => direction)
1075
+ end
1076
+ end
1077
+
1078
+ # Usage
1079
+ users = UserSearch.new(params).call
1080
+ ```