ActiveModel Extra provides powerful extensions to ActiveModel that fill common gaps in Rails' built-in functionality:
- Array Type: Handle arrays with properly typed elements (strings, integers, custom models, etc.)
 - Nested Model Support: Use ActiveModel classes as attribute types with automatic casting from hashes
 - Array Validator: Apply any ActiveModel validator to each element in an array
 
Add this line to your application's Gemfile:
gem 'active_model_extras'And then execute:
$ bundle install
Or install it yourself as:
$ gem install active_model_extras
The Array type allows you to specify a subtype for array elements, ensuring all elements are properly cast to the expected type:
class Product
  include ActiveModel::Model
  include ActiveModel::Attributes
  attribute :name, :string
  attribute :tags, :array, of: :string
  attribute :prices, :array, of: :decimal
  attribute :quantities, :array, of: :integer
end
product = Product.new(
  name: 'Widget', 
  tags: ['sale', 'new'], 
  prices: ['10.99', '9.99'],
  quantities: ['5', 10, nil]
)
product.tags       # => ["sale", "new"]
product.prices     # => [#<BigDecimal:10.99>, #<BigDecimal:9.99>]
product.quantities # => [5, 10, 0]  # Note: nil cast to 0 for integer typeThe NestedModel module allows you to use ActiveModel classes as attribute types with automatic casting from hashes to model instances:
class Address
  include ActiveModel::Model
  include ActiveModel::Attributes
  extend ActiveModelExtras::NestedModel
  attribute :street, :string
  attribute :city, :string
  attribute :zip, :string
end
class User
  include ActiveModel::Model
  include ActiveModel::Attributes
  attribute :name, :string
  attribute :address, Address
  attribute :previous_addresses, :array, of: Address  # Array of nested models!
end
user = User.new(
  name: 'John', 
  address: { street: 'Main St', city: 'New York', zip: '10001' },
  previous_addresses: [
    { street: '123 Oak Ave', city: 'Boston', zip: '02108' },
    { street: '456 Pine St', city: 'Chicago', zip: '60601' }
  ]
)
user.address.city  # => "New York"
user.previous_addresses[0].city  # => "Boston"
user.previous_addresses[1].zip   # => "60601"The ArrayValidator allows you to apply any ActiveModel validator to each element in an array:
class Survey
  include ActiveModel::Model
  include ActiveModel::Attributes
  include ActiveModel::Validations
  attribute :responses, :array, of: :string
  attribute :ratings, :array, of: :integer
  
  # Validate each response is one of the allowed values
  validates :responses, 
            array: { inclusion: { in: %w[yes no maybe] } }, 
            allow_blank: true
            
  # Validate each rating is between 1 and 5
  validates :ratings,
            array: { numericality: { greater_than: 0, less_than_or_equal_to: 5 } },
            allow_blank: true
end
survey = Survey.new(
  responses: ['yes', 'invalid', 'no'],
  ratings: [5, 3, 0, 4]
)
survey.valid?  # => false
survey.errors.full_messages  
# => ["Responses is not included in the list", "Ratings must be greater than 0"]All these features work together seamlessly:
class Comment
  include ActiveModel::Model
  include ActiveModel::Attributes
  extend ActiveModelExtras::NestedModel
  
  attribute :text, :string
  attribute :rating, :integer
  
  validates :text, presence: true
  validates :rating, numericality: { greater_than: 0, less_than_or_equal_to: 5 }
end
class BlogPost
  include ActiveModel::Model
  include ActiveModel::Attributes
  include ActiveModel::Validations
  
  attribute :title, :string
  attribute :comments, :array, of: Comment
  
  validates :title, presence: true
  validates :comments, array: { custom: true }
  
  # Custom validator that uses the model's own validation
  class CustomValidator < ActiveModel::EachValidator
    def validate_each(record, attribute, value)
      unless value.valid?
        record.errors.add(attribute, "contains invalid #{value.class.name.downcase}")
      end
    end
  end
end
post = BlogPost.new(
  title: "My Blog Post",
  comments: [
    { text: "Great post!", rating: 5 },
    { text: "", rating: 0 }  # Invalid comment
  ]
)
post.valid?  # => false
post.errors.full_messages  # => ["Comments contains invalid comment"]Bug reports and pull requests are welcome on GitHub at https://github.com/aha-app/active_model_extras.
The gem is available as open source under the terms of the MIT License.