agent-notes 2.0.4__py3-none-any.whl

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 (162) hide show
  1. agent_notes/VERSION +1 -0
  2. agent_notes/__init__.py +1 -0
  3. agent_notes/__main__.py +4 -0
  4. agent_notes/cli.py +348 -0
  5. agent_notes/commands/__init__.py +27 -0
  6. agent_notes/commands/_install_helpers.py +262 -0
  7. agent_notes/commands/build.py +170 -0
  8. agent_notes/commands/doctor.py +112 -0
  9. agent_notes/commands/info.py +95 -0
  10. agent_notes/commands/install.py +99 -0
  11. agent_notes/commands/list.py +169 -0
  12. agent_notes/commands/memory.py +430 -0
  13. agent_notes/commands/regenerate.py +152 -0
  14. agent_notes/commands/set_role.py +143 -0
  15. agent_notes/commands/uninstall.py +26 -0
  16. agent_notes/commands/update.py +169 -0
  17. agent_notes/commands/validate.py +199 -0
  18. agent_notes/commands/wizard.py +720 -0
  19. agent_notes/config.py +154 -0
  20. agent_notes/data/agents/agents.yaml +352 -0
  21. agent_notes/data/agents/analyst.md +45 -0
  22. agent_notes/data/agents/api-reviewer.md +47 -0
  23. agent_notes/data/agents/architect.md +46 -0
  24. agent_notes/data/agents/coder.md +28 -0
  25. agent_notes/data/agents/database-specialist.md +45 -0
  26. agent_notes/data/agents/debugger.md +47 -0
  27. agent_notes/data/agents/devil.md +47 -0
  28. agent_notes/data/agents/devops.md +38 -0
  29. agent_notes/data/agents/explorer.md +23 -0
  30. agent_notes/data/agents/integrations.md +44 -0
  31. agent_notes/data/agents/lead.md +216 -0
  32. agent_notes/data/agents/performance-profiler.md +44 -0
  33. agent_notes/data/agents/refactorer.md +48 -0
  34. agent_notes/data/agents/reviewer.md +44 -0
  35. agent_notes/data/agents/security-auditor.md +44 -0
  36. agent_notes/data/agents/system-auditor.md +38 -0
  37. agent_notes/data/agents/tech-writer.md +32 -0
  38. agent_notes/data/agents/test-runner.md +36 -0
  39. agent_notes/data/agents/test-writer.md +39 -0
  40. agent_notes/data/cli/claude.yaml +25 -0
  41. agent_notes/data/cli/copilot.yaml +18 -0
  42. agent_notes/data/cli/opencode.yaml +22 -0
  43. agent_notes/data/commands/brainstorm.md +8 -0
  44. agent_notes/data/commands/debug.md +9 -0
  45. agent_notes/data/commands/review.md +10 -0
  46. agent_notes/data/global-claude.md +290 -0
  47. agent_notes/data/global-copilot.md +27 -0
  48. agent_notes/data/global-opencode.md +40 -0
  49. agent_notes/data/hooks/session-context.md.tpl +19 -0
  50. agent_notes/data/models/claude-haiku-4-5.yaml +15 -0
  51. agent_notes/data/models/claude-opus-4-1.yaml +16 -0
  52. agent_notes/data/models/claude-opus-4-5.yaml +16 -0
  53. agent_notes/data/models/claude-opus-4-6.yaml +16 -0
  54. agent_notes/data/models/claude-opus-4-7.yaml +15 -0
  55. agent_notes/data/models/claude-sonnet-4-5.yaml +16 -0
  56. agent_notes/data/models/claude-sonnet-4-6.yaml +15 -0
  57. agent_notes/data/models/claude-sonnet-4.yaml +16 -0
  58. agent_notes/data/pricing.yaml +33 -0
  59. agent_notes/data/roles/orchestrator.yaml +5 -0
  60. agent_notes/data/roles/reasoner.yaml +5 -0
  61. agent_notes/data/roles/scout.yaml +5 -0
  62. agent_notes/data/roles/worker.yaml +5 -0
  63. agent_notes/data/rules/code-quality.md +9 -0
  64. agent_notes/data/rules/safety.md +10 -0
  65. agent_notes/data/scripts/cost-report +211 -0
  66. agent_notes/data/skills/brainstorming/SKILL.md +57 -0
  67. agent_notes/data/skills/code-review/SKILL.md +64 -0
  68. agent_notes/data/skills/debugging-protocol/SKILL.md +51 -0
  69. agent_notes/data/skills/docker-compose/SKILL.md +318 -0
  70. agent_notes/data/skills/docker-compose-advanced/SKILL.md +575 -0
  71. agent_notes/data/skills/docker-dockerfile/SKILL.md +385 -0
  72. agent_notes/data/skills/docker-dockerfile-languages/SKILL.md +293 -0
  73. agent_notes/data/skills/git/SKILL.md +87 -0
  74. agent_notes/data/skills/rails-active-storage/SKILL.md +321 -0
  75. agent_notes/data/skills/rails-broadcasting/SKILL.md +374 -0
  76. agent_notes/data/skills/rails-concerns/SKILL.md +806 -0
  77. agent_notes/data/skills/rails-controllers/SKILL.md +510 -0
  78. agent_notes/data/skills/rails-controllers-advanced/SKILL.md +441 -0
  79. agent_notes/data/skills/rails-helpers/SKILL.md +677 -0
  80. agent_notes/data/skills/rails-initializers/SKILL.md +79 -0
  81. agent_notes/data/skills/rails-javascript/SKILL.md +567 -0
  82. agent_notes/data/skills/rails-jobs/SKILL.md +700 -0
  83. agent_notes/data/skills/rails-kamal/SKILL.md +483 -0
  84. agent_notes/data/skills/rails-lib/SKILL.md +101 -0
  85. agent_notes/data/skills/rails-mailers/SKILL.md +321 -0
  86. agent_notes/data/skills/rails-migrations/SKILL.md +268 -0
  87. agent_notes/data/skills/rails-models/SKILL.md +459 -0
  88. agent_notes/data/skills/rails-models-advanced/SKILL.md +398 -0
  89. agent_notes/data/skills/rails-routes/SKILL.md +804 -0
  90. agent_notes/data/skills/rails-style/SKILL.md +538 -0
  91. agent_notes/data/skills/rails-testing-controllers/SKILL.md +343 -0
  92. agent_notes/data/skills/rails-testing-models/SKILL.md +296 -0
  93. agent_notes/data/skills/rails-testing-system/SKILL.md +375 -0
  94. agent_notes/data/skills/rails-validations/SKILL.md +108 -0
  95. agent_notes/data/skills/rails-view-components/SKILL.md +511 -0
  96. agent_notes/data/skills/rails-view-components-advanced/SKILL.md +376 -0
  97. agent_notes/data/skills/rails-views/SKILL.md +413 -0
  98. agent_notes/data/skills/rails-views-advanced/SKILL.md +450 -0
  99. agent_notes/data/skills/refactoring-protocol/SKILL.md +64 -0
  100. agent_notes/data/skills/tdd/SKILL.md +57 -0
  101. agent_notes/data/templates/__init__.py +1 -0
  102. agent_notes/data/templates/__pycache__/__init__.cpython-314.pyc +0 -0
  103. agent_notes/data/templates/frontmatter/__init__.py +1 -0
  104. agent_notes/data/templates/frontmatter/__pycache__/__init__.cpython-314.pyc +0 -0
  105. agent_notes/data/templates/frontmatter/__pycache__/claude.cpython-314.pyc +0 -0
  106. agent_notes/data/templates/frontmatter/__pycache__/cursor.cpython-314.pyc +0 -0
  107. agent_notes/data/templates/frontmatter/__pycache__/opencode.cpython-314.pyc +0 -0
  108. agent_notes/data/templates/frontmatter/claude.py +44 -0
  109. agent_notes/data/templates/frontmatter/opencode.py +104 -0
  110. agent_notes/doctor_checks.py +189 -0
  111. agent_notes/domain/__init__.py +17 -0
  112. agent_notes/domain/agent.py +34 -0
  113. agent_notes/domain/cli_backend.py +40 -0
  114. agent_notes/domain/diagnostics.py +29 -0
  115. agent_notes/domain/diff.py +44 -0
  116. agent_notes/domain/model.py +27 -0
  117. agent_notes/domain/role.py +13 -0
  118. agent_notes/domain/rule.py +13 -0
  119. agent_notes/domain/skill.py +15 -0
  120. agent_notes/domain/state.py +46 -0
  121. agent_notes/install_state.py +11 -0
  122. agent_notes/registries/__init__.py +16 -0
  123. agent_notes/registries/_base.py +46 -0
  124. agent_notes/registries/agent_registry.py +107 -0
  125. agent_notes/registries/cli_registry.py +89 -0
  126. agent_notes/registries/model_registry.py +85 -0
  127. agent_notes/registries/role_registry.py +64 -0
  128. agent_notes/registries/rule_registry.py +80 -0
  129. agent_notes/registries/skill_registry.py +141 -0
  130. agent_notes/services/__init__.py +8 -0
  131. agent_notes/services/diagnostics/__init__.py +47 -0
  132. agent_notes/services/diagnostics/_checks.py +272 -0
  133. agent_notes/services/diagnostics/_display.py +346 -0
  134. agent_notes/services/diagnostics/_fix.py +169 -0
  135. agent_notes/services/diff.py +349 -0
  136. agent_notes/services/fs.py +195 -0
  137. agent_notes/services/install_state_builder.py +210 -0
  138. agent_notes/services/installer.py +293 -0
  139. agent_notes/services/memory_backend.py +155 -0
  140. agent_notes/services/rendering.py +329 -0
  141. agent_notes/services/session_context.py +23 -0
  142. agent_notes/services/settings_writer.py +79 -0
  143. agent_notes/services/state_store.py +249 -0
  144. agent_notes/services/ui.py +419 -0
  145. agent_notes/services/user_config.py +62 -0
  146. agent_notes/services/validation.py +67 -0
  147. agent_notes/state.py +21 -0
  148. agent_notes-2.0.4.dist-info/METADATA +14 -0
  149. agent_notes-2.0.4.dist-info/RECORD +162 -0
  150. agent_notes-2.0.4.dist-info/WHEEL +5 -0
  151. agent_notes-2.0.4.dist-info/entry_points.txt +2 -0
  152. agent_notes-2.0.4.dist-info/licenses/LICENSE +21 -0
  153. agent_notes-2.0.4.dist-info/top_level.txt +2 -0
  154. tests/conftest.py +20 -0
  155. tests/functional/__init__.py +0 -0
  156. tests/functional/test_build_commands.py +88 -0
  157. tests/functional/test_registries.py +128 -0
  158. tests/integration/__init__.py +0 -0
  159. tests/integration/test_build_output.py +129 -0
  160. tests/plugins/__init__.py +0 -0
  161. tests/plugins/test_agents.py +93 -0
  162. tests/plugins/test_skills.py +77 -0
