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,991 @@
1
+ # Skill: SOLID Principles
2
+
3
+ ## Purpose
4
+ Apply SOLID principles to write maintainable, extensible, and testable Ruby and Rails code.
5
+
6
+ ---
7
+
8
+ ## S - Single Responsibility Principle (SRP)
9
+
10
+ **Definition:** A class should have only one reason to change. Each class should do one thing and do it well.
11
+
12
+ ### Bad Example
13
+ ```ruby
14
+ # This class does too many things: data access, validation, notifications, formatting
15
+ class User < ApplicationRecord
16
+ validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
17
+ validates :name, presence: true
18
+
19
+ def full_name
20
+ "#{first_name} #{last_name}"
21
+ end
22
+
23
+ def send_welcome_email
24
+ UserMailer.welcome(self).deliver_later
25
+ end
26
+
27
+ def send_password_reset
28
+ token = generate_reset_token
29
+ UserMailer.password_reset(self, token).deliver_later
30
+ end
31
+
32
+ def generate_monthly_report
33
+ orders = orders.where(created_at: 1.month.ago..Time.current)
34
+ total = orders.sum(:total)
35
+ # Generate PDF report...
36
+ end
37
+
38
+ def export_to_csv
39
+ CSV.generate do |csv|
40
+ csv << %w[id name email created_at]
41
+ csv << [id, full_name, email, created_at]
42
+ end
43
+ end
44
+
45
+ def calculate_loyalty_points
46
+ orders.sum(:total) / 10
47
+ end
48
+ end
49
+ ```
50
+
51
+ ### Good Example
52
+ ```ruby
53
+ # Model handles only data and validations
54
+ class User < ApplicationRecord
55
+ validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP }
56
+ validates :name, presence: true
57
+
58
+ def full_name
59
+ "#{first_name} #{last_name}"
60
+ end
61
+ end
62
+
63
+ # Separate service for notifications
64
+ class UserNotificationService
65
+ def initialize(user)
66
+ @user = user
67
+ end
68
+
69
+ def send_welcome_email
70
+ UserMailer.welcome(@user).deliver_later
71
+ end
72
+
73
+ def send_password_reset
74
+ token = PasswordResetToken.generate_for(@user)
75
+ UserMailer.password_reset(@user, token).deliver_later
76
+ end
77
+ end
78
+
79
+ # Separate service for reports
80
+ class UserReportService
81
+ def initialize(user)
82
+ @user = user
83
+ end
84
+
85
+ def monthly_report
86
+ orders = @user.orders.where(created_at: 1.month.ago..Time.current)
87
+ MonthlyReportGenerator.new(orders).generate
88
+ end
89
+ end
90
+
91
+ # Separate exporter
92
+ class UserCsvExporter
93
+ def initialize(users)
94
+ @users = users
95
+ end
96
+
97
+ def export
98
+ CSV.generate do |csv|
99
+ csv << headers
100
+ @users.each { |user| csv << row(user) }
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def headers
107
+ %w[id name email created_at]
108
+ end
109
+
110
+ def row(user)
111
+ [user.id, user.full_name, user.email, user.created_at]
112
+ end
113
+ end
114
+
115
+ # Separate calculator for loyalty
116
+ class LoyaltyPointsCalculator
117
+ def initialize(user)
118
+ @user = user
119
+ end
120
+
121
+ def calculate
122
+ @user.orders.sum(:total) / 10
123
+ end
124
+ end
125
+ ```
126
+
127
+ ### How to Apply in Rails
128
+ - **Models:** Only data, validations, associations, scopes
129
+ - **Controllers:** Only HTTP handling, delegate to services
130
+ - **Services:** One business operation per service
131
+ - **Jobs:** One task per job
132
+ - **Mailers:** Only email composition
133
+
134
+ ---
135
+
136
+ ## O - Open/Closed Principle (OCP)
137
+
138
+ **Definition:** Classes should be open for extension but closed for modification. Add new functionality by adding new code, not changing existing code.
139
+
140
+ ### Bad Example
141
+ ```ruby
142
+ class PaymentProcessor
143
+ def process(payment)
144
+ case payment.method
145
+ when :credit_card
146
+ process_credit_card(payment)
147
+ when :paypal
148
+ process_paypal(payment)
149
+ when :stripe
150
+ process_stripe(payment)
151
+ when :apple_pay
152
+ process_apple_pay(payment)
153
+ # Every new payment method requires modifying this class
154
+ end
155
+ end
156
+
157
+ private
158
+
159
+ def process_credit_card(payment)
160
+ # Credit card logic
161
+ end
162
+
163
+ def process_paypal(payment)
164
+ # PayPal logic
165
+ end
166
+
167
+ def process_stripe(payment)
168
+ # Stripe logic
169
+ end
170
+
171
+ def process_apple_pay(payment)
172
+ # Apple Pay logic
173
+ end
174
+ end
175
+ ```
176
+
177
+ ### Good Example
178
+ ```ruby
179
+ # Base class or interface
180
+ class PaymentMethod
181
+ def process(payment)
182
+ raise NotImplementedError, "#{self.class} must implement #process"
183
+ end
184
+ end
185
+
186
+ # Each payment method is a separate class
187
+ class CreditCardPayment < PaymentMethod
188
+ def process(payment)
189
+ # Credit card specific logic
190
+ gateway = CreditCardGateway.new
191
+ gateway.charge(payment.amount, payment.card_details)
192
+ end
193
+ end
194
+
195
+ class PaypalPayment < PaymentMethod
196
+ def process(payment)
197
+ # PayPal specific logic
198
+ client = PaypalClient.new
199
+ client.execute_payment(payment.paypal_order_id)
200
+ end
201
+ end
202
+
203
+ class StripePayment < PaymentMethod
204
+ def process(payment)
205
+ # Stripe specific logic
206
+ Stripe::Charge.create(
207
+ amount: payment.amount_in_cents,
208
+ source: payment.stripe_token
209
+ )
210
+ end
211
+ end
212
+
213
+ # Adding new payment method doesn't require changing existing code
214
+ class ApplePayPayment < PaymentMethod
215
+ def process(payment)
216
+ # Apple Pay specific logic
217
+ end
218
+ end
219
+
220
+ # Processor uses dependency injection
221
+ class PaymentProcessor
222
+ METHODS = {
223
+ credit_card: CreditCardPayment,
224
+ paypal: PaypalPayment,
225
+ stripe: StripePayment,
226
+ apple_pay: ApplePayPayment
227
+ }.freeze
228
+
229
+ def process(payment)
230
+ method_class = METHODS.fetch(payment.method) do
231
+ raise ArgumentError, "Unknown payment method: #{payment.method}"
232
+ end
233
+
234
+ method_class.new.process(payment)
235
+ end
236
+ end
237
+
238
+ # Or use a registry pattern for runtime registration
239
+ class PaymentMethodRegistry
240
+ class << self
241
+ def register(name, handler)
242
+ handlers[name] = handler
243
+ end
244
+
245
+ def for(name)
246
+ handlers.fetch(name) { raise "Unknown payment method: #{name}" }
247
+ end
248
+
249
+ private
250
+
251
+ def handlers
252
+ @handlers ||= {}
253
+ end
254
+ end
255
+ end
256
+
257
+ # Register handlers
258
+ PaymentMethodRegistry.register(:credit_card, CreditCardPayment)
259
+ PaymentMethodRegistry.register(:paypal, PaypalPayment)
260
+ ```
261
+
262
+ ### How to Apply in Rails
263
+ - Use **polymorphism** instead of conditionals
264
+ - Create **service objects** for each variant
265
+ - Use **concerns** to add behavior to models
266
+ - Use **decorators** to extend functionality
267
+ - Define **interfaces** (abstract classes) for extensibility
268
+
269
+ ---
270
+
271
+ ## L - Liskov Substitution Principle (LSP)
272
+
273
+ **Definition:** Objects of a superclass should be replaceable with objects of its subclasses without breaking the application. Subclasses must honor the contract of the parent class.
274
+
275
+ ### Bad Example
276
+ ```ruby
277
+ class Bird
278
+ def fly
279
+ puts "Flying..."
280
+ end
281
+ end
282
+
283
+ class Sparrow < Bird
284
+ def fly
285
+ puts "Sparrow flying..."
286
+ end
287
+ end
288
+
289
+ # Violates LSP: Penguin is a bird but can't fly
290
+ class Penguin < Bird
291
+ def fly
292
+ raise NotImplementedError, "Penguins can't fly!"
293
+ end
294
+ end
295
+
296
+ # This code breaks with Penguin
297
+ def make_bird_fly(bird)
298
+ bird.fly # Raises error for Penguin
299
+ end
300
+
301
+ make_bird_fly(Sparrow.new) # Works
302
+ make_bird_fly(Penguin.new) # Breaks!
303
+ ```
304
+
305
+ ### Good Example
306
+ ```ruby
307
+ # Better hierarchy based on capabilities
308
+ class Bird
309
+ def move
310
+ raise NotImplementedError
311
+ end
312
+ end
313
+
314
+ class FlyingBird < Bird
315
+ def move
316
+ fly
317
+ end
318
+
319
+ def fly
320
+ puts "Flying..."
321
+ end
322
+ end
323
+
324
+ class WalkingBird < Bird
325
+ def move
326
+ walk
327
+ end
328
+
329
+ def walk
330
+ puts "Walking..."
331
+ end
332
+ end
333
+
334
+ class Sparrow < FlyingBird
335
+ def fly
336
+ puts "Sparrow flying..."
337
+ end
338
+ end
339
+
340
+ class Penguin < WalkingBird
341
+ def walk
342
+ puts "Penguin waddling..."
343
+ end
344
+
345
+ def swim
346
+ puts "Penguin swimming..."
347
+ end
348
+ end
349
+
350
+ # Now this works for all birds
351
+ def make_bird_move(bird)
352
+ bird.move
353
+ end
354
+
355
+ make_bird_move(Sparrow.new) # "Sparrow flying..."
356
+ make_bird_move(Penguin.new) # "Penguin waddling..."
357
+ ```
358
+
359
+ ### Rails Example
360
+ ```ruby
361
+ # Bad: Subclass changes behavior in unexpected ways
362
+ class Document < ApplicationRecord
363
+ def publish
364
+ update(published_at: Time.current, status: :published)
365
+ end
366
+ end
367
+
368
+ class DraftDocument < Document
369
+ def publish
370
+ # Violates LSP: silently does nothing instead of publishing
371
+ false
372
+ end
373
+ end
374
+
375
+ # Good: Use state machine or explicit contract
376
+ class Document < ApplicationRecord
377
+ include AASM
378
+
379
+ aasm column: :status do
380
+ state :draft, initial: true
381
+ state :published
382
+
383
+ event :publish do
384
+ transitions from: :draft, to: :published
385
+ end
386
+ end
387
+ end
388
+
389
+ # Or use composition over inheritance
390
+ class Document < ApplicationRecord
391
+ def publish
392
+ publishing_strategy.publish(self)
393
+ end
394
+
395
+ def publishing_strategy
396
+ PublishingStrategy.for(self)
397
+ end
398
+ end
399
+
400
+ class PublishingStrategy
401
+ def self.for(document)
402
+ case document.document_type
403
+ when "standard"
404
+ StandardPublishing.new
405
+ when "review_required"
406
+ ReviewRequiredPublishing.new
407
+ end
408
+ end
409
+ end
410
+
411
+ class StandardPublishing
412
+ def publish(document)
413
+ document.update(published_at: Time.current, status: :published)
414
+ end
415
+ end
416
+
417
+ class ReviewRequiredPublishing
418
+ def publish(document)
419
+ document.update(status: :pending_review)
420
+ NotifyReviewersJob.perform_later(document.id)
421
+ end
422
+ end
423
+ ```
424
+
425
+ ### Rules for LSP
426
+ 1. **Preconditions cannot be strengthened** in subclass
427
+ 2. **Postconditions cannot be weakened** in subclass
428
+ 3. **Invariants must be preserved** in subclass
429
+ 4. **No new exceptions** that parent doesn't throw
430
+ 5. **Return types** must be compatible (same or more specific)
431
+
432
+ ---
433
+
434
+ ## I - Interface Segregation Principle (ISP)
435
+
436
+ **Definition:** Clients should not be forced to depend on interfaces they don't use. Many specific interfaces are better than one general-purpose interface.
437
+
438
+ ### Bad Example
439
+ ```ruby
440
+ # Fat interface - forces all implementations to define everything
441
+ class Worker
442
+ def work
443
+ raise NotImplementedError
444
+ end
445
+
446
+ def eat
447
+ raise NotImplementedError
448
+ end
449
+
450
+ def sleep
451
+ raise NotImplementedError
452
+ end
453
+
454
+ def manage_team
455
+ raise NotImplementedError
456
+ end
457
+
458
+ def attend_meetings
459
+ raise NotImplementedError
460
+ end
461
+ end
462
+
463
+ class Developer < Worker
464
+ def work
465
+ puts "Writing code..."
466
+ end
467
+
468
+ def eat
469
+ puts "Eating lunch..."
470
+ end
471
+
472
+ def sleep
473
+ puts "Sleeping..."
474
+ end
475
+
476
+ # Forced to implement but doesn't use
477
+ def manage_team
478
+ raise "Developers don't manage teams"
479
+ end
480
+
481
+ def attend_meetings
482
+ puts "Attending standup..."
483
+ end
484
+ end
485
+
486
+ class Robot < Worker
487
+ def work
488
+ puts "Processing..."
489
+ end
490
+
491
+ # Robots don't eat or sleep - forced to implement useless methods
492
+ def eat
493
+ raise "Robots don't eat"
494
+ end
495
+
496
+ def sleep
497
+ raise "Robots don't sleep"
498
+ end
499
+
500
+ def manage_team
501
+ raise "Robots don't manage"
502
+ end
503
+
504
+ def attend_meetings
505
+ raise "Robots don't attend meetings"
506
+ end
507
+ end
508
+ ```
509
+
510
+ ### Good Example
511
+ ```ruby
512
+ # Segregated interfaces using modules
513
+ module Workable
514
+ def work
515
+ raise NotImplementedError
516
+ end
517
+ end
518
+
519
+ module Eatable
520
+ def eat
521
+ raise NotImplementedError
522
+ end
523
+ end
524
+
525
+ module Sleepable
526
+ def sleep
527
+ raise NotImplementedError
528
+ end
529
+ end
530
+
531
+ module Manageable
532
+ def manage_team
533
+ raise NotImplementedError
534
+ end
535
+ end
536
+
537
+ module MeetingAttendable
538
+ def attend_meetings
539
+ raise NotImplementedError
540
+ end
541
+ end
542
+
543
+ # Classes include only what they need
544
+ class Developer
545
+ include Workable
546
+ include Eatable
547
+ include Sleepable
548
+ include MeetingAttendable
549
+
550
+ def work
551
+ puts "Writing code..."
552
+ end
553
+
554
+ def eat
555
+ puts "Eating lunch..."
556
+ end
557
+
558
+ def sleep
559
+ puts "Sleeping..."
560
+ end
561
+
562
+ def attend_meetings
563
+ puts "Attending standup..."
564
+ end
565
+ end
566
+
567
+ class Robot
568
+ include Workable
569
+
570
+ def work
571
+ puts "Processing..."
572
+ end
573
+ end
574
+
575
+ class Manager
576
+ include Workable
577
+ include Eatable
578
+ include Sleepable
579
+ include Manageable
580
+ include MeetingAttendable
581
+
582
+ def work
583
+ puts "Managing projects..."
584
+ end
585
+
586
+ def eat
587
+ puts "Business lunch..."
588
+ end
589
+
590
+ def sleep
591
+ puts "Sleeping..."
592
+ end
593
+
594
+ def manage_team
595
+ puts "Leading team..."
596
+ end
597
+
598
+ def attend_meetings
599
+ puts "Running meetings..."
600
+ end
601
+ end
602
+ ```
603
+
604
+ ### Rails Example
605
+ ```ruby
606
+ # Bad: One giant concern with everything
607
+ module Reportable
608
+ extend ActiveSupport::Concern
609
+
610
+ def generate_pdf_report
611
+ # PDF generation
612
+ end
613
+
614
+ def generate_csv_report
615
+ # CSV generation
616
+ end
617
+
618
+ def generate_excel_report
619
+ # Excel generation
620
+ end
621
+
622
+ def send_report_by_email
623
+ # Email sending
624
+ end
625
+
626
+ def schedule_report
627
+ # Scheduling
628
+ end
629
+ end
630
+
631
+ # Good: Segregated concerns
632
+ module PdfExportable
633
+ extend ActiveSupport::Concern
634
+
635
+ def to_pdf
636
+ PdfGenerator.new(self).generate
637
+ end
638
+ end
639
+
640
+ module CsvExportable
641
+ extend ActiveSupport::Concern
642
+
643
+ def to_csv
644
+ CsvGenerator.new(self).generate
645
+ end
646
+ end
647
+
648
+ module ExcelExportable
649
+ extend ActiveSupport::Concern
650
+
651
+ def to_excel
652
+ ExcelGenerator.new(self).generate
653
+ end
654
+ end
655
+
656
+ module Schedulable
657
+ extend ActiveSupport::Concern
658
+
659
+ def schedule(at:)
660
+ ScheduledJob.set(wait_until: at).perform_later(self)
661
+ end
662
+ end
663
+
664
+ # Models include only what they need
665
+ class Invoice < ApplicationRecord
666
+ include PdfExportable
667
+ include CsvExportable
668
+ # Doesn't need Excel or scheduling
669
+ end
670
+
671
+ class Report < ApplicationRecord
672
+ include PdfExportable
673
+ include ExcelExportable
674
+ include Schedulable
675
+ # Doesn't need CSV
676
+ end
677
+ ```
678
+
679
+ ### How to Apply in Rails
680
+ - Create **small, focused concerns** instead of large ones
681
+ - Use **modules** as interfaces
682
+ - **Service objects** should do one thing
683
+ - **Policies** should be granular (CanRead, CanWrite vs CanDoEverything)
684
+
685
+ ---
686
+
687
+ ## D - Dependency Inversion Principle (DIP)
688
+
689
+ **Definition:** High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details.
690
+
691
+ ### Bad Example
692
+ ```ruby
693
+ # High-level module depends directly on low-level module
694
+ class OrderProcessor
695
+ def initialize
696
+ # Direct dependency on concrete implementation
697
+ @email_sender = SmtpEmailSender.new
698
+ @payment_gateway = StripeGateway.new
699
+ @inventory = MySqlInventory.new
700
+ end
701
+
702
+ def process(order)
703
+ return false unless @inventory.check_stock(order.items)
704
+
705
+ @payment_gateway.charge(order.total, order.payment_info)
706
+ @inventory.reduce_stock(order.items)
707
+ @email_sender.send_confirmation(order)
708
+
709
+ true
710
+ end
711
+ end
712
+
713
+ # Problems:
714
+ # - Can't easily test without real SMTP, Stripe, MySQL
715
+ # - Can't swap implementations
716
+ # - Hard to reuse in different contexts
717
+ ```
718
+
719
+ ### Good Example
720
+ ```ruby
721
+ # Define abstractions (interfaces)
722
+ class EmailSender
723
+ def send_email(to:, subject:, body:)
724
+ raise NotImplementedError
725
+ end
726
+ end
727
+
728
+ class PaymentGateway
729
+ def charge(amount, payment_info)
730
+ raise NotImplementedError
731
+ end
732
+ end
733
+
734
+ class InventoryService
735
+ def check_stock(items)
736
+ raise NotImplementedError
737
+ end
738
+
739
+ def reduce_stock(items)
740
+ raise NotImplementedError
741
+ end
742
+ end
743
+
744
+ # Concrete implementations
745
+ class SmtpEmailSender < EmailSender
746
+ def send_email(to:, subject:, body:)
747
+ # SMTP implementation
748
+ end
749
+ end
750
+
751
+ class StripeGateway < PaymentGateway
752
+ def charge(amount, payment_info)
753
+ Stripe::Charge.create(amount: amount, source: payment_info[:token])
754
+ end
755
+ end
756
+
757
+ class DatabaseInventory < InventoryService
758
+ def check_stock(items)
759
+ items.all? { |item| Product.find(item.id).stock >= item.quantity }
760
+ end
761
+
762
+ def reduce_stock(items)
763
+ items.each { |item| Product.find(item.id).decrement!(:stock, item.quantity) }
764
+ end
765
+ end
766
+
767
+ # High-level module depends on abstractions via dependency injection
768
+ class OrderProcessor
769
+ def initialize(email_sender:, payment_gateway:, inventory:)
770
+ @email_sender = email_sender
771
+ @payment_gateway = payment_gateway
772
+ @inventory = inventory
773
+ end
774
+
775
+ def process(order)
776
+ return false unless @inventory.check_stock(order.items)
777
+
778
+ @payment_gateway.charge(order.total, order.payment_info)
779
+ @inventory.reduce_stock(order.items)
780
+ @email_sender.send_email(
781
+ to: order.user.email,
782
+ subject: "Order Confirmation",
783
+ body: "Your order has been processed"
784
+ )
785
+
786
+ true
787
+ end
788
+ end
789
+
790
+ # Usage in production
791
+ processor = OrderProcessor.new(
792
+ email_sender: SmtpEmailSender.new,
793
+ payment_gateway: StripeGateway.new,
794
+ inventory: DatabaseInventory.new
795
+ )
796
+
797
+ # Usage in tests - easy to mock
798
+ class MockEmailSender < EmailSender
799
+ attr_reader :sent_emails
800
+
801
+ def initialize
802
+ @sent_emails = []
803
+ end
804
+
805
+ def send_email(to:, subject:, body:)
806
+ @sent_emails << { to: to, subject: subject, body: body }
807
+ end
808
+ end
809
+
810
+ class MockPaymentGateway < PaymentGateway
811
+ def charge(amount, payment_info)
812
+ { success: true, charge_id: "test_123" }
813
+ end
814
+ end
815
+
816
+ # Test
817
+ processor = OrderProcessor.new(
818
+ email_sender: MockEmailSender.new,
819
+ payment_gateway: MockPaymentGateway.new,
820
+ inventory: MockInventory.new
821
+ )
822
+ ```
823
+
824
+ ### Rails Example with Dependency Injection
825
+ ```ruby
826
+ # config/initializers/dependencies.rb
827
+ Rails.application.config.after_initialize do
828
+ Rails.application.config.dependencies = {
829
+ email_sender: -> { SmtpEmailSender.new },
830
+ payment_gateway: -> { StripeGateway.new },
831
+ inventory: -> { DatabaseInventory.new }
832
+ }
833
+ end
834
+
835
+ # Helper to resolve dependencies
836
+ module DependencyResolver
837
+ def resolve(name)
838
+ Rails.application.config.dependencies[name].call
839
+ end
840
+ end
841
+
842
+ # Usage in service
843
+ class OrderProcessor
844
+ include DependencyResolver
845
+
846
+ def initialize(
847
+ email_sender: resolve(:email_sender),
848
+ payment_gateway: resolve(:payment_gateway),
849
+ inventory: resolve(:inventory)
850
+ )
851
+ @email_sender = email_sender
852
+ @payment_gateway = payment_gateway
853
+ @inventory = inventory
854
+ end
855
+ end
856
+
857
+ # Or using a container (dry-container gem)
858
+ class Container
859
+ extend Dry::Container::Mixin
860
+
861
+ register :email_sender do
862
+ SmtpEmailSender.new
863
+ end
864
+
865
+ register :payment_gateway do
866
+ StripeGateway.new
867
+ end
868
+ end
869
+
870
+ # config/environments/test.rb
871
+ class TestContainer < Container
872
+ register :email_sender do
873
+ MockEmailSender.new
874
+ end
875
+
876
+ register :payment_gateway do
877
+ MockPaymentGateway.new
878
+ end
879
+ end
880
+ ```
881
+
882
+ ### How to Apply in Rails
883
+ - **Inject dependencies** through constructor or method parameters
884
+ - **Use configuration** to wire up implementations
885
+ - **Default to production** implementations with ability to override
886
+ - **Create adapters** for external services
887
+ - **Test doubles** become trivial to use
888
+
889
+ ---
890
+
891
+ ## SOLID Cheat Sheet
892
+
893
+ | Principle | One-liner | Code Smell |
894
+ |-----------|-----------|------------|
895
+ | **S**ingle Responsibility | One class, one reason to change | Class has multiple unrelated methods |
896
+ | **O**pen/Closed | Add features by adding code, not changing it | Switch statements for types |
897
+ | **L**iskov Substitution | Subclasses must be substitutable | Subclass throws unexpected errors |
898
+ | **I**nterface Segregation | Small, focused interfaces | Fat classes/modules with unused methods |
899
+ | **D**ependency Inversion | Depend on abstractions, not concretions | `new` inside business logic |
900
+
901
+ ---
902
+
903
+ ## Applying SOLID in Rails Architecture
904
+
905
+ ```
906
+ app/
907
+ ├── models/ # S: Only data, validations, associations
908
+ ├── controllers/ # S: Only HTTP handling
909
+ ├── services/ # S: One business operation each
910
+ │ # O: Base service + specialized variants
911
+ │ # D: Inject dependencies
912
+ ├── policies/ # I: Granular authorization
913
+ ├── adapters/ # D: Wrap external services
914
+ │ ├── payment/ # O: PaymentAdapter interface + implementations
915
+ │ └── email/ # O: EmailAdapter interface + implementations
916
+ ├── presenters/ # S: View-specific formatting
917
+ └── validators/ # S: One validation concern each
918
+ ```
919
+
920
+ ### Example Service Following All Principles
921
+ ```ruby
922
+ # app/services/checkout_service.rb
923
+ class CheckoutService
924
+ # D: Dependencies injected, defaults for production
925
+ def initialize(
926
+ payment_processor: PaymentProcessor.new,
927
+ inventory_service: InventoryService.new,
928
+ notification_service: NotificationService.new
929
+ )
930
+ @payment_processor = payment_processor
931
+ @inventory_service = inventory_service
932
+ @notification_service = notification_service
933
+ end
934
+
935
+ # S: Single responsibility - orchestrate checkout
936
+ def call(cart:, user:, payment_info:)
937
+ # L: All services honor their contracts
938
+ validate_stock!(cart)
939
+ charge = process_payment(cart, payment_info)
940
+ order = create_order(cart, user, charge)
941
+ fulfill_order(order)
942
+ notify_user(user, order)
943
+
944
+ Result.success(order)
945
+ rescue CheckoutError => e
946
+ Result.failure(e.message)
947
+ end
948
+
949
+ private
950
+
951
+ def validate_stock!(cart)
952
+ cart.items.each do |item|
953
+ unless @inventory_service.available?(item.product_id, item.quantity)
954
+ raise CheckoutError, "#{item.name} is out of stock"
955
+ end
956
+ end
957
+ end
958
+
959
+ # O: Different payment methods handled by PaymentProcessor variants
960
+ def process_payment(cart, payment_info)
961
+ @payment_processor.charge(cart.total, payment_info)
962
+ end
963
+
964
+ def fulfill_order(order)
965
+ @inventory_service.reserve(order.items)
966
+ end
967
+
968
+ def notify_user(user, order)
969
+ @notification_service.order_confirmed(user, order)
970
+ end
971
+ end
972
+
973
+ # I: Focused interfaces
974
+ class PaymentProcessor
975
+ def charge(amount, payment_info)
976
+ raise NotImplementedError
977
+ end
978
+ end
979
+
980
+ class StripePaymentProcessor < PaymentProcessor
981
+ def charge(amount, payment_info)
982
+ Stripe::Charge.create(amount: amount, source: payment_info[:token])
983
+ end
984
+ end
985
+
986
+ class PaypalPaymentProcessor < PaymentProcessor
987
+ def charge(amount, payment_info)
988
+ PaypalClient.new.execute(payment_info[:order_id])
989
+ end
990
+ end
991
+ ```