Class: Kithe::Model

Inherits:
ActiveRecord::Base
  • Object
show all
Includes:
AttrJson::NestedAttributes, AttrJson::Record, AttrJson::Record::Dirty, Indexable, StiPreload
Defined in:
app/models/kithe/model.rb

Direct Known Subclasses

Asset, Collection, Work

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Indexable

auto_callbacks?, index_with, #update_index

Constructor Details

#initialize(*_) ⇒ Model

Returns a new instance of Model.

Raises:

  • (TypeError)


82
83
84
85
# File 'app/models/kithe/model.rb', line 82

def initialize(*_)
  raise TypeError.new("Kithe::Model is abstract and cannot be initialized") if self.class == ::Kithe::Model
  super
end

Class Method Details

.kithe_earlier_after_commit(*args, &block) ⇒ Object

Insert an after_commit hook that will run BEFORE any existing after_commit hooks, regardless of Rails version and run_after_transaction_callbacks_in_order_defined configuration.

Sometimes you need to insert an after_commit hook that goes BEFORE shrine’s after_commit callbacks for promotion in activerecord after_commit

In Rails prior to 7.1, that happens automatically just by adding an after_commit. But Rails 7.1 by default changes the order of after_commit AND removes the ability to alter it with prepend! github.com/rails/rails/issues/50118

We add this method, that will do the right thing – making sure the new hook we are adding is run BEFORE any existing ones – in both Rails < 7.1 and Rails 7.1 with run_after_transaction_callbacks_in_order_defined

Examples:


class MyAsset < Kithe::Asset
  kithe_earlier_after_commit :some_method_to_run_first

  kithe_earlier_after_commit do
    # This code will be in an after_commit that comes BEFORE
    # any existing ones
  end
end


188
189
190
191
192
193
194
195
# File 'app/models/kithe/model.rb', line 188

def self.kithe_earlier_after_commit(*args, &block)
  # confusingly in this state, we need prepend FALSE to have this new callback be registered to go
  # FIRST. And this actually is correct and works whether or not run_after_transaction_callbacks_in_order_defined
  # Very confusing, we test thorougly.
  set_options_for_callbacks!(args, {prepend: false})

  set_callback(:commit, :after, *args, &block)
end

Instance Method Details

#friendlier_id(*_) ⇒ Object

Due to rails bug, we don’t immediately have the database-provided value after create. :( If we ask for it and it’s empty, go to the db to get it github.com/rails/rails/issues/21627



95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'app/models/kithe/model.rb', line 95

def friendlier_id(*_)
  in_memory = super

  if !in_memory && persisted? && !@friendlier_id_retrieved
    in_memory = self.class.where(id: id).limit(1).pluck(:friendlier_id).first
    write_attribute(:friendlier_id, in_memory)
    clear_attribute_change(:friendlier_id)
    # just to avoid doing it multiple times if it's still unset in db for some reason
    @friendlier_id_retrieved = true
  end

  in_memory
end

#leaf_representativeObject

insist that leaf_representative is an Asset, otherwise return nil. nil means there is no asset leaf, and lets caller rely on leaf being an asset.



112
113
114
115
# File 'app/models/kithe/model.rb', line 112

def leaf_representative
  leaf = super
  leaf.kind_of?(Kithe::Asset) ? leaf : nil
end

#set_leaf_representativeObject

if a representative is set, set leaf_representative by following the tree with an efficient recursive CTE to find proper value.

Normally this is called for you in callbacks, and you don’t need to call manually. But if things get out of sync, you can.

work.set_leaf_representative
work.save!


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
# File 'app/models/kithe/model.rb', line 125

def set_leaf_representative
  if self.kind_of?(Kithe::Asset) # not applicable
    self.leaf_representative_id = nil
  end

  # a postgres recursive CTE to find the ultimate leaf through
  # a possible chain of works, guarding against cycles.
  # https://www.postgresql.org/docs/9.1/queries-with.html
  recursive_cte = <<~EOS
    WITH RECURSIVE find_terminal(id, link) AS (
        SELECT m.id, m.representative_id
        FROM kithe_models m
        WHERE m.id = $1
      UNION
        SELECT m.id, m.representative_id
        FROM kithe_models m, find_terminal ft
        WHERE m.id = ft.link
    ) SELECT id
      FROM find_terminal
      WHERE link IS NULL
      LIMIT 1;
  EOS

  # trying to use a prepared statement, hoping it means performance advantage,
  # this is super undocumented

  bind = ActiveRecord::Relation::QueryAttribute.new("m.id", self.representative_id, ActiveRecord::Type::Value.new)

  result = self.class.connection.select_all(
    recursive_cte,
    "set_leaf_representative",
    [bind],
    preparable: true
  ).first.try(:dig, "id")

  self.leaf_representative_id = result
end

#to_paramObject

We want friendlier_id to be in URLs, not id



88
89
90
# File 'app/models/kithe/model.rb', line 88

def to_param
  friendlier_id
end