@jaimevalasek/aioson 1.3.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 (288) hide show
  1. package/CHANGELOG.md +456 -0
  2. package/CODE_OF_CONDUCT.md +12 -0
  3. package/CONTRIBUTING.md +13 -0
  4. package/LICENSE +21 -0
  5. package/README.md +254 -0
  6. package/bin/aioson.js +4 -0
  7. package/docs/en/cli-reference.md +398 -0
  8. package/docs/en/i18n.md +52 -0
  9. package/docs/en/json-schemas.md +41 -0
  10. package/docs/en/mcp.md +56 -0
  11. package/docs/en/parallel.md +82 -0
  12. package/docs/en/qa-browser.md +339 -0
  13. package/docs/en/release-flow.md +22 -0
  14. package/docs/en/release-notes-template.md +41 -0
  15. package/docs/en/release.md +28 -0
  16. package/docs/en/schemas/agent-prompt.schema.json +17 -0
  17. package/docs/en/schemas/agents.schema.json +32 -0
  18. package/docs/en/schemas/context-validate.schema.json +36 -0
  19. package/docs/en/schemas/doctor.schema.json +89 -0
  20. package/docs/en/schemas/error.schema.json +24 -0
  21. package/docs/en/schemas/i18n-add.schema.json +15 -0
  22. package/docs/en/schemas/index.json +116 -0
  23. package/docs/en/schemas/info.schema.json +39 -0
  24. package/docs/en/schemas/init.schema.json +48 -0
  25. package/docs/en/schemas/install.schema.json +60 -0
  26. package/docs/en/schemas/locale-apply.schema.json +30 -0
  27. package/docs/en/schemas/mcp-doctor.schema.json +95 -0
  28. package/docs/en/schemas/mcp-init.schema.json +122 -0
  29. package/docs/en/schemas/package-test.schema.json +24 -0
  30. package/docs/en/schemas/parallel-assign.schema.json +57 -0
  31. package/docs/en/schemas/parallel-doctor.schema.json +86 -0
  32. package/docs/en/schemas/parallel-init.schema.json +53 -0
  33. package/docs/en/schemas/parallel-status.schema.json +94 -0
  34. package/docs/en/schemas/setup-context.schema.json +39 -0
  35. package/docs/en/schemas/smoke.schema.json +23 -0
  36. package/docs/en/schemas/update.schema.json +48 -0
  37. package/docs/en/schemas/workflow-plan.schema.json +30 -0
  38. package/docs/en/web3.md +54 -0
  39. package/docs/pt/README.md +46 -0
  40. package/docs/pt/advisor-spec.md +335 -0
  41. package/docs/pt/agentes.md +453 -0
  42. package/docs/pt/cenarios.md +1230 -0
  43. package/docs/pt/clientes-ai.md +224 -0
  44. package/docs/pt/comandos-cli.md +511 -0
  45. package/docs/pt/genome-3.0-spec.md +296 -0
  46. package/docs/pt/guia-engineer.md +226 -0
  47. package/docs/pt/inicio-rapido.md +138 -0
  48. package/docs/pt/profiler-system.md +214 -0
  49. package/docs/pt/runtime-observability.md +72 -0
  50. package/docs/pt/squad-genoma.md +777 -0
  51. package/docs/pt/web3.md +797 -0
  52. package/docs/testing/genome-2.0-manual-regression.md +23 -0
  53. package/docs/testing/genome-2.0-matrix.md +36 -0
  54. package/docs/testing/genome-2.0-rollout.md +184 -0
  55. package/package.json +50 -0
  56. package/src/agents.js +56 -0
  57. package/src/cli.js +497 -0
  58. package/src/commands/agents.js +142 -0
  59. package/src/commands/cloud.js +1767 -0
  60. package/src/commands/config.js +90 -0
  61. package/src/commands/context-validate.js +91 -0
  62. package/src/commands/doctor.js +123 -0
  63. package/src/commands/genome-doctor.js +41 -0
  64. package/src/commands/genome-migrate.js +49 -0
  65. package/src/commands/i18n-add.js +56 -0
  66. package/src/commands/info.js +41 -0
  67. package/src/commands/init.js +75 -0
  68. package/src/commands/install.js +68 -0
  69. package/src/commands/locale-apply.js +51 -0
  70. package/src/commands/locale-diff.js +126 -0
  71. package/src/commands/mcp-doctor.js +406 -0
  72. package/src/commands/mcp-init.js +379 -0
  73. package/src/commands/package-e2e.js +273 -0
  74. package/src/commands/parallel-assign.js +403 -0
  75. package/src/commands/parallel-doctor.js +437 -0
  76. package/src/commands/parallel-init.js +249 -0
  77. package/src/commands/parallel-status.js +290 -0
  78. package/src/commands/qa-doctor.js +185 -0
  79. package/src/commands/qa-init.js +161 -0
  80. package/src/commands/qa-report.js +58 -0
  81. package/src/commands/qa-run.js +873 -0
  82. package/src/commands/qa-scan.js +337 -0
  83. package/src/commands/runtime.js +948 -0
  84. package/src/commands/scan-project.js +1107 -0
  85. package/src/commands/setup-context.js +650 -0
  86. package/src/commands/smoke.js +426 -0
  87. package/src/commands/squad-doctor.js +358 -0
  88. package/src/commands/squad-export.js +46 -0
  89. package/src/commands/squad-pipeline.js +97 -0
  90. package/src/commands/squad-repair-genomes.js +39 -0
  91. package/src/commands/squad-status.js +424 -0
  92. package/src/commands/squad-validate.js +230 -0
  93. package/src/commands/test-agents.js +194 -0
  94. package/src/commands/update.js +55 -0
  95. package/src/commands/workflow-next.js +594 -0
  96. package/src/commands/workflow-plan.js +108 -0
  97. package/src/constants.js +314 -0
  98. package/src/context-parse-reason.js +22 -0
  99. package/src/context-writer.js +150 -0
  100. package/src/context.js +217 -0
  101. package/src/detector.js +261 -0
  102. package/src/doctor.js +289 -0
  103. package/src/execution-gateway.js +461 -0
  104. package/src/genome-files.js +198 -0
  105. package/src/genome-format.js +442 -0
  106. package/src/genome-schema.js +215 -0
  107. package/src/genomes/bindings.js +281 -0
  108. package/src/genomes.js +467 -0
  109. package/src/i18n/index.js +103 -0
  110. package/src/i18n/messages/en.js +784 -0
  111. package/src/i18n/messages/es.js +718 -0
  112. package/src/i18n/messages/fr.js +725 -0
  113. package/src/i18n/messages/pt-BR.js +818 -0
  114. package/src/i18n/scaffold.js +64 -0
  115. package/src/installer.js +232 -0
  116. package/src/lib/genomes/compat.js +206 -0
  117. package/src/lib/genomes/migrate.js +90 -0
  118. package/src/lib/squads/genome-repair.js +49 -0
  119. package/src/locales.js +84 -0
  120. package/src/onboarding.js +305 -0
  121. package/src/parser.js +53 -0
  122. package/src/prompt-tool.js +20 -0
  123. package/src/qa-html-report.js +472 -0
  124. package/src/runtime-store.js +1527 -0
  125. package/src/squads/apply-genome.js +21 -0
  126. package/src/squads/genome-binding-service.js +154 -0
  127. package/src/updater.js +32 -0
  128. package/src/utils.js +46 -0
  129. package/src/version.js +50 -0
  130. package/template/.aioson/advisors/.gitkeep +1 -0
  131. package/template/.aioson/agents/analyst.md +225 -0
  132. package/template/.aioson/agents/architect.md +221 -0
  133. package/template/.aioson/agents/dev.md +201 -0
  134. package/template/.aioson/agents/discovery-design-doc.md +196 -0
  135. package/template/.aioson/agents/genoma.md +300 -0
  136. package/template/.aioson/agents/orchestrator.md +107 -0
  137. package/template/.aioson/agents/pm.md +89 -0
  138. package/template/.aioson/agents/product.md +361 -0
  139. package/template/.aioson/agents/profiler-enricher.md +266 -0
  140. package/template/.aioson/agents/profiler-forge.md +188 -0
  141. package/template/.aioson/agents/profiler-researcher.md +245 -0
  142. package/template/.aioson/agents/qa.md +344 -0
  143. package/template/.aioson/agents/setup.md +381 -0
  144. package/template/.aioson/agents/squad.md +837 -0
  145. package/template/.aioson/agents/ux-ui.md +416 -0
  146. package/template/.aioson/config.md +56 -0
  147. package/template/.aioson/context/.gitkeep +0 -0
  148. package/template/.aioson/context/parallel/.gitkeep +0 -0
  149. package/template/.aioson/context/spec.md.template +37 -0
  150. package/template/.aioson/genomas/.gitkeep +0 -0
  151. package/template/.aioson/locales/en/agents/analyst.md +214 -0
  152. package/template/.aioson/locales/en/agents/architect.md +210 -0
  153. package/template/.aioson/locales/en/agents/dev.md +187 -0
  154. package/template/.aioson/locales/en/agents/discovery-design-doc.md +27 -0
  155. package/template/.aioson/locales/en/agents/genoma.md +212 -0
  156. package/template/.aioson/locales/en/agents/orchestrator.md +105 -0
  157. package/template/.aioson/locales/en/agents/pm.md +77 -0
  158. package/template/.aioson/locales/en/agents/product.md +310 -0
  159. package/template/.aioson/locales/en/agents/profiler-enricher.md +5 -0
  160. package/template/.aioson/locales/en/agents/profiler-forge.md +5 -0
  161. package/template/.aioson/locales/en/agents/profiler-researcher.md +5 -0
  162. package/template/.aioson/locales/en/agents/qa.md +214 -0
  163. package/template/.aioson/locales/en/agents/setup.md +342 -0
  164. package/template/.aioson/locales/en/agents/squad.md +247 -0
  165. package/template/.aioson/locales/en/agents/ux-ui.md +320 -0
  166. package/template/.aioson/locales/es/agents/analyst.md +203 -0
  167. package/template/.aioson/locales/es/agents/architect.md +208 -0
  168. package/template/.aioson/locales/es/agents/dev.md +183 -0
  169. package/template/.aioson/locales/es/agents/discovery-design-doc.md +19 -0
  170. package/template/.aioson/locales/es/agents/genoma.md +102 -0
  171. package/template/.aioson/locales/es/agents/orchestrator.md +108 -0
  172. package/template/.aioson/locales/es/agents/pm.md +81 -0
  173. package/template/.aioson/locales/es/agents/product.md +310 -0
  174. package/template/.aioson/locales/es/agents/profiler-enricher.md +5 -0
  175. package/template/.aioson/locales/es/agents/profiler-forge.md +5 -0
  176. package/template/.aioson/locales/es/agents/profiler-researcher.md +5 -0
  177. package/template/.aioson/locales/es/agents/qa.md +163 -0
  178. package/template/.aioson/locales/es/agents/setup.md +347 -0
  179. package/template/.aioson/locales/es/agents/squad.md +247 -0
  180. package/template/.aioson/locales/es/agents/ux-ui.md +201 -0
  181. package/template/.aioson/locales/fr/agents/analyst.md +203 -0
  182. package/template/.aioson/locales/fr/agents/architect.md +208 -0
  183. package/template/.aioson/locales/fr/agents/dev.md +183 -0
  184. package/template/.aioson/locales/fr/agents/discovery-design-doc.md +19 -0
  185. package/template/.aioson/locales/fr/agents/genoma.md +102 -0
  186. package/template/.aioson/locales/fr/agents/orchestrator.md +108 -0
  187. package/template/.aioson/locales/fr/agents/pm.md +81 -0
  188. package/template/.aioson/locales/fr/agents/product.md +310 -0
  189. package/template/.aioson/locales/fr/agents/profiler-enricher.md +5 -0
  190. package/template/.aioson/locales/fr/agents/profiler-forge.md +5 -0
  191. package/template/.aioson/locales/fr/agents/profiler-researcher.md +5 -0
  192. package/template/.aioson/locales/fr/agents/qa.md +163 -0
  193. package/template/.aioson/locales/fr/agents/setup.md +347 -0
  194. package/template/.aioson/locales/fr/agents/squad.md +247 -0
  195. package/template/.aioson/locales/fr/agents/ux-ui.md +201 -0
  196. package/template/.aioson/locales/pt-BR/agents/analyst.md +217 -0
  197. package/template/.aioson/locales/pt-BR/agents/architect.md +213 -0
  198. package/template/.aioson/locales/pt-BR/agents/dev.md +198 -0
  199. package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +198 -0
  200. package/template/.aioson/locales/pt-BR/agents/genoma.md +297 -0
  201. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +108 -0
  202. package/template/.aioson/locales/pt-BR/agents/pm.md +81 -0
  203. package/template/.aioson/locales/pt-BR/agents/product.md +316 -0
  204. package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +5 -0
  205. package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +5 -0
  206. package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +5 -0
  207. package/template/.aioson/locales/pt-BR/agents/qa.md +217 -0
  208. package/template/.aioson/locales/pt-BR/agents/setup.md +371 -0
  209. package/template/.aioson/locales/pt-BR/agents/squad.md +772 -0
  210. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +322 -0
  211. package/template/.aioson/mcp/servers.md +24 -0
  212. package/template/.aioson/profiler-reports/.gitkeep +1 -0
  213. package/template/.aioson/schemas/content-blueprint.schema.json +30 -0
  214. package/template/.aioson/schemas/genome-meta.schema.json +150 -0
  215. package/template/.aioson/schemas/genome.schema.json +115 -0
  216. package/template/.aioson/schemas/readiness.schema.json +27 -0
  217. package/template/.aioson/schemas/squad-blueprint.schema.json +172 -0
  218. package/template/.aioson/schemas/squad-manifest.schema.json +276 -0
  219. package/template/.aioson/skills/dynamic/README.md +30 -0
  220. package/template/.aioson/skills/dynamic/cardano-docs.md +16 -0
  221. package/template/.aioson/skills/dynamic/ethereum-docs.md +17 -0
  222. package/template/.aioson/skills/dynamic/flux-ui-docs.md +13 -0
  223. package/template/.aioson/skills/dynamic/laravel-docs.md +41 -0
  224. package/template/.aioson/skills/dynamic/npm-packages.md +16 -0
  225. package/template/.aioson/skills/dynamic/solana-docs.md +16 -0
  226. package/template/.aioson/skills/references/premium-command-center-ui/master-application-prompt.md +79 -0
  227. package/template/.aioson/skills/references/premium-command-center-ui/operational-ux-playbook.md +253 -0
  228. package/template/.aioson/skills/references/premium-command-center-ui/quality-validation-checklist.md +82 -0
  229. package/template/.aioson/skills/references/premium-command-center-ui/visual-system-and-component-patterns.md +270 -0
  230. package/template/.aioson/skills/static/django-patterns.md +342 -0
  231. package/template/.aioson/skills/static/fastapi-patterns.md +344 -0
  232. package/template/.aioson/skills/static/filament-patterns.md +267 -0
  233. package/template/.aioson/skills/static/flux-ui-components.md +262 -0
  234. package/template/.aioson/skills/static/git-conventions.md +227 -0
  235. package/template/.aioson/skills/static/interface-design.md +372 -0
  236. package/template/.aioson/skills/static/jetstream-setup.md +200 -0
  237. package/template/.aioson/skills/static/laravel-conventions.md +491 -0
  238. package/template/.aioson/skills/static/nextjs-patterns.md +321 -0
  239. package/template/.aioson/skills/static/node-express-patterns.md +317 -0
  240. package/template/.aioson/skills/static/node-typescript-patterns.md +282 -0
  241. package/template/.aioson/skills/static/premium-command-center-ui.md +190 -0
  242. package/template/.aioson/skills/static/rails-conventions.md +307 -0
  243. package/template/.aioson/skills/static/react-motion-patterns.md +577 -0
  244. package/template/.aioson/skills/static/static-html-patterns.md +1935 -0
  245. package/template/.aioson/skills/static/tall-stack-patterns.md +286 -0
  246. package/template/.aioson/skills/static/ui-ux-modern.md +75 -0
  247. package/template/.aioson/skills/static/web3-cardano-patterns.md +337 -0
  248. package/template/.aioson/skills/static/web3-ethereum-patterns.md +310 -0
  249. package/template/.aioson/skills/static/web3-security-checklist.md +284 -0
  250. package/template/.aioson/skills/static/web3-solana-patterns.md +324 -0
  251. package/template/.aioson/squads/.artisan/.gitkeep +0 -0
  252. package/template/.aioson/squads/.gitkeep +0 -0
  253. package/template/.aioson/squads/memory.md +5 -0
  254. package/template/.aioson/tasks/squad-analyze.md +83 -0
  255. package/template/.aioson/tasks/squad-create.md +99 -0
  256. package/template/.aioson/tasks/squad-design.md +100 -0
  257. package/template/.aioson/tasks/squad-export.md +20 -0
  258. package/template/.aioson/tasks/squad-extend.md +68 -0
  259. package/template/.aioson/tasks/squad-pipeline.md +122 -0
  260. package/template/.aioson/tasks/squad-repair.md +85 -0
  261. package/template/.aioson/tasks/squad-validate.md +58 -0
  262. package/template/.aioson/templates/squads/content-basic/template.json +21 -0
  263. package/template/.aioson/templates/squads/media-channel/template.json +24 -0
  264. package/template/.aioson/templates/squads/research-analysis/template.json +22 -0
  265. package/template/.aioson/templates/squads/software-delivery/template.json +21 -0
  266. package/template/.claude/commands/aioson/analyst.md +5 -0
  267. package/template/.claude/commands/aioson/architect.md +5 -0
  268. package/template/.claude/commands/aioson/dev.md +5 -0
  269. package/template/.claude/commands/aioson/orchestrator.md +5 -0
  270. package/template/.claude/commands/aioson/pm.md +5 -0
  271. package/template/.claude/commands/aioson/qa.md +5 -0
  272. package/template/.claude/commands/aioson/setup.md +5 -0
  273. package/template/.claude/commands/aioson/ux-ui.md +5 -0
  274. package/template/.gemini/GEMINI.md +10 -0
  275. package/template/.gemini/commands/aios-analyst.toml +4 -0
  276. package/template/.gemini/commands/aios-architect.toml +7 -0
  277. package/template/.gemini/commands/aios-dev.toml +8 -0
  278. package/template/.gemini/commands/aios-discovery-design-doc.toml +4 -0
  279. package/template/.gemini/commands/aios-orchestrator.toml +8 -0
  280. package/template/.gemini/commands/aios-pm.toml +8 -0
  281. package/template/.gemini/commands/aios-product.toml +4 -0
  282. package/template/.gemini/commands/aios-qa.toml +6 -0
  283. package/template/.gemini/commands/aios-setup.toml +3 -0
  284. package/template/.gemini/commands/aios-ux-ui.toml +8 -0
  285. package/template/AGENTS.md +67 -0
  286. package/template/CLAUDE.md +31 -0
  287. package/template/OPENCODE.md +24 -0
  288. package/template/aioson-models.json +40 -0
