rails_claude_skills 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. checksums.yaml +7 -0
  2. data/.github/ISSUE_TEMPLATE/bug_report.yml +134 -0
  3. data/.github/ISSUE_TEMPLATE/config.yml +11 -0
  4. data/.github/ISSUE_TEMPLATE/feature_request.yml +129 -0
  5. data/.github/ISSUE_TEMPLATE/question.yml +90 -0
  6. data/.github/dependabot.yml +19 -0
  7. data/.github/workflows/ci.yml +77 -0
  8. data/.github/workflows/release.yml +66 -0
  9. data/.rubocop.yml +52 -0
  10. data/CHANGELOG.md +94 -0
  11. data/CLAUDE.md +332 -0
  12. data/CODE_OF_CONDUCT.md +134 -0
  13. data/CONTRIBUTING.md +580 -0
  14. data/LICENSE.txt +21 -0
  15. data/README.md +544 -0
  16. data/Rakefile +8 -0
  17. data/lib/generators/claude/agent/agent_generator.rb +71 -0
  18. data/lib/generators/claude/agent/templates/agent.md.tt +62 -0
  19. data/lib/generators/claude/command/command_generator.rb +50 -0
  20. data/lib/generators/claude/command/templates/command.md.tt +28 -0
  21. data/lib/generators/claude/commands_library/create-pr.md +27 -0
  22. data/lib/generators/claude/commands_library/dbchange.md +19 -0
  23. data/lib/generators/claude/commands_library/quality.md +20 -0
  24. data/lib/generators/claude/commands_library/stimulus.md +19 -0
  25. data/lib/generators/claude/commands_library/turbo-feature.md +17 -0
  26. data/lib/generators/claude/install/install_generator.rb +211 -0
  27. data/lib/generators/claude/install/templates/README.md.tt +59 -0
  28. data/lib/generators/claude/install/templates/USAGE +28 -0
  29. data/lib/generators/claude/install/templates/agents/api-dev.md.tt +46 -0
  30. data/lib/generators/claude/install/templates/agents/fullstack-dev.md.tt +48 -0
  31. data/lib/generators/claude/install/templates/agents/rails-developer.md.tt +40 -0
  32. data/lib/generators/claude/install/templates/settings.local.json.tt +13 -0
  33. data/lib/generators/claude/rule/rule_generator.rb +175 -0
  34. data/lib/generators/claude/rule/templates/rule.md.tt +7 -0
  35. data/lib/generators/claude/rules_library/code-style.md +37 -0
  36. data/lib/generators/claude/rules_library/database.md +47 -0
  37. data/lib/generators/claude/rules_library/hotwire.md +56 -0
  38. data/lib/generators/claude/rules_library/security.md +54 -0
  39. data/lib/generators/claude/rules_library/testing.md +47 -0
  40. data/lib/generators/claude/skill/skill_generator.rb +196 -0
  41. data/lib/generators/claude/skill/templates/SKILL.md.tt +27 -0
  42. data/lib/generators/claude/skills_library/create-task-files/SKILL.md +311 -0
  43. data/lib/generators/claude/skills_library/create-task-files/templates/bug.md +60 -0
  44. data/lib/generators/claude/skills_library/create-task-files/templates/epic.md +47 -0
  45. data/lib/generators/claude/skills_library/create-task-files/templates/issue.md +45 -0
  46. data/lib/generators/claude/skills_library/create-task-files/templates/user-story.md +57 -0
  47. data/lib/generators/claude/skills_library/minitest-testing/SKILL.md +398 -0
  48. data/lib/generators/claude/skills_library/minitest-testing/references/examples.md +889 -0
  49. data/lib/generators/claude/skills_library/plan-feature/SKILL.md +253 -0
  50. data/lib/generators/claude/skills_library/rails-api-controllers/SKILL.md +1041 -0
  51. data/lib/generators/claude/skills_library/rails-api-controllers/references/api-documentation.md +422 -0
  52. data/lib/generators/claude/skills_library/rails-api-controllers/references/serialization.md +456 -0
  53. data/lib/generators/claude/skills_library/rails-auth-with-devise/SKILL.md +191 -0
  54. data/lib/generators/claude/skills_library/rails-auth-with-devise/references/advanced.md +331 -0
  55. data/lib/generators/claude/skills_library/rails-auth-with-devise/references/api-auth.md +266 -0
  56. data/lib/generators/claude/skills_library/rails-auth-with-devise/references/omniauth.md +194 -0
  57. data/lib/generators/claude/skills_library/rails-authorization-cancancan/SKILL.md +603 -0
  58. data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/api-authorization.md +543 -0
  59. data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/complex-permissions.md +572 -0
  60. data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/multi-tenancy.md +373 -0
  61. data/lib/generators/claude/skills_library/rails-controllers/SKILL.md +514 -0
  62. data/lib/generators/claude/skills_library/rails-debugging/SKILL.md +260 -0
  63. data/lib/generators/claude/skills_library/rails-deployment/SKILL.md +437 -0
  64. data/lib/generators/claude/skills_library/rails-deployment/references/examples.md +901 -0
  65. data/lib/generators/claude/skills_library/rails-hotwire/SKILL.md +367 -0
  66. data/lib/generators/claude/skills_library/rails-jobs/MISSION_CONTROL_SETUP.md +639 -0
  67. data/lib/generators/claude/skills_library/rails-jobs/SKILL.md +704 -0
  68. data/lib/generators/claude/skills_library/rails-mailers/SKILL.md +549 -0
  69. data/lib/generators/claude/skills_library/rails-models/SKILL.md +379 -0
  70. data/lib/generators/claude/skills_library/rails-pagination-kaminari/SKILL.md +622 -0
  71. data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/api-pagination.md +523 -0
  72. data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/custom-themes.md +498 -0
  73. data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/performance.md +478 -0
  74. data/lib/generators/claude/skills_library/rails-views/SKILL.md +508 -0
  75. data/lib/generators/claude/skills_library/refine-requirements/SKILL.md +226 -0
  76. data/lib/generators/claude/skills_library/refine-requirements/references/examples.md +344 -0
  77. data/lib/generators/claude/skills_library/refine-requirements/references/reference.md +298 -0
  78. data/lib/generators/claude/skills_library/rspec-testing/SKILL.md +572 -0
  79. data/lib/generators/claude/skills_library/rspec-testing/references/better_specs_guide.md +273 -0
  80. data/lib/generators/claude/skills_library/rspec-testing/references/thoughtbot_patterns.md +407 -0
  81. data/lib/generators/claude/skills_library/tailwindcss/SKILL.md +371 -0
  82. data/lib/generators/claude/views/views_generator.rb +113 -0
  83. data/lib/rails_claude_skills/railtie.rb +16 -0
  84. data/lib/rails_claude_skills/version.rb +5 -0
  85. data/lib/rails_claude_skills.rb +27 -0
  86. data/sig/rails_claude_skills.rbs +4 -0
  87. metadata +199 -0
