omgkit 2.1.1 → 2.2.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 (50) hide show
  1. package/package.json +1 -1
  2. package/plugin/skills/SKILL_STANDARDS.md +743 -0
  3. package/plugin/skills/databases/mongodb/SKILL.md +797 -28
  4. package/plugin/skills/databases/prisma/SKILL.md +776 -30
  5. package/plugin/skills/databases/redis/SKILL.md +885 -25
  6. package/plugin/skills/devops/aws/SKILL.md +686 -28
  7. package/plugin/skills/devops/github-actions/SKILL.md +684 -29
  8. package/plugin/skills/devops/kubernetes/SKILL.md +621 -24
  9. package/plugin/skills/frameworks/django/SKILL.md +920 -20
  10. package/plugin/skills/frameworks/express/SKILL.md +1361 -35
  11. package/plugin/skills/frameworks/fastapi/SKILL.md +1260 -33
  12. package/plugin/skills/frameworks/laravel/SKILL.md +1244 -31
  13. package/plugin/skills/frameworks/nestjs/SKILL.md +1005 -26
  14. package/plugin/skills/frameworks/rails/SKILL.md +594 -28
  15. package/plugin/skills/frameworks/spring/SKILL.md +528 -35
  16. package/plugin/skills/frameworks/vue/SKILL.md +1296 -27
  17. package/plugin/skills/frontend/accessibility/SKILL.md +1108 -34
  18. package/plugin/skills/frontend/frontend-design/SKILL.md +1304 -26
  19. package/plugin/skills/frontend/responsive/SKILL.md +847 -21
  20. package/plugin/skills/frontend/shadcn-ui/SKILL.md +976 -38
  21. package/plugin/skills/frontend/tailwindcss/SKILL.md +831 -35
  22. package/plugin/skills/frontend/threejs/SKILL.md +1298 -29
  23. package/plugin/skills/languages/javascript/SKILL.md +935 -31
  24. package/plugin/skills/methodology/brainstorming/SKILL.md +597 -23
  25. package/plugin/skills/methodology/defense-in-depth/SKILL.md +832 -34
  26. package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +665 -31
  27. package/plugin/skills/methodology/executing-plans/SKILL.md +556 -24
  28. package/plugin/skills/methodology/finishing-development-branch/SKILL.md +595 -25
  29. package/plugin/skills/methodology/problem-solving/SKILL.md +429 -61
  30. package/plugin/skills/methodology/receiving-code-review/SKILL.md +536 -24
  31. package/plugin/skills/methodology/requesting-code-review/SKILL.md +632 -21
  32. package/plugin/skills/methodology/root-cause-tracing/SKILL.md +641 -30
  33. package/plugin/skills/methodology/sequential-thinking/SKILL.md +262 -3
  34. package/plugin/skills/methodology/systematic-debugging/SKILL.md +571 -32
  35. package/plugin/skills/methodology/test-driven-development/SKILL.md +779 -24
  36. package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +691 -29
  37. package/plugin/skills/methodology/token-optimization/SKILL.md +598 -29
  38. package/plugin/skills/methodology/verification-before-completion/SKILL.md +543 -22
  39. package/plugin/skills/methodology/writing-plans/SKILL.md +590 -18
  40. package/plugin/skills/omega/omega-architecture/SKILL.md +838 -39
  41. package/plugin/skills/omega/omega-coding/SKILL.md +636 -39
  42. package/plugin/skills/omega/omega-sprint/SKILL.md +855 -48
  43. package/plugin/skills/omega/omega-testing/SKILL.md +940 -41
  44. package/plugin/skills/omega/omega-thinking/SKILL.md +703 -50
  45. package/plugin/skills/security/better-auth/SKILL.md +1065 -28
  46. package/plugin/skills/security/oauth/SKILL.md +968 -31
  47. package/plugin/skills/security/owasp/SKILL.md +894 -33
  48. package/plugin/skills/testing/playwright/SKILL.md +764 -38
  49. package/plugin/skills/testing/pytest/SKILL.md +873 -36
  50. package/plugin/skills/testing/vitest/SKILL.md +980 -35
@@ -1,56 +1,622 @@
1
1
  ---
2
2
  name: rails
