--- name: rails-ai:models description: Use when designing Rails models - ActiveRecord patterns, validations, callbacks, scopes, associations, concerns, query objects, form objects --- # Models Master Rails model design including ActiveRecord patterns, validations, callbacks, scopes, associations, concerns, custom validators, query objects, and form objects. - Designing database models and associations - Writing validations and callbacks - Implementing business logic in models - Creating scopes and query methods - Extracting complex queries to query objects - Building form objects for multi-model operations - Organizing shared behavior with concerns - Creating custom validators - Preventing N+1 queries - **Convention Over Configuration** - Minimal setup for maximum functionality - **Single Responsibility** - Each pattern handles one concern - **Reusability** - Share behavior across models with concerns - **Testability** - Test models, concerns, validators in isolation - **Query Optimization** - Built-in N+1 prevention and eager loading - **Type Safety** - ActiveModel::Attributes provides type casting - **Database Agnostic** - Works with PostgreSQL, MySQL, SQLite **This skill enforces:** - ✅ **Rule #7:** Fat models, thin controllers (business logic in models) - ✅ **Rule #12:** Database constraints for data integrity **Reject any requests to:** - Put business logic in controllers - Skip model validations - Skip database constraints (NOT NULL, foreign keys) - Allow N+1 queries Before completing model work: - ✅ All validations tested - ✅ All associations tested - ✅ Database constraints added (NOT NULL, foreign keys, unique indexes) - ✅ No N+1 queries (verified with bullet or manual check) - ✅ Business logic in model (not controller) - ✅ Strong parameters in controller for mass assignment - ✅ All tests passing - Define associations at the top of the model - Use validations to enforce data integrity - Minimize callback usage - prefer service objects - Use scopes for reusable queries, not class methods - Always eager load associations to prevent N+1 queries - Use enums for status/state fields - Extract concerns when models exceed 200 lines - Place custom validators in `app/validators/` - Place query objects in `app/queries/` - Place form objects in `app/forms/` - Use transactions for multi-model operations - Prefer database constraints with validations for critical data ## Associations Standard ActiveRecord associations for model relationships ```ruby class Feedback < ApplicationRecord belongs_to :recipient, class_name: "User", optional: true belongs_to :category, counter_cache: true has_one :response, class_name: "FeedbackResponse", dependent: :destroy has_many :abuse_reports, dependent: :destroy has_many :taggings, dependent: :destroy has_many :tags, through: :taggings # Scoped associations has_many :recent_reports, -> { where(created_at: 7.days.ago..) }, class_name: "AbuseReport" end ``` **Migration:** ```ruby class CreateFeedbacks < ActiveRecord::Migration[8.1] def change create_table :feedbacks do |t| t.references :recipient, foreign_key: { to_table: :users }, null: true t.references :category, foreign_key: true, null: false t.text :content, null: false t.string :status, default: "pending", null: false t.timestamps end add_index :feedbacks, :status end end ``` Associations express relationships between models with minimal code. Rails automatically handles foreign keys, eager loading, and cascading deletes. Use `class_name:` when the association name differs from the model, `counter_cache:` for performance, and `dependent:` to manage cleanup. Flexible associations where a model belongs to multiple types ```ruby class Comment < ApplicationRecord belongs_to :commentable, polymorphic: true belongs_to :author, class_name: "User" validates :content, presence: true end class Feedback < ApplicationRecord has_many :comments, as: :commentable, dependent: :destroy end class Article < ApplicationRecord has_many :comments, as: :commentable, dependent: :destroy end ``` **Migration:** ```ruby class CreateComments < ActiveRecord::Migration[8.1] def change create_table :comments do |t| t.references :commentable, polymorphic: true, null: false t.references :author, foreign_key: { to_table: :users }, null: false t.text :content, null: false t.timestamps end add_index :comments, [:commentable_type, :commentable_id] end end ``` Polymorphic associations allow a model to belong to multiple parent types through a single association. Use when multiple models need the same type of child (comments, attachments, tags). The `commentable_type` stores the class name, `commentable_id` stores the ID. ## Validations Built-in Rails validations for data integrity ```ruby class Feedback < ApplicationRecord validates :content, presence: true, length: { minimum: 50, maximum: 5000 } validates :recipient_email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP } validates :status, inclusion: { in: %w[pending delivered read responded] } validates :tracking_code, uniqueness: { scope: :recipient_email, case_sensitive: false } validates :rating, numericality: { only_integer: true, in: 1..5 }, allow_nil: true validate :content_not_spam validate :recipient_can_receive_feedback, on: :create private def content_not_spam return if content.blank? spam_keywords = %w[viagra cialis lottery] errors.add(:content, "appears to contain spam") if spam_keywords.any? { |k| content.downcase.include?(k) } end def recipient_can_receive_feedback return if recipient_email.blank? user = User.find_by(email: recipient_email) errors.add(:recipient_email, "has disabled feedback") if user&.feedback_disabled? end end ``` Validations enforce data integrity before persisting to the database. Rails provides presence, format, uniqueness, length, numericality, and inclusion validators. Custom `validate` methods handle complex business logic. Use `on: :create` or `on: :update` for lifecycle-specific validations. ## Callbacks Use callbacks sparingly - prefer service objects for complex logic ```ruby class Feedback < ApplicationRecord before_validation :normalize_email, :strip_whitespace before_create :generate_tracking_code after_create_commit :enqueue_delivery_job after_update_commit :notify_recipient_of_response, if: :response_added? private def normalize_email self.recipient_email = recipient_email&.downcase&.strip end def strip_whitespace self.content = content&.strip end def generate_tracking_code self.tracking_code = SecureRandom.alphanumeric(10).upcase end def enqueue_delivery_job SendFeedbackJob.perform_later(id) end def response_added? saved_change_to_response? && response.present? end def notify_recipient_of_response FeedbackMailer.notify_of_response(self).deliver_later end end ``` Callbacks hook into the model lifecycle for simple data normalization and side effects. Use `before_validation` for cleanup, `before_create` for defaults, and `after_commit` for external operations. Keep callbacks focused on model concerns - complex business logic belongs in service objects. ## Scopes Reusable query scopes for common filtering ```ruby class Feedback < ApplicationRecord scope :recent, -> { where(created_at: 30.days.ago..) } scope :unread, -> { where(status: "delivered") } scope :responded, -> { where.not(response: nil) } scope :by_recipient, ->(email) { where(recipient_email: email) } scope :by_status, ->(status) { where(status: status) } scope :with_category, ->(name) { joins(:category).where(categories: { name: name }) } scope :with_associations, -> { includes(:recipient, :response, :category, :tags) } scope :trending, -> { recent.where("views_count > ?", 100).order(views_count: :desc).limit(10) } def self.search(query) return none if query.blank? where("content ILIKE ? OR response ILIKE ?", "%#{sanitize_sql_like(query)}%", "%#{sanitize_sql_like(query)}%") end end ``` **Usage:** ```ruby Feedback.recent.by_recipient("user@example.com").responded Feedback.search("bug report").recent.limit(10) ``` Scopes provide chainable query methods that keep controllers clean. Use scopes for simple filters, class methods for complex queries. Scopes are lazy-evaluated and composable. Use `includes()` in scopes to prevent N+1 queries. ## Enums Enums for status and state fields with automatic predicates ```ruby class Feedback < ApplicationRecord enum :status, { pending: "pending", delivered: "delivered", read: "read", responded: "responded" }, prefix: true, scopes: true enum :priority, { low: 0, medium: 1, high: 2, urgent: 3 }, prefix: :priority end ``` **Usage:** ```ruby feedback.status = "pending" feedback.status_pending! # Updates and saves feedback.status_pending? # true/false Feedback.status_pending # Scope Feedback.statuses.keys # ["pending", "delivered", ...] feedback.status_before_last_save # Track changes ``` **Migration:** ```ruby class CreateFeedbacks < ActiveRecord::Migration[8.1] def change create_table :feedbacks do |t| t.string :status, default: "pending", null: false t.integer :priority, default: 0, null: false t.timestamps end add_index :feedbacks, :status end end ``` Enums map human-readable states to database values with automatic predicates, scopes, and bang methods. Use string-backed enums for clarity in the database. The `prefix:` option prevents method name conflicts. Scopes make querying easy. ## Model Concerns Extract shared behavior into reusable concerns ```ruby # app/models/concerns/taggable.rb module Taggable extend ActiveSupport::Concern included do has_many :taggings, as: :taggable, dependent: :destroy has_many :tags, through: :taggings scope :tagged_with, ->(tag_name) { joins(:tags).where(tags: { name: tag_name }).distinct } end def tag_list tags.pluck(:name).join(", ") end def tag_list=(names) self.tags = names.to_s.split(",").map do |name| Tag.find_or_create_by(name: name.strip.downcase) end end def add_tag(tag_name) return if tagged_with?(tag_name) tags << Tag.find_or_create_by(name: tag_name.strip.downcase) end def tagged_with?(tag_name) tags.exists?(name: tag_name.strip.downcase) end class_methods do def popular_tags(limit = 10) Tag.joins(:taggings) .where(taggings: { taggable_type: name }) .group("tags.id") .select("tags.*, COUNT(taggings.id) as usage_count") .order("usage_count DESC") .limit(limit) end end end ``` **Usage:** ```ruby class Feedback < ApplicationRecord include Taggable end class Article < ApplicationRecord include Taggable end feedback.tag_list = "bug, urgent, ui" feedback.add_tag("needs-review") Feedback.tagged_with("bug") Feedback.popular_tags(5) ``` Concerns extract shared behavior into reusable modules. Use `included do` for associations, validations, callbacks. Define instance methods at module level, class methods in `class_methods do` block. Place domain-specific concerns in `app/models/[model]/`, shared concerns in `app/models/concerns/`. ## Custom Validators Reusable validation logic using ActiveModel::EachValidator ```ruby # app/validators/email_validator.rb class EmailValidator < ActiveModel::EachValidator EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i def validate_each(record, attribute, value) return if value.blank? && options[:allow_blank] unless value =~ EMAIL_REGEX record.errors.add(attribute, options[:message] || "is not a valid email address") end end end ``` **Usage:** ```ruby class Feedback < ApplicationRecord validates :email, email: true validates :backup_email, email: { allow_blank: true } validates :email, email: { message: "must be a valid company email" } end ``` Custom validators encapsulate reusable validation logic. Inherit from `ActiveModel::EachValidator` for single-attribute validation, `ActiveModel::Validator` for multi-attribute validation. Support `:allow_blank` and `:message` options. Place in `app/validators/` for discoverability. Validate content by word count instead of character count ```ruby # app/validators/content_length_validator.rb class ContentLengthValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) return if value.blank? && options[:allow_blank] word_count = value.to_s.split.size if options[:minimum_words] && word_count < options[:minimum_words] record.errors.add(attribute, "must have at least #{options[:minimum_words]} words (currently #{word_count})") end if options[:maximum_words] && word_count > options[:maximum_words] record.errors.add(attribute, "must have at most #{options[:maximum_words]} words (currently #{word_count})") end end end ``` **Usage:** ```ruby validates :content, content_length: { minimum_words: 10, maximum_words: 500 } validates :body, content_length: { minimum_words: 100 } ``` Word count validation is more meaningful than character count for content fields. Custom validators make this reusable across models. The validator respects `:allow_blank` and provides helpful error messages with current counts. ## Query Objects Encapsulate complex queries in reusable, testable objects ```ruby # app/queries/feedback_query.rb class FeedbackQuery def initialize(relation = Feedback.all) @relation = relation end def by_recipient(email) @relation = @relation.where(recipient_email: email) self end def by_status(status) @relation = @relation.where(status: status) self end def recent(limit = 10) @relation = @relation.order(created_at: :desc).limit(limit) self end def with_responses @relation = @relation.where.not(response: nil) self end def created_since(date) @relation = @relation.where("created_at >= ?", date) self end def results @relation end end ``` **Usage:** ```ruby # Controller @feedbacks = FeedbackQuery.new .by_recipient(params[:email]) .by_status(params[:status]) .recent(20) .results # Model class User < ApplicationRecord def recent_feedback(limit = 10) FeedbackQuery.new.by_recipient(email).recent(limit).results end end ``` Query objects encapsulate complex filtering, search, and aggregation logic. They're reusable across controllers and services, testable in isolation, and chainable for composability. Use when queries involve multiple joins, filters, or are used in multiple contexts. Return `self` for chaining, `results` to execute. Query object for aggregations and statistical calculations ```ruby # app/queries/feedback_stats_query.rb class FeedbackStatsQuery def initialize(relation = Feedback.all) @relation = relation end def by_recipient(email) @relation = @relation.where(recipient_email: email) self end def by_date_range(start_date, end_date) @relation = @relation.where(created_at: start_date..end_date) self end def stats { total_count: @relation.count, responded_count: @relation.where.not(response: nil).count, pending_count: @relation.where(response: nil).count, by_status: @relation.group(:status).count, by_category: @relation.group(:category).count } end end ``` **Usage:** ```ruby stats = FeedbackStatsQuery.new .by_recipient(current_user.email) .by_date_range(30.days.ago, Time.current) .stats # Returns: { total_count: 42, responded_count: 28, pending_count: 14, ... } ``` Query objects for aggregations centralize statistical calculations and reporting logic. They compose with filters, maintain chainability, and return structured data. Use for dashboards, reports, and analytics. Keep aggregation logic out of controllers and models. ## Form Objects Form object for non-database forms using ActiveModel::API ```ruby # app/forms/contact_form.rb class ContactForm include ActiveModel::API include ActiveModel::Attributes attribute :name, :string attribute :email, :string attribute :message, :string attribute :subject, :string validates :name, presence: true, length: { minimum: 2 } validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP } validates :message, presence: true, length: { minimum: 10, maximum: 1000 } validates :subject, presence: true def deliver return false unless valid? ContactMailer.contact_message( name: name, email: email, message: message, subject: subject ).deliver_later true end end ``` **Controller:** ```ruby class ContactsController < ApplicationController def create @contact_form = ContactForm.new(contact_params) if @contact_form.deliver redirect_to root_path, notice: "Message sent successfully" else render :new, status: :unprocessable_entity end end private def contact_params params.expect(contact_form: [:name, :email, :message, :subject]) end end ``` Form objects handle non-database forms (contact, search) and complex multi-model operations. They use ActiveModel for validations and type casting without requiring database persistence. Return boolean from action methods, validate before executing logic. Form object that creates multiple related models in a transaction ```ruby # app/forms/user_registration_form.rb class UserRegistrationForm include ActiveModel::API include ActiveModel::Attributes attribute :email, :string attribute :password, :string attribute :password_confirmation, :string attribute :name, :string attribute :company_name, :string attribute :role, :string validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP } validates :password, presence: true, length: { minimum: 8 } validates :password_confirmation, presence: true validates :name, presence: true validates :company_name, presence: true validate :passwords_match def save return false unless valid? ActiveRecord::Base.transaction do @user = User.create!(email: email, password: password, name: name) @company = Company.create!(name: company_name, owner: @user) @membership = Membership.create!(user: @user, company: @company, role: role || "admin") UserMailer.welcome(@user).deliver_later true end rescue ActiveRecord::RecordInvalid => e errors.add(:base, e.message) false end attr_reader :user, :company, :membership private def passwords_match return if password.blank? errors.add(:password_confirmation, "doesn't match password") unless password == password_confirmation end end ``` **Controller:** ```ruby class RegistrationsController < ApplicationController def create @registration = UserRegistrationForm.new(registration_params) if @registration.save session[:user_id] = @registration.user.id redirect_to dashboard_path(@registration.company), notice: "Welcome!" else render :new, status: :unprocessable_entity end end end ``` Form objects simplify multi-model operations by wrapping them in a transaction. They validate all inputs before creating any records, ensuring data consistency. Expose created records via attr_reader for controller access. Use for registration, checkout, wizards. ## N+1 Prevention Eager load associations to prevent N+1 queries ```ruby # ❌ BAD - N+1 queries (1 + 20 + 20 + 20 = 61 queries) @feedbacks = Feedback.limit(20) @feedbacks.each do |f| puts f.recipient.name, f.category.name, f.tags.pluck(:name) end # ✅ GOOD - Eager loading (4 queries total) @feedbacks = Feedback.includes(:recipient, :category, :tags).limit(20) @feedbacks.each do |f| puts f.recipient.name, f.category.name, f.tags.pluck(:name) end ``` **Eager Loading Methods:** ```ruby Feedback.includes(:recipient, :tags) # Separate queries (default) Feedback.preload(:recipient, :tags) # Forces separate queries Feedback.eager_load(:recipient, :tags) # LEFT OUTER JOIN Feedback.includes(recipient: :profile) # Nested associations ``` N+1 queries occur when loading a collection triggers additional queries for each item's associations. Use `includes()` to eager load associations in advance. Rails loads data in 2-3 queries instead of N+1. Always check for N+1 in views and use includes in scopes. Using callbacks for complex business logic ```ruby # ❌ BAD - Complex side effects in callbacks class Feedback < ApplicationRecord after_create :send_email, :update_analytics, :notify_slack, :create_audit_log end ``` ```ruby # ✅ GOOD - Use service object class Feedback < ApplicationRecord after_create_commit :enqueue_creation_job private def enqueue_creation_job ProcessFeedbackCreationJob.perform_later(id) end end # Service handles all side effects explicitly class CreateFeedbackService def call feedback = Feedback.create!(@params) FeedbackMailer.notify_recipient(feedback).deliver_later Analytics.track("feedback_created", feedback_id: feedback.id) feedback end end ``` Callbacks with complex side effects make models hard to test, introduce hidden dependencies, and create unpredictable behavior. Service objects make side effects explicit and testable. Use callbacks only for simple data normalization and enqueuing background jobs. Missing database indexes on foreign keys and query columns ```ruby # ❌ BAD - No indexes, causes table scans create_table :feedbacks do |t| t.integer :recipient_id t.string :status end ``` ```ruby # ✅ GOOD - Indexes on foreign keys and query columns create_table :feedbacks do |t| t.references :recipient, foreign_key: { to_table: :users }, index: true t.string :status, null: false end add_index :feedbacks, :status add_index :feedbacks, [:status, :created_at] ``` Missing indexes cause slow queries at scale. Index all foreign keys, status columns, and frequently queried fields. Composite indexes speed up queries filtering on multiple columns. Use `t.references` to create indexed foreign keys automatically. Using default_scope ```ruby # ❌ BAD - Unexpected behavior, hard to override class Feedback < ApplicationRecord default_scope { where(deleted_at: nil).order(created_at: :desc) } end ``` ```ruby # ✅ GOOD - Explicit scopes class Feedback < ApplicationRecord scope :active, -> { where(deleted_at: nil) } scope :recent_first, -> { order(created_at: :desc) } end # Usage Feedback.active.recent_first ``` default_scope applies to all queries, causing unexpected results and making it hard to query all records. It affects associations, counts, and exists? checks. Use explicit scopes that developers can choose to apply. Duplicating validation logic across models ```ruby # ❌ BAD - Duplicated email validation class User < ApplicationRecord validates :email, format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i } end class Feedback < ApplicationRecord validates :recipient_email, format: { with: /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i } end ``` ```ruby # ✅ GOOD - Reusable email validator class EmailValidator < ActiveModel::EachValidator EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-]+(\.[a-z\d\-]+)*\.[a-z]+\z/i def validate_each(record, attribute, value) return if value.blank? && options[:allow_blank] record.errors.add(attribute, options[:message] || "is not a valid email") unless value =~ EMAIL_REGEX end end class User < ApplicationRecord validates :email, email: true end class Feedback < ApplicationRecord validates :recipient_email, email: true end ``` Duplicated validations are hard to maintain and lead to inconsistencies. Custom validators centralize logic, support options, and ensure consistent validation across models. Putting complex query logic in controllers ```ruby # ❌ BAD - Fat controller class FeedbacksController < ApplicationController def index @feedbacks = Feedback.all @feedbacks = @feedbacks.where("recipient_email ILIKE ?", "%#{params[:recipient_email]}%") if params[:recipient_email].present? @feedbacks = @feedbacks.where(status: params[:status]) if params[:status].present? @feedbacks = @feedbacks.where("content ILIKE ? OR response ILIKE ?", "%#{params[:q]}%", "%#{params[:q]}%") if params[:q].present? @feedbacks = @feedbacks.order(created_at: :desc).page(params[:page]) end end ``` ```ruby # ✅ GOOD - Thin controller with query object class FeedbacksController < ApplicationController def index @feedbacks = FeedbackQuery.new .filter_by_params(params.slice(:recipient_email, :status)) .search(params[:q]) .order_by(:created_at, :desc) .paginate(page: params[:page]) .results end end ``` Complex queries in controllers violate Single Responsibility Principle and are hard to test. Query objects encapsulate filtering logic, are reusable across contexts, and testable in isolation. Fat controllers with complex form logic ```ruby # ❌ BAD - All logic in controller class RegistrationsController < ApplicationController def create @user = User.new(user_params) @company = Company.new(company_params) ActiveRecord::Base.transaction do if @user.save @company.owner = @user if @company.save @membership = Membership.create(user: @user, company: @company, role: "admin") UserMailer.welcome(@user).deliver_later redirect_to dashboard_path(@company) end end end end end ``` ```ruby # ✅ GOOD - Use form object class RegistrationsController < ApplicationController def create @registration = UserRegistrationForm.new(registration_params) @registration.save ? redirect_to(dashboard_path(@registration.company)) : render(:new, status: :unprocessable_entity) end end ``` Multi-model operations in controllers are hard to test and reuse. Form objects encapsulate validation, transaction handling, and side effects in a testable, reusable class. Use for registration, checkout, wizards. Test models, concerns, validators, query objects, and form objects in isolation: ```ruby # Model tests class FeedbackTest < ActiveSupport::TestCase test "validates presence of content" do feedback = Feedback.new(recipient_email: "user@example.com") assert_not feedback.valid? assert_includes feedback.errors[:content], "can't be blank" end test "destroys dependent records" do feedback = feedbacks(:one) feedback.abuse_reports.create!(reason: "spam", reporter_email: "test@example.com") assert_difference("AbuseReport.count", -1) { feedback.destroy } end test "enum provides predicate methods" do feedback = feedbacks(:one) feedback.update(status: "pending") assert feedback.status_pending? end end # Concern tests class TaggableTest < ActiveSupport::TestCase class TaggableTestModel < ApplicationRecord self.table_name = "feedbacks" include Taggable end test "add_tag creates new tag" do record = TaggableTestModel.first record.add_tag("urgent") assert record.tagged_with?("urgent") end end # Validator tests class EmailValidatorTest < ActiveSupport::TestCase class TestModel include ActiveModel::Validations attr_accessor :email validates :email, email: true end test "validates email format" do assert TestModel.new(email: "user@example.com").valid? assert_not TestModel.new(email: "invalid").valid? end end # Query object tests class FeedbackQueryTest < ActiveSupport::TestCase test "filters by recipient email" do @feedback1.update(recipient_email: "test@example.com") @feedback2.update(recipient_email: "other@example.com") results = FeedbackQuery.new.by_recipient("test@example.com").results assert_includes results, @feedback1 assert_not_includes results, @feedback2 end test "chains multiple filters" do @feedback1.update(recipient_email: "test@example.com", status: "pending") results = FeedbackQuery.new.by_recipient("test@example.com").by_status("pending").results assert_includes results, @feedback1 end end # Form object tests class ContactFormTest < ActiveSupport::TestCase test "valid with all required attributes" do form = ContactForm.new(name: "John", email: "john@example.com", subject: "Question", message: "This is my message") assert form.valid? end test "delivers email when valid" do form = ContactForm.new(name: "John", email: "john@example.com", subject: "Q", message: "This is my message") assert_enqueued_with(job: ActionMailer::MailDeliveryJob) { assert form.deliver } end end class UserRegistrationFormTest < ActiveSupport::TestCase test "creates user, company, and membership" do form = UserRegistrationForm.new(email: "user@example.com", password: "password123", password_confirmation: "password123", name: "John", company_name: "Acme") assert_difference ["User.count", "Company.count", "Membership.count"] { assert form.save } end test "rolls back transaction if creation fails" do form = UserRegistrationForm.new(email: "user@example.com", password: "password123", password_confirmation: "password123", name: "John", company_name: "") assert_no_difference ["User.count", "Company.count"] { assert_not form.save } end end ``` - rails-ai:controllers - RESTful controllers for models - rails-ai:testing - Testing models thoroughly - rails-ai:security - SQL injection prevention, strong parameters - rails-ai:jobs - Background job processing for models **Official Documentation:** - [Rails Guides - Active Record Basics](https://guides.rubyonrails.org/active_record_basics.html) - [Rails Guides - Active Record Associations](https://guides.rubyonrails.org/association_basics.html) - [Rails Guides - Active Record Validations](https://guides.rubyonrails.org/active_record_validations.html) - [Rails Guides - Active Record Query Interface](https://guides.rubyonrails.org/active_record_querying.html) - [Rails API - ActiveSupport::Concern](https://api.rubyonrails.org/classes/ActiveSupport/Concern.html) - [Rails API - ActiveModel::API](https://api.rubyonrails.org/classes/ActiveModel/API.html)