@@ -0,0 +1,543 @@
1
+ # API Authorization with CanCanCan
2
+
3
+ Implementing authorization for JSON APIs with CanCanCan, including token-based auth and proper error handling.
4
+
5
+ ## Basic API Setup
6
+
7
+ ### API Base Controller
8
+
9
+ ```ruby
10
+ # app/controllers/api/v1/base_controller.rb
11
+ module Api
12
+ module V1
13
+ class BaseController < ActionController::API
14
+ before_action :authenticate_api_user!
15
+
16
+ rescue_from CanCan::AccessDenied do |exception|
17
+ render json: {
18
+ error: 'Forbidden',
19
+ message: exception.message
20
+ }, status: :forbidden
21
+ end
22
+
23
+ private
24
+
25
+ def authenticate_api_user!
26
+ token = request.headers['Authorization']&.split(' ')&.last
27
+ @current_user = User.find_by(api_token: token)
28
+
29
+ render json: { error: 'Unauthorized' }, status: :unauthorized unless @current_user
30
+ end
31
+
32
+ def current_user
33
+ @current_user
34
+ end
35
+
36
+ def current_ability
37
+ @current_ability ||= Ability.new(current_user)
38
+ end
39
+ end
40
+ end
41
+ end
42
+ ```
43
+
44
+ ### API Resource Controller
45
+
46
+ ```ruby
47
+ # app/controllers/api/v1/posts_controller.rb
48
+ module Api
49
+ module V1
50
+ class PostsController < BaseController
51
+ load_and_authorize_resource
52
+
53
+ def index
54
+ render json: @posts
55
+ end
56
+
57
+ def show
58
+ render json: @post
59
+ end
60
+
61
+ def create
62
+ if @post.save
63
+ render json: @post, status: :created
64
+ else
65
+ render json: { errors: @post.errors }, status: :unprocessable_entity
66
+ end
67
+ end
68
+
69
+ def update
70
+ if @post.update(post_params)
71
+ render json: @post
72
+ else
73
+ render json: { errors: @post.errors }, status: :unprocessable_entity
74
+ end
75
+ end
76
+
77
+ def destroy
78
+ @post.destroy
79
+ head :no_content
80
+ end
81
+
82
+ private
83
+
84
+ def post_params
85
+ params.require(:post).permit(:title, :body, :published)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ ```
91
+
92
+ ## JWT Authentication
93
+
94
+ ### Setup with devise-jwt
95
+
96
+ ```ruby
97
+ # Gemfile
98
+ gem 'devise'
99
+ gem 'devise-jwt'
100
+
101
+ # app/models/user.rb
102
+ class User < ApplicationRecord
103
+ devise :database_authenticatable,
104
+ :jwt_authenticatable,
105
+ jwt_revocation_strategy: JwtDenylist
106
+ end
107
+
108
+ # app/models/jwt_denylist.rb
109
+ class JwtDenylist < ApplicationRecord
110
+ include Devise::JWT::RevocationStrategies::Denylist
111
+
112
+ self.table_name = 'jwt_denylist'
113
+ end
114
+ ```
115
+
116
+ ### API Controller with JWT
117
+
118
+ ```ruby
119
+ # app/controllers/api/v1/base_controller.rb
120
+ module Api
121
+ module V1
122
+ class BaseController < ActionController::API
123
+ before_action :authenticate_user!
124
+
125
+ rescue_from CanCan::AccessDenied do |exception|
126
+ render json: {
127
+ error: 'Forbidden',
128
+ message: exception.message,
129
+ action: exception.action,
130
+ subject: exception.subject.class.name
131
+ }, status: :forbidden
132
+ end
133
+
134
+ def current_ability
135
+ @current_ability ||= Ability.new(current_user)
136
+ end
137
+ end
138
+ end
139
+ end
140
+ ```
141
+
142
+ ## Token-Based Authorization
143
+
144
+ ### Simple Token Authentication
145
+
146
+ ```ruby
147
+ # app/models/user.rb
148
+ class User < ApplicationRecord
149
+ has_secure_token :api_token
150
+
151
+ def regenerate_api_token
152
+ regenerate_api_token!
153
+ end
154
+ end
155
+
156
+ # Migration
157
+ class AddApiTokenToUsers < ActiveRecord::Migration[7.0]
158
+ def change
159
+ add_column :users, :api_token, :string
160
+ add_index :users, :api_token, unique: true
161
+ end
162
+ end
163
+ ```
164
+
165
+ ### Token Authentication Controller
166
+
167
+ ```ruby
168
+ # app/controllers/api/v1/authentication_controller.rb
169
+ module Api
170
+ module V1
171
+ class AuthenticationController < ActionController::API
172
+ def create
173
+ user = User.find_by(email: params[:email])
174
+
175
+ if user&.authenticate(params[:password])
176
+ render json: {
177
+ token: user.api_token,
178
+ user: {
179
+ id: user.id,
180
+ email: user.email,
181
+ role: user.role
182
+ }
183
+ }
184
+ else
185
+ render json: { error: 'Invalid credentials' }, status: :unauthorized
186
+ end
187
+ end
188
+
189
+ def destroy
190
+ current_user.regenerate_api_token
191
+ head :no_content
192
+ end
193
+ end
194
+ end
195
+ end
196
+ ```
197
+
198
+ ## Granular API Permissions
199
+
200
+ ### API-Specific Abilities
201
+
202
+ ```ruby
203
+ # app/models/ability.rb
204
+ class Ability
205
+ include CanCan::Ability
206
+
207
+ def initialize(user, options = {})
208
+ return unless user.present?
209
+
210
+ # Different permissions for API vs web
211
+ if options[:api]
212
+ define_api_abilities(user)
213
+ else
214
+ define_web_abilities(user)
215
+ end
216
+ end
217
+
218
+ private
219
+
220
+ def define_api_abilities(user)
221
+ # API users have more restricted access
222
+ can :read, Post, published: true
223
+ can :read, Post, user_id: user.id
224
+
225
+ if user.api_access_level == 'full'
226
+ can :create, Post
227
+ can :update, Post, user_id: user.id
228
+ can :destroy, Post, user_id: user.id
229
+ end
230
+
231
+ # Admins have full API access
232
+ can :manage, :all if user.admin?
233
+ end
234
+
235
+ def define_web_abilities(user)
236
+ # Web users have standard access
237
+ can :read, Post
238
+ can :create, Post
239
+ can :update, Post, user_id: user.id
240
+ can :destroy, Post, user_id: user.id
241
+
242
+ can :manage, :all if user.admin?
243
+ end
244
+ end
245
+
246
+ # app/controllers/api/v1/base_controller.rb
247
+ def current_ability
248
+ @current_ability ||= Ability.new(current_user, api: true)
249
+ end
250
+ ```
251
+
252
+ ## Scoped API Keys
253
+
254
+ ### Per-Resource API Keys
255
+
256
+ ```ruby
257
+ # app/models/api_key.rb
258
+ class ApiKey < ApplicationRecord
259
+ belongs_to :user
260
+
261
+ enum scope: {
262
+ read_only: 0,
263
+ read_write: 1,
264
+ admin: 2
265
+ }
266
+
267
+ has_secure_token :token
268
+
269
+ def can_write?
270
+ read_write? || admin?
271
+ end
272
+ end
273
+
274
+ # app/models/ability.rb
275
+ class Ability
276
+ include CanCan::Ability
277
+
278
+ def initialize(user, api_key: nil)
279
+ return unless user.present?
280
+
281
+ if api_key
282
+ define_api_key_abilities(user, api_key)
283
+ else
284
+ define_standard_abilities(user)
285
+ end
286
+ end
287
+
288
+ private
289
+
290
+ def define_api_key_abilities(user, api_key)
291
+ case api_key.scope
292
+ when 'read_only'
293
+ can :read, Post, user_id: user.id
294
+ when 'read_write'
295
+ can :read, Post
296
+ can [:create, :update], Post, user_id: user.id
297
+ when 'admin'
298
+ can :manage, :all
299
+ end
300
+ end
301
+ end
302
+
303
+ # app/controllers/api/v1/base_controller.rb
304
+ def authenticate_api_user!
305
+ token = request.headers['Authorization']&.split(' ')&.last
306
+ @api_key = ApiKey.find_by(token: token)
307
+
308
+ if @api_key
309
+ @current_user = @api_key.user
310
+ else
311
+ render json: { error: 'Unauthorized' }, status: :unauthorized
312
+ end
313
+ end
314
+
315
+ def current_ability
316
+ @current_ability ||= Ability.new(current_user, api_key: @api_key)
317
+ end
318
+ ```
319
+
320
+ ## Rate Limiting with Authorization
321
+
322
+ ```ruby
323
+ # app/controllers/api/v1/base_controller.rb
324
+ module Api
325
+ module V1
326
+ class BaseController < ActionController::API
327
+ before_action :authenticate_api_user!
328
+ before_action :check_rate_limit
329
+
330
+ private
331
+
332
+ def check_rate_limit
333
+ if current_user.admin?
334
+ # Admins have higher rate limits
335
+ rate_limit = 1000
336
+ elsif can? :manage, Post
337
+ # Power users
338
+ rate_limit = 500
339
+ else
340
+ # Regular users
341
+ rate_limit = 100
342
+ end
343
+
344
+ # Implement rate limiting logic
345
+ # (using Redis, rack-attack, etc.)
346
+ end
347
+ end
348
+ end
349
+ end
350
+ ```
351
+
352
+ ## Error Handling and Responses
353
+
354
+ ### Detailed Error Messages
355
+
356
+ ```ruby
357
+ # app/controllers/api/v1/base_controller.rb
358
+ rescue_from CanCan::AccessDenied do |exception|
359
+ render json: {
360
+ error: {
361
+ type: 'Forbidden',
362
+ message: exception.message,
363
+ details: {
364
+ action: exception.action,
365
+ subject: exception.subject.class.name,
366
+ subject_id: exception.subject.try(:id)
367
+ }
368
+ }
369
+ }, status: :forbidden
370
+ end
371
+
372
+ # Example response:
373
+ # {
374
+ # "error": {
375
+ # "type": "Forbidden",
376
+ # "message": "You are not authorized to update this Post",
377
+ # "details": {
378
+ # "action": "update",
379
+ # "subject": "Post",
380
+ # "subject_id": 123
381
+ # }
382
+ # }
383
+ # }
384
+ ```
385
+
386
+ ### Custom Authorization Messages
387
+
388
+ ```ruby
389
+ # app/controllers/api/v1/posts_controller.rb
390
+ def update
391
+ authorize! :update, @post, message: "You can only edit your own posts"
392
+
393
+ if @post.update(post_params)
394
+ render json: @post
395
+ else
396
+ render json: { errors: @post.errors }, status: :unprocessable_entity
397
+ end
398
+ end
399
+ ```
400
+
401
+ ## Testing API Authorization
402
+
403
+ ### Request Specs
404
+
405
+ ```ruby
406
+ # spec/requests/api/v1/posts_spec.rb
407
+ require 'rails_helper'
408
+
409
+ RSpec.describe 'Api::V1::Posts', type: :request do
410
+ let(:user) { create(:user) }
411
+ let(:other_user) { create(:user) }
412
+ let(:headers) { { 'Authorization' => "Bearer #{user.api_token}" } }
413
+
414
+ describe 'GET /api/v1/posts' do
415
+ let!(:posts) { create_list(:post, 3, user: user) }
416
+
417
+ it 'returns authorized posts' do
418
+ get '/api/v1/posts', headers: headers
419
+ expect(response).to have_http_status(:ok)
420
+ expect(JSON.parse(response.body).size).to eq(3)
421
+ end
422
+
423
+ it 'requires authentication' do
424
+ get '/api/v1/posts'
425
+ expect(response).to have_http_status(:unauthorized)
426
+ end
427
+ end
428
+
429
+ describe 'PUT /api/v1/posts/:id' do
430
+ let(:post) { create(:post, user: user) }
431
+ let(:other_post) { create(:post, user: other_user) }
432
+
433
+ it 'allows updating own post' do
434
+ put "/api/v1/posts/#{post.id}",
435
+ params: { post: { title: 'New Title' } },
436
+ headers: headers
437
+
438
+ expect(response).to have_http_status(:ok)
439
+ expect(JSON.parse(response.body)['title']).to eq('New Title')
440
+ end
441
+
442
+ it 'denies updating other user post' do
443
+ put "/api/v1/posts/#{other_post.id}",
444
+ params: { post: { title: 'New Title' } },
445
+ headers: headers
446
+
447
+ expect(response).to have_http_status(:forbidden)
448
+ expect(JSON.parse(response.body)).to have_key('error')
449
+ end
450
+ end
451
+ end
452
+ ```
453
+
454
+ ### Testing Different API Key Scopes
455
+
456
+ ```ruby
457
+ # spec/requests/api/v1/posts_with_api_keys_spec.rb
458
+ RSpec.describe 'Api::V1::Posts with API keys', type: :request do
459
+ let(:user) { create(:user) }
460
+ let(:read_only_key) { create(:api_key, user: user, scope: :read_only) }
461
+ let(:read_write_key) { create(:api_key, user: user, scope: :read_write) }
462
+
463
+ describe 'with read-only key' do
464
+ let(:headers) { { 'Authorization' => "Bearer #{read_only_key.token}" } }
465
+
466
+ it 'allows reading posts' do
467
+ get '/api/v1/posts', headers: headers
468
+ expect(response).to have_http_status(:ok)
469
+ end
470
+
471
+ it 'denies creating posts' do
472
+ post '/api/v1/posts',
473
+ params: { post: { title: 'Test' } },
474
+ headers: headers
475
+
476
+ expect(response).to have_http_status(:forbidden)
477
+ end
478
+ end
479
+
480
+ describe 'with read-write key' do
481
+ let(:headers) { { 'Authorization' => "Bearer #{read_write_key.token}" } }
482
+
483
+ it 'allows creating posts' do
484
+ post '/api/v1/posts',
485
+ params: { post: { title: 'Test', body: 'Content' } },
486
+ headers: headers
487
+
488
+ expect(response).to have_http_status(:created)
489
+ end
490
+ end
491
+ end
492
+ ```
493
+
494
+ ## GraphQL Integration
495
+
496
+ ```ruby
497
+ # app/graphql/types/query_type.rb
498
+ module Types
499
+ class QueryType < Types::BaseObject
500
+ field :posts, [Types::PostType], null: false
501
+
502
+ def posts
503
+ Post.accessible_by(context[:current_ability])
504
+ end
505
+ end
506
+ end
507
+
508
+ # app/graphql/mutations/create_post.rb
509
+ module Mutations
510
+ class CreatePost < BaseMutation
511
+ argument :title, String, required: true
512
+ argument :body, String, required: true
513
+
514
+ field :post, Types::PostType, null: true
515
+ field :errors, [String], null: false
516
+
517
+ def resolve(title:, body:)
518
+ post = Post.new(title: title, body: body, user: context[:current_user])
519
+
520
+ context[:current_ability].authorize! :create, post
521
+
522
+ if post.save
523
+ { post: post, errors: [] }
524
+ else
525
+ { post: nil, errors: post.errors.full_messages }
526
+ end
527
+ end
528
+ end
529
+ end
530
+ ```
531
+
532
+ ## Best Practices for API Authorization
533
+
534
+ 1. **Always authenticate API requests**: Never skip authentication for API endpoints
535
+ 2. **Use HTTPS**: Always use SSL/TLS for API requests with sensitive tokens
536
+ 3. **Implement rate limiting**: Protect against abuse based on user abilities
537
+ 4. **Return proper HTTP status codes**: 401 for unauthenticated, 403 for unauthorized
538
+ 5. **Provide clear error messages**: Help API consumers understand authorization failures
539
+ 6. **Version your API**: Include authorization changes in API versioning
540
+ 7. **Log authorization failures**: Track unauthorized access attempts
541
+ 8. **Use scoped tokens**: Limit token permissions to minimum required access
542
+ 9. **Implement token expiration**: Refresh tokens periodically
543
+ 10. **Test authorization thoroughly**: Cover all permission scenarios in specs