Class: Nexo::ElementService

Inherits:
Object
  • Object
show all
Defined in:
app/lib/nexo/element_service.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(element_version: nil, element: nil) ⇒ ElementService

Returns a new instance of ElementService.



5
6
7
8
# File 'app/lib/nexo/element_service.rb', line 5

def initialize(element_version: nil, element: nil)
  @element_version = element_version
  @element = element || element_version&.element
end

Instance Attribute Details

#elementObject

Returns the value of attribute element.



3
4
5
# File 'app/lib/nexo/element_service.rb', line 3

def element
  @element
end

#element_versionObject

Returns the value of attribute element_version.



3
4
5
# File 'app/lib/nexo/element_service.rb', line 3

def element_version
  @element_version
end

Instance Method Details

#create_element_for!(folder, synchronizable) ⇒ Object



168
169
170
171
172
173
174
175
176
177
178
179
# File 'app/lib/nexo/element_service.rb', line 168

def create_element_for!(folder, synchronizable)
  Element.transaction do
    @element = Element.create!(
      synchronizable:,
      folder:,
      ne_status: :pending_local_sync
    )
    Nexo.logger.debug { "Element created" }

    _create_internal_version!
  end
end

#create_element_for_remote_resource!(folder, response) ⇒ Object



160
161
162
163
164
165
166
# File 'app/lib/nexo/element_service.rb', line 160

def create_element_for_remote_resource!(folder, response)
  Element.create!(
    folder:,
    uuid: response.id,
    ne_status: :pending_external_sync
  )
end

#create_element_version!(attributes) ⇒ Object



10
11
12
13
14
# File 'app/lib/nexo/element_service.rb', line 10

def create_element_version!(attributes)
  element.with_lock do
    _create_element_version!(attributes)
  end
end

#create_internal_version_if_none!Object



145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'app/lib/nexo/element_service.rb', line 145

def create_internal_version_if_none!
  # NOTE: though synchronizable it's not locked and could change in between
  # the block, this shouldn't run concurrently because its called from
  # SynchronizableChangedJob that its limited to one perform at a time per
  # synchronizable
  element.with_lock do
    if element.element_versions.where(sequence: element.synchronizable.sequence).any?
      Nexo.logger.debug { "There is a version for current sequence, nothing to do" }
      return
    end

    _create_internal_version!
  end
end

#discard!Object



27
28
29
30
# File 'app/lib/nexo/element_service.rb', line 27

def discard!
  element.update!(discarded_at: Time.current)
  _update_ne_status!
end

#flag_for_removal!(removal_reason) ⇒ Object

The reason of ‘flagged_for_removal` is that there is a time gap between the the user action of removing the element and the actual API call that deletes the remote element. At some point is necesary to know if the element is being deleted, like when fetching a remote version previous to the actual delete API call.



37
38
39
40
41
42
# File 'app/lib/nexo/element_service.rb', line 37

def flag_for_removal!(removal_reason)
  Nexo.logger.debug("Flagging an element for removal")

  element.update!(flagged_for_removal: true, removal_reason:)
  _update_ne_status!
end

#resolve_conflict!Object



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
# File 'app/lib/nexo/element_service.rb', line 104

def resolve_conflict!
  unless element.conflicted?
    raise "element not conflicted"
  end

  # both lock on Element and start a transaction
  element.with_lock do
    external_change = element.element_versions.where(origin: :external, nev_status: :pending_sync).order(:etag).last
    local_change = element.element_versions.where(origin: :internal, nev_status: :pending_sync).order(:sequence).last
    last_synced = element.element_versions.where(nev_status: :synced).order(:sequence).last

    if local_change.sequence < last_synced.sequence
      raise "there a newer synced sequence"
    end

    if external_change.etag < last_synced.etag
      raise "there a newer synced etag"
    end

    Nexo.logger.debug { "resolving conflict" }
    remote_update = Time.zone.parse(external_change.payload["updated"])
    local_update = element.synchronizable.updated_at
    Nexo.logger.debug { "Remote updated at: #{remote_update}. Local updated at #{local_update}" }
    if remote_update > local_update
      Nexo.logger.debug { "Remote wins, ignoring local change" }
      _update_status_on_conflict_with_winner!(external_change)
      ImportRemoteElementVersion.new.perform(external_change)
    else
      Nexo.logger.debug { "Local wins, discarding remote changes" }
      _update_status_on_conflict_with_winner!(local_change)
      UpdateRemoteResourceJob.perform_later(local_change)
    end
  end
end

#update_element!(attributes) ⇒ Object



23
24
25
# File 'app/lib/nexo/element_service.rb', line 23

def update_element!(attributes)
  element.update!(attributes)
end

#update_element_version!(attributes) ⇒ Object



16
17
18
19
20
21
# File 'app/lib/nexo/element_service.rb', line 16

def update_element_version!(attributes)
  element.with_lock do
    element_version.update!(attributes)
    _update_ne_status!
  end
end

#update_ne_status!Object



139
140
141
142
143
# File 'app/lib/nexo/element_service.rb', line 139

def update_ne_status!
  element.with_lock do
    _update_ne_status!
  end
end

#update_synchronizable!Object

Raises:

  • ActiveRecord::RecordNotUnique



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
# File 'app/lib/nexo/element_service.rb', line 45

def update_synchronizable!
  Nexo.logger.debug("update_synchronizable!")

  element.with_lock do
    service = ServiceBuilder.instance.build_protocol_service(element_version.element.folder)
    fields = service.fields_from_payload(element_version.payload)

    # and set the Synchronizable fields according to the Folder#nexo_protocol
    synchronizable = element_version.element.synchronizable
    if element.flagged_for_removal?
      Nexo.logger.info("Element flagged for removal")
      ElementService.new(element_version:).update_element_version!(
        nev_status: :ignored_by_deletion
      )
    elsif synchronizable.present?
      synchronizable.update_from_fields!(fields)

      # synchronizable could have been destroyed
      if synchronizable.persisted?
        # si esto se ejecuta en paralelo con SynchronizableChangedJob? (para otro
        # element del mismo synchronizable) puede haber race conditions
        synchronizable.increment_sequence!
        synchronizable.reload

        ElementService.new(element_version:).update_element_version!(
          sequence: synchronizable.sequence,
          nev_status: :synced
        )

        SynchronizableChangedJob.perform_later(synchronizable, excluded_folders: [ element.folder.id ])
      else
        Nexo.logger.debug("Synchronizable destroyed. Removing other elements")
        ElementService.new(element_version:).update_element_version!(
          sequence: nil,
          nev_status: :synced
        )

        FolderService.new.destroy_elements(
          synchronizable, :synchronizable_destroyed, exclude_elements: [ element.id ])
      end
    else
      Nexo.logger.info("Synchronizable not found")
      policies = PolicyService.instance.policies_for(element.folder)
      importer_rule = policies.select { |p| p.import_payload?(element_version.payload) }.first
      if importer_rule.present?
        Nexo.logger.debug("Found an importer rule")
        synchronizable = importer_rule.create_synchronizable_from_payload!(element_version.payload)
        ElementService.new(element:).update_element!(synchronizable:)
        ElementService.new(element_version:).update_element_version!(
          nev_status: :synced,
          sequence: synchronizable.sequence
        )
      else
        Nexo.logger.info("No importer rule found for event. Skipping")
      end
    end
  end
end