ima-claude 2.18.0 → 2.25.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.
- package/README.md +55 -9
- package/dist/cli.js +5 -1
- package/package.json +1 -1
- package/plugins/ima-claude/.claude-plugin/plugin.json +2 -2
- package/plugins/ima-claude/agents/explorer.md +29 -15
- package/plugins/ima-claude/agents/implementer.md +58 -13
- package/plugins/ima-claude/agents/memory.md +19 -19
- package/plugins/ima-claude/agents/reviewer.md +56 -34
- package/plugins/ima-claude/agents/tester.md +59 -16
- package/plugins/ima-claude/agents/wp-developer.md +66 -21
- package/plugins/ima-claude/hooks/bootstrap.sh +42 -44
- package/plugins/ima-claude/hooks/prompt_coach_digest.md +14 -17
- package/plugins/ima-claude/hooks/prompt_coach_system.md +10 -12
- package/plugins/ima-claude/personalities/README.md +17 -6
- package/plugins/ima-claude/personalities/enable-efficient.md +61 -0
- package/plugins/ima-claude/personalities/enable-terse.md +71 -0
- package/plugins/ima-claude/skills/agentic-workflows/SKILL.md +97 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/phases/deliver.md +181 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/phases/draft.md +99 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/phases/gather.md +130 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/phases/outline.md +106 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/phases/review.md +137 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/standards/draft-format.md +159 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/standards/editorial-standards.md +160 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/standards/outline-format.md +110 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/avada-construction-guide.md +263 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/avada-webinar-example.txt +275 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/cta-block-catalog.md +169 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/espo-email-preparation.md +241 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/webinar-recap-email-espo.html +339 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/templates/webinar-reminder-email-espo.html +458 -0
- package/plugins/ima-claude/skills/agentic-workflows/references/workflows/editorial/webinar-summary.md +81 -0
- package/plugins/ima-claude/skills/architect/SKILL.md +54 -168
- package/plugins/ima-claude/skills/compound-bridge/SKILL.md +41 -94
- package/plugins/ima-claude/skills/design-to-code/SKILL.md +91 -0
- package/plugins/ima-claude/skills/design-to-code/references/guardrails.md +46 -0
- package/plugins/ima-claude/skills/design-to-code/references/phase-a-design-to-prompt.md +141 -0
- package/plugins/ima-claude/skills/design-to-code/references/phase-b-prompt-to-code.md +155 -0
- package/plugins/ima-claude/skills/design-to-code/references/prompt-template.md +95 -0
- package/plugins/ima-claude/skills/discourse/SKILL.md +79 -194
- package/plugins/ima-claude/skills/discourse-admin/SKILL.md +41 -103
- package/plugins/ima-claude/skills/docs-organize/SKILL.md +63 -203
- package/plugins/ima-claude/skills/ember-discourse/SKILL.md +90 -200
- package/plugins/ima-claude/skills/espocrm/SKILL.md +14 -23
- package/plugins/ima-claude/skills/espocrm-api/SKILL.md +79 -192
- package/plugins/ima-claude/skills/functional-programmer/SKILL.md +33 -237
- package/plugins/ima-claude/skills/gh-cli/SKILL.md +26 -65
- package/plugins/ima-claude/skills/ima-bootstrap/SKILL.md +71 -104
- package/plugins/ima-claude/skills/ima-bootstrap/references/ima-brand.md +32 -22
- package/plugins/ima-claude/skills/ima-brand/SKILL.md +18 -23
- package/plugins/ima-claude/skills/ima-copywriting/SKILL.md +68 -179
- package/plugins/ima-claude/skills/ima-doc2pdf/SKILL.md +32 -102
- package/plugins/ima-claude/skills/ima-editorial-scorecard/SKILL.md +38 -63
- package/plugins/ima-claude/skills/ima-editorial-workflow/SKILL.md +69 -114
- package/plugins/ima-claude/skills/ima-email-creator/SKILL.md +16 -22
- package/plugins/ima-claude/skills/ima-forms-expert/SKILL.md +21 -37
- package/plugins/ima-claude/skills/jira-checkpoint/SKILL.md +39 -120
- package/plugins/ima-claude/skills/jquery/SKILL.md +107 -233
- package/plugins/ima-claude/skills/js-fp/SKILL.md +75 -296
- package/plugins/ima-claude/skills/js-fp-api/SKILL.md +52 -162
- package/plugins/ima-claude/skills/js-fp-react/SKILL.md +47 -270
- package/plugins/ima-claude/skills/js-fp-vue/SKILL.md +55 -209
- package/plugins/ima-claude/skills/js-fp-wordpress/SKILL.md +59 -204
- package/plugins/ima-claude/skills/livecanvas/SKILL.md +19 -32
- package/plugins/ima-claude/skills/mcp-atlassian/SKILL.md +146 -136
- package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-attachments.md +115 -0
- package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-auth.md +103 -0
- package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-bulk.md +149 -0
- package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-misc.md +195 -0
- package/plugins/ima-claude/skills/mcp-atlassian/references/direct-api-sprints.md +158 -0
- package/plugins/ima-claude/skills/mcp-context7/SKILL.md +32 -64
- package/plugins/ima-claude/skills/mcp-gitea/SKILL.md +98 -188
- package/plugins/ima-claude/skills/mcp-github/SKILL.md +60 -124
- package/plugins/ima-claude/skills/mcp-memory/SKILL.md +1 -177
- package/plugins/ima-claude/skills/mcp-qdrant/SKILL.md +58 -115
- package/plugins/ima-claude/skills/mcp-sequential/SKILL.md +32 -87
- package/plugins/ima-claude/skills/mcp-serena/SKILL.md +54 -80
- package/plugins/ima-claude/skills/mcp-tavily/SKILL.md +40 -63
- package/plugins/ima-claude/skills/mcp-vestige/SKILL.md +75 -116
- package/plugins/ima-claude/skills/php-authnet/SKILL.md +32 -65
- package/plugins/ima-claude/skills/php-fp/SKILL.md +50 -129
- package/plugins/ima-claude/skills/php-fp-wordpress/SKILL.md +25 -73
- package/plugins/ima-claude/skills/phpunit-wp/SKILL.md +103 -463
- package/plugins/ima-claude/skills/playwright/SKILL.md +69 -220
- package/plugins/ima-claude/skills/prompt-starter/SKILL.md +35 -82
- package/plugins/ima-claude/skills/prompt-starter/references/code-review.md +38 -0
- package/plugins/ima-claude/skills/py-fp/SKILL.md +78 -384
- package/plugins/ima-claude/skills/quasar-fp/SKILL.md +54 -255
- package/plugins/ima-claude/skills/quickstart/SKILL.md +7 -11
- package/plugins/ima-claude/skills/rails/SKILL.md +63 -184
- package/plugins/ima-claude/skills/resume-session/SKILL.md +14 -35
- package/plugins/ima-claude/skills/rg/SKILL.md +61 -146
- package/plugins/ima-claude/skills/ruby-fp/SKILL.md +66 -163
- package/plugins/ima-claude/skills/save-session/SKILL.md +10 -39
- package/plugins/ima-claude/skills/scorecard/SKILL.md +24 -38
- package/plugins/ima-claude/skills/skill-analyzer/SKILL.md +42 -71
- package/plugins/ima-claude/skills/skill-creator/SKILL.md +79 -250
- package/plugins/ima-claude/skills/task-master/SKILL.md +11 -31
- package/plugins/ima-claude/skills/task-planner/SKILL.md +44 -153
- package/plugins/ima-claude/skills/task-runner/SKILL.md +61 -143
- package/plugins/ima-claude/skills/unit-testing/SKILL.md +59 -134
- package/plugins/ima-claude/skills/wp-ddev/SKILL.md +38 -120
- package/plugins/ima-claude/skills/wp-local/SKILL.md +26 -108
|
@@ -3,203 +3,118 @@ name: "rg"
|
|
|
3
3
|
description: "Ripgrep (rg) - fast recursive search tool. Prefer over grep/find for code search. Respects .gitignore, searches recursively, supports regex. Use for file content search, file listing, pattern matching. Triggers on: ripgrep, rg, search files, find in files, grep, search code, find pattern."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
# Ripgrep (rg)
|
|
6
|
+
# Ripgrep (rg)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Use `rg` instead of `grep` or `find -name`. Faster, better defaults, respects `.gitignore`.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Basic Search
|
|
11
11
|
|
|
12
|
-
### Basic Search
|
|
13
12
|
```bash
|
|
14
|
-
|
|
15
|
-
rg "pattern"
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
rg "
|
|
19
|
-
rg "pattern" file.ts
|
|
20
|
-
|
|
21
|
-
# Case-insensitive search
|
|
22
|
-
rg -i "pattern"
|
|
23
|
-
|
|
24
|
-
# Word boundary match (whole words only)
|
|
25
|
-
rg -w "function"
|
|
26
|
-
|
|
27
|
-
# Fixed string (not regex)
|
|
28
|
-
rg -F "exact.string.match"
|
|
13
|
+
rg "pattern" # Recursive from cwd
|
|
14
|
+
rg "pattern" src/ # Specific directory
|
|
15
|
+
rg -i "pattern" # Case-insensitive
|
|
16
|
+
rg -w "function" # Whole words only
|
|
17
|
+
rg -F "exact.string.match" # Fixed string (not regex)
|
|
29
18
|
```
|
|
30
19
|
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
# Show N lines of context (before and after)
|
|
34
|
-
rg -C 3 "pattern"
|
|
35
|
-
|
|
36
|
-
# Lines before (-B) or after (-A) only
|
|
37
|
-
rg -B 2 -A 5 "pattern"
|
|
38
|
-
|
|
39
|
-
# Count matches per file
|
|
40
|
-
rg -c "pattern"
|
|
41
|
-
|
|
42
|
-
# List files with matches only (no content)
|
|
43
|
-
rg -l "pattern"
|
|
44
|
-
rg --files-with-matches "pattern"
|
|
45
|
-
|
|
46
|
-
# List files WITHOUT matches
|
|
47
|
-
rg --files-without-match "pattern"
|
|
48
|
-
|
|
49
|
-
# Show only the matched text (not full line)
|
|
50
|
-
rg -o "pattern"
|
|
51
|
-
```
|
|
20
|
+
## Output Control
|
|
52
21
|
|
|
53
|
-
### File Filtering
|
|
54
22
|
```bash
|
|
55
|
-
#
|
|
56
|
-
rg -
|
|
57
|
-
rg -
|
|
58
|
-
rg -
|
|
59
|
-
rg -
|
|
60
|
-
|
|
61
|
-
# Exclude file type
|
|
62
|
-
rg -T js "pattern" # Exclude JavaScript
|
|
63
|
-
|
|
64
|
-
# By glob pattern
|
|
65
|
-
rg -g "*.vue" "pattern" # Only .vue files
|
|
66
|
-
rg -g "!*.test.ts" "pattern" # Exclude test files
|
|
67
|
-
rg -g "src/**/*.ts" "pattern" # TypeScript in src/
|
|
68
|
-
|
|
69
|
-
# Multiple globs
|
|
70
|
-
rg -g "*.ts" -g "*.vue" "pattern"
|
|
23
|
+
rg -C 3 "pattern" # 3 lines context before+after
|
|
24
|
+
rg -B 2 -A 5 "pattern" # 2 before, 5 after
|
|
25
|
+
rg -c "pattern" # Count matches per file
|
|
26
|
+
rg -l "pattern" # Files with matches only
|
|
27
|
+
rg --files-without-match "pat" # Files without matches
|
|
28
|
+
rg -o "pattern" # Matched text only (not full line)
|
|
71
29
|
```
|
|
72
30
|
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
# List all files that would be searched
|
|
76
|
-
rg --files
|
|
77
|
-
|
|
78
|
-
# List files matching glob
|
|
79
|
-
rg --files -g "*.ts"
|
|
80
|
-
|
|
81
|
-
# List files in specific directory
|
|
82
|
-
rg --files src/components/
|
|
83
|
-
```
|
|
31
|
+
## File Filtering
|
|
84
32
|
|
|
85
|
-
### Bypass Filters
|
|
86
33
|
```bash
|
|
87
|
-
|
|
88
|
-
rg -
|
|
89
|
-
rg -
|
|
90
|
-
rg -
|
|
91
|
-
|
|
92
|
-
#
|
|
93
|
-
rg
|
|
94
|
-
rg --no-ignore "pattern" # Ignore all ignore files
|
|
34
|
+
rg -t ts "pattern" # TypeScript only
|
|
35
|
+
rg -t py "pattern" # Python only
|
|
36
|
+
rg -T js "pattern" # Exclude JavaScript
|
|
37
|
+
rg -g "*.vue" "pattern" # By glob
|
|
38
|
+
rg -g "!*.test.ts" "pattern" # Exclude test files
|
|
39
|
+
rg -g "src/**/*.ts" "pattern" # Scoped glob
|
|
40
|
+
rg -g "*.ts" -g "*.vue" "pat" # Multiple globs
|
|
95
41
|
```
|
|
96
42
|
|
|
97
|
-
|
|
43
|
+
## List Files (No Search)
|
|
44
|
+
|
|
98
45
|
```bash
|
|
99
|
-
#
|
|
100
|
-
rg -
|
|
46
|
+
rg --files # All files that would be searched
|
|
47
|
+
rg --files -g "*.ts" # Files matching glob
|
|
48
|
+
rg --files src/components/ # Files in directory
|
|
49
|
+
```
|
|
101
50
|
|
|
102
|
-
|
|
103
|
-
rg -P "(?<=prefix)pattern(?=suffix)"
|
|
51
|
+
## Bypass Filters
|
|
104
52
|
|
|
105
|
-
|
|
106
|
-
rg "
|
|
53
|
+
```bash
|
|
54
|
+
rg -u "pattern" # Ignore .gitignore
|
|
55
|
+
rg -uu "pattern" # + include hidden files
|
|
56
|
+
rg -uuu "pattern" # + include binary files
|
|
57
|
+
rg --hidden "pat" # Include hidden files only
|
|
58
|
+
```
|
|
107
59
|
|
|
108
|
-
|
|
109
|
-
rg "fn (\w+)" -r "function $1"
|
|
60
|
+
## Advanced
|
|
110
61
|
|
|
111
|
-
|
|
112
|
-
rg
|
|
62
|
+
```bash
|
|
63
|
+
rg -U "start.*\n.*end" # Multiline
|
|
64
|
+
rg -P "(?<=prefix)pattern(?=suffix)" # PCRE2 (lookahead/lookbehind)
|
|
65
|
+
rg "old" -r "new" # Replace preview (no file modification)
|
|
66
|
+
rg "fn (\w+)" -r "function $1" # Capture groups in replace
|
|
67
|
+
rg --json "pattern" # JSON output for scripting
|
|
113
68
|
```
|
|
114
69
|
|
|
115
70
|
## Common Recipes
|
|
116
71
|
|
|
117
|
-
### Find Function/Class Definitions
|
|
118
72
|
```bash
|
|
119
|
-
#
|
|
120
|
-
rg "^(export )?(async )?(function|const|class) \w+"
|
|
121
|
-
rg "^
|
|
73
|
+
# Function/class definitions
|
|
74
|
+
rg "^(export )?(async )?(function|const|class) \w+" # JS/TS
|
|
75
|
+
rg "^(async )?def \w+|^class \w+" # Python
|
|
76
|
+
rg "^(public |private |protected )?(static )?function \w+" # PHP
|
|
122
77
|
|
|
123
|
-
#
|
|
124
|
-
rg "^(async )?def \w+|^class \w+"
|
|
125
|
-
|
|
126
|
-
# PHP
|
|
127
|
-
rg "^(public |private |protected )?(static )?(function) \w+"
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
### Find Imports/Requires
|
|
131
|
-
```bash
|
|
78
|
+
# Imports
|
|
132
79
|
rg "^import .+ from"
|
|
133
80
|
rg "require\(['\"]"
|
|
134
|
-
```
|
|
135
81
|
|
|
136
|
-
|
|
137
|
-
```bash
|
|
82
|
+
# TODOs
|
|
138
83
|
rg "TODO|FIXME|HACK|XXX" -g "!node_modules"
|
|
139
|
-
```
|
|
140
84
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
rg --files -g "
|
|
144
|
-
rg --files -g "*.{ts,tsx}" # TS and TSX
|
|
145
|
-
rg --files -g "!*.test.*" # Exclude test files
|
|
146
|
-
```
|
|
85
|
+
# Files by extension
|
|
86
|
+
rg --files -g "*.{ts,tsx}"
|
|
87
|
+
rg --files -g "!*.test.*"
|
|
147
88
|
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
# See what would change (doesn't modify files)
|
|
89
|
+
# Search-replace preview
|
|
151
90
|
rg "oldFunction" -r "newFunction" --passthru
|
|
152
91
|
```
|
|
153
92
|
|
|
154
93
|
## vs grep/find
|
|
155
94
|
|
|
156
|
-
| Task |
|
|
157
|
-
|
|
158
|
-
| Search text | `grep -r "
|
|
95
|
+
| Task | avoid | prefer |
|
|
96
|
+
|------|-------|--------|
|
|
97
|
+
| Search text | `grep -r "pat" .` | `rg "pat"` |
|
|
159
98
|
| Find files | `find . -name "*.ts"` | `rg --files -g "*.ts"` |
|
|
160
|
-
| Case insensitive | `grep -ri "
|
|
99
|
+
| Case insensitive | `grep -ri "pat" .` | `rg -i "pat"` |
|
|
161
100
|
| Whole word | `grep -rw "word" .` | `rg -w "word"` |
|
|
162
|
-
|
|
|
163
|
-
|
|
|
164
|
-
|
|
165
|
-
## Key Advantages
|
|
166
|
-
|
|
167
|
-
1. **Speed**: 2-10x faster than grep on large codebases
|
|
168
|
-
2. **Smart defaults**: Respects `.gitignore`, skips binary/hidden files
|
|
169
|
-
3. **Recursive by default**: No `-r` flag needed
|
|
170
|
-
4. **Better regex**: Rust regex engine, optional PCRE2
|
|
171
|
-
5. **Built-in file types**: `-t ts`, `-t py`, etc.
|
|
172
|
-
6. **Colored output**: Easy to read in terminal
|
|
101
|
+
| Context | `grep -r -C 3 "pat" .` | `rg -C 3 "pat"` |
|
|
102
|
+
| Files only | `grep -rl "pat" .` | `rg -l "pat"` |
|
|
173
103
|
|
|
174
104
|
## Configuration
|
|
175
105
|
|
|
176
|
-
Create `~/.ripgreprc` for persistent settings:
|
|
177
106
|
```shell
|
|
178
|
-
#
|
|
107
|
+
# ~/.ripgreprc
|
|
179
108
|
--smart-case
|
|
180
|
-
|
|
181
|
-
# Max line length for display
|
|
182
109
|
--max-columns=150
|
|
183
110
|
--max-columns-preview
|
|
184
|
-
|
|
185
|
-
# Include hidden files by default
|
|
186
|
-
# --hidden
|
|
187
|
-
|
|
188
|
-
# Custom file type
|
|
189
111
|
--type-add
|
|
190
112
|
web:*.{html,css,js,ts,vue}
|
|
191
113
|
```
|
|
192
114
|
|
|
193
|
-
Set the config file path:
|
|
194
115
|
```bash
|
|
195
116
|
export RIPGREP_CONFIG_PATH="$HOME/.ripgreprc"
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
## Type List
|
|
199
|
-
|
|
200
|
-
View all built-in types:
|
|
201
|
-
```bash
|
|
202
|
-
rg --type-list
|
|
117
|
+
rg --type-list # View all built-in types
|
|
203
118
|
```
|
|
204
119
|
|
|
205
120
|
Common types: `ts`, `js`, `py`, `rust`, `go`, `java`, `php`, `ruby`, `css`, `html`, `json`, `yaml`, `md`, `sh`
|
|
@@ -5,31 +5,17 @@ description: "Functional Ruby patterns - Enumerable as FP toolkit, lambdas, free
|
|
|
5
5
|
|
|
6
6
|
# Ruby FP
|
|
7
7
|
|
|
8
|
-
Ruby is OOP-first, but its FP toolkit is excellent.
|
|
8
|
+
Ruby is OOP-first, but its FP toolkit is excellent. Goal: **functional core, imperative shell** — pure methods for logic, OOP shell only where the framework demands it.
|
|
9
9
|
|
|
10
|
-
##
|
|
10
|
+
## Core Rules
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
- Building Ruby utilities or service objects
|
|
14
|
-
- Refactoring procedural Ruby toward composable units
|
|
15
|
-
- Any Ruby code where framework conventions don't dictate the pattern
|
|
16
|
-
|
|
17
|
-
## Core Philosophy
|
|
18
|
-
|
|
19
|
-
> Ruby's Enumerable is a functional toolkit hiding inside an OOP language.
|
|
20
|
-
|
|
21
|
-
The architect's lens applied to Ruby:
|
|
22
|
-
- **Pure methods** for logic (no instance variable mutation in calculation methods)
|
|
12
|
+
- **Pure methods** for logic — no instance variable mutation in calculation methods
|
|
23
13
|
- **Enumerable over explicit loops** — `map/select/reduce` not `each` + accumulator
|
|
24
|
-
- **Lambdas** for first-class functions
|
|
14
|
+
- **Lambdas** (`->`) for first-class functions — strict arity, scoped `return`
|
|
25
15
|
- **freeze** for value objects and constants
|
|
26
16
|
- **Functional core** isolated from I/O, database, external state
|
|
27
17
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
## Enumerable: The FP Toolkit
|
|
31
|
-
|
|
32
|
-
These are your primary tools. Prefer them over `each` + mutation.
|
|
18
|
+
## Enumerable: Primary Tools
|
|
33
19
|
|
|
34
20
|
```ruby
|
|
35
21
|
users = [
|
|
@@ -38,61 +24,40 @@ users = [
|
|
|
38
24
|
{ name: 'Carol', age: 25, active: true }
|
|
39
25
|
]
|
|
40
26
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
#
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
total_age = users.reduce(0) { |sum, u| sum + u[:age] }
|
|
49
|
-
|
|
50
|
-
# filter_map — select + transform in one pass (Ruby 2.7+, preferred)
|
|
51
|
-
active_names = users.filter_map { |u| u[:name] if u[:active] }
|
|
52
|
-
|
|
53
|
-
# flat_map — map + flatten
|
|
54
|
-
tags = posts.flat_map { |p| p[:tags] }
|
|
55
|
-
|
|
56
|
-
# each_with_object — build a hash/array without external mutation
|
|
57
|
-
index = users.each_with_object({}) { |u, h| h[u[:name]] = u[:age] }
|
|
58
|
-
|
|
59
|
-
# group_by — partition
|
|
60
|
-
by_status = users.group_by { |u| u[:active] ? :active : :inactive }
|
|
27
|
+
adults = users.select { |u| u[:age] >= 18 }
|
|
28
|
+
names = users.map { |u| u[:name] }
|
|
29
|
+
total_age = users.reduce(0) { |sum, u| sum + u[:age] }
|
|
30
|
+
active_names = users.filter_map { |u| u[:name] if u[:active] } # Ruby 2.7+, preferred
|
|
31
|
+
tags = posts.flat_map { |p| p[:tags] }
|
|
32
|
+
index = users.each_with_object({}) { |u, h| h[u[:name]] = u[:age] }
|
|
33
|
+
by_status = users.group_by { |u| u[:active] ? :active : :inactive }
|
|
61
34
|
|
|
62
|
-
#
|
|
35
|
+
# Functional pipeline
|
|
63
36
|
result = users
|
|
64
37
|
.select { |u| u[:active] }
|
|
65
38
|
.map { |u| u.merge(display_name: u[:name].upcase) }
|
|
66
39
|
.sort_by { |u| u[:age] }
|
|
67
40
|
```
|
|
68
41
|
|
|
69
|
-
|
|
42
|
+
If writing `results = []; collection.each { |x| results << x if ... }` — use `filter_map` instead.
|
|
70
43
|
|
|
71
44
|
## Lambdas vs Procs
|
|
72
45
|
|
|
73
|
-
Use `lambda` (or `->`) for reusable, composable functions. Avoid bare `proc` for logic units.
|
|
74
|
-
|
|
75
46
|
```ruby
|
|
76
|
-
|
|
77
|
-
validate_age = ->(age) { age.is_a?(Integer) && age >= 0 && age <= 150 }
|
|
47
|
+
validate_age = ->(age) { age.is_a?(Integer) && age >= 0 && age <= 150 }
|
|
78
48
|
normalize_email = ->(email) { email.to_s.strip.downcase }
|
|
79
49
|
|
|
80
50
|
# Compose with >> (Ruby 2.6+)
|
|
81
51
|
process_user = normalize_email >> method(:save_user)
|
|
82
52
|
|
|
83
|
-
#
|
|
84
|
-
def transform_all(items, transformer)
|
|
85
|
-
items.map(&transformer)
|
|
86
|
-
end
|
|
87
|
-
|
|
53
|
+
# Higher-order functions
|
|
54
|
+
def transform_all(items, transformer) = items.map(&transformer)
|
|
88
55
|
transform_all(emails, normalize_email)
|
|
89
56
|
|
|
90
|
-
# Method references
|
|
91
|
-
validator = method(:validate_age)
|
|
57
|
+
# Method references
|
|
92
58
|
emails.map(&method(:normalize_email))
|
|
93
59
|
```
|
|
94
60
|
|
|
95
|
-
**Lambda vs Proc differences that matter:**
|
|
96
61
|
| | Lambda | Proc |
|
|
97
62
|
|-|--------|------|
|
|
98
63
|
| Arity | Strict (raises ArgumentError) | Loose (fills nil) |
|
|
@@ -102,27 +67,23 @@ emails.map(&method(:normalize_email))
|
|
|
102
67
|
## Immutability with freeze
|
|
103
68
|
|
|
104
69
|
```ruby
|
|
105
|
-
# Freeze constants
|
|
106
70
|
VALID_STATUSES = %w[active inactive pending].freeze
|
|
107
71
|
DEFAULT_CONFIG = { timeout: 30, retries: 3 }.freeze
|
|
108
72
|
|
|
109
|
-
# Value object
|
|
110
|
-
UserRecord = Data.define(:name, :email, :age)
|
|
73
|
+
# Value object — Ruby 3.2+
|
|
74
|
+
UserRecord = Data.define(:name, :email, :age)
|
|
111
75
|
user = UserRecord.new(name: 'Alice', email: 'alice@example.com', age: 30)
|
|
112
|
-
# user is immutable — no setters
|
|
113
76
|
|
|
114
|
-
#
|
|
77
|
+
# Older Ruby
|
|
115
78
|
Config = Struct.new(:host, :port, :timeout).new('localhost', 5432, 30).freeze
|
|
116
79
|
```
|
|
117
80
|
|
|
118
|
-
|
|
81
|
+
Any hash/array constant gets `.freeze`. Value objects use `Data.define` (3.2+) or frozen `Struct`.
|
|
119
82
|
|
|
120
83
|
## Functional Core / Imperative Shell
|
|
121
84
|
|
|
122
|
-
The central pattern. Separate pure logic from I/O and external state.
|
|
123
|
-
|
|
124
85
|
```ruby
|
|
125
|
-
# PURE CORE — no I/O,
|
|
86
|
+
# PURE CORE — no I/O, fully testable
|
|
126
87
|
module UserLogic
|
|
127
88
|
def self.validate(attrs)
|
|
128
89
|
errors = []
|
|
@@ -133,10 +94,7 @@ module UserLogic
|
|
|
133
94
|
end
|
|
134
95
|
|
|
135
96
|
def self.normalize(attrs)
|
|
136
|
-
attrs.merge(
|
|
137
|
-
name: attrs[:name].to_s.strip,
|
|
138
|
-
email: attrs[:email].to_s.strip.downcase
|
|
139
|
-
)
|
|
97
|
+
attrs.merge(name: attrs[:name].to_s.strip, email: attrs[:email].to_s.strip.downcase)
|
|
140
98
|
end
|
|
141
99
|
|
|
142
100
|
def self.prepare_for_save(raw_attrs)
|
|
@@ -150,21 +108,15 @@ end
|
|
|
150
108
|
def create_user(raw_params)
|
|
151
109
|
result = UserLogic.prepare_for_save(raw_params)
|
|
152
110
|
return { success: false, errors: result[:errors] } unless result[:ok]
|
|
153
|
-
|
|
154
|
-
user = User.create!(result[:attrs]) # database side effect here only
|
|
111
|
+
user = User.create!(result[:attrs])
|
|
155
112
|
{ success: true, user: user }
|
|
156
113
|
end
|
|
157
114
|
```
|
|
158
115
|
|
|
159
|
-
## Pure Method
|
|
160
|
-
|
|
161
|
-
A method is pure when:
|
|
162
|
-
1. Same inputs always return same output
|
|
163
|
-
2. No mutation of instance variables during computation
|
|
164
|
-
3. No I/O (logging, DB, network) inside the calculation
|
|
116
|
+
## Pure Method: Good vs Bad
|
|
165
117
|
|
|
166
118
|
```ruby
|
|
167
|
-
# BAD — mutates
|
|
119
|
+
# BAD — mutates instance variables during calculation
|
|
168
120
|
def calculate_totals
|
|
169
121
|
@subtotal = @items.sum { |i| i[:price] * i[:qty] }
|
|
170
122
|
@tax = @subtotal * 0.08
|
|
@@ -182,155 +134,106 @@ end
|
|
|
182
134
|
## Composition Patterns
|
|
183
135
|
|
|
184
136
|
```ruby
|
|
185
|
-
#
|
|
137
|
+
# Immutable pipeline
|
|
186
138
|
class Pipeline
|
|
187
|
-
def initialize(steps = [])
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def add(step)
|
|
192
|
-
Pipeline.new(@steps + [step]) # returns new instance, immutable
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def call(input)
|
|
196
|
-
@steps.reduce(input) { |data, step| step.call(data) }
|
|
197
|
-
end
|
|
139
|
+
def initialize(steps = []) = @steps = steps.freeze
|
|
140
|
+
def add(step) = Pipeline.new(@steps + [step])
|
|
141
|
+
def call(input) = @steps.reduce(input) { |data, step| step.call(data) }
|
|
198
142
|
end
|
|
199
143
|
|
|
200
|
-
# Lambda composition
|
|
144
|
+
# Lambda composition
|
|
201
145
|
sanitize = ->(s) { s.strip.downcase }
|
|
202
146
|
validate = ->(s) { raise "empty" if s.empty?; s }
|
|
203
147
|
normalize = sanitize >> validate
|
|
204
148
|
|
|
205
149
|
# Callable objects (duck-typed lambdas)
|
|
206
|
-
class Validator
|
|
207
|
-
def call(value)
|
|
208
|
-
value.is_a?(String) && !value.empty?
|
|
209
|
-
end
|
|
210
|
-
end
|
|
211
|
-
|
|
212
150
|
validators = [Validator.new, ->(v) { v.length < 255 }]
|
|
213
151
|
valid = validators.all? { |v| v.call(input) }
|
|
214
152
|
```
|
|
215
153
|
|
|
216
|
-
## Anti-Patterns
|
|
154
|
+
## Anti-Patterns
|
|
217
155
|
|
|
218
156
|
```ruby
|
|
219
|
-
# BAD — accumulator mutation
|
|
157
|
+
# BAD — accumulator mutation
|
|
220
158
|
result = []
|
|
221
159
|
items.each { |item| result << transform(item) if item[:active] }
|
|
222
160
|
# GOOD
|
|
223
161
|
result = items.filter_map { |item| transform(item) if item[:active] }
|
|
224
162
|
|
|
225
163
|
# BAD — output parameter mutation
|
|
226
|
-
def process(items, output)
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
# GOOD — return new value
|
|
230
|
-
def process(items)
|
|
231
|
-
items.map { |i| i * 2 }
|
|
232
|
-
end
|
|
164
|
+
def process(items, output) = items.each { |i| output << i * 2 }
|
|
165
|
+
# GOOD
|
|
166
|
+
def process(items) = items.map { |i| i * 2 }
|
|
233
167
|
|
|
234
|
-
# BAD —
|
|
168
|
+
# BAD — mixed concerns
|
|
235
169
|
def calculate_and_save
|
|
236
|
-
total = @items.sum(&:price)
|
|
237
|
-
@total = total # side effect
|
|
238
|
-
DB.save(total) # side effect
|
|
239
|
-
total
|
|
170
|
+
total = @items.sum(&:price); @total = total; DB.save(total); total
|
|
240
171
|
end
|
|
241
|
-
# GOOD
|
|
242
|
-
total = calculate_total(@items)
|
|
243
|
-
update_total(total)
|
|
172
|
+
# GOOD
|
|
173
|
+
total = calculate_total(@items) # pure
|
|
174
|
+
update_total(total) # side effect isolated
|
|
244
175
|
|
|
245
176
|
# BAD — bare proc for reusable function
|
|
246
|
-
adder = proc { |a, b| a + b }
|
|
177
|
+
adder = proc { |a, b| a + b }
|
|
247
178
|
# GOOD
|
|
248
179
|
adder = ->(a, b) { a + b }
|
|
249
180
|
```
|
|
250
181
|
|
|
251
|
-
## When to Use Classes
|
|
182
|
+
## When to Use Classes vs Modules
|
|
252
183
|
|
|
253
|
-
|
|
184
|
+
**Use classes for:** stateful entities (User, Order, Connection), framework integration (ActiveRecord), objects with lifecycle.
|
|
254
185
|
|
|
255
|
-
Use modules with class methods
|
|
186
|
+
**Use modules with class methods for:** pure logic, utilities, transformations, validators.
|
|
256
187
|
|
|
257
188
|
```ruby
|
|
258
|
-
# Module for pure logic — no instances needed
|
|
259
189
|
module PriceCalculator
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
price * (1 - rates.fetch(tier, 0))
|
|
263
|
-
end
|
|
190
|
+
RATES = { bronze: 0.05, silver: 0.10, gold: 0.20 }.freeze
|
|
191
|
+
def self.discount(price, tier) = price * (1 - RATES.fetch(tier, 0))
|
|
264
192
|
end
|
|
265
193
|
|
|
266
|
-
# Class for stateful lifecycle
|
|
267
194
|
class ImportJob
|
|
268
|
-
def initialize(source_db, config)
|
|
269
|
-
|
|
270
|
-
@config = config
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
def run
|
|
274
|
-
records = fetch_records # I/O
|
|
275
|
-
processed = process(records) # pure
|
|
276
|
-
persist(processed) # I/O
|
|
277
|
-
end
|
|
278
|
-
|
|
195
|
+
def initialize(source_db, config) = (@source_db, @config = source_db, config)
|
|
196
|
+
def run = persist(process(fetch_records))
|
|
279
197
|
private
|
|
280
|
-
|
|
281
|
-
def process(records)
|
|
282
|
-
records
|
|
283
|
-
.select { |r| valid?(r) }
|
|
284
|
-
.map { |r| transform(r) }
|
|
285
|
-
end
|
|
198
|
+
def process(records) = records.select { |r| valid?(r) }.map { |r| transform(r) }
|
|
286
199
|
end
|
|
287
200
|
```
|
|
288
201
|
|
|
289
|
-
## Security
|
|
290
|
-
|
|
291
|
-
- **Never interpolate external input into shell commands** — use `Open3.capture3` with array args, not `system("cmd #{input}")`
|
|
292
|
-
- **Never interpolate input into SQL** — use parameterized queries or the driver's escape method
|
|
293
|
-
- **ENV for credentials** — never hardcode; raise if missing: `ENV.fetch('DB_PASSWORD')`
|
|
202
|
+
## Security (Standalone Scripts)
|
|
294
203
|
|
|
295
204
|
```ruby
|
|
296
205
|
# BAD — shell injection
|
|
297
206
|
system("convert #{filename} output.png")
|
|
298
|
-
|
|
299
207
|
# GOOD — array form, no shell expansion
|
|
300
|
-
require 'open3'
|
|
301
208
|
stdout, stderr, status = Open3.capture3('convert', filename, 'output.png')
|
|
302
209
|
|
|
303
210
|
# BAD — hardcoded credential
|
|
304
211
|
client = Mysql2::Client.new(password: 'secret123')
|
|
305
|
-
|
|
306
212
|
# GOOD — fail loudly if not set
|
|
307
213
|
client = Mysql2::Client.new(password: ENV.fetch('MYSQL_PASSWORD'))
|
|
308
214
|
```
|
|
309
215
|
|
|
310
|
-
|
|
216
|
+
- Never interpolate external input into shell commands
|
|
217
|
+
- Never interpolate input into SQL — use parameterized queries
|
|
218
|
+
- Use `ENV.fetch` for credentials — raises KeyError if missing
|
|
219
|
+
|
|
220
|
+
## Quick Reference
|
|
311
221
|
|
|
312
222
|
| Need | Use |
|
|
313
223
|
|------|-----|
|
|
314
|
-
| Transform
|
|
315
|
-
| Filter
|
|
316
|
-
| Transform + filter
|
|
317
|
-
| Accumulate to
|
|
318
|
-
| Build
|
|
319
|
-
| Group by
|
|
224
|
+
| Transform collection | `map` |
|
|
225
|
+
| Filter collection | `select` / `reject` |
|
|
226
|
+
| Transform + filter | `filter_map` |
|
|
227
|
+
| Accumulate to single value | `reduce` / `inject` |
|
|
228
|
+
| Build hash from collection | `each_with_object` / `to_h` |
|
|
229
|
+
| Group by property | `group_by` |
|
|
320
230
|
| Reusable function | `lambda` / `->` |
|
|
321
231
|
| Compose functions | `>>` operator |
|
|
322
232
|
| Immutable constant | `.freeze` |
|
|
323
233
|
| Immutable value object | `Data.define` (Ruby 3.2+) or frozen `Struct` |
|
|
324
234
|
| Pure logic module | `module Foo; def self.method...` |
|
|
325
235
|
|
|
326
|
-
##
|
|
327
|
-
|
|
328
|
-
### FP Patterns Deep Dive
|
|
329
|
-
**File**: [`references/patterns.md`](references/patterns.md)
|
|
330
|
-
**Load when**: Need advanced composition, lazy enumerables, currying, memoization
|
|
331
|
-
**Contains**: Lazy evaluation, `Comparable`/`Enumerable` mixin, memoization patterns, full pipeline example
|
|
236
|
+
## Reference Files
|
|
332
237
|
|
|
333
|
-
|
|
334
|
-
**
|
|
335
|
-
**Load when**: Working with external input, SQL, shell commands, file operations
|
|
336
|
-
**Contains**: SQL parameterization, shell safety, input validation, ENV credential management
|
|
238
|
+
- **[`references/patterns.md`](references/patterns.md)** — Lazy enumerables, currying, memoization, advanced composition. Load when: need advanced FP patterns.
|
|
239
|
+
- **[`references/security.md`](references/security.md)** — SQL parameterization, shell safety, input validation. Load when: working with external input.
|