Class: PaperTrailDiff::PreparedEdgeLoader

Inherits:
Object
  • Object
show all
Defined in:
lib/paper_trail_diff/prepared_edge_loader.rb,
sig/generated/paper_trail_diff/prepared_edge_loader.rbs

Overview

Loads every historical identity that can participate in one association edge.

Constant Summary collapse

EDGE_LOADERS =

Returns:

  • (Object)
{
  belongs_to: :belongs_to_edge,
  has_one: :child_edge,
  has_many: :child_edge,
  has_and_belongs_to_many: :habtm_edge
}.freeze

Instance Method Summary collapse

Constructor Details

#initialize(records, root_versions, start_at: root_versions.first&.created_at) ⇒ PreparedEdgeLoader

: (PreparedRecordIndex, Array, ?start_at: untyped) -> void

Parameters:

  • (PreparedRecordIndex)
  • (Array[untyped])
  • start_at: (Object) (defaults to: root_versions.first&.created_at)


16
17
18
19
20
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 16

def initialize(records, root_versions, start_at: root_versions.first&.created_at)
  @records = records
  @transaction_ids = root_versions.filter_map(&:transaction_id).uniq.freeze
  @start_at = start_at
end

Instance Method Details

#add_identity(groups, model_class, id) ⇒ void

This method returns an undefined value.

: (Hash[String, Hash[Symbol, untyped]], untyped, untyped) -> void

Parameters:

  • (Hash[String, Hash[Symbol, untyped]])
  • (Object)
  • (Object)


152
153
154
155
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 152

def add_identity(groups, model_class, id)
  group = groups[model_class.name.to_s] ||= { model: model_class, ids: [] }
  group.fetch(:ids) << id unless group.fetch(:ids).include?(id)
end

#belongs_to_edge(owner_class, owner_ids, reflection) ⇒ [ Hash[String, Hash[Symbol, untyped]], nil ]

: (untyped, Array, untyped) -> [Hash[String, Hash[Symbol, untyped]], nil]

Parameters:

  • (Object)
  • (Array[untyped])
  • (Object)

Returns:

  • ([ Hash[String, Hash[Symbol, untyped]], nil ])


35
36
37
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 35

def belongs_to_edge(owner_class, owner_ids, reflection)
  [belongs_to_groups(owner_class, owner_ids, reflection), nil]
end

#belongs_to_groups(owner_class, owner_ids, reflection) ⇒ Hash[String, Hash[Symbol, untyped]]

: (untyped, Array, untyped) -> Hash[String, Hash[Symbol, untyped]]

Parameters:

  • (Object)
  • (Array[untyped])
  • (Object)

Returns:

  • (Hash[String, Hash[Symbol, untyped]])


50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 50

def belongs_to_groups(owner_class, owner_ids, reflection)
  key = scalar_foreign_key(reflection)
  return {} unless key

  grouped = {} #: Hash[String, Hash[Symbol, untyped]]
  @records.records_for(owner_class, owner_ids).each do |owner|
    id = owner.public_send(key)
    model_class = target_class(reflection, owner)
    add_identity(grouped, model_class, id) if model_class && id
  end
  grouped
end

#call(owner_class, owner_ids, reflection) ⇒ [ Hash[String, Hash[Symbol, untyped]], Hash[String, Array[untyped]]? ]

: (untyped, Array, untyped) -> [Hash[String, Hash[Symbol, untyped]], Hash[String, Array[untyped]]?]

Parameters:

  • (Object)
  • (Array[untyped])
  • (Object)

Returns:

  • ([ Hash[String, Hash[Symbol, untyped]], Hash[String, Array[untyped]]? ])


23
24
25
26
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 23

def call(owner_class, owner_ids, reflection)
  loader = EDGE_LOADERS.fetch(reflection.macro)
  send(loader, owner_class, owner_ids, reflection)
end

#child_candidate_scope(version_class) ⇒ VersionAssociationCandidateScope

: (untyped) -> VersionAssociationCandidateScope

Parameters:

  • (Object)

Returns:



126
127
128
129
130
131
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 126

def child_candidate_scope(version_class)
  VersionAssociationCandidateScope.new(
    version_class,
    @start_at
  )
end

#child_edge(owner_class, owner_ids, reflection) ⇒ [ Hash[String, Hash[Symbol, untyped]], nil ]

: (untyped, Array, untyped) -> [Hash[String, Hash[Symbol, untyped]], nil]

