Module: ActiveRecord::ConnectionAdapters::ConstraintHandlers::Postgresql::ClassMethods

Defined in:
lib/activerecord_constraint_handlers.rb

Overview

We need class methods and class instance variables to hold the data. We want them in the class so that the work is done only once for the life of the application. In the PostgreSQL case, we leverage off of ActiveRecord::Base by creating three nested models so they are hidden syntactically that use the model as their base class so that they use the same connection as the model itself. This allows other models to use other connections and the data is kept separate.

Instance Method Summary collapse

Instance Method Details

#constraint_to_columns(constraint) ⇒ Object

Converts the constraint name into a list of column names.



181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
# File 'lib/activerecord_constraint_handlers.rb', line 181

def constraint_to_columns(constraint)
  ActiveRecord::Base.logger.debug("constraint_to_columns: '#{constraint}' (#{constraint.class})")

  # Should never hit this now... added during debugging.
  unless pg_constraint_hash.has_key?(constraint)
    ActiveRecord::Base.logger.debug("constraint_to_columns: constraint not found")
    return
  end
  # pg_constraint_hash is a hash from the contraint name to
  # the constraint.  The conkey is a string of the form:
  # +{2,3,4}+ (with the curly braces).  The numbers are
  # column indexes which we pull out from pg_attribute and
  # convert to a name.  Note that the PostgreSQL tables are
  # singular in name: pg_constraint and pg_attribute
  k = pg_constraint_hash[constraint].conkey
  k[1 ... (k.length - 1)].
    split(',').
    map{ |s| pg_attribute_hash[s.to_i].attname }
end

#create_subclassesObject

At the time of the first call, we create the models needed for the code above as a nested subclass of the model using the model as the base.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/activerecord_constraint_handlers.rb', line 140

def create_subclasses
  ActiveRecord::Base.logger.debug("create_subclasses")
  self.class_eval <<-EOF
    class PgClass < #{self}
      set_table_name "pg_class"
      set_primary_key "oid"
      self.default_scoping = []
    end

    class PgAttribute < #{self}
      set_table_name "pg_attribute"
      set_primary_key "oid"
      belongs_to :attrel, :class_name => "PgClass", :foreign_key => :attrelid
      self.default_scoping = []
    end

    class PgConstraint < #{self}
      set_table_name "pg_constraint"
      set_primary_key "oid"
      belongs_to :conrel, :class_name => "PgClass", :foreign_key => :conrelid
      self.default_scoping = []
    end
  EOF
end

#pg_attribute_constantObject

Create the constant for the PgAttribute nested model.



88
89
90
# File 'lib/activerecord_constraint_handlers.rb', line 88

def pg_attribute_constant
  "#{self}::PgAttribute".constantize
end

#pg_attribute_hashObject

Accessor for the attribute hash



133
134
135
# File 'lib/activerecord_constraint_handlers.rb', line 133

def pg_attribute_hash
  @pg_attribute_hash
end

#pg_attributesObject

Find the attributes for this model



120
121
122
123
124
125
126
127
128
129
130
# File 'lib/activerecord_constraint_handlers.rb', line 120

def pg_attributes
  ActiveRecord::Base.logger.debug("pg_attributes")
  if @pg_attributes.nil?
    @pg_attributes = pg_attribute_constant.find(:all,
                                                :joins => :attrel,
                                                :conditions => { :pg_class => { :relname => table_name }})
    @pg_attribute_hash = Hash.new
    @pg_attributes.each { |a| @pg_attribute_hash[a.attnum] = a }
  end
  @pg_attributes
end

#pg_classObject

Turns out, we don’t really use this…



93
94
95
96
# File 'lib/activerecord_constraint_handlers.rb', line 93

def pg_class
  ActiveRecord::Base.logger.debug("pg_class")
  @pg_class ||= pg_class_constant.find_by_relname(table_name)
end

#pg_class_constantObject

Create the constant for the PgClass nested model.



78
79
80
# File 'lib/activerecord_constraint_handlers.rb', line 78

def pg_class_constant
  "#{self}::PgClass".constantize
end

#pg_constraint_constantObject

Create the constant for the PgConstraint nested model.



83
84
85
# File 'lib/activerecord_constraint_handlers.rb', line 83

def pg_constraint_constant
  "#{self}::PgConstraint".constantize
end

#pg_constraint_hashObject

Accessor for the constraint hash



115
116
117
# File 'lib/activerecord_constraint_handlers.rb', line 115

def pg_constraint_hash
  @pg_constraint_hash
end

#pg_constraintsObject

Find the constraints for this model / table



99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/activerecord_constraint_handlers.rb', line 99

def pg_constraints
  ActiveRecord::Base.logger.debug("pg_constraints")
  if @pg_constraints.nil?
    @pg_constraints = pg_constraint_constant.find(:all,
                                                  :joins => :conrel,
                                                  :conditions => { :pg_class => { :relname => table_name }})
    @pg_constraint_hash = Hash.new
    @pg_constraints.each { |c|
      ActiveRecord::Base.logger.debug("Adding '#{c.conname}' to constraint_hash")
      @pg_constraint_hash[c.conname] = c
    }
  end
  @pg_constraints
end

#pre_fetchObject

We can not rummage around in the database after an error has occurred or we will get back more errors that an error has already occurred and further queries will be ignored. So, we pre-fetch the system tables that we need and save them in our pockets.



170
171
172
173
174
175
176
177
178
# File 'lib/activerecord_constraint_handlers.rb', line 170

def pre_fetch
  ActiveRecord::Base.logger.debug("pre_fetch #{self} #{table_name} #{@pg_class.nil?}")
  if @pg_class.nil?
    create_subclasses
    pg_class
    pg_constraints
    pg_attributes
  end
end