rails_skills 0.1.0 → 0.2.1
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.
- checksums.yaml +4 -4
- data/README.md +21 -32
- data/lib/generators/rails_skills/install/install_generator.rb +41 -67
- data/lib/generators/rails_skills/skill/skill_generator.rb +6 -2
- data/lib/generators/rails_skills/skill/templates/SKILL.md.tt +2 -2
- data/lib/generators/rails_skills/skills_library/domains/.keep +0 -0
- data/lib/generators/rails_skills/skills_library/stack/ruby/SKILL.md +86 -0
- data/lib/generators/rails_skills/skills_library/workflows/commit/SKILL.md +51 -0
- data/lib/rails_skills/version.rb +1 -1
- metadata +5 -12
- data/lib/generators/rails_skills/install/templates/agents/api-dev.md.tt +0 -28
- data/lib/generators/rails_skills/install/templates/agents/fullstack-dev.md.tt +0 -28
- data/lib/generators/rails_skills/rules_library/security.md +0 -33
- data/lib/generators/rails_skills/skills_library/rails-api-controllers/SKILL.md +0 -106
- data/lib/generators/rails_skills/skills_library/rails-controllers/SKILL.md +0 -125
- data/lib/generators/rails_skills/skills_library/rails-hotwire/SKILL.md +0 -89
- data/lib/generators/rails_skills/skills_library/rails-jobs/SKILL.md +0 -39
- data/lib/generators/rails_skills/skills_library/rails-models/SKILL.md +0 -105
- data/lib/generators/rails_skills/skills_library/rails-views/SKILL.md +0 -109
- data/lib/generators/rails_skills/skills_library/rspec-testing/SKILL.md +0 -105
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 03c6340c54476e1461bcd09b5555135c7394feb027482313bfa70cc9b03c040c
|
|
4
|
+
data.tar.gz: dc0fed169889191177353cc7732dbb0a1585515a8c95bcbaca089f26c3fc4272
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2ad4b1bc1c3d7849cfbbd784b2c913d9f8075ba41ad84108d0ddb3fa1c8e4b91da529112b1a54d1f99d92f8900292551c7acf0ef3c6ce8ea492028473caee9fe
|
|
7
|
+
data.tar.gz: 2aa688d5b29e489796708a42542a5aded29a46f9c58c1189aaf969942867342c4307f396d4408fa3132f3cc37c186d87f9ce5b5ee24335fa4ee5f7d089232532
|
data/README.md
CHANGED
|
@@ -20,31 +20,25 @@ bundle install
|
|
|
20
20
|
|
|
21
21
|
## Usage
|
|
22
22
|
|
|
23
|
-
### Install
|
|
23
|
+
### Install
|
|
24
24
|
|
|
25
25
|
```bash
|
|
26
26
|
rails generate rails_skills:install
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
### Install with a preset
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
rails generate rails_skills:install --preset=basic
|
|
33
|
-
rails generate rails_skills:install --preset=fullstack
|
|
34
|
-
rails generate rails_skills:install --preset=api
|
|
35
|
-
```
|
|
36
|
-
|
|
37
29
|
### What it creates
|
|
38
30
|
|
|
39
31
|
```
|
|
40
32
|
your_rails_app/
|
|
41
33
|
├── skills/ # Shared AI skill files (canonical location)
|
|
42
|
-
│ ├──
|
|
43
|
-
│ │ └──
|
|
44
|
-
│ ├──
|
|
45
|
-
│ │ └──
|
|
46
|
-
│ └──
|
|
47
|
-
│
|
|
34
|
+
│ ├── domains/ # Domain-specific skills
|
|
35
|
+
│ │ └── .keep
|
|
36
|
+
│ ├── stack/ # Technology stack skills
|
|
37
|
+
│ │ └── ruby/
|
|
38
|
+
│ │ └── SKILL.md
|
|
39
|
+
│ └── workflows/ # Workflow skills
|
|
40
|
+
│ └── commit/
|
|
41
|
+
│ └── SKILL.md
|
|
48
42
|
├── .claude/
|
|
49
43
|
│ ├── skills -> ../skills # Symlink to shared skills
|
|
50
44
|
│ ├── agents/
|
|
@@ -57,35 +51,30 @@ your_rails_app/
|
|
|
57
51
|
|
|
58
52
|
Both Claude and Codex read from the same `skills/` directory via symlinks.
|
|
59
53
|
|
|
60
|
-
###
|
|
54
|
+
### Skill Categories
|
|
61
55
|
|
|
62
|
-
|
|
|
63
|
-
|
|
64
|
-
| **
|
|
65
|
-
| **
|
|
66
|
-
| **
|
|
56
|
+
| Category | Purpose | Example |
|
|
57
|
+
|----------|---------|---------|
|
|
58
|
+
| **domains/** | Business domain knowledge | `domains/payments`, `domains/auth` |
|
|
59
|
+
| **stack/** | Technology stack skills | `stack/ruby`, `stack/postgres` |
|
|
60
|
+
| **workflows/** | Development workflows | `workflows/commit`, `workflows/deploy` |
|
|
67
61
|
|
|
68
62
|
### Create a custom skill
|
|
69
63
|
|
|
70
64
|
```bash
|
|
71
|
-
rails generate rails_skills:skill
|
|
72
|
-
rails generate rails_skills:skill
|
|
73
|
-
rails generate rails_skills:skill
|
|
65
|
+
rails generate rails_skills:skill domains/payments
|
|
66
|
+
rails generate rails_skills:skill stack/postgres
|
|
67
|
+
rails generate rails_skills:skill workflows/deploy --description="Deployment workflow"
|
|
74
68
|
```
|
|
75
69
|
|
|
76
70
|
Skills are created in `skills/` and automatically available to both Claude and Codex.
|
|
77
71
|
|
|
78
|
-
##
|
|
72
|
+
## Default Skills
|
|
79
73
|
|
|
80
74
|
The gem ships with these pre-built skills:
|
|
81
75
|
|
|
82
|
-
- **
|
|
83
|
-
- **
|
|
84
|
-
- **rails-views** - ERB templates, layouts, partials, forms, helpers
|
|
85
|
-
- **rails-hotwire** - Turbo Drive, Turbo Frames, Turbo Streams, Stimulus
|
|
86
|
-
- **rails-api-controllers** - API-only controllers, serialization, versioning
|
|
87
|
-
- **rspec-testing** - RSpec patterns for models, requests, and system tests
|
|
88
|
-
- **rails-jobs** - Active Job patterns, background processing
|
|
76
|
+
- **stack/ruby** - Ruby language patterns, idioms, and best practices
|
|
77
|
+
- **workflows/commit** - Git commit workflow and conventions
|
|
89
78
|
|
|
90
79
|
## How It Works
|
|
91
80
|
|
|
@@ -7,13 +7,14 @@ module RailsSkills
|
|
|
7
7
|
class InstallGenerator < Rails::Generators::Base
|
|
8
8
|
source_root File.expand_path("templates", __dir__)
|
|
9
9
|
|
|
10
|
-
class_option :preset, type: :string, default: "basic",
|
|
11
|
-
desc: "Preset bundle (basic, fullstack, api)"
|
|
12
10
|
class_option :skip_agents, type: :boolean, default: false,
|
|
13
11
|
desc: "Don't create default agents"
|
|
14
12
|
|
|
15
13
|
def create_skills_directory
|
|
16
14
|
empty_directory "skills"
|
|
15
|
+
empty_directory "skills/domains"
|
|
16
|
+
empty_directory "skills/stack"
|
|
17
|
+
empty_directory "skills/workflows"
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def create_claude_directory
|
|
@@ -36,15 +37,25 @@ module RailsSkills
|
|
|
36
37
|
template "settings.local.json.tt", ".claude/settings.local.json"
|
|
37
38
|
end
|
|
38
39
|
|
|
39
|
-
def
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
40
|
+
def install_default_skills
|
|
41
|
+
# domains - empty with .keep
|
|
42
|
+
install_skill_folder("domains")
|
|
43
|
+
|
|
44
|
+
# stack - ruby skill
|
|
45
|
+
install_skill("stack/ruby")
|
|
46
|
+
|
|
47
|
+
# workflows - commit skill
|
|
48
|
+
install_skill("workflows/commit")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def install_rules_and_commands
|
|
52
|
+
install_rule("code-style")
|
|
53
|
+
install_rule("testing")
|
|
54
|
+
install_command("quality")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def create_default_agent
|
|
58
|
+
template "agents/rails-developer.md.tt", ".claude/agents/rails-developer.md" unless options[:skip_agents]
|
|
48
59
|
end
|
|
49
60
|
|
|
50
61
|
def show_instructions
|
|
@@ -52,14 +63,19 @@ module RailsSkills
|
|
|
52
63
|
say "RailsSkills installed successfully!", :green
|
|
53
64
|
say ""
|
|
54
65
|
say "Directory structure:", :yellow
|
|
55
|
-
say " skills/
|
|
56
|
-
say "
|
|
57
|
-
say "
|
|
66
|
+
say " skills/"
|
|
67
|
+
say " domains/ <- domain-specific skills"
|
|
68
|
+
say " stack/ <- technology stack skills (ruby)"
|
|
69
|
+
say " workflows/ <- workflow skills (commit)"
|
|
70
|
+
say " .claude/skills -> ../skills (symlink)"
|
|
71
|
+
say " .codex/skills -> ../skills (symlink)"
|
|
58
72
|
say ""
|
|
59
|
-
say "Both Claude and Codex
|
|
73
|
+
say "Both Claude and Codex share skills from skills/", :blue
|
|
60
74
|
say ""
|
|
61
75
|
say "Next steps:", :yellow
|
|
62
|
-
say " rails g rails_skills:skill
|
|
76
|
+
say " rails g rails_skills:skill domains/my_domain # Create a domain skill"
|
|
77
|
+
say " rails g rails_skills:skill stack/postgres # Create a stack skill"
|
|
78
|
+
say " rails g rails_skills:skill workflows/deploy # Create a workflow skill"
|
|
63
79
|
say ""
|
|
64
80
|
end
|
|
65
81
|
|
|
@@ -75,52 +91,22 @@ module RailsSkills
|
|
|
75
91
|
end
|
|
76
92
|
end
|
|
77
93
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
install_rule("code-style")
|
|
84
|
-
install_rule("testing")
|
|
85
|
-
install_command("quality")
|
|
86
|
-
create_basic_agent unless options[:skip_agents]
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def install_fullstack_preset
|
|
90
|
-
say "Installing fullstack preset...", :green
|
|
91
|
-
install_skill("rails-models")
|
|
92
|
-
install_skill("rails-controllers")
|
|
93
|
-
install_skill("rails-views")
|
|
94
|
-
install_skill("rails-hotwire")
|
|
95
|
-
install_skill("rspec-testing")
|
|
96
|
-
install_rule("code-style")
|
|
97
|
-
install_rule("testing")
|
|
98
|
-
install_rule("security")
|
|
99
|
-
install_command("quality")
|
|
100
|
-
create_fullstack_agent unless options[:skip_agents]
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def install_api_preset
|
|
104
|
-
say "Installing API preset...", :green
|
|
105
|
-
install_skill("rails-models")
|
|
106
|
-
install_skill("rails-api-controllers")
|
|
107
|
-
install_skill("rspec-testing")
|
|
108
|
-
install_rule("code-style")
|
|
109
|
-
install_rule("testing")
|
|
110
|
-
install_rule("security")
|
|
111
|
-
install_command("quality")
|
|
112
|
-
create_api_agent unless options[:skip_agents]
|
|
94
|
+
def install_skill_folder(folder_name)
|
|
95
|
+
skill_source = File.expand_path("../skills_library/#{folder_name}", __dir__)
|
|
96
|
+
if File.directory?(skill_source)
|
|
97
|
+
directory skill_source, "skills/#{folder_name}"
|
|
98
|
+
end
|
|
113
99
|
end
|
|
114
100
|
|
|
115
|
-
def install_skill(
|
|
116
|
-
skill_dir = "skills/#{
|
|
101
|
+
def install_skill(skill_path)
|
|
102
|
+
skill_dir = "skills/#{skill_path}"
|
|
117
103
|
empty_directory skill_dir
|
|
118
104
|
|
|
119
|
-
skill_source = File.expand_path("../skills_library/#{
|
|
105
|
+
skill_source = File.expand_path("../skills_library/#{skill_path}", __dir__)
|
|
120
106
|
if File.directory?(skill_source)
|
|
121
107
|
directory skill_source, skill_dir
|
|
122
108
|
else
|
|
123
|
-
create_file "#{skill_dir}/SKILL.md", default_skill_content(
|
|
109
|
+
create_file "#{skill_dir}/SKILL.md", default_skill_content(skill_path.split("/").last)
|
|
124
110
|
end
|
|
125
111
|
end
|
|
126
112
|
|
|
@@ -146,18 +132,6 @@ module RailsSkills
|
|
|
146
132
|
end
|
|
147
133
|
end
|
|
148
134
|
|
|
149
|
-
def create_basic_agent
|
|
150
|
-
template "agents/rails-developer.md.tt", ".claude/agents/rails-developer.md"
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
def create_fullstack_agent
|
|
154
|
-
template "agents/fullstack-dev.md.tt", ".claude/agents/fullstack-dev.md"
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def create_api_agent
|
|
158
|
-
template "agents/api-dev.md.tt", ".claude/agents/api-dev.md"
|
|
159
|
-
end
|
|
160
|
-
|
|
161
135
|
def default_skill_content(skill_name)
|
|
162
136
|
<<~SKILL
|
|
163
137
|
---
|
|
@@ -28,7 +28,7 @@ module RailsSkills
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def show_instructions
|
|
31
|
-
say "\nSkill '#{
|
|
31
|
+
say "\nSkill '#{skill_name}' created in #{skill_path}/", :green
|
|
32
32
|
say "This skill is available to both Claude and Codex via symlinks.", :blue
|
|
33
33
|
end
|
|
34
34
|
|
|
@@ -38,8 +38,12 @@ module RailsSkills
|
|
|
38
38
|
"skills/#{file_name}"
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
+
def skill_name
|
|
42
|
+
file_name.split("/").last
|
|
43
|
+
end
|
|
44
|
+
|
|
41
45
|
def skill_description
|
|
42
|
-
options[:description] || "Custom skill for #{
|
|
46
|
+
options[:description] || "Custom skill for #{skill_name.tr('-', ' ')}"
|
|
43
47
|
end
|
|
44
48
|
end
|
|
45
49
|
end
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: <%=
|
|
2
|
+
name: <%= skill_name %>
|
|
3
3
|
description: <%= skill_description %>
|
|
4
4
|
version: 1.0.0
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# <%=
|
|
7
|
+
# <%= skill_name.tr('-', ' ').split.map(&:capitalize).join(' ') %>
|
|
8
8
|
|
|
9
9
|
## Quick Reference
|
|
10
10
|
|
|
File without changes
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ruby
|
|
3
|
+
description: Ruby language patterns, idioms, and best practices
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Ruby
|
|
8
|
+
|
|
9
|
+
## Quick Reference
|
|
10
|
+
|
|
11
|
+
| Pattern | Example |
|
|
12
|
+
|---------|---------|
|
|
13
|
+
| **Safe navigation** | `user&.name` |
|
|
14
|
+
| **Dig** | `hash.dig(:user, :address, :city)` |
|
|
15
|
+
| **Transform keys** | `hash.transform_keys(&:to_sym)` |
|
|
16
|
+
| **Compact** | `array.compact` |
|
|
17
|
+
| **Then/yield_self** | `value.then { \|v\| process(v) }` |
|
|
18
|
+
|
|
19
|
+
## Idioms
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
# Guard clauses
|
|
23
|
+
def process(user)
|
|
24
|
+
return unless user
|
|
25
|
+
return if user.inactive?
|
|
26
|
+
# main logic
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Safe navigation
|
|
30
|
+
user&.profile&.avatar_url
|
|
31
|
+
|
|
32
|
+
# Hash with default
|
|
33
|
+
counts = Hash.new(0)
|
|
34
|
+
counts[:views] += 1
|
|
35
|
+
|
|
36
|
+
# Destructuring
|
|
37
|
+
first, *rest = array
|
|
38
|
+
hash => { name:, email: }
|
|
39
|
+
|
|
40
|
+
# Enumerable
|
|
41
|
+
users.map(&:name)
|
|
42
|
+
users.select(&:active?)
|
|
43
|
+
users.find { |u| u.admin? }
|
|
44
|
+
users.group_by(&:role)
|
|
45
|
+
users.index_by(&:id)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Collections
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
# Map with index
|
|
52
|
+
items.each_with_index { |item, i| }
|
|
53
|
+
items.map.with_index { |item, i| }
|
|
54
|
+
|
|
55
|
+
# Reduce
|
|
56
|
+
numbers.reduce(0) { |sum, n| sum + n }
|
|
57
|
+
numbers.sum
|
|
58
|
+
|
|
59
|
+
# Partition
|
|
60
|
+
active, inactive = users.partition(&:active?)
|
|
61
|
+
|
|
62
|
+
# Zip
|
|
63
|
+
names.zip(emails).map { |name, email| { name:, email: } }
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Blocks & Procs
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
# Block to proc
|
|
70
|
+
users.map(&:name)
|
|
71
|
+
|
|
72
|
+
# Method reference
|
|
73
|
+
users.map(&method(:process))
|
|
74
|
+
|
|
75
|
+
# Proc/Lambda
|
|
76
|
+
square = ->(x) { x * x }
|
|
77
|
+
square.call(5)
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Best Practices
|
|
81
|
+
|
|
82
|
+
1. Prefer guard clauses over nested conditionals
|
|
83
|
+
2. Use `&.` safe navigation over `try` or conditionals
|
|
84
|
+
3. Use symbols for hash keys
|
|
85
|
+
4. Prefer `each` over `for`
|
|
86
|
+
5. Use `freeze` for constants: `ROLES = %w[admin user].freeze`
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: commit
|
|
3
|
+
description: Git commit workflow and conventions
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Workflow
|
|
7
|
+
|
|
8
|
+
1. **Check status**: `git status`
|
|
9
|
+
2. **Review changes**: `git diff`
|
|
10
|
+
3. **Analyze missing docs/skills** - If you see changes to domains listed in skills/domains - review the need to update the skill doc
|
|
11
|
+
3. **Stage files**: `git add <files>` (prefer specific files over `-A`)
|
|
12
|
+
4. **Commit**: `git commit -m "type: message"`
|
|
13
|
+
5. **Push**: Push only if explicitly asked so, use `git push origin {current branch}`
|
|
14
|
+
|
|
15
|
+
# Commit Message Format
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
<type>: <subject>
|
|
19
|
+
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<footer>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Types
|
|
26
|
+
|
|
27
|
+
- **feat**: New feature
|
|
28
|
+
- **fix**: Bug fix
|
|
29
|
+
- **docs**: Documentation only
|
|
30
|
+
- **style**: Formatting, no code change
|
|
31
|
+
- **refactor**: Code change that neither fixes nor adds
|
|
32
|
+
- **test**: Adding or updating tests
|
|
33
|
+
- **chore**: Maintenance tasks
|
|
34
|
+
|
|
35
|
+
## Examples
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
feat: Add user authentication
|
|
39
|
+
|
|
40
|
+
Implement login/logout functionality using Devise.
|
|
41
|
+
Includes email/password authentication and session management.
|
|
42
|
+
|
|
43
|
+
Closes #123
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
fix: Prevent duplicate form submissions
|
|
48
|
+
|
|
49
|
+
Add disable_with to submit buttons to prevent
|
|
50
|
+
users from clicking multiple times.
|
|
51
|
+
```
|
data/lib/rails_skills/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails_skills
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sergii Mostovyi
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|
|
@@ -38,22 +38,15 @@ files:
|
|
|
38
38
|
- Rakefile
|
|
39
39
|
- lib/generators/rails_skills/commands_library/quality.md
|
|
40
40
|
- lib/generators/rails_skills/install/install_generator.rb
|
|
41
|
-
- lib/generators/rails_skills/install/templates/agents/api-dev.md.tt
|
|
42
|
-
- lib/generators/rails_skills/install/templates/agents/fullstack-dev.md.tt
|
|
43
41
|
- lib/generators/rails_skills/install/templates/agents/rails-developer.md.tt
|
|
44
42
|
- lib/generators/rails_skills/install/templates/settings.local.json.tt
|
|
45
43
|
- lib/generators/rails_skills/rules_library/code-style.md
|
|
46
|
-
- lib/generators/rails_skills/rules_library/security.md
|
|
47
44
|
- lib/generators/rails_skills/rules_library/testing.md
|
|
48
45
|
- lib/generators/rails_skills/skill/skill_generator.rb
|
|
49
46
|
- lib/generators/rails_skills/skill/templates/SKILL.md.tt
|
|
50
|
-
- lib/generators/rails_skills/skills_library/
|
|
51
|
-
- lib/generators/rails_skills/skills_library/
|
|
52
|
-
- lib/generators/rails_skills/skills_library/
|
|
53
|
-
- lib/generators/rails_skills/skills_library/rails-jobs/SKILL.md
|
|
54
|
-
- lib/generators/rails_skills/skills_library/rails-models/SKILL.md
|
|
55
|
-
- lib/generators/rails_skills/skills_library/rails-views/SKILL.md
|
|
56
|
-
- lib/generators/rails_skills/skills_library/rspec-testing/SKILL.md
|
|
47
|
+
- lib/generators/rails_skills/skills_library/domains/.keep
|
|
48
|
+
- lib/generators/rails_skills/skills_library/stack/ruby/SKILL.md
|
|
49
|
+
- lib/generators/rails_skills/skills_library/workflows/commit/SKILL.md
|
|
57
50
|
- lib/rails_skills.rb
|
|
58
51
|
- lib/rails_skills/railtie.rb
|
|
59
52
|
- lib/rails_skills/version.rb
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# API Developer Agent
|
|
2
|
-
|
|
3
|
-
You are an API-focused Rails developer working on **<%= app_name %>** (Rails <%= rails_version %>).
|
|
4
|
-
|
|
5
|
-
## Skills
|
|
6
|
-
|
|
7
|
-
Load and follow all skills from the `skills/` directory, with emphasis on:
|
|
8
|
-
- Rails models
|
|
9
|
-
- API controllers
|
|
10
|
-
- RSpec testing
|
|
11
|
-
|
|
12
|
-
## Guidelines
|
|
13
|
-
|
|
14
|
-
- Build RESTful JSON APIs
|
|
15
|
-
- Use proper HTTP status codes
|
|
16
|
-
- Implement authentication and authorization
|
|
17
|
-
- Write comprehensive API tests
|
|
18
|
-
- Document endpoints clearly
|
|
19
|
-
- Follow JSON:API or similar conventions
|
|
20
|
-
|
|
21
|
-
## Workflow
|
|
22
|
-
|
|
23
|
-
1. Understand the API requirement
|
|
24
|
-
2. Design the endpoint (HTTP method, path, request/response format)
|
|
25
|
-
3. Start with the model and migrations
|
|
26
|
-
4. Build the API controller
|
|
27
|
-
5. Write request specs
|
|
28
|
-
6. Document the endpoint
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
# Fullstack Developer Agent
|
|
2
|
-
|
|
3
|
-
You are a fullstack Rails developer working on **<%= app_name %>** (Rails <%= rails_version %>).
|
|
4
|
-
|
|
5
|
-
## Skills
|
|
6
|
-
|
|
7
|
-
Load and follow all skills from the `skills/` directory, with emphasis on:
|
|
8
|
-
- Rails models, controllers, and views
|
|
9
|
-
- Hotwire (Turbo + Stimulus)
|
|
10
|
-
- RSpec testing
|
|
11
|
-
|
|
12
|
-
## Guidelines
|
|
13
|
-
|
|
14
|
-
- Follow Rails conventions and best practices
|
|
15
|
-
- Build reactive UIs with Turbo Frames and Turbo Streams
|
|
16
|
-
- Use Stimulus for JavaScript behavior
|
|
17
|
-
- Write comprehensive RSpec tests
|
|
18
|
-
- Use the project's existing patterns and style
|
|
19
|
-
|
|
20
|
-
## Workflow
|
|
21
|
-
|
|
22
|
-
1. Understand the requirement
|
|
23
|
-
2. Plan the full-stack implementation (model, controller, views)
|
|
24
|
-
3. Start with the model and migrations
|
|
25
|
-
4. Build the controller with proper actions
|
|
26
|
-
5. Create views with Hotwire integration
|
|
27
|
-
6. Write RSpec tests for all layers
|
|
28
|
-
7. Verify everything works end-to-end
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
# Security Rules
|
|
2
|
-
|
|
3
|
-
## Input Handling
|
|
4
|
-
|
|
5
|
-
- Always use strong parameters in controllers
|
|
6
|
-
- Never trust user input - validate and sanitize
|
|
7
|
-
- Use parameterized queries (ActiveRecord handles this by default)
|
|
8
|
-
- Never interpolate user input into SQL strings
|
|
9
|
-
|
|
10
|
-
## Authentication
|
|
11
|
-
|
|
12
|
-
- Use established libraries (Devise, etc.) for authentication
|
|
13
|
-
- Store passwords with bcrypt (`has_secure_password`)
|
|
14
|
-
- Use CSRF protection (enabled by default in Rails)
|
|
15
|
-
- Implement proper session management
|
|
16
|
-
|
|
17
|
-
## Authorization
|
|
18
|
-
|
|
19
|
-
- Check permissions in every controller action
|
|
20
|
-
- Use authorization libraries (Pundit, CanCanCan) for complex rules
|
|
21
|
-
- Never rely on hiding UI elements as a security measure
|
|
22
|
-
|
|
23
|
-
## Output
|
|
24
|
-
|
|
25
|
-
- ERB auto-escapes output by default - don't bypass with `raw` or `html_safe` unless necessary
|
|
26
|
-
- Sanitize HTML content with `sanitize` helper
|
|
27
|
-
- Set proper Content Security Policy headers
|
|
28
|
-
|
|
29
|
-
## Secrets
|
|
30
|
-
|
|
31
|
-
- Never commit secrets, API keys, or credentials to git
|
|
32
|
-
- Use Rails credentials (`rails credentials:edit`) or environment variables
|
|
33
|
-
- Add sensitive files to `.gitignore`
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: rails-api-controllers
|
|
3
|
-
description: API-only controllers, serialization, authentication, versioning
|
|
4
|
-
version: 1.0.0
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Rails API Controllers
|
|
8
|
-
|
|
9
|
-
## Quick Reference
|
|
10
|
-
|
|
11
|
-
| Pattern | Example |
|
|
12
|
-
|---------|---------|
|
|
13
|
-
| **Base class** | `class Api::V1::PostsController < ActionController::API` |
|
|
14
|
-
| **Render JSON** | `render json: @post, status: :ok` |
|
|
15
|
-
| **Error response** | `render json: { error: "Not found" }, status: :not_found` |
|
|
16
|
-
| **Pagination** | `@posts = Post.page(params[:page]).per(25)` |
|
|
17
|
-
|
|
18
|
-
## API Controller Structure
|
|
19
|
-
|
|
20
|
-
```ruby
|
|
21
|
-
module Api
|
|
22
|
-
module V1
|
|
23
|
-
class PostsController < ApplicationController
|
|
24
|
-
def index
|
|
25
|
-
posts = Post.order(created_at: :desc).page(params[:page])
|
|
26
|
-
render json: posts
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def show
|
|
30
|
-
post = Post.find(params[:id])
|
|
31
|
-
render json: post
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def create
|
|
35
|
-
post = Post.new(post_params)
|
|
36
|
-
|
|
37
|
-
if post.save
|
|
38
|
-
render json: post, status: :created
|
|
39
|
-
else
|
|
40
|
-
render json: { errors: post.errors.full_messages }, status: :unprocessable_entity
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def update
|
|
45
|
-
post = Post.find(params[:id])
|
|
46
|
-
|
|
47
|
-
if post.update(post_params)
|
|
48
|
-
render json: post
|
|
49
|
-
else
|
|
50
|
-
render json: { errors: post.errors.full_messages }, status: :unprocessable_entity
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def destroy
|
|
55
|
-
post = Post.find(params[:id])
|
|
56
|
-
post.destroy
|
|
57
|
-
head :no_content
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
private
|
|
61
|
-
|
|
62
|
-
def post_params
|
|
63
|
-
params.require(:post).permit(:title, :body, :published)
|
|
64
|
-
end
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
```
|
|
69
|
-
|
|
70
|
-
## API Routing
|
|
71
|
-
|
|
72
|
-
```ruby
|
|
73
|
-
Rails.application.routes.draw do
|
|
74
|
-
namespace :api do
|
|
75
|
-
namespace :v1 do
|
|
76
|
-
resources :posts
|
|
77
|
-
resources :users, only: [:index, :show]
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Error Handling
|
|
84
|
-
|
|
85
|
-
```ruby
|
|
86
|
-
module Api
|
|
87
|
-
class ApplicationController < ActionController::API
|
|
88
|
-
rescue_from ActiveRecord::RecordNotFound do |e|
|
|
89
|
-
render json: { error: e.message }, status: :not_found
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
rescue_from ActionController::ParameterMissing do |e|
|
|
93
|
-
render json: { error: e.message }, status: :bad_request
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Best Practices
|
|
100
|
-
|
|
101
|
-
1. Use API-only base class (`ActionController::API`)
|
|
102
|
-
2. Version your API with namespaces
|
|
103
|
-
3. Return consistent error formats
|
|
104
|
-
4. Use proper HTTP status codes
|
|
105
|
-
5. Paginate list endpoints
|
|
106
|
-
6. Use serializers for complex JSON responses
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: rails-controllers
|
|
3
|
-
description: Controller actions, routing, REST conventions, filters, strong parameters
|
|
4
|
-
version: 1.0.0
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Rails Controllers
|
|
8
|
-
|
|
9
|
-
## Quick Reference
|
|
10
|
-
|
|
11
|
-
| Pattern | Example |
|
|
12
|
-
|---------|---------|
|
|
13
|
-
| **Generate** | `rails g controller Posts index show` |
|
|
14
|
-
| **Route** | `resources :posts` |
|
|
15
|
-
| **Filter** | `before_action :set_post, only: [:show, :edit, :update, :destroy]` |
|
|
16
|
-
| **Strong params** | `params.require(:post).permit(:title, :body)` |
|
|
17
|
-
| **Redirect** | `redirect_to @post, notice: "Created!"` |
|
|
18
|
-
| **Render** | `render :new, status: :unprocessable_entity` |
|
|
19
|
-
|
|
20
|
-
## Controller Structure
|
|
21
|
-
|
|
22
|
-
```ruby
|
|
23
|
-
class PostsController < ApplicationController
|
|
24
|
-
before_action :set_post, only: [:show, :edit, :update, :destroy]
|
|
25
|
-
|
|
26
|
-
def index
|
|
27
|
-
@posts = Post.all.order(created_at: :desc)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def show; end
|
|
31
|
-
|
|
32
|
-
def new
|
|
33
|
-
@post = Post.new
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def create
|
|
37
|
-
@post = Post.new(post_params)
|
|
38
|
-
|
|
39
|
-
if @post.save
|
|
40
|
-
redirect_to @post, notice: "Post created."
|
|
41
|
-
else
|
|
42
|
-
render :new, status: :unprocessable_entity
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def edit; end
|
|
47
|
-
|
|
48
|
-
def update
|
|
49
|
-
if @post.update(post_params)
|
|
50
|
-
redirect_to @post, notice: "Post updated."
|
|
51
|
-
else
|
|
52
|
-
render :edit, status: :unprocessable_entity
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
def destroy
|
|
57
|
-
@post.destroy
|
|
58
|
-
redirect_to posts_path, notice: "Post deleted."
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
private
|
|
62
|
-
|
|
63
|
-
def set_post
|
|
64
|
-
@post = Post.find(params[:id])
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
def post_params
|
|
68
|
-
params.require(:post).permit(:title, :body, :published)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Routing
|
|
74
|
-
|
|
75
|
-
```ruby
|
|
76
|
-
Rails.application.routes.draw do
|
|
77
|
-
resources :posts
|
|
78
|
-
resources :posts, only: [:index, :show]
|
|
79
|
-
|
|
80
|
-
resources :authors do
|
|
81
|
-
resources :posts, shallow: true
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
resources :posts do
|
|
85
|
-
member { post :publish }
|
|
86
|
-
collection { get :archived }
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
namespace :admin do
|
|
90
|
-
resources :users
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Response Formats
|
|
96
|
-
|
|
97
|
-
```ruby
|
|
98
|
-
respond_to do |format|
|
|
99
|
-
format.html { redirect_to @post }
|
|
100
|
-
format.json { render json: @post, status: :created }
|
|
101
|
-
format.turbo_stream
|
|
102
|
-
end
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Error Handling
|
|
106
|
-
|
|
107
|
-
```ruby
|
|
108
|
-
class ApplicationController < ActionController::Base
|
|
109
|
-
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
|
110
|
-
|
|
111
|
-
private
|
|
112
|
-
|
|
113
|
-
def not_found
|
|
114
|
-
render file: Rails.root.join("public/404.html"), status: :not_found
|
|
115
|
-
end
|
|
116
|
-
end
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
## Best Practices
|
|
120
|
-
|
|
121
|
-
1. Keep controllers thin - move logic to models or service objects
|
|
122
|
-
2. Always use strong parameters
|
|
123
|
-
3. Use `before_action` for shared setup
|
|
124
|
-
4. Return proper HTTP status codes
|
|
125
|
-
5. Follow RESTful conventions
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: rails-hotwire
|
|
3
|
-
description: Turbo Drive, Turbo Frames, Turbo Streams, and Stimulus
|
|
4
|
-
version: 1.0.0
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Rails Hotwire
|
|
8
|
-
|
|
9
|
-
## Quick Reference
|
|
10
|
-
|
|
11
|
-
| Pattern | Example |
|
|
12
|
-
|---------|---------|
|
|
13
|
-
| **Turbo Frame** | `<%= turbo_frame_tag "name" do %>` |
|
|
14
|
-
| **Turbo Stream** | `<%= turbo_stream.append "list" do %>` |
|
|
15
|
-
| **Stimulus controller** | `data-controller="toggle"` |
|
|
16
|
-
| **Stimulus action** | `data-action="click->toggle#switch"` |
|
|
17
|
-
| **Stimulus target** | `data-toggle-target="content"` |
|
|
18
|
-
|
|
19
|
-
## Turbo Frames
|
|
20
|
-
|
|
21
|
-
```erb
|
|
22
|
-
<%# Wrap content in a frame %>
|
|
23
|
-
<%= turbo_frame_tag "post_#{@post.id}" do %>
|
|
24
|
-
<h2><%= @post.title %></h2>
|
|
25
|
-
<%= link_to "Edit", edit_post_path(@post) %>
|
|
26
|
-
<% end %>
|
|
27
|
-
|
|
28
|
-
<%# Lazy-loaded frame %>
|
|
29
|
-
<%= turbo_frame_tag "comments", src: post_comments_path(@post), loading: :lazy do %>
|
|
30
|
-
<p>Loading comments...</p>
|
|
31
|
-
<% end %>
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
## Turbo Streams
|
|
35
|
-
|
|
36
|
-
```erb
|
|
37
|
-
<%# app/views/posts/create.turbo_stream.erb %>
|
|
38
|
-
<%= turbo_stream.prepend "posts" do %>
|
|
39
|
-
<%= render @post %>
|
|
40
|
-
<% end %>
|
|
41
|
-
|
|
42
|
-
<%= turbo_stream.update "flash" do %>
|
|
43
|
-
<div class="notice">Post created!</div>
|
|
44
|
-
<% end %>
|
|
45
|
-
|
|
46
|
-
<%# Actions: append, prepend, replace, update, remove, before, after %>
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
### Broadcasts from Model
|
|
50
|
-
|
|
51
|
-
```ruby
|
|
52
|
-
class Post < ApplicationRecord
|
|
53
|
-
after_create_commit { broadcast_prepend_to "posts" }
|
|
54
|
-
after_update_commit { broadcast_replace_to "posts" }
|
|
55
|
-
after_destroy_commit { broadcast_remove_to "posts" }
|
|
56
|
-
end
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
## Stimulus
|
|
60
|
-
|
|
61
|
-
```javascript
|
|
62
|
-
// app/javascript/controllers/toggle_controller.js
|
|
63
|
-
import { Controller } from "@hotwired/stimulus"
|
|
64
|
-
|
|
65
|
-
export default class extends Controller {
|
|
66
|
-
static targets = ["content"]
|
|
67
|
-
|
|
68
|
-
toggle() {
|
|
69
|
-
this.contentTarget.classList.toggle("hidden")
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
```erb
|
|
75
|
-
<div data-controller="toggle">
|
|
76
|
-
<button data-action="click->toggle#toggle">Toggle</button>
|
|
77
|
-
<div data-toggle-target="content">
|
|
78
|
-
Content here
|
|
79
|
-
</div>
|
|
80
|
-
</div>
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
## Best Practices
|
|
84
|
-
|
|
85
|
-
1. Start with Turbo Drive (enabled by default)
|
|
86
|
-
2. Use Turbo Frames for in-page updates
|
|
87
|
-
3. Use Turbo Streams for multi-element updates
|
|
88
|
-
4. Use Stimulus only when HTML-over-the-wire isn't enough
|
|
89
|
-
5. Keep Stimulus controllers small and focused
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: rails-jobs
|
|
3
|
-
description: Active Job patterns, Sidekiq, background processing
|
|
4
|
-
version: 1.0.0
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Rails Background Jobs
|
|
8
|
-
|
|
9
|
-
## Quick Reference
|
|
10
|
-
|
|
11
|
-
| Pattern | Example |
|
|
12
|
-
|---------|---------|
|
|
13
|
-
| **Generate** | `rails g job ProcessOrder` |
|
|
14
|
-
| **Enqueue** | `ProcessOrderJob.perform_later(order)` |
|
|
15
|
-
| **Enqueue later** | `ProcessOrderJob.set(wait: 5.minutes).perform_later(order)` |
|
|
16
|
-
| **Enqueue at** | `ProcessOrderJob.set(wait_until: Date.tomorrow.noon).perform_later(order)` |
|
|
17
|
-
|
|
18
|
-
## Job Structure
|
|
19
|
-
|
|
20
|
-
```ruby
|
|
21
|
-
class ProcessOrderJob < ApplicationJob
|
|
22
|
-
queue_as :default
|
|
23
|
-
retry_on ActiveRecord::Deadlocked, wait: 5.seconds, attempts: 3
|
|
24
|
-
discard_on ActiveJob::DeserializationError
|
|
25
|
-
|
|
26
|
-
def perform(order)
|
|
27
|
-
order.process!
|
|
28
|
-
OrderMailer.confirmation(order).deliver_later
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
## Best Practices
|
|
34
|
-
|
|
35
|
-
1. Keep jobs idempotent
|
|
36
|
-
2. Pass IDs instead of full objects when possible
|
|
37
|
-
3. Use appropriate queues for different priorities
|
|
38
|
-
4. Handle retries and failures gracefully
|
|
39
|
-
5. Monitor queue depths in production
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: rails-models
|
|
3
|
-
description: ActiveRecord patterns, migrations, validations, callbacks, associations
|
|
4
|
-
version: 1.0.0
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Rails Models (ActiveRecord)
|
|
8
|
-
|
|
9
|
-
## Quick Reference
|
|
10
|
-
|
|
11
|
-
| Pattern | Example |
|
|
12
|
-
|---------|---------|
|
|
13
|
-
| **Generate model** | `rails g model User name:string email:string` |
|
|
14
|
-
| **Migration** | `rails g migration AddAgeToUsers age:integer` |
|
|
15
|
-
| **Validation** | `validates :email, presence: true, uniqueness: true` |
|
|
16
|
-
| **Association** | `has_many :posts, dependent: :destroy` |
|
|
17
|
-
| **Scope** | `scope :active, -> { where(active: true) }` |
|
|
18
|
-
|
|
19
|
-
## Model Structure
|
|
20
|
-
|
|
21
|
-
```ruby
|
|
22
|
-
class User < ApplicationRecord
|
|
23
|
-
# Constants
|
|
24
|
-
ROLES = %w[admin editor viewer].freeze
|
|
25
|
-
|
|
26
|
-
# Associations
|
|
27
|
-
has_many :posts, dependent: :destroy
|
|
28
|
-
belongs_to :organization, optional: true
|
|
29
|
-
|
|
30
|
-
# Validations
|
|
31
|
-
validates :email, presence: true, uniqueness: { case_sensitive: false }
|
|
32
|
-
validates :name, presence: true, length: { minimum: 2, maximum: 100 }
|
|
33
|
-
validates :role, inclusion: { in: ROLES }
|
|
34
|
-
|
|
35
|
-
# Callbacks
|
|
36
|
-
before_save :normalize_email
|
|
37
|
-
|
|
38
|
-
# Scopes
|
|
39
|
-
scope :active, -> { where(active: true) }
|
|
40
|
-
scope :recent, -> { order(created_at: :desc) }
|
|
41
|
-
|
|
42
|
-
# Enums
|
|
43
|
-
enum :status, { pending: 0, active: 1, archived: 2 }
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def normalize_email
|
|
48
|
-
self.email = email.downcase.strip
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Migrations
|
|
54
|
-
|
|
55
|
-
```ruby
|
|
56
|
-
class CreateUsers < ActiveRecord::Migration[7.0]
|
|
57
|
-
def change
|
|
58
|
-
create_table :users do |t|
|
|
59
|
-
t.string :name, null: false
|
|
60
|
-
t.string :email, null: false
|
|
61
|
-
t.boolean :active, default: true
|
|
62
|
-
t.references :organization, foreign_key: true
|
|
63
|
-
t.timestamps
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
add_index :users, :email, unique: true
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
## Associations
|
|
72
|
-
|
|
73
|
-
- `has_many :items, dependent: :destroy`
|
|
74
|
-
- `belongs_to :parent, optional: true`
|
|
75
|
-
- `has_many :tags, through: :taggings`
|
|
76
|
-
- `has_one :profile, dependent: :destroy`
|
|
77
|
-
- `has_many :comments, as: :commentable` (polymorphic)
|
|
78
|
-
|
|
79
|
-
## Validations
|
|
80
|
-
|
|
81
|
-
- `validates :field, presence: true`
|
|
82
|
-
- `validates :email, uniqueness: { case_sensitive: false }`
|
|
83
|
-
- `validates :age, numericality: { greater_than: 0 }`
|
|
84
|
-
- `validates :field, length: { minimum: 2, maximum: 100 }`
|
|
85
|
-
- `validates :field, format: { with: /\Apattern\z/ }`
|
|
86
|
-
- `validate :custom_validation_method`
|
|
87
|
-
|
|
88
|
-
## Queries
|
|
89
|
-
|
|
90
|
-
```ruby
|
|
91
|
-
User.where(active: true)
|
|
92
|
-
User.where.not(role: "guest")
|
|
93
|
-
User.joins(:posts).where(posts: { published: true })
|
|
94
|
-
User.includes(:posts).order(created_at: :desc)
|
|
95
|
-
User.pluck(:id, :name)
|
|
96
|
-
User.group(:role).count
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
## Best Practices
|
|
100
|
-
|
|
101
|
-
1. Add database-level constraints (NOT NULL, unique indexes, foreign keys)
|
|
102
|
-
2. Use `includes` to avoid N+1 queries
|
|
103
|
-
3. Keep callbacks simple, use service objects for complex logic
|
|
104
|
-
4. Use scopes for reusable query fragments
|
|
105
|
-
5. Use concerns to share behavior across models
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: rails-views
|
|
3
|
-
description: ERB templates, layouts, partials, forms, and view helpers
|
|
4
|
-
version: 1.0.0
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# Rails Views
|
|
8
|
-
|
|
9
|
-
## Quick Reference
|
|
10
|
-
|
|
11
|
-
| Pattern | Example |
|
|
12
|
-
|---------|---------|
|
|
13
|
-
| **Output** | `<%= @post.title %>` |
|
|
14
|
-
| **Logic** | `<% if condition %>` |
|
|
15
|
-
| **Partial** | `<%= render "shared/header" %>` |
|
|
16
|
-
| **Collection** | `<%= render @posts %>` |
|
|
17
|
-
| **Form** | `<%= form_with model: @post do \|f\| %>` |
|
|
18
|
-
| **Link** | `<%= link_to "Home", root_path %>` |
|
|
19
|
-
|
|
20
|
-
## Layouts
|
|
21
|
-
|
|
22
|
-
```erb
|
|
23
|
-
<!DOCTYPE html>
|
|
24
|
-
<html>
|
|
25
|
-
<head>
|
|
26
|
-
<title><%= content_for?(:title) ? yield(:title) : "App" %></title>
|
|
27
|
-
<%= csrf_meta_tags %>
|
|
28
|
-
<%= stylesheet_link_tag "application" %>
|
|
29
|
-
<%= javascript_importmap_tags %>
|
|
30
|
-
</head>
|
|
31
|
-
<body>
|
|
32
|
-
<% flash.each do |type, message| %>
|
|
33
|
-
<div class="flash flash-<%= type %>"><%= message %></div>
|
|
34
|
-
<% end %>
|
|
35
|
-
<%= yield %>
|
|
36
|
-
</body>
|
|
37
|
-
</html>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Partials
|
|
41
|
-
|
|
42
|
-
```erb
|
|
43
|
-
<%# Render a partial %>
|
|
44
|
-
<%= render "post", post: @post %>
|
|
45
|
-
|
|
46
|
-
<%# Render a collection %>
|
|
47
|
-
<%= render partial: "post", collection: @posts %>
|
|
48
|
-
|
|
49
|
-
<%# Shorthand for collection %>
|
|
50
|
-
<%= render @posts %>
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Forms
|
|
54
|
-
|
|
55
|
-
```erb
|
|
56
|
-
<%= form_with model: @post do |f| %>
|
|
57
|
-
<div>
|
|
58
|
-
<%= f.label :title %>
|
|
59
|
-
<%= f.text_field :title %>
|
|
60
|
-
</div>
|
|
61
|
-
|
|
62
|
-
<div>
|
|
63
|
-
<%= f.label :body %>
|
|
64
|
-
<%= f.text_area :body %>
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
<div>
|
|
68
|
-
<%= f.label :category_id %>
|
|
69
|
-
<%= f.collection_select :category_id, Category.all, :id, :name, prompt: "Select" %>
|
|
70
|
-
</div>
|
|
71
|
-
|
|
72
|
-
<%= f.submit %>
|
|
73
|
-
<% end %>
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
## Helpers
|
|
77
|
-
|
|
78
|
-
```ruby
|
|
79
|
-
module ApplicationHelper
|
|
80
|
-
def page_title(title)
|
|
81
|
-
content_for(:title) { title }
|
|
82
|
-
content_tag(:h1, title)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def active_class(path)
|
|
86
|
-
current_page?(path) ? "active" : ""
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## Turbo Frames
|
|
92
|
-
|
|
93
|
-
```erb
|
|
94
|
-
<%= turbo_frame_tag "post_#{@post.id}" do %>
|
|
95
|
-
<%= render @post %>
|
|
96
|
-
<% end %>
|
|
97
|
-
|
|
98
|
-
<%= turbo_frame_tag "post", src: post_path(@post), loading: :lazy do %>
|
|
99
|
-
Loading...
|
|
100
|
-
<% end %>
|
|
101
|
-
```
|
|
102
|
-
|
|
103
|
-
## Best Practices
|
|
104
|
-
|
|
105
|
-
1. Keep logic out of views - use helpers or presenters
|
|
106
|
-
2. Use partials for reusable components
|
|
107
|
-
3. Always escape user input (ERB does this by default)
|
|
108
|
-
4. Use `content_for` for flexible layouts
|
|
109
|
-
5. Use Turbo Frames for partial page updates
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: rspec-testing
|
|
3
|
-
description: RSpec testing patterns for models, controllers, requests, and system tests
|
|
4
|
-
version: 1.0.0
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
# RSpec Testing
|
|
8
|
-
|
|
9
|
-
## Quick Reference
|
|
10
|
-
|
|
11
|
-
| Pattern | Example |
|
|
12
|
-
|---------|---------|
|
|
13
|
-
| **Run all** | `bundle exec rspec` |
|
|
14
|
-
| **Run file** | `bundle exec rspec spec/models/user_spec.rb` |
|
|
15
|
-
| **Run line** | `bundle exec rspec spec/models/user_spec.rb:15` |
|
|
16
|
-
| **Run tag** | `bundle exec rspec --tag focus` |
|
|
17
|
-
|
|
18
|
-
## Model Specs
|
|
19
|
-
|
|
20
|
-
```ruby
|
|
21
|
-
RSpec.describe User, type: :model do
|
|
22
|
-
describe "validations" do
|
|
23
|
-
it { is_expected.to validate_presence_of(:email) }
|
|
24
|
-
it { is_expected.to validate_uniqueness_of(:email).case_insensitive }
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
describe "associations" do
|
|
28
|
-
it { is_expected.to have_many(:posts).dependent(:destroy) }
|
|
29
|
-
it { is_expected.to belong_to(:organization).optional }
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
describe "#full_name" do
|
|
33
|
-
it "returns first and last name" do
|
|
34
|
-
user = build(:user, first_name: "Jane", last_name: "Doe")
|
|
35
|
-
expect(user.full_name).to eq("Jane Doe")
|
|
36
|
-
end
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## Request Specs
|
|
42
|
-
|
|
43
|
-
```ruby
|
|
44
|
-
RSpec.describe "Posts", type: :request do
|
|
45
|
-
describe "GET /posts" do
|
|
46
|
-
it "returns a successful response" do
|
|
47
|
-
get posts_path
|
|
48
|
-
expect(response).to have_http_status(:ok)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
describe "POST /posts" do
|
|
53
|
-
let(:valid_params) { { post: { title: "Test", body: "Content" } } }
|
|
54
|
-
|
|
55
|
-
it "creates a post" do
|
|
56
|
-
expect {
|
|
57
|
-
post posts_path, params: valid_params
|
|
58
|
-
}.to change(Post, :count).by(1)
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
## System Specs
|
|
65
|
-
|
|
66
|
-
```ruby
|
|
67
|
-
RSpec.describe "Managing posts", type: :system do
|
|
68
|
-
before { driven_by(:rack_test) }
|
|
69
|
-
|
|
70
|
-
it "allows creating a new post" do
|
|
71
|
-
visit new_post_path
|
|
72
|
-
fill_in "Title", with: "My Post"
|
|
73
|
-
fill_in "Body", with: "Content"
|
|
74
|
-
click_on "Create Post"
|
|
75
|
-
|
|
76
|
-
expect(page).to have_content("Post created")
|
|
77
|
-
expect(page).to have_content("My Post")
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
```
|
|
81
|
-
|
|
82
|
-
## Factories (FactoryBot)
|
|
83
|
-
|
|
84
|
-
```ruby
|
|
85
|
-
FactoryBot.define do
|
|
86
|
-
factory :user do
|
|
87
|
-
name { "John Doe" }
|
|
88
|
-
email { Faker::Internet.email }
|
|
89
|
-
role { "viewer" }
|
|
90
|
-
|
|
91
|
-
trait :admin do
|
|
92
|
-
role { "admin" }
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
end
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
## Best Practices
|
|
99
|
-
|
|
100
|
-
1. Test behavior, not implementation
|
|
101
|
-
2. Use `let` and `before` for setup, keep `it` blocks focused
|
|
102
|
-
3. Use factories instead of fixtures
|
|
103
|
-
4. Write request specs over controller specs
|
|
104
|
-
5. Use `have_http_status` for response assertions
|
|
105
|
-
6. Keep specs fast - minimize database interactions
|