Parameters:

  • (Object)
  • (Array[untyped])
  • (Object)

Returns:

  • ([ Hash[String, Hash[Symbol, untyped]], nil ])


40
41
42
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 40

def child_edge(owner_class, owner_ids, reflection)
  [child_groups(owner_class, owner_ids, reflection), nil]
end

#child_groups(owner_class, owner_ids, reflection) ⇒ Hash[String, Hash[Symbol, untyped]]

: (untyped, Array, untyped) -> Hash[String, Hash[Symbol, untyped]]

Parameters:

  • (Object)
  • (Array[untyped])
  • (Object)

Returns:

  • (Hash[String, Hash[Symbol, untyped]])


64
65
66
67
68
69
70
71
72
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 64

def child_groups(owner_class, owner_ids, reflection)
  key = scalar_foreign_key(reflection)
  return {} unless key

  historical = historical_child_ids(owner_class, owner_ids, reflection, key)
  live = live_child_ids(owner_class, owner_ids, reflection, key)
  owners = merge_owners(historical, live)
  group_for(reflection.klass, owners.values.flatten, owners: owners)
end

#group_for(model_class, ids, owners: nil) ⇒ Hash[String, Hash[Symbol, untyped]]

: (untyped, Array, ?owners: Hash[String, Array[untyped]]?) -> Hash[String, Hash[Symbol, untyped]]

Parameters:

  • (Object)
  • (Array[untyped])
  • owners: (Hash[String, Array[untyped]], nil) (defaults to: nil)

Returns:

  • (Hash[String, Hash[Symbol, untyped]])


158
159
160
161
162
163
164
165
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 158

def group_for(model_class, ids, owners: nil)
  unique = ids.compact.uniq
  return {} if unique.empty?

  group = { model: model_class, ids: unique } #: Hash[Symbol, untyped]
  group[:owners] = owners if owners
  { model_class.name.to_s => group }
end

#group_ids_by_owner(pairs) ⇒ Hash[String, Array[untyped]]

Keyed rather than scanned so that a wide edge stays linear in its width. : (Array[Array[untyped]]) -> Hash[String, Array[untyped]]

Parameters:

  • (Array[Array[untyped]])

Returns:

  • (Hash[String, Array[untyped]])


169
170
171
172
173
174
175
176
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 169

def group_ids_by_owner(pairs)
  grouped = {} #: Hash[String, Hash[untyped, true]]
  pairs.each do |owner_id, child_id|
    ids = grouped[owner_id.to_s] ||= {} #: Hash[untyped, true]
    ids[child_id] = true
  end
  grouped.transform_values(&:keys)
end

#habtm_edge(owner_class, _owner_ids, reflection) ⇒ [ Hash[String, Hash[Symbol, untyped]], Hash[String, Array[untyped]] ]

: (untyped, Array, untyped) -> [Hash[String, Hash[Symbol, untyped]], Hash[String, Array[untyped]]]

Parameters:

  • (Object)
  • (Array[untyped])
  • (Object)

Returns:

  • ([ Hash[String, Hash[Symbol, untyped]], Hash[String, Array[untyped]] ])


45
46
47
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 45

def habtm_edge(owner_class, _owner_ids, reflection)
  habtm_groups(owner_class, reflection)
end

#habtm_groups(owner_class, reflection) ⇒ [ Hash[String, Hash[Symbol, untyped]], Hash[String, Array[untyped]] ]

: (untyped, untyped) -> [Hash[String, Hash[Symbol, untyped]], Hash[String, Array[untyped]]]

Parameters:

  • (Object)
  • (Object)

Returns:

  • ([ Hash[String, Hash[Symbol, untyped]], Hash[String, Array[untyped]] ])


75
76
77
78
79
80
81
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 75

def habtm_groups(owner_class, reflection)
  rows = habtm_rows(owner_class, reflection)
  memberships = @transaction_ids.to_h do |transaction_id|
    [transaction_id.to_s, membership_ids(rows, transaction_id)]
  end
  [group_for(reflection.klass, rows.filter_map(&:foreign_key_id)), memberships]
end

#habtm_rows(owner_class, reflection) ⇒ Array[untyped]

: (untyped, untyped) -> Array

Parameters:

  • (Object)
  • (Object)

Returns:

  • (Array[untyped])


84
85
86
87
88
89
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 84

def habtm_rows(owner_class, reflection)
  owner_class.paper_trail.version_association_class.where(
    version_id: @transaction_ids,
    foreign_key_name: reflection.name.to_s
  ).to_a
