Module: Replicate::AR::InstanceMethods

Defined in:
lib/replicate/active_record.rb

Overview

Mixin for the ActiveRecord instance.

Instance Method Summary collapse

Instance Method Details

#dump_all_association_replicants(dumper, association_type) ⇒ Object

Dump all associations of a given type.

dumper - The Dumper object used to dump additional objects. association_type - :has_one, :belongs_to, :has_many

Returns nothing.



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/replicate/active_record.rb', line 128

def dump_all_association_replicants(dumper, association_type)
  self.class.reflect_on_all_associations(association_type).each do |reflection|
    next if omitted_attributes.include?(reflection.name)

    # bail when this object has already been dumped
    next if (info = replicate_reflection_info(reflection)) &&
      (replicant_id = info[:replicant_id]) &&
      dumper.dumped?(replicant_id)

    next if (dependent = __send__(reflection.name)).nil?

    case dependent
    when ActiveRecord::Base, Array
      dumper.dump(dependent)

      # clear reference to allow GC
      if respond_to?(:association)
        association(reflection.name).reset
      elsif respond_to?(:association_instance_set, true)
        association_instance_set(reflection.name, nil)
      end
    else
      warn "warn: #{self.class}##{reflection.name} #{association_type} association " \
           "unexpectedly returned a #{dependent.class}. skipping."
    end
  end
end

#dump_association_replicants(dumper, association) ⇒ Object

Dump objects associated with an AR object through an association name.

object - AR object instance. association - Name of the association whose objects should be dumped.

Returns nothing.



162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/replicate/active_record.rb', line 162

def dump_association_replicants(dumper, association)
  if reflection = self.class.reflect_on_association(association)
    objects = __send__(reflection.name)
    dumper.dump(objects)
    if reflection.macro == :has_and_belongs_to_many
      dump_has_and_belongs_to_many_replicant(dumper, reflection)
    end
    __send__(reflection.name).reset # clear to allow GC
  else
    warn "error: #{self.class}##{association} is invalid"
  end
end

#dump_has_and_belongs_to_many_replicant(dumper, reflection) ⇒ Object

Dump the special Habtm object used to establish many-to-many relationships between objects that have already been dumped. Note that this object and all objects referenced must have already been dumped before calling this method.



179
180
181
# File 'lib/replicate/active_record.rb', line 179

def dump_has_and_belongs_to_many_replicant(dumper, reflection)
  dumper.dump Habtm.new(self, reflection)
end

#dump_replicant(dumper, opts = {}) ⇒ Object

Replicate::Dumper calls this method on objects to trigger dumping a replicant object tuple. The default implementation dumps all belongs_to associations, then self, then all has_one associations, then any has_many or has_and_belongs_to_many associations declared with the replicate_associations macro.

dumper - Dumper object whose #write method must be called with the

type, id, and attributes hash.

Returns nothing.



22
23
24
25
26
27
28
29
30
31
32
# File 'lib/replicate/active_record.rb', line 22

def dump_replicant(dumper, opts={})
  @replicate_opts = opts
  @replicate_opts[:associations] ||= []
  @replicate_opts[:omit] ||= []
  dump_all_association_replicants dumper, :belongs_to
  dumper.write self.class.to_s, id, replicant_attributes, self
  dump_all_association_replicants dumper, :has_one
  included_associations.each do |association|
    dump_association_replicants dumper, association
  end
end

#included_associationsObject

List of associations to explicitly include when dumping this object.



35
36
37
# File 'lib/replicate/active_record.rb', line 35

def included_associations
  (self.class.replicate_associations + @replicate_opts[:associations]).uniq
end

#omitted_attributesObject

List of attributes and associations to omit when dumping this object.



40
41
42
# File 'lib/replicate/active_record.rb', line 40

def omitted_attributes
  (self.class.replicate_omit_attributes + @replicate_opts[:omit]).uniq
end

#replicant_attributesObject

Attributes hash used to persist this object. This consists of simply typed values (no complex types or objects) with the exception of special foreign key values. When an attribute value is [:id, “SomeClass:1234”], the loader will handle translating the id value to the local system’s version of the same object.



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/replicate/active_record.rb', line 49

def replicant_attributes
  attributes = self.attributes.dup

  omitted_attributes.each { |omit| attributes.delete(omit.to_s) }
  self.class.reflect_on_all_associations(:belongs_to).each do |reflection|
    if info = replicate_reflection_info(reflection)
      if replicant_id = info[:replicant_id]
        foreign_key = info[:foreign_key].to_s
        attributes[foreign_key] = [:id, *replicant_id]
      end
    end
  end

  attributes
end

#replicant_idObject

The replicant id is a two tuple containing the class and object id. This is used by Replicant::Dumper to determine if the object has already been dumped or not.



118
119
120
# File 'lib/replicate/active_record.rb', line 118

def replicant_id
  [self.class.name, id]
end

#replicate_reflection_info(reflection) ⇒ Object

Retrieve information on a reflection’s associated class and various keys.

Returns an info hash with these keys:

:class - The class object the association points to.
:primary_key  - The string primary key column name.
:foreign_key  - The string foreign key column name.
:replicant_id - The [classname, id] tuple identifying the record.

Returns nil when the reflection can not be linked to a model.



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
# File 'lib/replicate/active_record.rb', line 75

def replicate_reflection_info(reflection)
  options = reflection.options
  if options[:polymorphic]
    reference_class =
      if ::ActiveRecord::VERSION::MAJOR == 3 && ::ActiveRecord::VERSION::MINOR > 0
        attributes[reflection.foreign_type]
      else
        attributes[options[:foreign_type]]
      end
    return if reference_class.nil?

    klass = Kernel.const_get(reference_class)
    primary_key = klass.primary_key
    foreign_key = "#{reflection.name}_id"
  else
    klass = reflection.klass
    primary_key = (options[:primary_key] || klass.primary_key).to_s
    foreign_key = (options[:foreign_key] || "#{reflection.name}_id").to_s
  end

  info = {
    :class       => klass,
    :primary_key => primary_key,
    :foreign_key => foreign_key
  }

  if primary_key == klass.primary_key
    if id = attributes[foreign_key]
      info[:replicant_id] = [klass.to_s, id]
    else
      # nil value in association reference
    end
  else
    # association uses non-primary-key foreign key. no special key
    # conversion needed.
  end

  info
end