@@ -0,0 +1,459 @@
1
+ ---
2
+ name: rails-models
3
+ description: "Rails models: structure template, associations, callbacks, scopes, and validations"
4
+ group: rails
5
+ ---
6
+
7
+ # Models
8
+
9
+ Comprehensive patterns and best practices for Rails Active Record models.
10
+
11
+ ---
12
+
13
+ ## File Structure
14
+
15
+ ```
16
+ app/models/
17
+ ├── card.rb # Main model
18
+ ├── card/
19
+ │ ├── closeable.rb # Feature concern
20
+ │ ├── golden.rb # Feature concern
21
+ │ ├── pinnable.rb # Feature concern
22
+ │ └── goldness.rb # Associated model (Card::Goldness)
23
+ ├── concerns/
24
+ │ ├── eventable.rb # Shared concern (multiple models)
25
+ │ └── searchable.rb # Shared concern
26
+ └── application_record.rb # Base model
27
+ ```
28
+
29
+ ---
30
+
31
+ ## Model Structure Template
32
+
33
+ ```ruby
34
+ class Card < ApplicationRecord
35
+ # 1. CONCERNS (alphabetically for easy scanning)
36
+ include Assignable, Attachments, Broadcastable, Closeable,
37
+ Colored, Entropic, Eventable, Exportable, Golden, Mentions,
38
+ Multistep, Pinnable, Postponable, Promptable, Readable,
39
+ Searchable, Stallable, Statuses, Storage::Tracked,
40
+ Taggable, Triageable, Watchable
41
+
42
+ # 2. ASSOCIATIONS
43
+ # Order: belongs_to, has_one, has_many, has_and_belongs_to_many,
44
+ # has_one_attached, has_many_attached, has_rich_text
45
+
46
+ # belongs_to (with defaults if needed)
47
+ belongs_to :account, default: -> { board.account }
48
+ belongs_to :board
49
+ belongs_to :creator, class_name: "User", default: -> { Current.user }
50
+
51
+ # has_one
52
+ has_one :closure, dependent: :destroy
53
+ has_one :goldness, dependent: :destroy
54
+
55
+ # has_many
56
+ has_many :comments, dependent: :destroy
57
+ has_many :events, as: :eventable, dependent: :destroy
58
+ has_many :assignments, dependent: :destroy
59
+ has_many :assignees, through: :assignments, source: :user
60
+
61
+ # Active Storage attachments
62
+ has_one_attached :image, dependent: :purge_later
63
+
64
+ # Action Text
65
+ has_rich_text :description
66
+
67
+ # 3. CALLBACKS (in lifecycle order)
68
+ # Lifecycle: before_validation, after_validation, before_save,
69
+ # around_save, before_create, around_create, after_create,
70
+ # before_update, around_update, after_update,
71
+ # before_destroy, around_destroy, after_destroy,
72
+ # after_save, after_commit, after_rollback
73
+
74
+ before_validation :normalize_title
75
+ before_save :set_default_title, if: :published?
76
+ before_create :assign_number
77
+
78
+ after_save -> { board.touch }, if: :published?
79
+ after_touch -> { board.touch }, if: :published?
80
+ after_update :handle_board_change, if: :saved_change_to_board_id?
81
+
82
+ after_create_commit :broadcast_creation
83
+ after_update_commit :broadcast_updates
84
+ after_destroy_commit :broadcast_removal
85
+
86
+ # 4. VALIDATIONS
87
+ validates :title, presence: true, if: :published?
88
+ validates :number, uniqueness: { scope: :account_id }
89
+ validates :status, inclusion: { in: %w[draft published] }
90
+
91
+ # Custom validations
92
+ validate :ensure_board_accessible, if: :board_id_changed?
93
+
94
+ # 5. NORMALIZATIONS (Rails 7.1+)
95
+ normalizes :title, with: -> value { value.strip }
96
+
97
+ # 6. SCOPES (grouped by purpose)
98
+ # Ordering scopes
99
+ scope :reverse_chronologically, -> { order created_at: :desc, id: :desc }
100
+ scope :chronologically, -> { order created_at: :asc, id: :asc }
101
+ scope :latest, -> { order last_active_at: :desc, id: :desc }
102
+
103
+ # Filtering scopes
104
+ scope :published, -> { where(status: :published) }
105
+ scope :drafted, -> { where(status: :draft) }
106
+
107
+ # Association scopes
108
+ scope :assigned_to, ->(users) {
109
+ joins(:assignees).where(assignees: { user: users })
110
+ }
111
+ scope :tagged_with, ->(tags) {
112
+ joins(:taggings).where(taggings: { tag: tags })
113
+ }
114
+
115
+ # Complex query scopes
116
+ scope :preloaded, -> {
117
+ with_users.preload(:column, :tags, :steps, :closure, :goldness,
118
+ board: [:columns]).with_rich_text_description_and_embeds
119
+ }
120
+
121
+ # Conditional scopes
122
+ scope :indexed_by, ->(index) do
123
+ case index
124
+ when "stalled" then stalled
125
+ when "active" then published.latest
126
+ when "closed" then closed
127
+ else all
128
+ end
129
+ end
130
+
131
+ # 7. ENUMS
132
+ enum :status, %w[ draft published ].index_by(&:itself)
133
+ enum :color, %w[ red blue green ].index_by(&:itself), prefix: true
134
+
135
+ # 8. DELEGATIONS
136
+ delegate :accessible_to?, to: :board
137
+ delegate :name, to: :creator, prefix: true
138
+
139
+ # 9. PUBLIC METHODS
140
+ # Action methods (change state, use transactions)
141
+ def move_to(new_board)
142
+ transaction do
143
+ update!(board: new_board)
144
+ events.update_all(board_id: new_board.id)
145
+ end
146
+ end
147
+
148
+ def archive
149
+ transaction do
150
+ update!(archived_at: Time.current)
151
+ track_event :archived
152
+ end
153
+ end
154
+
155
+ # Query methods (return data/booleans)
156
+ def archived?
157
+ archived_at.present?
158
+ end
159
+
160
+ def filled?
161
+ title.present? || description.present?
162
+ end
163
+
164
+ # 10. PRIVATE METHODS (ordered by invocation)
165
+ private
166
+ def normalize_title
167
+ self.title = title&.strip
168
+ end
169
+
170
+ def set_default_title
171
+ self.title = "Untitled" if title.blank?
172
+ end
173
+
174
+ def assign_number
175
+ self.number ||= account.increment!(:cards_count).cards_count
176
+ end
177
+
178
+ def handle_board_change
179
+ old_board = account.boards.find_by(id: board_id_before_last_save)
180
+
181
+ transaction do
182
+ update! column: nil
183
+ track_board_change_event(old_board.name)
184
+ grant_access_to_assignees unless board.all_access?
185
+ end
186
+
187
+ remove_inaccessible_notifications_later
188
+ end
189
+
190
+ def track_board_change_event(old_board_name)
191
+ track_event "board_changed",
192
+ particulars: { old_board: old_board_name, new_board: board.name }
193
+ end
194
+
195
+ def grant_access_to_assignees
196
+ board.accesses.grant_to(assignees)
197
+ end
198
+
199
+ def ensure_board_accessible
200
+ unless creator.boards.include?(board)
201
+ errors.add(:board, "must be accessible to creator")
202
+ end
203
+ end
204
+ end
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Association Patterns
210
+
211
+ ### belongs_to with Defaults
212
+
213
+ ```ruby
214
+ # Default value from lambda
215
+ belongs_to :account, default: -> { board.account }
216
+ belongs_to :creator, class_name: "User", default: -> { Current.user }
217
+
218
+ # Optional association
219
+ belongs_to :parent, optional: true
220
+
221
+ # Polymorphic
222
+ belongs_to :eventable, polymorphic: true
223
+ ```
224
+
225
+ ### has_many with Extensions
226
+
227
+ ```ruby
228
+ has_many :accesses, dependent: :delete_all do
229
+ def revise(granted: [], revoked: [])
230
+ transaction do
231
+ grant_to granted
232
+ revoke_from revoked
233
+ end
234
+ end
235
+
236
+ def grant_to(user_ids)
237
+ user_ids.each { |id| create(user_id: id) }
238
+ end
239
+
240
+ def revoke_from(user_ids)
241
+ where(user_id: user_ids).delete_all
242
+ end
243
+ end
244
+
245
+ # Usage:
246
+ board.accesses.revise(granted: [user1.id], revoked: [user2.id])
247
+ ```
248
+
249
+ ### Counter Caches
250
+
251
+ ```ruby
252
+ # In parent model
253
+ has_many :comments, dependent: :destroy
254
+
255
+ # In child model
256
+ belongs_to :card, counter_cache: true
257
+
258
+ # Migration
259
+ add_column :cards, :comments_count, :integer, default: 0, null: false
260
+ ```
261
+
262
+ ### Dependent Options
263
+
264
+ ```ruby
265
+ has_many :comments, dependent: :destroy # Calls destroy on each
266
+ has_many :events, dependent: :delete_all # SQL DELETE (faster, no callbacks)
267
+ has_one :closure, dependent: :destroy
268
+ has_one :avatar, dependent: :purge_later # For Active Storage
269
+ ```
270
+
271
+ ---
272
+
273
+ ## Callback Patterns
274
+
275
+ ### Conditional Callbacks
276
+
277
+ ```ruby
278
+ # With if/unless
279
+ before_save :set_defaults, if: :new_record?
280
+ after_save :notify_users, unless: :draft?
281
+
282
+ # With Proc
283
+ before_save :set_title, if: -> { title.blank? && published? }
284
+
285
+ # Multiple conditions
286
+ after_update :reindex,
287
+ if: :saved_change_to_title?,
288
+ unless: :draft?
289
+ ```
290
+
291
+ ### Lambda Callbacks
292
+
293
+ ```ruby
294
+ # Inline logic
295
+ after_save -> { board.touch }, if: :published?
296
+
297
+ # With parameters (using stabby lambda)
298
+ after_create ->(record) { NotificationJob.perform_later(record) }
299
+ ```
300
+
301
+ ### Transaction Callbacks
302
+
303
+ ```ruby
304
+ # After transaction commits
305
+ after_create_commit :send_notifications
306
+ after_update_commit :reindex_search
307
+ after_destroy_commit :cleanup_storage
308
+
309
+ # All commits
310
+ after_commit :broadcast_changes
311
+
312
+ # On rollback
313
+ after_rollback :log_failure
314
+ ```
315
+
316
+ ### Callback Methods Location
317
+
318
+ ```ruby
319
+ class Card < ApplicationRecord
320
+ after_save :do_something
321
+
322
+ private
323
+ # Callback methods in private section
324
+ def do_something
325
+ # Implementation
326
+ end
327
+ end
328
+ ```
329
+
330
+ ---
331
+
332
+ ## Scope Patterns
333
+
334
+ ### Basic Scopes
335
+
336
+ ```ruby
337
+ # Simple where
338
+ scope :published, -> { where(status: :published) }
339
+ scope :recent, -> { where(created_at: 1.week.ago..) }
340
+
341
+ # Ordering
342
+ scope :latest, -> { order(created_at: :desc) }
343
+ scope :alphabetical, -> { order(name: :asc) }
344
+
345
+ # Joins
346
+ scope :with_comments, -> { joins(:comments).distinct }
347
+ scope :closed, -> { joins(:closure) }
348
+
349
+ # Missing associations (Rails 7+)
350
+ scope :open, -> { where.missing(:closure) }
351
+ ```
352
+
353
+ ### Parameterized Scopes
354
+
355
+ ```ruby
356
+ scope :created_after, ->(date) { where(created_at: date..) }
357
+ scope :assigned_to, ->(user) { where(assignee: user) }
358
+ scope :tagged_with, ->(tag_titles) {
359
+ joins(:taggings).where(taggings: { tag: Tag.where(title: tag_titles) })
360
+ }
361
+ ```
362
+
363
+ ### Complex Scopes
364
+
365
+ ```ruby
366
+ scope :preloaded, -> {
367
+ includes(:creator, :assignees, :tags)
368
+ .preload(board: :columns)
369
+ .with_rich_text_description_and_embeds
370
+ }
371
+
372
+ scope :search_results, ->(query) do
373
+ left_joins(:tags)
374
+ .where("cards.title LIKE ? OR tags.title LIKE ?", "%#{query}%", "%#{query}%")
375
+ .distinct
376
+ end
377
+
378
+ scope :indexed_by, ->(index) do
379
+ case index
380
+ when "stalled" then stalled.latest
381
+ when "active" then published.latest
382
+ when "closed" then closed.recently_closed_first
383
+ else all
384
+ end
385
+ end
386
+ ```
387
+
388
+ ### Scope Composition
389
+
390
+ ```ruby
391
+ # Scopes are chainable
392
+ Card.published.tagged_with(["bug"]).assigned_to(current_user).latest
393
+
394
+ # Can be used in associations
395
+ has_many :published_cards, -> { published }, class_name: "Card"
396
+ ```
397
+
398
+ ---
399
+
400
+ ## Validation Patterns
401
+
402
+ ### Built-in Validations
403
+
404
+ ```ruby
405
+ # Presence
406
+ validates :title, presence: true
407
+ validates :title, presence: true, if: :published?
408
+
409
+ # Uniqueness
410
+ validates :email, uniqueness: true
411
+ validates :number, uniqueness: { scope: :account_id }
412
+ validates :slug, uniqueness: { case_sensitive: false }
413
+
414
+ # Format
415
+ validates :email, format: { with: URI::MailTo::EMAIL_REGEXP }
416
+ validates :url, format: { with: /\Ahttps?:\/\// }
417
+
418
+ # Length
419
+ validates :title, length: { maximum: 200 }
420
+ validates :password, length: { minimum: 8 }
421
+ validates :code, length: { is: 6 }
422
+
423
+ # Inclusion/Exclusion
424
+ validates :status, inclusion: { in: %w[draft published] }
425
+ validates :role, exclusion: { in: %w[super_admin] }
426
+
427
+ # Numericality
428
+ validates :age, numericality: { only_integer: true }
429
+ validates :price, numericality: { greater_than: 0 }
430
+ ```
431
+
432
+ ### Custom Validations
433
+
434
+ ```ruby
435
+ # Method validation
436
+ validate :url_must_be_valid
437
+
438
+ private
439
+ def url_must_be_valid
440
+ return if url.blank?
441
+
442
+ uri = URI.parse(url)
443
+ unless PERMITTED_SCHEMES.include?(uri.scheme)
444
+ errors.add(:url, "must use http or https")
445
+ end
446
+ rescue URI::InvalidURIError
447
+ errors.add(:url, "is not a valid URL")
448
+ end
449
+ ```
450
+
451
+ ### Conditional Validations
452
+
453
+ ```ruby
454
+ validates :title, presence: true, if: :published?
455
+ validates :description, presence: true, on: :update
456
+ validates :email, uniqueness: true, unless: :skip_email_validation?
457
+ ```
458
+
459
+ ---