ActiveLayer

ActiveLayer provides validations, attribute filtering, and persistence in a layer sitting on top of a model.

This was born out of frustration with overly complex ActiveRecord validations and model contortions.

Quick Example

    class User < ActiveRecord::Base; end
    # Every user field you want.. 

    # This layer uses the ActiveRecord adapter which pulls in several other pieces
    class UserLayer
        include ActiveLayer::ActiveRecord

        attr_accessible :name, :email, :address

        validates :name, :email, :presence => true
        validates :address_validation

        before_validate :normalize_address

        def normalize_address; end
        def address_validation
            errors.add(:address, :invalid) if address.blank?
        end
    end

    # Now how to use it:
    user = User.find(1)
    layer = UserLayer.new(user)
    layer.update_attributes( {:name => '', :admin => true, :address => 'on a busy road'} )   # => false
    layer.errors[:name].present # => true
    user.errors[:name].present => true
    user.admin  # => false   - was filtered out

Some of the players

  • ActiveLayer::Proxy
  • ActiveLayer::Validations
  • ActiveLayer::Attributes
  • ActiveLayer::Persistence

ActiveLayer::Proxy

This module provides the ability to delegate method calls down to the object and typically you would not use this element directly. However, it does provide one key method active_layer_object which provides access to the underlying object/model. This may be necessary in certain cases.

ActiveLayer::Validations

Currently the majority of the functionality resides in this module and provides the ability for composable validation classes to be layered together. This module is completely compatible with the wonderful ActiveModel::Validation module and in fact just wraps it and provides extra functionality.

Currently the following is supported:

  • Standard ActiveModel::Validators support (Each, With, Custom, etc.)
  • Custom validation methods
  • before_validation and after_validation hooks (Just like ActiveRecord)
  • Nested ActiveLayer validations (This is neat!)
  • All existing custom validators should 'just work'

Examples

    class AddressValidator
        include ActiveLayer::Validations

        before_validation :normalize_address
        validates :address, :presence => true
        validate :custom_method

        def normalize_address; end
        def custom_method; end
    end

    class EmailValidator
        include ActiveLayer::Validations

        validates :email, :presence => true
    end

    class UserValidator
        include ActiveLayer::Validations

        validates :name, :presence => true
        validates_with EmailValidator 
        validates_with AddressValidator, :attributes => [:addresses]
    end

    # the EmailValidator will get passed the user object
    # the AddressValidator will get passed each address of the user and merge the errors back up
    user = User.find(1)
    validator = UserValidator.new(user)
    validator.valid?   # => true/false

ActiveLayer::Attributes

This module provides the ability to bulk assign attributes with attribute protection via att_accessible. By default all attributes are not assignable and you must declare which attributes are accessible. Note, each attribute is available for individual assigning or reading.

Examples

    class FlawedUserFilter
        all_attributes_accessible!
        # All attributes are now assignable
    end

    class UserFilter
        attr_accessible :name, :address
    end

    user = User.find(1)
    filter = UserFilter.new(user)
    filter.attributes = {:name => 'Bob', 'admin' => true}
    user.admin  #  => false

ActiveLayer::Persistence

This layer is responsible for providing the save and update_attributes methods that function very similar to ActiveRecord. Both layers invoke the validations in the layer, if currently mixed in and ultimately delegate to the proxy object for saving. If the Attributes module is mixed in then the guards will be used however, if it is not mixed then active_layer_object.attributes=(hash) will be invoked.

Note: If you plan to use Persistence with Attributes then you must mix-in the Attributes module after the Persistence module

Examples

    class UserPersistor
        include ActiveLayer::Persistence
    end

    user = User.find(1)
    persistor = UserPersistor.new(user)
    persistor.save
    persistor.save!  # will raise an ActiveLayer::RecordInvalidError with a #record method
    persistor.update_attributes(:name => 'new')
    persistor.update_attributes!(:name => 'new')

TODO / Plans

  • Ensure that the valid? method calls the underlying proxy object.valid?
  • Nested attribute support - similar to Rails - but in an adaptable manner for use in other ORMs
  • Add support for additional ORMs
  • Ideas?

License

ActiveLayer is available under the terms of the MIT license (see LICENSE.txt for details).