3
- description: Ruby on Rails development. Use for Rails projects, MVC, ActiveRecord.
3
+ description: Enterprise Ruby on Rails development with Active Record, API mode, testing, and production patterns
4
+ category: frameworks
5
+ triggers:
6
+ - rails
7
+ - ruby on rails
8
+ - ror
9
+ - active record
10
+ - ruby api
11
+ - rails api
12
+ - ruby web
13
+ - rubygems
4
14
  ---
5
15
 
6
- # Ruby on Rails Skill
16
+ # Ruby on Rails
7
17
 
8
- ## Patterns
18
+ Enterprise-grade **Ruby on Rails development** following industry best practices. This skill covers Active Record, API mode, service objects, authentication, testing with RSpec, background jobs, and production deployment configurations used by top engineering teams.
19
+
20
+ ## Purpose
21
+
22
+ Build scalable Ruby applications with confidence:
23
+
24
+ - Design clean model architectures with Active Record
25
+ - Implement REST APIs with Rails API mode
26
+ - Use service objects and concerns for clean code
27
+ - Handle authentication with JWT or Devise
28
+ - Write comprehensive tests with RSpec
29
+ - Deploy production-ready applications
30
+ - Leverage background jobs with Sidekiq
31
+
32
+ ## Features
33
+
34
+ ### 1. Model Design and Associations
9
35
 
10
- ### Model
11
36
  ```ruby
37
+ # app/models/user.rb
12
38
  class User < ApplicationRecord
13
- validates :email, presence: true, uniqueness: true
14
- has_many :posts
39
+ has_secure_password
40
+
41
+ # Associations
42
+ has_many :memberships, dependent: :destroy
43
+ has_many :organizations, through: :memberships
44
+ has_many :owned_organizations, class_name: 'Organization', foreign_key: :owner_id
45
+ has_many :projects, foreign_key: :created_by
46
+
47
+ # Validations
48
+ validates :email, presence: true, uniqueness: { case_sensitive: false },
49
+ format: { with: URI::MailTo::EMAIL_REGEXP }
50
+ validates :name, presence: true, length: { minimum: 2, maximum: 100 }
51
+ validates :password, length: { minimum: 8 }, if: -> { new_record? || password.present? }
52
+ validates :role, inclusion: { in: %w[admin user guest] }
53
+
54
+ # Callbacks
55
+ before_save :downcase_email
56
+
57
+ # Scopes
58
+ scope :active, -> { where(is_active: true) }
59
+ scope :admins, -> { where(role: 'admin') }
60
+ scope :search, ->(query) {
61
+ return all if query.blank?
62
+ where('name ILIKE :q OR email ILIKE :q', q: "%#{query}%")
63
+ }
64
+
65
+ # Enums
66
+ enum :role, { guest: 'guest', user: 'user', admin: 'admin' }, default: :user
67
+
68
+ # Instance methods
69
+ def admin?
70
+ role == 'admin'
71
+ end
72
+
73
+ def member_of?(organization)
74
+ organizations.exists?(organization.id)
75
+ end
15
76
 
16
- scope :active, -> { where(active: true) }
77
+ private
78
+
79
+ def downcase_email
80
+ self.email = email.downcase
81
+ end
82
+ end
83
+
84
+
85
+ # app/models/organization.rb
86
+ class Organization < ApplicationRecord
87
+ belongs_to :owner, class_name: 'User'
88
+ has_many :memberships, dependent: :destroy
89
+ has_many :members, through: :memberships, source: :user
90
+ has_many :projects, dependent: :destroy
91
+
92
+ validates :name, presence: true, length: { maximum: 255 }
93
+ validates :slug, presence: true, uniqueness: true,
94
+ format: { with: /\A[a-z0-9-]+\z/ }
95
+
96
+ scope :for_user, ->(user) { joins(:memberships).where(memberships: { user_id: user.id }) }
97
+
98
+ before_validation :generate_slug, on: :create
99
+
100
+ private
101
+
102
+ def generate_slug
103
+ self.slug ||= name&.parameterize
104
+ end
105
+ end
106
+
107
+
108
+ # app/models/project.rb
109
+ class Project < ApplicationRecord
110
+ belongs_to :organization
111
+ belongs_to :creator, class_name: 'User', foreign_key: :created_by
112
+ has_many :tasks, dependent: :destroy
113
+
114
+ validates :name, presence: true, length: { maximum: 255 }
115
+ validates :name, uniqueness: { scope: :organization_id }
116
+
117
+ enum :status, { draft: 'draft', active: 'active', completed: 'completed', archived: 'archived' }
118
+
119
+ scope :active, -> { where(status: :active) }
120
+
121
+ include Discard::Model
122
+ default_scope -> { kept }
17
123
  end
18
124
  ```
