Module: NormalizeIt::ClassMethods

Defined in:
lib/normalize_it.rb

Overview

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 = '[email protected]' 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 '[email protected]'
  * new objects may be initialized either with attributes or through later assignment. e.g. Customer.new(:email_address => '[email protected]')

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#_normalized_modelsObject (readonly)

Returns the value of attribute _normalized_models.



63
64
65
# File 'lib/normalize_it.rb', line 63

def _normalized_models
  @_normalized_models
end

Instance Method Details

#has_normalized(model, options = {}) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
# 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 = {}) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# 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