module ActiveRecord::PGArray

Public Class Methods

config_array_serializer(hash) click to toggle source
# File lib/activerecord/pg_array.rb, line 9
def self.config_array_serializer(hash)
  @@id_attr_map = hash
end
included(base) click to toggle source
# File lib/activerecord/pg_array.rb, line 6
def self.included(base)
  base.class_eval do

    def self.config_array_serializer(hash)
      @@id_attr_map = hash
    end

    self.column_types.to_a.select { |c| c[1].instance_variable_get('@array') }.map(&:first).each do |attr_name|
      ids_regex = /_ids$/
      friendly_attr = attr_name.sub(ids_regex,'')
      segs = friendly_attr.split('_')
      segs[-1] = segs[-1].singularize
      friendly_attr_singular = segs.join('_')
      segs[-1] = segs[-1].pluralize
      friendly_attr_plural = segs.join('_')

      obj_convert = ->(obj) do
        custom_serializer = @@id_attr_map[attr_name.to_sym][obj.class.name.to_sym] rescue nil

        if custom_serializer
          obj = obj.send(custom_serializer)
        elsif attr_name =~ ids_regex && obj.kind_of?(ActiveRecord::Base) and
              self.column_types[attr_name].type == :integer
          obj = obj.id
        end
        obj
      end

      atr = ->(slf) do
        slf.send attr_name.to_sym
      end

      atr_will_change = ->(slf) do
        slf.send(:"#{attr_name}_will_change!")
      end

      define_method :"add_#{friendly_attr_singular}" do |obj|
        obj = obj_convert[obj]
        unless atr[self].include?(obj)
          atr[self].push(obj)
          atr_will_change[self]
        end
      end

      define_method :"add_#{friendly_attr_singular}!" do |obj|
        obj = obj_convert[obj]
        atr_will_change[self] # seems strange that calling this is needed
        
        # There are two external issues that block atomic updates to one attribute.
        # 1. ActiveRecord update_attribute actually updates all attributes that are dirty! This surprised me.
        # 2. update_column doesn't work on pg arrays for rails < 4.0.4 (which is not yet released)
        #    https://github.com/rails/rails/issues/12261
        atr[self].push(obj).uniq!
        self.update_attribute attr_name.to_sym, atr[self]
      end

      define_method :"add_#{friendly_attr_plural}" do |*objs|
        objs.each do |obj|
          if obj.kind_of? Array
            self.send :"add_#{friendly_attr_plural}", *obj
          else
            self.send :"add_#{friendly_attr_singular}", obj
          end
        end
      end

      define_method :"add_#{friendly_attr_plural}!" do |*objs|
        self.send :"add_#{friendly_attr_plural}", *objs
        self.save!
      end

      define_method :"remove_#{friendly_attr_singular}" do |obj|
        obj = obj_convert.call(obj)
        if atr[self].include?(obj)
          atr[self].delete(obj)
          atr_will_change[self]
        end
      end

      define_method :"remove_#{friendly_attr_singular}!" do |obj|
        self.send :"remove_#{friendly_attr_singular}", obj
        self.save!
      end

      define_method :"remove_#{friendly_attr_plural}" do |*objs|
        objs.each do |obj|
          if obj.kind_of? Array
            self.send :"remove_#{friendly_attr_plural}", *obj
          else
            self.send :"remove_#{friendly_attr_singular}", obj
          end
        end
      end

      define_method :"remove_#{friendly_attr_plural}!" do |*objs|
        self.send :"remove_#{friendly_attr_plural}", *objs
        self.save!
      end
      
      # define basic relational lookup methods
      # example:
      #   Given wolf_ids is the attribute
      #   Then it will try to define method wolves that retrieves wolf objects
      if attr_name =~ ids_regex
        if defined?(friendly_attr_singular.camelize.to_sym) and
           self.column_types[attr_name].type == :integer
          begin
            klass = friendly_attr_singular.camelize.constantize

            # it might be better to define a scope instead
            define_method friendly_attr_plural.to_sym do
              klass.where(id: [atr[self]])
            end
          rescue NameError
          end
        end
      end

    end
  end # base.class_eval
end