19
125
 
20
- ### Controller
126
+ ### 2. Serializers
127
+
21
128
  ```ruby
22
- class UsersController < ApplicationController
23
- def index
24
- @users = User.all
25
- render json: @users
129
+ # app/serializers/user_serializer.rb
130
+ class UserSerializer
131
+ include JSONAPI::Serializer
132
+
133
+ attributes :id, :email, :name, :role, :is_active, :created_at, :updated_at
134
+
135
+ attribute :organization_count do |user|
136
+ user.organizations.count
26
137
  end
27
138
 
28
- def create
29
- @user = User.new(user_params)
30
- if @user.save
31
- render json: @user, status: :created
32
- else
33
- render json: @user.errors, status: :unprocessable_entity
34
- end
139
+ has_many :organizations, serializer: OrganizationSerializer, if: Proc.new { |_record, params|
140
+ params && params[:include_organizations]
141
+ }
142
+ end
143
+
144
+
145
+ # app/serializers/organization_serializer.rb
146
+ class OrganizationSerializer
147
+ include JSONAPI::Serializer
148
+
149
+ attributes :id, :name, :slug, :created_at
150
+
151
+ attribute :member_count do |organization|
152
+ organization.members.count
153
+ end
154
+
155
+ belongs_to :owner, serializer: UserSerializer
156
+ end
157
+
158
+
159
+ # app/serializers/pagination_serializer.rb
160
+ class PaginationSerializer
161
+ def initialize(collection, serializer_class, options = {})
162
+ @collection = collection
163
+ @serializer_class = serializer_class
164
+ @options = options
165
+ end
166
+
167
+ def as_json
168
+ {
169
+ data: serialized_data,
170
+ pagination: {
171
+ current_page: @collection.current_page,
172
+ per_page: @collection.limit_value,
173
+ total: @collection.total_count,
174
+ total_pages: @collection.total_pages,
175
+ has_more: @collection.current_page < @collection.total_pages
176
+ }
177
+ }
35
178
  end
36
179
 
37
180
  private
38
181
 
39
- def user_params
40
- params.require(:user).permit(:email, :password)
182
+ def serialized_data
183
+ @serializer_class.new(@collection, @options).serializable_hash[:data]
41
184
  end
42
185
  end
43
186
  ```
44
187
 
