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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 178573c6ee15fecc50390d962b0497fe63f76caa23a763105bb02cf2bd035742
4
- data.tar.gz: 38fe007e723b016ea6ba499681baa7e76b2cdadbe59d8221697e60cd3719c31b
3
+ metadata.gz: 03c6340c54476e1461bcd09b5555135c7394feb027482313bfa70cc9b03c040c
4
+ data.tar.gz: dc0fed169889191177353cc7732dbb0a1585515a8c95bcbaca089f26c3fc4272
5
5
  SHA512:
6
- metadata.gz: 58f7d7ea5fd40d5467f11182985ccecd43395f0a2e31aad41894d9a955088ef8c577e93568be38667a2a08e7f66f59bfea57995ad7fcb6a6993f90d7e3e46ad6
7
- data.tar.gz: 1303af0ddab82bb8c0eae537997ca52b5ecf945bc1df025f23da37bf027d774826591249a0c7164bd61cdbe3ba088d23a454c17e7ce9036f430bd84ce1d9a97a
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 with default (basic) preset
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
- │ ├── rails-models/
43
- │ │ └── SKILL.md
44
- │ ├── rails-controllers/
45
- │ │ └── SKILL.md
46
- │ └── rails-views/
47
- └── SKILL.md
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
- ### Presets
54
+ ### Skill Categories
61
55
 
62
- | Preset | Skills included |
63
- |--------|----------------|
64
- | **basic** | rails-models, rails-controllers, rails-views |
65
- | **fullstack** | basic + rails-hotwire, rspec-testing |
66
- | **api** | rails-models, rails-api-controllers, rspec-testing |
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 my_custom_skill
72
- rails generate rails_skills:skill my_custom_skill --description="My skill description"
73
- rails generate rails_skills:skill my_custom_skill --with-references
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
- ## Skill Library
72
+ ## Default Skills
79
73
 
80
74
  The gem ships with these pre-built skills:
81
75
 
82
- - **rails-models** - ActiveRecord patterns, migrations, validations, associations
83
- - **rails-controllers** - Controller actions, routing, REST conventions, filters
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 install_preset_skills
40
- case options[:preset]
41
- when "fullstack"
42
- install_fullstack_preset
43
- when "api"
44
- install_api_preset
45
- else
46
- install_basic_preset
47
- end
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/ <- shared AI skill files (canonical location)"
56
- say " .claude/skills -> ../skills (symlink)"
57
- say " .codex/skills -> ../skills (symlink)"
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 now share the same skills from skills/", :blue
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 my_skill # Create a custom 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 install_basic_preset
79
- say "Installing basic preset...", :green
80
- install_skill("rails-models")
81
- install_skill("rails-controllers")
82
- install_skill("rails-views")
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(skill_name)
116
- skill_dir = "skills/#{skill_name}"
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/#{skill_name}", __dir__)
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(skill_name)
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 '#{file_name}' created in #{skill_path}/", :green
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 #{file_name.tr('-', ' ')}"
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: <%= file_name %>
2
+ name: <%= skill_name %>
3
3
  description: <%= skill_description %>
4
4
  version: 1.0.0
5
5
  ---
6
6
 
7
- # <%= file_name.tr('-', ' ').split.map(&:capitalize).join(' ') %>
7
+ # <%= skill_name.tr('-', ' ').split.map(&:capitalize).join(' ') %>
8
8
 
9
9
  ## Quick Reference
10
10
 
@@ -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
+ ```
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsSkills
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.1"
5
5
  end
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.0
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-28 00:00:00.000000000 Z
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/rails-api-controllers/SKILL.md
51
- - lib/generators/rails_skills/skills_library/rails-controllers/SKILL.md
52
- - lib/generators/rails_skills/skills_library/rails-hotwire/SKILL.md
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