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,373 @@
1
+ # Multi-Tenancy Authorization with CanCanCan
2
+
3
+ Managing permissions in multi-tenant applications where users belong to organizations, accounts, or workspaces.
4
+
5
+ ## Basic Multi-Tenant Setup
6
+
7
+ ### Organization Model
8
+
9
+ ```ruby
10
+ # app/models/organization.rb
11
+ class Organization < ApplicationRecord
12
+ has_many :users
13
+ has_many :posts
14
+ has_many :projects
15
+ end
16
+
17
+ # app/models/user.rb
18
+ class User < ApplicationRecord
19
+ belongs_to :organization
20
+
21
+ enum role: { member: 0, manager: 1, admin: 2 }
22
+ end
23
+
24
+ # app/models/post.rb
25
+ class Post < ApplicationRecord
26
+ belongs_to :organization
27
+ belongs_to :user
28
+ end
29
+ ```
30
+
31
+ ### Ability Class for Multi-Tenancy
32
+
33
+ ```ruby
34
+ # app/models/ability.rb
35
+ class Ability
36
+ include CanCan::Ability
37
+
38
+ def initialize(user)
39
+ return unless user.present?
40
+
41
+ # Scope all resources to user's organization
42
+ can :read, Post, organization_id: user.organization_id
43
+ can :create, Post, organization_id: user.organization_id
44
+ can :update, Post, organization_id: user.organization_id, user_id: user.id
45
+ can :destroy, Post, organization_id: user.organization_id, user_id: user.id
46
+
47
+ # Managers can manage all posts in their organization
48
+ if user.manager? || user.admin?
49
+ can :manage, Post, organization_id: user.organization_id
50
+ end
51
+
52
+ # Only admins can manage organization settings
53
+ if user.admin?
54
+ can :manage, Organization, id: user.organization_id
55
+ can :manage, User, organization_id: user.organization_id
56
+ end
57
+ end
58
+ end
59
+ ```
60
+
61
+ ## Controller Setup
62
+
63
+ ### Setting Organization Context
64
+
65
+ ```ruby
66
+ # app/controllers/application_controller.rb
67
+ class ApplicationController < ActionController::Base
68
+ before_action :authenticate_user!
69
+ before_action :set_organization
70
+
71
+ private
72
+
73
+ def set_organization
74
+ @current_organization = current_user.organization
75
+ end
76
+
77
+ def current_organization
78
+ @current_organization
79
+ end
80
+ helper_method :current_organization
81
+ end
82
+ ```
83
+
84
+ ### Scoping Resources
85
+
86
+ ```ruby
87
+ # app/controllers/posts_controller.rb
88
+ class PostsController < ApplicationController
89
+ load_and_authorize_resource
90
+
91
+ def index
92
+ # @posts automatically scoped to organization via accessible_by
93
+ @posts = @posts.where(organization_id: current_organization.id)
94
+ end
95
+
96
+ def create
97
+ @post.organization = current_organization
98
+ if @post.save
99
+ redirect_to @post
100
+ else
101
+ render :new
102
+ end
103
+ end
104
+
105
+ private
106
+
107
+ def post_params
108
+ params.require(:post).permit(:title, :body)
109
+ end
110
+ end
111
+ ```
112
+
113
+ ## Account-Based Multi-Tenancy
114
+
115
+ For apps where users can belong to multiple accounts/workspaces:
116
+
117
+ ### Models
118
+
119
+ ```ruby
120
+ # app/models/user.rb
121
+ class User < ApplicationRecord
122
+ has_many :memberships
123
+ has_many :accounts, through: :memberships
124
+
125
+ def current_account=(account)
126
+ @current_account = account
127
+ end
128
+
129
+ def current_account
130
+ @current_account
131
+ end
132
+ end
133
+
134
+ # app/models/membership.rb
135
+ class Membership < ApplicationRecord
136
+ belongs_to :user
137
+ belongs_to :account
138
+
139
+ enum role: { member: 0, admin: 1, owner: 2 }
140
+ end
141
+
142
+ # app/models/account.rb
143
+ class Account < ApplicationRecord
144
+ has_many :memberships
145
+ has_many :users, through: :memberships
146
+ has_many :projects
147
+ end
148
+ ```
149
+
150
+ ### Ability with Current Account
151
+
152
+ ```ruby
153
+ # app/models/ability.rb
154
+ class Ability
155
+ include CanCan::Ability
156
+
157
+ def initialize(user, current_account = nil)
158
+ return unless user.present?
159
+ return unless current_account.present?
160
+
161
+ # Find user's membership in current account
162
+ membership = user.memberships.find_by(account: current_account)
163
+ return unless membership
164
+
165
+ # Base permissions for all members
166
+ can :read, Project, account_id: current_account.id
167
+
168
+ # Members can create projects
169
+ if membership.member? || membership.admin? || membership.owner?
170
+ can :create, Project, account_id: current_account.id
171
+ can :update, Project, account_id: current_account.id, user_id: user.id
172
+ end
173
+
174
+ # Admins and owners can manage all projects
175
+ if membership.admin? || membership.owner?
176
+ can :manage, Project, account_id: current_account.id
177
+ end
178
+
179
+ # Only owners can manage account settings
180
+ if membership.owner?
181
+ can :manage, Account, id: current_account.id
182
+ can :manage, Membership, account_id: current_account.id
183
+ end
184
+ end
185
+ end
186
+ ```
187
+
188
+ ### Controller with Account Context
189
+
190
+ ```ruby
191
+ # app/controllers/application_controller.rb
192
+ class ApplicationController < ActionController::Base
193
+ before_action :authenticate_user!
194
+ before_action :set_current_account
195
+
196
+ private
197
+
198
+ def set_current_account
199
+ if params[:account_id]
200
+ @current_account = current_user.accounts.find(params[:account_id])
201
+ current_user.current_account = @current_account
202
+ elsif session[:current_account_id]
203
+ @current_account = current_user.accounts.find_by(id: session[:current_account_id])
204
+ else
205
+ @current_account = current_user.accounts.first
206
+ end
207
+
208
+ session[:current_account_id] = @current_account&.id
209
+ end
210
+
211
+ def current_account
212
+ @current_account
213
+ end
214
+ helper_method :current_account
215
+
216
+ def current_ability
217
+ @current_ability ||= Ability.new(current_user, current_account)
218
+ end
219
+ end
220
+ ```
221
+
222
+ ### Account Switcher in Views
223
+
224
+ ```ruby
225
+ # app/views/layouts/_account_switcher.html.erb
226
+ <div class="account-switcher">
227
+ <% current_user.accounts.each do |account| %>
228
+ <%= link_to account.name,
229
+ account_path(account),
230
+ class: (account == current_account ? 'active' : '') %>
231
+ <% end %>
232
+ </div>
233
+ ```
234
+
235
+ ## Row-Level Security Pattern
236
+
237
+ For more complex authorization with nested resources:
238
+
239
+ ```ruby
240
+ class Ability
241
+ include CanCan::Ability
242
+
243
+ def initialize(user, current_account = nil)
244
+ return unless user.present?
245
+ return unless current_account.present?
246
+
247
+ # Projects
248
+ can :manage, Project, account_id: current_account.id
249
+
250
+ # Tasks belong to projects, which belong to accounts
251
+ can :read, Task, project: { account_id: current_account.id }
252
+ can :create, Task, project: { account_id: current_account.id }
253
+ can :update, Task, project: { account_id: current_account.id }
254
+
255
+ # Comments belong to tasks, which belong to projects, which belong to accounts
256
+ can :read, Comment, task: { project: { account_id: current_account.id } }
257
+ can :create, Comment, task: { project: { account_id: current_account.id } }
258
+ end
259
+ end
260
+ ```
261
+
262
+ ## Testing Multi-Tenancy
263
+
264
+ ```ruby
265
+ # spec/models/ability_spec.rb
266
+ require 'rails_helper'
267
+ require 'cancan/matchers'
268
+
269
+ RSpec.describe Ability, type: :model do
270
+ let(:organization1) { create(:organization) }
271
+ let(:organization2) { create(:organization) }
272
+ let(:user) { create(:user, organization: organization1) }
273
+ let(:post_in_org1) { create(:post, organization: organization1) }
274
+ let(:post_in_org2) { create(:post, organization: organization2) }
275
+
276
+ subject(:ability) { Ability.new(user) }
277
+
278
+ it 'allows access to own organization resources' do
279
+ expect(ability).to be_able_to(:read, post_in_org1)
280
+ end
281
+
282
+ it 'denies access to other organization resources' do
283
+ expect(ability).not_to be_able_to(:read, post_in_org2)
284
+ end
285
+
286
+ describe 'with multiple accounts' do
287
+ let(:account1) { create(:account) }
288
+ let(:account2) { create(:account) }
289
+ let(:user) { create(:user) }
290
+ let!(:membership1) { create(:membership, user: user, account: account1, role: :admin) }
291
+ let!(:membership2) { create(:membership, user: user, account: account2, role: :member) }
292
+
293
+ it 'has different permissions based on current account' do
294
+ ability_in_account1 = Ability.new(user, account1)
295
+ ability_in_account2 = Ability.new(user, account2)
296
+
297
+ expect(ability_in_account1).to be_able_to(:manage, Project.new(account: account1))
298
+ expect(ability_in_account2).not_to be_able_to(:manage, Project.new(account: account2))
299
+ end
300
+ end
301
+ end
302
+ ```
303
+
304
+ ## Common Pitfalls
305
+
306
+ ### 1. Forgetting to Scope Queries
307
+
308
+ ```ruby
309
+ # Bad - exposes all organizations' data
310
+ def index
311
+ @posts = Post.accessible_by(current_ability)
312
+ end
313
+
314
+ # Good - explicitly scope to current organization
315
+ def index
316
+ @posts = current_organization.posts.accessible_by(current_ability)
317
+ end
318
+ ```
319
+
320
+ ### 2. Not Setting Organization on Create
321
+
322
+ ```ruby
323
+ # Bad - user could potentially set any organization_id
324
+ def create
325
+ @post = Post.new(post_params)
326
+ authorize! :create, @post
327
+ end
328
+
329
+ # Good - always set organization from context
330
+ def create
331
+ @post = current_organization.posts.new(post_params)
332
+ @post.user = current_user
333
+ authorize! :create, @post
334
+ end
335
+ ```
336
+
337
+ ### 3. Missing Organization in Nested Resources
338
+
339
+ ```ruby
340
+ # app/controllers/comments_controller.rb
341
+ class CommentsController < ApplicationController
342
+ before_action :set_post
343
+
344
+ def create
345
+ # Verify post belongs to current organization
346
+ authorize! :read, @post
347
+
348
+ @comment = @post.comments.build(comment_params)
349
+ @comment.user = current_user
350
+
351
+ if @comment.save
352
+ redirect_to @post
353
+ else
354
+ render 'posts/show'
355
+ end
356
+ end
357
+
358
+ private
359
+
360
+ def set_post
361
+ @post = current_organization.posts.find(params[:post_id])
362
+ end
363
+ end
364
+ ```
365
+
366
+ ## Best Practices
367
+
368
+ 1. **Always scope to tenant**: Use `current_organization.posts` instead of `Post.all`
369
+ 2. **Set organization on create**: Automatically assign organization from context
370
+ 3. **Verify nested resources**: Ensure parent resources belong to current tenant
371
+ 4. **Test cross-tenant access**: Write specs that verify users can't access other tenants' data
372
+ 5. **Use database constraints**: Add foreign keys and indexes on organization_id
373
+ 6. **Cache ability per account**: Use `@current_ability ||= Ability.new(current_user, current_account)`