45
- ### Routes
188
+ ### 3. Controllers
189
+
46
190
  ```ruby
47
- Rails.application.routes.draw do
48
- resources :users, only: [:index, :create, :show]
191
+ # app/controllers/application_controller.rb
192
+ class ApplicationController < ActionController::API
193
+ include ActionController::HttpAuthentication::Token::ControllerMethods
194
+
195
+ before_action :authenticate_user!
196
+
197
+ rescue_from ActiveRecord::RecordNotFound, with: :not_found
198
+ rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity
199
+
200
+ private
201
+
202
+ def authenticate_user!
203
+ authenticate_or_request_with_http_token do |token, _options|
204
+ @current_user = JsonWebToken.decode(token)
205
+ end
206
+ end
207
+
208
+ def current_user
209
+ @current_user
210
+ end
211
+
212
+ def authorize_admin!
213
+ render_forbidden unless current_user.admin?
214
+ end
215
+
216
+ def not_found(exception)
217
+ render json: { error: { code: 'NOT_FOUND', message: exception.message } }, status: :not_found
218
+ end
219
+
220
+ def unprocessable_entity(exception)
221
+ render json: { error: { code: 'VALIDATION_ERROR', details: exception.record.errors } }, status: :unprocessable_entity
222
+ end
223
+
224
+ def render_forbidden
225
+ render json: { error: { code: 'FORBIDDEN', message: 'Access denied' } }, status: :forbidden
226
+ end
227
+ end
228
+
229
+
230
+ # app/controllers/api/v1/users_controller.rb
231
+ module Api
232
+ module V1
233
+ class UsersController < ApplicationController
234
+ before_action :authorize_admin!, except: [:me, :update_me]
235
+ before_action :set_user, only: [:show, :update, :destroy]
236
+
237
+ def index
238
+ users = User.active.search(params[:search])
239
+ users = users.where(role: params[:role]) if params[:role].present?
240
+ users = users.page(params[:page]).per(params[:per_page] || 20)
241
+
242
+ render json: PaginationSerializer.new(users, UserSerializer).as_json
243
+ end
244
+
245
+ def show
246
+ render json: UserSerializer.new(@user, include_organizations: true).serializable_hash
247
+ end
248
+
249
+ def create
250
+ user = User.new(user_params)
251
+ user.save!
252
+ render json: UserSerializer.new(user).serializable_hash, status: :created
253
+ end
254
+
255
+ def update
256
+ @user.update!(user_params)
257
+ render json: UserSerializer.new(@user).serializable_hash
258
+ end
259
+
260
+ def destroy
261
+ @user.destroy
262
+ head :no_content
263
+ end
264
+
265
+ def me
266
+ render json: UserSerializer.new(current_user, include_organizations: true).serializable_hash
267
+ end
268
+
269
+ private
270
+
271
+ def set_user
272
+ @user = User.find(params[:id])
273
+ end
274
+
275
+ def user_params
276
+ params.require(:user).permit(:name, :email, :password, :role, :is_active)
277
+ end
278
+ end
279
+ end
280
+ end
281
+
282
+
283
+ # app/controllers/api/v1/auth_controller.rb
284
+ module Api
285
+ module V1
286
+ class AuthController < ApplicationController
287
+ skip_before_action :authenticate_user!, only: [:register, :login]
288
+
289
+ def register
290
+ user = User.new(register_params)
291
+ user.save!
292
+
293
+ token = JsonWebToken.encode(user_id: user.id)
294
+ render json: { user: UserSerializer.new(user).serializable_hash, token: token }, status: :created
295
+ end
296
+
297
+ def login
298
+ user = User.find_by(email: params[:email]&.downcase)
299
+
300
+ if user&.authenticate(params[:password]) && user.is_active?
301
+ token = JsonWebToken.encode(user_id: user.id)
302
+ render json: { user: UserSerializer.new(user).serializable_hash, token: token }
303
+ else
304
+ render json: { error: { code: 'UNAUTHORIZED', message: 'Invalid credentials' } }, status: :unauthorized
305
+ end
306
+ end
307
+
308
+ private
309
+
310
+ def register_params
311
+ params.require(:user).permit(:name, :email, :password)
312
+ end
313
+ end
314
+ end
315
+ end
316
+ ```
317
+
318
+ ### 4. Service Objects
319
+
320
+ ```ruby
321
+ # app/services/application_service.rb
322
+ class ApplicationService
323
+ def self.call(...)
324
+ new(...).call
325
+ end
326
+ end
327
+
328
+
329
+ # app/services/users/create_user_service.rb
330
+ module Users
331
+ class CreateUserService < ApplicationService
332
+ def initialize(params)
333
+ @params = params
334
+ end
335
+
336
+ def call
337
+ user = User.new(@params)
338
+ user.save!
339
+
340
+ SendWelcomeEmailJob.perform_later(user.id)
341
+
342
+ Result.success(user: user)
343
+ rescue ActiveRecord::RecordInvalid => e
344
+ Result.failure(errors: e.record.errors)
345
+ end
346
+ end
347
+ end
348
+
349
+
350
+ # app/services/organizations/create_organization_service.rb
351
+ module Organizations
352
+ class CreateOrganizationService < ApplicationService
353
+ def initialize(owner:, params:)
354
+ @owner = owner
355
+ @params = params
356
+ end
357
+
358
+ def call
359
+ ActiveRecord::Base.transaction do
360
+ organization = Organization.create!(@params.merge(owner: @owner))
361
+ Membership.create!(user: @owner, organization: organization, role: :owner)
362
+
363
+ Result.success(organization: organization)
364
+ end
365
+ rescue ActiveRecord::RecordInvalid => e
366
+ Result.failure(errors: e.record.errors)
367
+ end
368
+ end
369
+ end
370
+
371
+
372
+ # app/services/result.rb
373
+ class Result
374
+ attr_reader :data, :errors
375
+
376
+ def initialize(success:, data: {}, errors: nil)
377
+ @success = success
378
+ @data = data
379
+ @errors = errors
380
+ end
381
+
382
+ def success?
383
+ @success
384
+ end
385
+
386
+ def failure?
387
+ !@success
388
+ end
389
+
390
+ def self.success(data = {})
391
+ new(success: true, data: data)
392
+ end
393
+
394
+ def self.failure(errors:)
395
+ new(success: false, errors: errors)
396
+ end
397
+ end
398
+ ```
399
+
400
+ ### 5. JWT Authentication
401
+
402
+ ```ruby
403
+ # lib/json_web_token.rb
404
+ class JsonWebToken
405
+ SECRET_KEY = Rails.application.credentials.secret_key_base
406
+
407
+ def self.encode(payload, exp = 24.hours.from_now)
408
+ payload[:exp] = exp.to_i
409
+ JWT.encode(payload, SECRET_KEY)
410
+ end
411
+
412
+ def self.decode(token)
413
+ decoded = JWT.decode(token, SECRET_KEY)[0]
414
+ User.find(decoded['user_id'])
415
+ rescue JWT::DecodeError, ActiveRecord::RecordNotFound
416
+ nil
417
+ end
418
+ end
419
+ ```
420
+
421
+ ### 6. Background Jobs
422
+
423
+ ```ruby
424
+ # app/jobs/application_job.rb
425
+ class ApplicationJob < ActiveJob::Base
426
+ queue_as :default
427
+
428
+ retry_on StandardError, wait: :exponentially_longer, attempts: 3
429
+ discard_on ActiveJob::DeserializationError
430
+ end
431
+
432
+
433
+ # app/jobs/send_welcome_email_job.rb
434
+ class SendWelcomeEmailJob < ApplicationJob
435
+ queue_as :mailers
436
+
437
+ def perform(user_id)
438
+ user = User.find(user_id)
439
+ UserMailer.welcome_email(user).deliver_now
440
+ end
441
+ end
442
+ ```
443
+
444
+ ### 7. Testing with RSpec
445
+
446
+ ```ruby
447
+ # spec/models/user_spec.rb
448
+ require 'rails_helper'
449
+
450
+ RSpec.describe User, type: :model do
451
+ describe 'validations' do
452
+ subject { build(:user) }
453
+
454
+ it { should validate_presence_of(:email) }
455
+ it { should validate_uniqueness_of(:email).case_insensitive }
456
+ it { should validate_presence_of(:name) }
457
+ end
458
+
459
+ describe 'associations' do
460
+ it { should have_many(:memberships).dependent(:destroy) }
461
+ it { should have_many(:organizations).through(:memberships) }
462
+ end
463
+
464
+ describe 'scopes' do
465
+ describe '.active' do
466
+ it 'returns only active users' do
467
+ active_user = create(:user, is_active: true)
468
+ inactive_user = create(:user, is_active: false)
469
+
470
+ expect(User.active).to include(active_user)
471
+ expect(User.active).not_to include(inactive_user)
472
+ end
473
+ end
474
+
475
+ describe '.search' do
476
+ it 'searches by name and email' do
477
+ user = create(:user, name: 'John Doe', email: 'john@example.com')
478
+
479
+ expect(User.search('john')).to include(user)
480
+ expect(User.search('jane')).not_to include(user)
481
+ end
482
+ end
483
+ end
484
+
485
+ describe '#admin?' do
486
+ it 'returns true for admin users' do
487
+ admin = build(:user, role: :admin)
488
+ expect(admin.admin?).to be true
489
+ end
490
+ end
491
+ end
492
+
493
+
494
+ # spec/requests/api/v1/users_spec.rb
495
+ require 'rails_helper'
496
+
497
+ RSpec.describe 'Api::V1::Users', type: :request do
498
+ let(:admin) { create(:user, role: :admin) }
499
+ let(:auth_headers) { { 'Authorization' => "Bearer #{JsonWebToken.encode(user_id: admin.id)}" } }
500
+
501
+ describe 'GET /api/v1/users' do
502
+ before { create_list(:user, 5) }
503
+
504
+ it 'returns paginated users for admin' do
505
+ get '/api/v1/users', headers: auth_headers
506
+
507
+ expect(response).to have_http_status(:ok)
508
+ expect(json_response['data']).to be_an(Array)
509
+ expect(json_response['pagination']).to include('current_page', 'total')
510
+ end
511
+
512
+ it 'returns 403 for non-admin' do
513
+ user = create(:user)
514
+ headers = { 'Authorization' => "Bearer #{JsonWebToken.encode(user_id: user.id)}" }
515
+
516
+ get '/api/v1/users', headers: headers
517
+
518
+ expect(response).to have_http_status(:forbidden)
519
+ end
520
+ end
521
+
522
+ describe 'POST /api/v1/users' do
523
+ let(:valid_params) { { user: { name: 'New User', email: 'new@example.com', password: 'SecurePass123!' } } }
524
+
525
+ it 'creates a new user' do
526
+ expect {
527
+ post '/api/v1/users', params: valid_params, headers: auth_headers
528
+ }.to change(User, :count).by(1)
529
+
530
+ expect(response).to have_http_status(:created)
531
+ end
532
+ end
533
+
534
+ def json_response
535
+ JSON.parse(response.body)
536
+ end
537
+ end
538
+
539
+
540
+ # spec/factories/users.rb
541
+ FactoryBot.define do
542
+ factory :user do
543
+ sequence(:email) { |n| "user#{n}@example.com" }
544
+ name { Faker::Name.name }
545
+ password { 'Password123!' }
546
+ role { :user }
547
+ is_active { true }
548
+
549
+ trait :admin do
550
+ role { :admin }
551
+ end
552
+ end
553
+ end
554
+ ```
555
+
556
+ ## Use Cases
557
+
558
+ ### API Rate Limiting
559
+
560
+ ```ruby
561
+ # config/initializers/rack_attack.rb
562
+ class Rack::Attack
563
+ throttle('requests by ip', limit: 100, period: 1.minute) do |request|
564
+ request.ip
565
+ end
566
+
567
+ throttle('login attempts', limit: 5, period: 1.minute) do |request|
568
+ if request.path == '/api/v1/auth/login' && request.post?
569
+ request.ip
570
+ end
571
+ end
572
+ end
573
+ ```
574
+
575
+ ### Caching with Redis
576
+
577
+ ```ruby
578
+ # app/controllers/api/v1/organizations_controller.rb
579
+ def show
580
+ @organization = Rails.cache.fetch("organization:#{params[:id]}", expires_in: 5.minutes) do
581
+ Organization.includes(:owner, :members).find(params[:id])
582
+ end
583
+
584
+ render json: OrganizationSerializer.new(@organization).serializable_hash
49
585
  end
50
586
  ```
