Module: Transferable

Included in:
Accession, ArchivalObject, Resource
Defined in:
backend/app/model/mixins/transferable.rb

Instance Method Summary collapse

Instance Method Details

#transfer_to_repository(repository, transfer_group = []) ⇒ Object



3
4
5
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
116
117
118
119
120
121
122
123
124
125
126
127
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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'backend/app/model/mixins/transferable.rb', line 3

def transfer_to_repository(repository, transfer_group = [])
  events_to_clone = []
  assessments_to_clone = []
  containers_to_clone = {}

  graph = self.object_graph

  ## Digital object instances will trigger their linked digital objects to
  ## transfer too, as long as those digital objects aren't linked to by other
  ## records.

  # We skip over tree nodes because the root record will take care of
  # transferring their linked digital objects.
  unless self.class.included_modules.include?(TreeNodes)
    do_instance_relationship = Instance.find_relationship(:instance_do_link)

    # The list of instance record IDs connected to our transferee
    instance_ids = graph.ids_for(Instance)

    # The list of digital objects our transferee links to
    linked_digital_objects = do_instance_relationship
                             .filter(:id => graph.ids_for(do_instance_relationship))
                             .select(:digital_object_id)
                             .map {|row| row[:digital_object_id]}

    # The list of instance IDs that link to those digital objects (which may or
    # may not be connected to our transferee)
    instances_referencing_digital_objects = do_instance_relationship
                                            .find_by_participant_ids(DigitalObject, linked_digital_objects)
                                            .map {|r| r.instance_id}


    linked_instances_outside_transfer_set = (instances_referencing_digital_objects - instance_ids)

    if linked_instances_outside_transfer_set.empty?
      # Our record to be transferred is the only thing referencing the digital
      # objects it links to.  We can safely migrate them as well.

      DigitalObject.any_repo.filter(:id => linked_digital_objects).each do |digital_object|
        digital_object.transfer_to_repository(repository, transfer_group + [self])
      end
    else
      # Abort the transfer and provide the list of top-level records that are
      # preventing it from completing.
      exception = TransferConstraintError.new

      ASModel.all_models.each do |model|
        next unless model.associations.include?(:instance)

        model
          .eager_graph(:instance)
          .filter(:instance__id => linked_instances_outside_transfer_set)
          .select(Sequel.qualify(model.table_name, :id))
          .each do |row|
          exception.add_conflict(model.my_jsonmodel.uri_for(row[:id], :repo_id => self.class.active_repository),
                                 {:json_property => 'instances',
                                  :message => "DIGITAL_OBJECT_IN_USE"})
        end
      end

      raise exception
    end
  end

  ## Event records will be transferred if they only link to records that are
  ## being transferred too.  Otherwise, we clone the event.
  Event.find_relationship(:event_link).who_participates_with(self).each do |event|
    linked_records = event.related_records(:event_link)

    if linked_records.length == 1
      # Events whose linked_records list contains only the record being
      # transferred should themselves be transferred.
      event.transfer_to_repository(repository, transfer_group + [self])
    else
      event_json = Event.to_jsonmodel(event)
      event_role = event_json.linked_records.find {|link| link['ref'] == self.uri}['role']

      events_to_clone << {:event => event_json, :role => event_role}
    end
  end

  ## As with events, Assessment records will be transferred if they only link
  ## to records that are being transferred too.  Otherwise, we clone the
  ## assessment in the target repository.
  Assessment.find_relationship(:assessment).who_participates_with(self).each do |assessment|
    linked_records = assessment.related_records(:assessment)

    if linked_records.length == 1
      # Assessments whose linked_records list contains only the record being
      # transferred should themselves be transferred.
      assessment.transfer_to_repository(repository, transfer_group + [self])
    else
      assessment_json = Assessment.to_jsonmodel(assessment)

      assessments_to_clone << {:assessment => assessment_json}
    end
  end

  ## Transfer any top containers that aren't linked outside of the record set
  ## being cloned.  For any other linked top containers, we'll clone them in
  ## the target repository (much like events).

  # the relationship between SC and TCs
  topcon_rlshp = SubContainer.find_relationship(:top_container_link)
  # now we get  all the relationships in this graph
  all_ids = graph.ids_for(topcon_rlshp)
  # add any conflicts to this, then raise unless it is empty
  error = TransferConstraintError.new
  top_container_transferees = []

  DB.open do |db|
    # Find relationships that are in our set of IDs that haven't been
    # transferred yet
    db[:top_container_link_rlshp]
      .join(:top_container, :id => :top_container_id)
      .filter(:top_container_link_rlshp__id => all_ids)
      .filter(:top_container__repo_id => self.class.active_repository)
      .each do |tc_rel|

      top_container = TopContainer.this_repo.filter(id: tc_rel[:top_container_id]).first
      # Already transferred
      next unless top_container

      # lets get all this container's links outside transferred object's graph
      number_of_tc_links = db[:top_container_link_rlshp]
                           .join(:top_container, :id => :top_container_id)
                           .filter(:top_container__repo_id => self.class.active_repository)
                           .filter(:top_container_id => tc_rel[:top_container_id])
                           .exclude(:top_container_link_rlshp__id => all_ids)
                           .count

      # this tc linked beyond this graph..so it's an error
      if number_of_tc_links > 0
        # ANW-979
        # Cancel transfer and let user know that they must unlink other top container
        error.add_conflict(top_container.uri, message: "TOP_CONTAINER_IN_USE")
      end

      if top_container.barcode && TopContainer.any_repo[:barcode => top_container.barcode, :repo_id => repository.id]
        # There's already a top container with our barcode in the target
        # repository.  Not sure if merging them is the right strategy or
        # not, so throwing an error for now
        error.add_conflict("#{top_container.uri}", message: "BARCODE_IN_USE")
      end

      top_container_transferees << top_container
    end
    raise error unless error.conflicts.empty?

    top_container_transferees.each do |top_container|
      top_container.transfer_to_repository(repository, transfer_group + [self]) # i guess we always add self just in case. dups are uniqed out.
    end
  end

  ## Transfer the current record
  super

  ## Clone the top containers that we marked for cloning
  containers_to_clone.each do |tc_to_clone, sces|
    # we copy the TC to be cloned
    hash = TopContainer.to_jsonmodel(tc_to_clone).to_hash(:trusted)

    # we make the TC in the new repo context
    RequestContext.open(:repo_id => repository.id) do
      tc = nil

      # but first lets check if this exists already by barcode
      if hash["barcode"]
        tc = TopContainer.for_barcode(hash["barcode"])
      end

      # not found, we make the clone
      unless tc
        tc = TopContainer.create_from_json(JSONModel(:top_container).from_hash(hash),
                                           :repo_id => repository.id)
      end

      # now we linke the clone to all the sub_containers
      sces.each do |sc|
        # the record is broken now, so we have to shuck it in the backdoor
        DB.open do |db|
          db[:top_container_link_rlshp].insert(:top_container_id => tc.id,
                                               :system_mtime => Time.now,
                                               :user_mtime => Time.now,
                                               :sub_container_id => sc)
        end
      end
    end
  end

  ## Clone any required events and assessments in the new repository
  RequestContext.open(:repo_id => repository.id) do
    # Events
    events_to_clone.each do |to_clone|
      event = to_clone[:event].to_hash(:trusted).
              merge('linked_records' => [{
                                           'ref' => self.uri,
                                           'role' => to_clone[:role]
                                         }])

      Event.create_from_json(JSONModel(:event).from_hash(event),
                             :repo_id => repository.id)
    end

    # Assessments
    assessments_to_clone.each do |to_clone|
      assessment = to_clone[:assessment].to_hash(:trusted).
                     merge('records' => [{
                                           'ref' => self.uri,
                                         }])

      # Note: we use JSONModel#new here and not from_hash because we want to
      # keep the readonly attribute (like attribute labels).  The clone needs
      # access to those for the sake of cross-repository attribute matching.
      Assessment.clone_from_json(JSONModel(:assessment).new(assessment),
                                 :repo_id => repository.id)
    end
  end
end