Module: ActiveRecord::PGArray

Defined in:
lib/activerecord/pg_array.rb

Class Method Summary collapse

Class Method Details

.included(base) ⇒ Object



6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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
107
108
109
110
111
112
113
114
115
# File 'lib/activerecord/pg_array.rb', line 6

def self.included(base)
  base.class_eval do
    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
        if 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