Module: AgentCode::HasValidation
- Extended by:
- ActiveSupport::Concern
- Included in:
- AgentCodeModel
- Defined in:
- lib/agentcode/concerns/has_validation.rb
Overview
Format validation concern for models.
This concern runs ActiveModel validations on request data before it reaches the database. Field permissions (which fields each role can write) are controlled by the policy, not the model.
Also provides cross-tenant FK validation: any belongs_to FK in the submitted data is checked to ensure the referenced record belongs to the current organization (directly or via FK chain).
Usage: class Post < ApplicationRecord include AgentCode::HasValidation
# Standard Rails validations for type/format (use allow_nil: true)
validates :title, length: { maximum: 255 }, allow_nil: true
validates :status, inclusion: { in: %w[draft published] }, allow_nil: true
end
Field permissions are defined on the policy: class PostPolicy < AgentCode::ResourcePolicy def permitted_attributes_for_create(user) has_role?(user, 'admin') ? ['*'] : ['title', 'content'] end end
Instance Method Summary collapse
-
#validate_for_action(params, permitted_fields:, organization: nil) ⇒ Hash
Validate data for a given action.
Instance Method Details
#validate_for_action(params, permitted_fields:, organization: nil) ⇒ Hash
Validate data for a given action. Filters to only permitted fields, then runs ActiveModel validations and cross-tenant FK validation.
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
# File 'lib/agentcode/concerns/has_validation.rb', line 40 def validate_for_action(params, permitted_fields:, organization: nil) # Filter to only permitted fields if permitted_fields == ['*'] filtered = params.each_with_object({}) { |(k, v), h| h[k.to_s] = v } else permitted = permitted_fields.map(&:to_s) filtered = params.each_with_object({}) do |(k, v), h| h[k.to_s] = v if permitted.include?(k.to_s) end end # Remove organization_id from validated data — managed by framework filtered.delete("organization_id") if organization # Run ActiveModel validations on a temp instance temp = self.class.new safe_attrs = filtered.select { |k, _| temp.respond_to?("#{k}=") } temp.assign_attributes(safe_attrs) errors = {} unless temp.valid? temp.errors.each do |error| field_name = error.attribute.to_s if filtered.key?(field_name) errors[field_name] ||= [] errors[field_name] << error. end end end # Cross-tenant FK validation if organization fk_errors = validate_foreign_keys_for_organization(filtered, organization) errors.merge!(fk_errors) end if errors.any? { valid: false, errors: errors, validated: {} } else { valid: true, errors: {}, validated: filtered } end end |