51
587
 
52
588
  ## Best Practices
53
- - Use strong params
54
- - Use scopes for queries
55
- - Use concerns for shared code
56
- - Use background jobs for async
589
+
590
+ ### Do's
591
+
592
+ - Use UUID primary keys for public APIs
593
+ - Use service objects for business logic
594
+ - Use serializers for consistent responses
595
+ - Use concerns for shared behavior
596
+ - Use scopes for reusable queries
597
+ - Use background jobs for heavy operations
598
+ - Use strong parameters
599
+ - Write comprehensive tests with RSpec
600
+ - Use database indexes for performance
601
+ - Use soft deletes for important data
602
+
603
+ ### Don'ts
604
+
605
+ - Don't put business logic in controllers
606
+ - Don't use N+1 queries
607
+ - Don't skip validations
608
+ - Don't ignore security headers
609
+ - Don't expose internal errors
610
+ - Don't use callbacks for business logic
611
+ - Don't skip authentication
612
+ - Don't ignore test coverage
613
+ - Don't use sync operations for heavy tasks
614
+ - Don't forget rate limiting
615
+
616
+ ## References
617
+
618
+ - [Rails Guides](https://guides.rubyonrails.org/)
619
+ - [RSpec Documentation](https://rspec.info/)
620
+ - [Rails API Best Practices](https://github.com/rubocop/rubocop-rails)
621
+ - [JSON:API Serializer](https://github.com/jsonapi-serializer/jsonapi-serializer)
622
+ - [Sidekiq Documentation](https://github.com/mperham/sidekiq)