@@ -0,0 +1,307 @@
1
+ # Rails Conventions
2
+
3
+ > Rails way, done intentionally. Convention over configuration — but know what the conventions are.
4
+
5
+ ---
6
+
7
+ ## Controllers — HTTP coordination only
8
+
9
+ ```ruby
10
+ # WRONG — business logic in controller
11
+ class AppointmentsController < ApplicationController
12
+ def create
13
+ if Appointment.where(doctor_id: params[:doctor_id], date: params[:date]).exists?
14
+ render json: { error: 'Already booked' }, status: :conflict and return
15
+ end
16
+ appointment = Appointment.create!(appointment_params)
17
+ AppointmentMailer.confirmation(appointment).deliver_later
18
+ render json: appointment, status: :created
19
+ end
20
+ end
21
+
22
+ # RIGHT — controller as coordinator
23
+ class AppointmentsController < ApplicationController
24
+ before_action :authenticate_user!
25
+
26
+ def create
27
+ result = CreateAppointmentService.call(
28
+ user: current_user,
29
+ params: appointment_params
30
+ )
31
+ if result.success?
32
+ render json: AppointmentSerializer.new(result.appointment), status: :created
33
+ else
34
+ render json: { errors: result.errors }, status: :unprocessable_entity
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def appointment_params
41
+ params.require(:appointment).permit(:doctor_id, :date, :notes)
42
+ end
43
+ end
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Service Objects — single responsibility
49
+
50
+ ```ruby
51
+ # app/services/create_appointment_service.rb
52
+ class CreateAppointmentService
53
+ Result = Data.define(:success?, :appointment, :errors)
54
+
55
+ def self.call(user:, params:)
56
+ new(user: user, params: params).call
57
+ end
58
+
59
+ def initialize(user:, params:)
60
+ @user = user
61
+ @params = params
62
+ end
63
+
64
+ def call
65
+ check_conflict!
66
+
67
+ appointment = Appointment.create!(
68
+ user: @user,
69
+ doctor_id: @params[:doctor_id],
70
+ date: @params[:date],
71
+ notes: @params[:notes]
72
+ )
73
+
74
+ AppointmentCreatedJob.perform_later(appointment.id)
75
+
76
+ Result.new(success?: true, appointment: appointment, errors: [])
77
+ rescue ActiveRecord::RecordInvalid => e
78
+ Result.new(success?: false, appointment: nil, errors: e.record.errors.full_messages)
79
+ rescue ConflictError => e
80
+ Result.new(success?: false, appointment: nil, errors: [e.message])
81
+ end
82
+
83
+ private
84
+
85
+ def check_conflict!
86
+ conflict = Appointment
87
+ .where(doctor_id: @params[:doctor_id])
88
+ .where(date: @params[:date])
89
+ .where.not(status: :cancelled)
90
+ .exists?
91
+
92
+ raise ConflictError, 'This time slot is already booked.' if conflict
93
+ end
94
+ end
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Models — domain behavior, not business workflows
100
+
101
+ ```ruby
102
+ # app/models/appointment.rb
103
+ class Appointment < ApplicationRecord
104
+ belongs_to :user
105
+ belongs_to :doctor
106
+
107
+ # Validations belong in the model
108
+ validates :date, presence: true
109
+ validates :status, inclusion: { in: %w[pending confirmed cancelled] }
110
+ validate :date_must_be_in_future, on: :create
111
+
112
+ # Scopes for reusable query logic
113
+ scope :upcoming, -> { where('date > ?', Time.current).order(:date) }
114
+ scope :pending, -> { where(status: :pending) }
115
+ scope :for_doctor, ->(doctor_id) { where(doctor_id: doctor_id) }
116
+
117
+ # Enums for status fields
118
+ enum :status, { pending: 'pending', confirmed: 'confirmed', cancelled: 'cancelled' }
119
+
120
+ # Business state checks belong here
121
+ def cancellable?
122
+ pending? || confirmed?
123
+ end
124
+
125
+ def within_cancellation_window?
126
+ date > 24.hours.from_now
127
+ end
128
+
129
+ private
130
+
131
+ def date_must_be_in_future
132
+ errors.add(:date, 'must be in the future') if date.present? && date <= Time.current
133
+ end
134
+ end
135
+ ```
136
+
137
+ ---
138
+
139
+ ## Active Record — query patterns
140
+
141
+ ```ruby
142
+ # N+1 prevention — always eager load
143
+ # WRONG
144
+ @appointments = Appointment.all
145
+ @appointments.each { |a| puts a.doctor.name } # N queries
146
+
147
+ # RIGHT
148
+ @appointments = Appointment.includes(:doctor, user: :profile).upcoming
149
+
150
+ # Efficient bulk operations
151
+ Appointment.where(status: :pending, date: ..Time.current).update_all(status: :cancelled)
152
+
153
+ # Pagination with Kaminari or Pagy
154
+ @appointments = Appointment.includes(:doctor).upcoming.page(params[:page]).per(20)
155
+
156
+ # Select only needed columns for lists
157
+ @doctors = Doctor.select(:id, :name, :specialty).order(:name)
158
+
159
+ # Avoid select * for large tables
160
+ Appointment.select(:id, :date, :status, :doctor_id).upcoming
161
+ ```
162
+
163
+ ---
164
+
165
+ ## Jobs — async and retriable
166
+
167
+ ```ruby
168
+ # app/jobs/appointment_created_job.rb
169
+ class AppointmentCreatedJob < ApplicationJob
170
+ queue_as :default
171
+ retry_on StandardError, wait: :polynomially_longer, attempts: 3
172
+
173
+ def perform(appointment_id)
174
+ appointment = Appointment.find(appointment_id)
175
+ AppointmentMailer.confirmation(appointment).deliver_now
176
+ SlackNotificationService.call(event: :appointment_created, resource: appointment)
177
+ rescue ActiveRecord::RecordNotFound
178
+ # Record deleted — no action needed, stop retrying
179
+ end
180
+ end
181
+ ```
182
+
183
+ ---
184
+
185
+ ## Mailers
186
+
187
+ ```ruby
188
+ # app/mailers/appointment_mailer.rb
189
+ class AppointmentMailer < ApplicationMailer
190
+ default from: 'noreply@clinic.com'
191
+
192
+ def confirmation(appointment)
193
+ @appointment = appointment
194
+ @doctor = appointment.doctor
195
+ @patient = appointment.user
196
+
197
+ mail(
198
+ to: @patient.email,
199
+ subject: "Appointment confirmed with #{@doctor.name}"
200
+ )
201
+ end
202
+ end
203
+
204
+ # Always use deliver_later — never deliver_now in request cycle
205
+ AppointmentMailer.confirmation(appointment).deliver_later
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Serializers (with ActiveModel::Serializer or JSONAPI)
211
+
212
+ ```ruby
213
+ # app/serializers/appointment_serializer.rb
214
+ class AppointmentSerializer < ActiveModel::Serializer
215
+ attributes :id, :date, :status, :notes
216
+
217
+ belongs_to :doctor, serializer: DoctorSerializer
218
+ belongs_to :user, serializer: UserSerializer
219
+
220
+ attribute :can_cancel do
221
+ scope&.can?(:cancel, object) # scope = current_user
222
+ end
223
+
224
+ attribute :formatted_date do
225
+ object.date.strftime('%B %-d, %Y at %H:%M')
226
+ end
227
+ end
228
+ ```
229
+
230
+ ---
231
+
232
+ ## Authorization with Pundit
233
+
234
+ ```ruby
235
+ # app/policies/appointment_policy.rb
236
+ class AppointmentPolicy < ApplicationPolicy
237
+ def show? = record.user == user || user.admin?
238
+ def cancel? = show? && record.within_cancellation_window?
239
+ def update? = user.admin?
240
+ def destroy? = user.admin?
241
+ end
242
+
243
+ # In controller
244
+ def cancel
245
+ @appointment = Appointment.find(params[:id])
246
+ authorize @appointment, :cancel?
247
+ # ...
248
+ end
249
+ ```
250
+
251
+ ---
252
+
253
+ ## Testing with RSpec
254
+
255
+ ```ruby
256
+ # spec/services/create_appointment_service_spec.rb
257
+ RSpec.describe CreateAppointmentService do
258
+ subject(:service) { described_class.call(user: user, params: params) }
259
+
260
+ let(:user) { create(:user) }
261
+ let(:doctor) { create(:doctor) }
262
+ let(:params) { { doctor_id: doctor.id, date: 2.days.from_now } }
263
+
264
+ it 'creates an appointment' do
265
+ expect { service }.to change(Appointment, :count).by(1)
266
+ expect(service).to be_success
267
+ end
268
+
269
+ it 'returns error when time slot is taken' do
270
+ create(:appointment, doctor: doctor, date: params[:date])
271
+ expect(service).not_to be_success
272
+ expect(service.errors).to include('This time slot is already booked.')
273
+ end
274
+
275
+ it 'enqueues confirmation job' do
276
+ expect { service }.to have_enqueued_job(AppointmentCreatedJob)
277
+ end
278
+ end
279
+
280
+ # spec/requests/appointments_spec.rb
281
+ RSpec.describe 'POST /appointments', type: :request do
282
+ let(:user) { create(:user) }
283
+ before { sign_in user }
284
+
285
+ it 'returns 201 on valid input' do
286
+ post appointments_path, params: { appointment: { doctor_id: create(:doctor).id, date: 2.days.from_now } }
287
+ expect(response).to have_http_status(:created)
288
+ end
289
+ end
290
+ ```
291
+
292
+ ---
293
+
294
+ ## ALWAYS
295
+ - Service objects for multi-step or conditional business workflows
296
+ - Enums for status/type columns (with string values for readability)
297
+ - Scopes for reusable query conditions
298
+ - `includes()` to prevent N+1 in list views
299
+ - `deliver_later` for all mailers
300
+ - Pundit policies for authorization
301
+
302
+ ## NEVER
303
+ - Business logic in controllers
304
+ - Raw SQL for queries that Active Record can express
305
+ - `deliver_now` in the request cycle
306
+ - `update_attributes` (deprecated) — use `update`
307
+ - Callbacks for cross-model side effects (use services + jobs instead)