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,972 @@
1
+ # Skill: Functional Programming in Ruby
2
+
3
+ ## Purpose
4
+ Apply functional programming concepts in Ruby and Rails to write more predictable, testable, and maintainable code.
5
+
6
+ ---
7
+
8
+ ## Immutability
9
+
10
+ ### Freeze Objects to Prevent Mutation
11
+ ```ruby
12
+ # Mutable (dangerous)
13
+ CONFIG = { api_key: "secret", timeout: 30 }
14
+ CONFIG[:api_key] = "hacked" # Oops, modified!
15
+
16
+ # Immutable (safe)
17
+ CONFIG = { api_key: "secret", timeout: 30 }.freeze
18
+ CONFIG[:api_key] = "hacked" # => FrozenError
19
+
20
+ # Deep freeze for nested structures
21
+ module DeepFreeze
22
+ def self.freeze(obj)
23
+ case obj
24
+ when Hash
25
+ obj.each_value { |v| freeze(v) }
26
+ when Array
27
+ obj.each { |v| freeze(v) }
28
+ end
29
+ obj.freeze
30
+ end
31
+ end
32
+
33
+ SETTINGS = DeepFreeze.freeze({
34
+ database: { host: "localhost", port: 5432 },
35
+ features: ["auth", "api"]
36
+ })
37
+ ```
38
+
39
+ ### Check if Object is Frozen
40
+ ```ruby
41
+ string = "hello"
42
+ string.frozen? # => false
43
+
44
+ string.freeze
45
+ string.frozen? # => true
46
+
47
+ # Many Ruby methods return new objects instead of mutating
48
+ original = "hello"
49
+ uppercase = original.upcase # Returns new string
50
+ original # => "hello" (unchanged)
51
+
52
+ # Methods ending with ! typically mutate
53
+ original = "hello"
54
+ original.upcase! # Mutates in place
55
+ original # => "HELLO"
56
+ ```
57
+
58
+ ### Prefer Non-Mutating Methods
59
+ ```ruby
60
+ # Mutating (avoid when possible)
61
+ array = [3, 1, 2]
62
+ array.sort! # Mutates array
63
+ array.reverse! # Mutates array
64
+
65
+ # Non-mutating (preferred)
66
+ array = [3, 1, 2]
67
+ sorted = array.sort # Returns new array
68
+ reversed = array.reverse # Returns new array
69
+ array # => [3, 1, 2] (unchanged)
70
+
71
+ # String methods that don't mutate
72
+ name = "john doe"
73
+ formatted = name.split.map(&:capitalize).join(" ")
74
+ # name is still "john doe"
75
+
76
+ # Rails: dup for ActiveRecord
77
+ user = User.find(1)
78
+ draft = user.dup
79
+ draft.name = "New Name"
80
+ # user.name unchanged
81
+ ```
82
+
83
+ ### Immutable Data Structures with Structs
84
+ ```ruby
85
+ # Regular struct is mutable
86
+ Point = Struct.new(:x, :y)
87
+ point = Point.new(1, 2)
88
+ point.x = 10 # Mutable
89
+
90
+ # Immutable with Data class (Ruby 3.2+)
91
+ Point = Data.define(:x, :y)
92
+ point = Point.new(x: 1, y: 2)
93
+ point.x = 10 # => NoMethodError
94
+
95
+ # Create modified copy
96
+ new_point = point.with(x: 10) # => Point(x: 10, y: 2)
97
+ point # => Point(x: 1, y: 2) (unchanged)
98
+
99
+ # For older Ruby, use custom immutable struct
100
+ class ImmutablePoint
101
+ attr_reader :x, :y
102
+
103
+ def initialize(x, y)
104
+ @x = x
105
+ @y = y
106
+ freeze
107
+ end
108
+
109
+ def with(x: @x, y: @y)
110
+ self.class.new(x, y)
111
+ end
112
+ end
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Pure Functions
118
+
119
+ ### Definition: Same Input Always Produces Same Output, No Side Effects
120
+ ```ruby
121
+ # Impure - depends on external state
122
+ @tax_rate = 0.1
123
+
124
+ def calculate_total(price)
125
+ price * (1 + @tax_rate) # Depends on @tax_rate
126
+ end
127
+
128
+ # Impure - modifies external state
129
+ def process_order(order)
130
+ @order_count += 1 # Side effect
131
+ order.save # Side effect (database)
132
+ send_email(order) # Side effect (I/O)
133
+ end
134
+
135
+ # Pure - no dependencies, no side effects
136
+ def calculate_total(price, tax_rate)
137
+ price * (1 + tax_rate)
138
+ end
139
+
140
+ # Always returns same result for same input
141
+ calculate_total(100, 0.1) # => 110.0
142
+ calculate_total(100, 0.1) # => 110.0 (always)
143
+ ```
144
+
145
+ ### Benefits of Pure Functions
146
+ ```ruby
147
+ # 1. Easy to test - no setup required
148
+ def add(a, b)
149
+ a + b
150
+ end
151
+
152
+ # Test
153
+ expect(add(2, 3)).to eq(5) # Simple, no mocks
154
+
155
+ # 2. Easy to reason about
156
+ def full_name(first, last)
157
+ "#{first} #{last}"
158
+ end
159
+
160
+ # 3. Cacheable (memoization)
161
+ def fibonacci(n)
162
+ return n if n <= 1
163
+ fibonacci(n - 1) + fibonacci(n - 2)
164
+ end
165
+
166
+ # 4. Parallelizable - no shared state
167
+ results = items.map { |item| pure_transform(item) }
168
+ # Can safely run in parallel
169
+
170
+ # 5. Composable
171
+ def double(x); x * 2; end
172
+ def add_one(x); x + 1; end
173
+
174
+ double(add_one(5)) # => 12
175
+ ```
176
+
177
+ ### Pushing Side Effects to the Edges
178
+ ```ruby
179
+ # Bad - side effects mixed with logic
180
+ class OrderProcessor
181
+ def process(order)
182
+ return if order.items.empty?
183
+
184
+ total = order.items.sum(&:price)
185
+ tax = total * 0.1
186
+ shipping = total > 100 ? 0 : 10
187
+ final_total = total + tax + shipping
188
+
189
+ order.update!(total: final_total) # Side effect mixed in
190
+ OrderMailer.confirmation(order).deliver_later # Side effect
191
+ InventoryService.reduce_stock(order.items) # Side effect
192
+
193
+ final_total
194
+ end
195
+ end
196
+
197
+ # Good - pure calculation, side effects at edges
198
+ class OrderCalculator
199
+ # Pure function - no side effects
200
+ def calculate(items, tax_rate: 0.1, free_shipping_threshold: 100)
201
+ return nil if items.empty?
202
+
203
+ subtotal = items.sum(&:price)
204
+ tax = subtotal * tax_rate
205
+ shipping = subtotal > free_shipping_threshold ? 0 : 10
206
+
207
+ {
208
+ subtotal: subtotal,
209
+ tax: tax,
210
+ shipping: shipping,
211
+ total: subtotal + tax + shipping
212
+ }
213
+ end
214
+ end
215
+
216
+ class OrderProcessor
217
+ def initialize(calculator: OrderCalculator.new)
218
+ @calculator = calculator
219
+ end
220
+
221
+ def process(order)
222
+ # Pure calculation
223
+ totals = @calculator.calculate(order.items)
224
+ return false unless totals
225
+
226
+ # Side effects at the edge
227
+ persist_order(order, totals)
228
+ send_notifications(order)
229
+ update_inventory(order)
230
+
231
+ true
232
+ end
233
+
234
+ private
235
+
236
+ def persist_order(order, totals)
237
+ order.update!(totals)
238
+ end
239
+
240
+ def send_notifications(order)
241
+ OrderMailer.confirmation(order).deliver_later
242
+ end
243
+
244
+ def update_inventory(order)
245
+ InventoryService.reduce_stock(order.items)
246
+ end
247
+ end
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Higher-Order Functions
253
+
254
+ ### Functions That Receive Functions
255
+ ```ruby
256
+ # map - transform each element
257
+ numbers = [1, 2, 3, 4, 5]
258
+ doubled = numbers.map { |n| n * 2 } # => [2, 4, 6, 8, 10]
259
+
260
+ # select/filter - keep elements matching condition
261
+ evens = numbers.select { |n| n.even? } # => [2, 4]
262
+
263
+ # reject - remove elements matching condition
264
+ odds = numbers.reject { |n| n.even? } # => [1, 3, 5]
265
+
266
+ # reduce/inject - combine elements into single value
267
+ sum = numbers.reduce(0) { |acc, n| acc + n } # => 15
268
+ sum = numbers.reduce(:+) # Shorthand => 15
269
+
270
+ # find/detect - first element matching condition
271
+ first_even = numbers.find { |n| n.even? } # => 2
272
+
273
+ # any? - at least one matches
274
+ numbers.any? { |n| n > 4 } # => true
275
+
276
+ # all? - all match
277
+ numbers.all? { |n| n > 0 } # => true
278
+
279
+ # none? - none match
280
+ numbers.none? { |n| n > 10 } # => true
281
+
282
+ # count - count matching elements
283
+ numbers.count { |n| n.even? } # => 2
284
+
285
+ # partition - split into two arrays
286
+ evens, odds = numbers.partition(&:even?)
287
+ # evens => [2, 4], odds => [1, 3, 5]
288
+
289
+ # group_by - group into hash
290
+ grouped = numbers.group_by { |n| n.even? ? :even : :odd }
291
+ # => { odd: [1, 3, 5], even: [2, 4] }
292
+
293
+ # sort_by - sort by transformation
294
+ users.sort_by { |u| u.created_at }
295
+ users.sort_by(&:created_at) # Shorthand
296
+ ```
297
+
298
+ ### Functions That Return Functions
299
+ ```ruby
300
+ # Factory for validators
301
+ def minimum_length_validator(min)
302
+ ->(string) { string.length >= min }
303
+ end
304
+
305
+ validate_password = minimum_length_validator(8)
306
+ validate_password.call("hello") # => false
307
+ validate_password.call("hello123!") # => true
308
+
309
+ # Factory for comparators
310
+ def compare_by(attribute)
311
+ ->(a, b) { a.send(attribute) <=> b.send(attribute) }
312
+ end
313
+
314
+ users.sort(&compare_by(:name))
315
+ users.sort(&compare_by(:created_at))
316
+
317
+ # Decorator/wrapper functions
318
+ def with_logging(func)
319
+ ->(*args) do
320
+ puts "Calling with args: #{args}"
321
+ result = func.call(*args)
322
+ puts "Result: #{result}"
323
+ result
324
+ end
325
+ end
326
+
327
+ add = ->(a, b) { a + b }
328
+ logged_add = with_logging(add)
329
+ logged_add.call(2, 3)
330
+ # Output:
331
+ # Calling with args: [2, 3]
332
+ # Result: 5
333
+ ```
334
+
335
+ ---
336
+
337
+ ## Blocks, Procs, and Lambdas
338
+
339
+ ### Blocks
340
+ ```ruby
341
+ # Implicit block with yield
342
+ def with_timing
343
+ start = Time.now
344
+ result = yield
345
+ elapsed = Time.now - start
346
+ puts "Took #{elapsed} seconds"
347
+ result
348
+ end
349
+
350
+ with_timing { sleep(1); "done" }
351
+
352
+ # Explicit block with &block
353
+ def transform(items, &block)
354
+ items.map(&block)
355
+ end
356
+
357
+ transform([1, 2, 3]) { |n| n * 2 } # => [2, 4, 6]
358
+
359
+ # Check if block given
360
+ def maybe_yield
361
+ if block_given?
362
+ yield
363
+ else
364
+ "No block given"
365
+ end
366
+ end
367
+ ```
368
+
369
+ ### Procs
370
+ ```ruby
371
+ # Create a Proc
372
+ double = Proc.new { |x| x * 2 }
373
+ double = proc { |x| x * 2 } # Shorthand
374
+
375
+ # Call a Proc
376
+ double.call(5) # => 10
377
+ double.(5) # => 10
378
+ double[5] # => 10
379
+
380
+ # Procs are lenient with arguments
381
+ flexible = proc { |a, b| [a, b] }
382
+ flexible.call(1) # => [1, nil]
383
+ flexible.call(1, 2, 3) # => [1, 2]
384
+
385
+ # Procs return from enclosing method
386
+ def test_proc
387
+ my_proc = proc { return "from proc" }
388
+ my_proc.call
389
+ "after proc" # Never reached
390
+ end
391
+ test_proc # => "from proc"
392
+ ```
393
+
394
+ ### Lambdas
395
+ ```ruby
396
+ # Create a lambda
397
+ double = lambda { |x| x * 2 }
398
+ double = ->(x) { x * 2 } # Stabby lambda
399
+
400
+ # Call a lambda
401
+ double.call(5) # => 10
402
+ double.(5) # => 10
403
+ double[5] # => 10
404
+
405
+ # Lambdas are strict with arguments
406
+ strict = ->(a, b) { [a, b] }
407
+ strict.call(1) # => ArgumentError
408
+ strict.call(1, 2, 3) # => ArgumentError
409
+ strict.call(1, 2) # => [1, 2]
410
+
411
+ # Lambdas return to caller
412
+ def test_lambda
413
+ my_lambda = -> { return "from lambda" }
414
+ my_lambda.call
415
+ "after lambda" # This IS reached
416
+ end
417
+ test_lambda # => "after lambda"
418
+
419
+ # Lambdas with multiple arguments
420
+ calculate = ->(a, b, operation) { operation.call(a, b) }
421
+ add = ->(x, y) { x + y }
422
+ multiply = ->(x, y) { x * y }
423
+
424
+ calculate.call(2, 3, add) # => 5
425
+ calculate.call(2, 3, multiply) # => 6
426
+ ```
427
+
428
+ ### When to Use Each
429
+ ```ruby
430
+ # Use blocks for:
431
+ # - One-time use, inline code
432
+ # - Iterators (each, map, select)
433
+ [1, 2, 3].map { |n| n * 2 }
434
+
435
+ # Use Procs for:
436
+ # - Storing blocks for later
437
+ # - When you need lenient argument handling
438
+ # - When you want return to exit enclosing method
439
+ callback = proc { |result| handle_result(result) }
440
+
441
+ # Use Lambdas for:
442
+ # - Strict argument checking
443
+ # - First-class functions
444
+ # - Functional programming patterns
445
+ validator = ->(value) { value.present? && value.length > 3 }
446
+ ```
447
+
448
+ ### Symbol to Proc
449
+ ```ruby
450
+ # Long form
451
+ users.map { |user| user.name }
452
+
453
+ # Short form with &
454
+ users.map(&:name)
455
+
456
+ # How it works:
457
+ # &:name calls :name.to_proc
458
+ # :name.to_proc returns a proc that calls .name on its argument
459
+
460
+ # Custom to_proc
461
+ class Integer
462
+ def to_proc
463
+ ->(obj) { obj * self }
464
+ end
465
+ end
466
+
467
+ [1, 2, 3].map(&2) # => [2, 4, 6]
468
+ ```
469
+
470
+ ---
471
+
472
+ ## Method Chaining
473
+
474
+ ### Building Fluent Interfaces
475
+ ```ruby
476
+ # ActiveRecord is a great example
477
+ User
478
+ .where(active: true)
479
+ .where("created_at > ?", 1.month.ago)
480
+ .includes(:orders)
481
+ .order(created_at: :desc)
482
+ .limit(10)
483
+
484
+ # Custom chainable query builder
485
+ class ProductQuery
486
+ def initialize(scope = Product.all)
487
+ @scope = scope
488
+ end
489
+
490
+ def active
491
+ chain { @scope.where(active: true) }
492
+ end
493
+
494
+ def in_category(category)
495
+ chain { @scope.where(category: category) }
496
+ end
497
+
498
+ def price_between(min, max)
499
+ chain { @scope.where(price: min..max) }
500
+ end
501
+
502
+ def search(query)
503
+ return self if query.blank?
504
+ chain { @scope.where("name ILIKE ?", "%#{query}%") }
505
+ end
506
+
507
+ def to_a
508
+ @scope.to_a
509
+ end
510
+
511
+ def count
512
+ @scope.count
513
+ end
514
+
515
+ private
516
+
517
+ def chain
518
+ self.class.new(yield)
519
+ end
520
+ end
521
+
522
+ # Usage
523
+ products = ProductQuery.new
524
+ .active
525
+ .in_category("Electronics")
526
+ .price_between(100, 500)
527
+ .search(params[:q])
528
+ .to_a
529
+ ```
530
+
531
+ ### Tap for Side Effects in Chains
532
+ ```ruby
533
+ # tap yields self and returns self
534
+ User.new
535
+ .tap { |u| u.name = "John" }
536
+ .tap { |u| u.email = "john@example.com" }
537
+ .tap { |u| puts "Created user: #{u.name}" }
538
+ .save
539
+
540
+ # Useful for debugging chains
541
+ users
542
+ .select(&:active?)
543
+ .tap { |list| puts "Active users: #{list.count}" }
544
+ .map(&:email)
545
+ .tap { |emails| puts "Emails: #{emails}" }
546
+ .join(", ")
547
+
548
+ # then/yield_self for transformations
549
+ "hello"
550
+ .then { |s| s.upcase }
551
+ .then { |s| "#{s}!" }
552
+ # => "HELLO!"
553
+ ```
554
+
555
+ ---
556
+
557
+ ## Currying and Partial Application
558
+
559
+ ### Currying
560
+ Transforms a function that takes multiple arguments into a sequence of functions that each take a single argument.
561
+
562
+ ```ruby
563
+ # Regular function
564
+ add = ->(a, b, c) { a + b + c }
565
+ add.call(1, 2, 3) # => 6
566
+
567
+ # Curried function
568
+ curried_add = add.curry
569
+ curried_add.call(1).call(2).call(3) # => 6
570
+ curried_add.(1).(2).(3) # => 6
571
+
572
+ # Partial application with currying
573
+ add_one = curried_add.(1) # Returns a lambda waiting for 2 more args
574
+ add_one_and_two = add_one.(2) # Returns a lambda waiting for 1 more arg
575
+ add_one_and_two.(3) # => 6
576
+
577
+ # Practical example
578
+ multiply = ->(x, y) { x * y }.curry
579
+
580
+ double = multiply.(2)
581
+ triple = multiply.(3)
582
+
583
+ [1, 2, 3, 4].map(&double) # => [2, 4, 6, 8]
584
+ [1, 2, 3, 4].map(&triple) # => [3, 6, 9, 12]
585
+ ```
586
+
587
+ ### Partial Application
588
+ ```ruby
589
+ # Using curry for partial application
590
+ greet = ->(greeting, name) { "#{greeting}, #{name}!" }
591
+ say_hello = greet.curry.("Hello")
592
+ say_goodbye = greet.curry.("Goodbye")
593
+
594
+ say_hello.("World") # => "Hello, World!"
595
+ say_goodbye.("World") # => "Goodbye, World!"
596
+
597
+ # Manual partial application
598
+ def partial(func, *fixed_args)
599
+ ->(*args) { func.call(*fixed_args, *args) }
600
+ end
601
+
602
+ log = ->(level, message) { puts "[#{level}] #{message}" }
603
+ info = partial(log, "INFO")
604
+ error = partial(log, "ERROR")
605
+
606
+ info.("Application started") # => [INFO] Application started
607
+ error.("Something went wrong") # => [ERROR] Something went wrong
608
+ ```
609
+
610
+ ---
611
+
612
+ ## Function Composition
613
+
614
+ ### Composing Functions
615
+ ```ruby
616
+ # Manual composition
617
+ double = ->(x) { x * 2 }
618
+ add_one = ->(x) { x + 1 }
619
+
620
+ # Compose: add_one after double
621
+ composed = ->(x) { add_one.(double.(x)) }
622
+ composed.(5) # => 11 (5 * 2 + 1)
623
+
624
+ # Compose helper
625
+ def compose(*functions)
626
+ ->(arg) { functions.reverse.reduce(arg) { |result, fn| fn.(result) } }
627
+ end
628
+
629
+ pipeline = compose(add_one, double, add_one)
630
+ pipeline.(5) # => 13 ((5 + 1) * 2 + 1)
631
+
632
+ # Using >> and << operators (Ruby 2.6+)
633
+ double = ->(x) { x * 2 }
634
+ add_one = ->(x) { x + 1 }
635
+
636
+ # >> composes left to right
637
+ (double >> add_one).call(5) # => 11 (5 * 2, then + 1)
638
+
639
+ # << composes right to left
640
+ (double << add_one).call(5) # => 12 (5 + 1, then * 2)
641
+
642
+ # Chain multiple
643
+ square = ->(x) { x ** 2 }
644
+ pipeline = double >> add_one >> square
645
+ pipeline.(3) # => 49 ((3 * 2 + 1) ** 2)
646
+ ```
647
+
648
+ ### Practical Composition in Rails
649
+ ```ruby
650
+ # Compose transformations
651
+ class DataPipeline
652
+ def initialize
653
+ @steps = []
654
+ end
655
+
656
+ def add_step(step)
657
+ @steps << step
658
+ self
659
+ end
660
+
661
+ def process(data)
662
+ @steps.reduce(data) { |result, step| step.call(result) }
663
+ end
664
+ end
665
+
666
+ # Define transformation steps
667
+ strip_whitespace = ->(data) { data.transform_values(&:strip) }
668
+ downcase_email = ->(data) { data.merge(email: data[:email]&.downcase) }
669
+ validate = ->(data) { data[:email]&.include?("@") ? data : raise("Invalid email") }
670
+ normalize_phone = ->(data) { data.merge(phone: data[:phone]&.gsub(/\D/, "")) }
671
+
672
+ # Build pipeline
673
+ pipeline = DataPipeline.new
674
+ .add_step(strip_whitespace)
675
+ .add_step(downcase_email)
676
+ .add_step(normalize_phone)
677
+ .add_step(validate)
678
+
679
+ # Use
680
+ clean_data = pipeline.process({
681
+ name: " John ",
682
+ email: " JOHN@EXAMPLE.COM ",
683
+ phone: "(555) 123-4567"
684
+ })
685
+ # => { name: "John", email: "john@example.com", phone: "5551234567" }
686
+ ```
687
+
688
+ ---
689
+
690
+ ## Lazy Evaluation
691
+
692
+ ### Enumerator::Lazy
693
+ ```ruby
694
+ # Eager - processes all elements immediately
695
+ (1..Float::INFINITY)
696
+ .select { |n| n.even? }
697
+ .first(5)
698
+ # => Never finishes! (infinite sequence)
699
+
700
+ # Lazy - processes elements on demand
701
+ (1..Float::INFINITY)
702
+ .lazy
703
+ .select { |n| n.even? }
704
+ .first(5)
705
+ # => [2, 4, 6, 8, 10]
706
+
707
+ # Useful for large files
708
+ File.open("huge_file.csv")
709
+ .lazy
710
+ .map { |line| line.split(",") }
711
+ .select { |row| row[0] == "active" }
712
+ .first(100)
713
+ # Only reads enough lines to get 100 matches
714
+
715
+ # Chaining lazy operations
716
+ numbers = (1..1_000_000).lazy
717
+
718
+ result = numbers
719
+ .select { |n| n % 3 == 0 } # Divisible by 3
720
+ .map { |n| n * 2 } # Double it
721
+ .select { |n| n.to_s.include?("7") } # Contains 7
722
+ .first(10)
723
+ # Only processes what's needed to find 10 matches
724
+ ```
725
+
726
+ ### Custom Lazy Enumerators
727
+ ```ruby
728
+ # Lazy API paginator
729
+ class LazyApiPaginator
730
+ include Enumerable
731
+
732
+ def initialize(client, resource)
733
+ @client = client
734
+ @resource = resource
735
+ end
736
+
737
+ def each
738
+ return to_enum(:each).lazy unless block_given?
739
+
740
+ page = 1
741
+ loop do
742
+ response = @client.get(@resource, page: page)
743
+ break if response.data.empty?
744
+
745
+ response.data.each { |item| yield item }
746
+ page += 1
747
+ end
748
+ end
749
+ end
750
+
751
+ # Usage - only fetches pages as needed
752
+ paginator = LazyApiPaginator.new(api_client, "/users")
753
+ paginator.lazy.select { |u| u["active"] }.first(10)
754
+ # Stops fetching pages once we have 10 active users
755
+ ```
756
+
757
+ ### Memoization as Lazy Evaluation
758
+ ```ruby
759
+ # Using ||= for lazy initialization
760
+ class ExpensiveCalculator
761
+ def result
762
+ @result ||= calculate_expensive_value
763
+ end
764
+
765
+ private
766
+
767
+ def calculate_expensive_value
768
+ puts "Calculating..."
769
+ sleep(2)
770
+ 42
771
+ end
772
+ end
773
+
774
+ calc = ExpensiveCalculator.new
775
+ calc.result # Calculates (takes 2 seconds)
776
+ calc.result # Returns cached value (instant)
777
+ ```
778
+
779
+ ---
780
+
781
+ ## Functional Patterns in Rails
782
+
783
+ ### Service Objects as Pure Functions
784
+ ```ruby
785
+ class CalculateShipping
786
+ def self.call(weight:, destination:, expedited: false)
787
+ new(weight, destination, expedited).call
788
+ end
789
+
790
+ def initialize(weight, destination, expedited)
791
+ @weight = weight
792
+ @destination = destination
793
+ @expedited = expedited
794
+ end
795
+
796
+ def call
797
+ base_rate = calculate_base_rate
798
+ distance_factor = calculate_distance_factor
799
+ expedited_factor = @expedited ? 1.5 : 1.0
800
+
801
+ (base_rate * distance_factor * expedited_factor).round(2)
802
+ end
803
+
804
+ private
805
+
806
+ def calculate_base_rate
807
+ case @weight
808
+ when 0..1 then 5.0
809
+ when 1..5 then 10.0
810
+ when 5..20 then 20.0
811
+ else 30.0 + (@weight - 20) * 1.5
812
+ end
813
+ end
814
+
815
+ def calculate_distance_factor
816
+ DISTANCE_FACTORS.fetch(@destination, 1.0)
817
+ end
818
+
819
+ DISTANCE_FACTORS = {
820
+ local: 1.0,
821
+ regional: 1.25,
822
+ national: 1.5,
823
+ international: 2.5
824
+ }.freeze
825
+ end
826
+
827
+ # Pure - same inputs always produce same output
828
+ CalculateShipping.call(weight: 3, destination: :regional) # => 12.5
829
+ ```
830
+
831
+ ### Transformation Pipelines
832
+ ```ruby
833
+ class ImportUsersPipeline
834
+ TRANSFORMATIONS = [
835
+ :parse_csv,
836
+ :normalize_data,
837
+ :validate_records,
838
+ :deduplicate,
839
+ :transform_to_users
840
+ ].freeze
841
+
842
+ def call(file_content)
843
+ TRANSFORMATIONS.reduce(file_content) do |data, transformation|
844
+ send(transformation, data)
845
+ end
846
+ end
847
+
848
+ private
849
+
850
+ def parse_csv(content)
851
+ CSV.parse(content, headers: true).map(&:to_h)
852
+ end
853
+
854
+ def normalize_data(records)
855
+ records.map do |record|
856
+ record.transform_keys(&:downcase)
857
+ .transform_values(&:strip)
858
+ end
859
+ end
860
+
861
+ def validate_records(records)
862
+ records.select { |r| r["email"].present? }
863
+ end
864
+
865
+ def deduplicate(records)
866
+ records.uniq { |r| r["email"].downcase }
867
+ end
868
+
869
+ def transform_to_users(records)
870
+ records.map { |r| UserBuilder.from_hash(r) }
871
+ end
872
+ end
873
+ ```
874
+
875
+ ### Functional Error Handling with Result Objects
876
+ ```ruby
877
+ class Result
878
+ attr_reader :value, :error
879
+
880
+ def self.success(value)
881
+ new(value: value, success: true)
882
+ end
883
+
884
+ def self.failure(error)
885
+ new(error: error, success: false)
886
+ end
887
+
888
+ def initialize(value: nil, error: nil, success:)
889
+ @value = value
890
+ @error = error
891
+ @success = success
892
+ end
893
+
894
+ def success?
895
+ @success
896
+ end
897
+
898
+ def failure?
899
+ !@success
900
+ end
901
+
902
+ # Monadic bind/flatmap
903
+ def and_then(&block)
904
+ return self if failure?
905
+ block.call(@value)
906
+ end
907
+
908
+ # Functor map
909
+ def map(&block)
910
+ return self if failure?
911
+ Result.success(block.call(@value))
912
+ end
913
+
914
+ # Handle error
915
+ def or_else(&block)
916
+ return self if success?
917
+ block.call(@error)
918
+ end
919
+ end
920
+
921
+ # Usage with chaining
922
+ class RegisterUser
923
+ def call(params)
924
+ validate(params)
925
+ .and_then { |p| check_uniqueness(p) }
926
+ .and_then { |p| create_user(p) }
927
+ .and_then { |user| send_welcome_email(user) }
928
+ end
929
+
930
+ private
931
+
932
+ def validate(params)
933
+ if params[:email].present? && params[:password].length >= 8
934
+ Result.success(params)
935
+ else
936
+ Result.failure("Invalid params")
937
+ end
938
+ end
939
+
940
+ def check_uniqueness(params)
941
+ if User.exists?(email: params[:email])
942
+ Result.failure("Email already taken")
943
+ else
944
+ Result.success(params)
945
+ end
946
+ end
947
+
948
+ def create_user(params)
949
+ user = User.create(params)
950
+ if user.persisted?
951
+ Result.success(user)
952
+ else
953
+ Result.failure(user.errors.full_messages.join(", "))
954
+ end
955
+ end
956
+
957
+ def send_welcome_email(user)
958
+ UserMailer.welcome(user).deliver_later
959
+ Result.success(user)
960
+ end
961
+ end
962
+
963
+ # In controller
964
+ result = RegisterUser.new.call(user_params)
965
+
966
+ if result.success?
967
+ redirect_to dashboard_path, notice: "Welcome!"
968
+ else
969
+ flash.now[:alert] = result.error
970
+ render :new
971
+ end
972
+ ```