end

#historical_child_ids(owner_class, owner_ids, reflection, foreign_key) ⇒ Hash[String, Array[untyped]]

: (untyped, Array, untyped, String) -> Hash[String, Array[untyped]]

Parameters:

  • (Object)
  • (Array[untyped])
  • (Object)
  • (String)

Returns:

  • (Hash[String, Array[untyped]])


99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 99

def historical_child_ids(
  owner_class,
  owner_ids,
  reflection,
  foreign_key
)
  version_class = owner_class.paper_trail.version_class
  association_class = owner_class.paper_trail.version_association_class
  relation = historical_child_relation(owner_class, owner_ids, reflection, foreign_key)
  pairs = child_candidate_scope(version_class).call(relation).distinct.pluck(
    association_class.arel_table[:foreign_key_id],
    version_class.arel_table[:item_id]
  )
  group_ids_by_owner(pairs)
end

#historical_child_relation(owner_class, owner_ids, reflection, foreign_key) ⇒ Object

: (untyped, Array, untyped, String) -> untyped

Parameters:

  • (Object)
  • (Array[untyped])
  • (Object)
  • (String)

Returns:

  • (Object)


116
117
118
119
120
121
122
123
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 116

def historical_child_relation(owner_class, owner_ids, reflection, foreign_key)
  version_class = owner_class.paper_trail.version_class
  owner_class.paper_trail.version_association_class.joins(:version).where(
    foreign_key_name: foreign_key,
    foreign_key_id: owner_ids,
    foreign_type: parent_types(owner_class)
  ).where(version_class.table_name => { item_type: reflection.klass.base_class.name })
end

#live_child_ids(owner_class, owner_ids, reflection, foreign_key) ⇒ Hash[String, Array[untyped]]

: (untyped, Array, untyped, String) -> Hash[String, Array[untyped]]

Parameters:

  • (Object)
  • (Array[untyped])
  • (Object)
  • (String)

Returns:

  • (Hash[String, Array[untyped]])


134
135
136
137
138
139
140
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 134

def live_child_ids(owner_class, owner_ids, reflection, foreign_key)
  relation = reflection.klass.base_class.unscoped.where(foreign_key => owner_ids)
  if reflection.options[:as]
    relation = relation.where(reflection.type => parent_types(owner_class))
  end
  group_ids_by_owner(relation.pluck(foreign_key, reflection.klass.primary_key))
end

#membership_ids(rows, transaction_id) ⇒ Array[untyped]

: (Array, untyped) -> Array

Parameters:

  • (Array[untyped])
  • (Object)

Returns:

  • (Array[untyped])


92
93
94
95
96
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 92

def membership_ids(rows, transaction_id)
  rows.filter_map do |row|
    row.foreign_key_id if row.version_id.to_s == transaction_id.to_s
  end.uniq
end

#merge_owners(left, right) ⇒ Hash[String, Array[untyped]]

: (Hash[String, Array[untyped]], Hash[String, Array[untyped]]) -> Hash[String, Array[untyped]]

Parameters:

  • (Hash[String, Array[untyped]])
  • (Hash[String, Array[untyped]])

Returns:

  • (Hash[String, Array[untyped]])


179
180
181
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 179

def merge_owners(left, right)
  left.merge(right) { |_owner, first, second| first | second }
end

#parent_types(owner_class) ⇒ Array[String?]

: (untyped) -> Array

Parameters:

  • (Object)

Returns:

  • (Array[String?])


190
191
192
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 190

def parent_types(owner_class)
  [nil, '', owner_class.name.to_s, owner_class.base_class.name.to_s].uniq
end

#scalar_foreign_key(reflection) ⇒ String?

: (untyped) -> String?

Parameters:

  • (Object)

Returns:

  • (String, nil)


184
185
186
187
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 184

def scalar_foreign_key(reflection)
  key = reflection.foreign_key
  key.to_s unless key.is_a?(Array)
end

#target_class(reflection, owner) ⇒ Object

: (untyped, untyped) -> untyped

Parameters:

  • (Object)
  • (Object)

Returns:

  • (Object)


143
144
145
146
147
148
149
# File 'lib/paper_trail_diff/prepared_edge_loader.rb', line 143

def target_class(reflection, owner)
  return reflection.klass unless reflection.polymorphic?

  Object.const_get(owner.public_send(reflection.foreign_type).to_s)
rescue NameError
  nil
end