module NormalizeIt::ClassMethods

normalize_it makes it easy to seamlessly manage database tables that have been normalized.

Example:

create_table :customer_statuses, :force => true do |t|
  t.string :customer_status
end

create_table :email_addresses, :force => true do |t|
  t.string :email_address
end

create_table :customers, :force => true do |t|
  t.string  :name
  t.integer :customer_status_id
  t.integer :email_address_id
end

class CustomerStatus < ActiveRecord::Base
  normalizes :customers, :with_field => :customer_status
  validates :customer_status, :presence => true
  validates_uniqueness_of :customer_status
end

class EmailAddress < ActiveRecord::Base
  normalizes :customers, :allow_inserts => true
  validates :email_address, :presence => true
  validates_uniqueness_of :email_address
end

class Customer < ActiveRecord::Base
  has_normalized :customer_status
  has_normalized :email_address, :allow_inserts => true
end

after calling normalizes:
  * if :allow_inserts is false (default), a :with_field argument must be passed in
  ** new objects of CustomerStatus may not be created by the app
  ** CustomerStatus objects may be referenced by [] notation, eg CustomerStatus[:new] or CustomerStatus['new']
  ** CustomerStatus is given a has_many association to Customer, has_many options may be passed in to normalizes
  * if :allow_inserts is true
  ** only the has_many association is set up
  ** [] notation may be used only if the :with_field option is passed

after calling has_normalized:
  * if :allow_inserts is false (default)
  ** customer.customer_status = 'new' will only search for CustomerStatus['new'] and assign it if it is found
  * if :allow_inserts is true
  ** customer.email_address = 'x@example.com' will search for the email address, and create it if not found
  * all columns on the normalized tables are delegated to the parent object. e.g. customer.email_address
  * static rails-like finders may be used. e.g. Customer.find_by_email_address 'x@example.com'
  * new objects may be initialized either with attributes or through later assignment. e.g. Customer.new(:email_address => 'x@example.com')

Attributes

_normalized_models[R]

Public Instance Methods

has_normalized(model, options = {}) click to toggle source
# File lib/normalize_it.rb, line 65
    def has_normalized(model, options = {})
      include NormalizeIt::BaseClassMethods
      @_normalized_models ||= []
      allow_inserts = options.try(:delete, :allow_inserts) || false
      opts = { :class_name  => model.to_s.camelize,
               :autosave    => true,
               :foreign_key => model.to_s.foreign_key }.merge!(options)
      belongs_to "__#{opts[:class_name].underscore}".to_sym, opts
      validates "__#{opts[:class_name].underscore}".to_sym, :presence => true
      validates_associated "__#{opts[:class_name].underscore}".to_sym
      @_normalized_models << opts.merge( { :allow_inserts => allow_inserts } )
      opts[:class_name].constantize.content_columns.each do |column|
        next if ['created_at', 'updated_at'].include?(column.name)
        delegate "#{column.name}", "#{column.name}?", :to => "__#{opts[:class_name].underscore}".to_sym
        if allow_inserts
          delegate "#{column.name}=", :to => "__#{opts[:class_name].underscore}".to_sym
        else
          self.class_eval do
            define_method "#{column.name}=" do |value|
              self.send("__#{opts[:class_name].underscore}=",
                        value.is_a?(opts[:class_name].constantize) ? value :
                                    opts[:class_name].constantize[value]
                       )
            end
          end
        end
        eval <<-NEWFINDERS
          def self.find_by_#{column.name} value
            self.send("find_by_#{opts[:foreign_key]}",
                      #{opts[:class_name]}.send("find_by_#{column.name}", value)
                     )
          end
          def self.find_all_by_#{column.name} value
            self.send("find_all_by_#{opts[:foreign_key]}",
                      #{opts[:class_name]}.send("find_by_#{column.name}", value)
                     )
          end
        NEWFINDERS
      end

      before_validation :handle_normalized_objects if allow_inserts
    end
normalizes(model, options = {}) click to toggle source
# File lib/normalize_it.rb, line 108
    def normalizes(model, options = {})
      include NormalizeIt::NormalizedClassMethods
      allow_inserts = options.try(:delete, :allow_inserts) || false
      if !allow_inserts
        normalized_field = options.try(:delete, :with_field)
        raise NormalizeItException, "Normalized field must be specified on static tables!" unless normalized_field
        before_create :disallow_create unless allow_inserts
        if normalized_field
          eval <<-HASHNOTATIONACCESS
            def self.[] value
              @@#{self.name.underscore.pluralize} ||= HashWithIndifferentAccess.new
              @@#{self.name.underscore.pluralize}[value] ||= find_by_#{normalized_field.to_s}(value.to_s)
              @@#{self.name.underscore.pluralize}[value]
            end
          HASHNOTATIONACCESS
        end
      end
      has_many model, options
    end