Class: ZeroRuby::LmidStores::ActiveRecordStore

Inherits:
ZeroRuby::LmidStore show all
Defined in:
lib/zero_ruby/lmid_stores/active_record_store.rb

Overview

ActiveRecord-based LMID store using Zero’s zero_0.clients table. This store provides proper transaction support with atomic LMID updates for concurrent access in production environments.

Uses the same atomic increment pattern as Zero’s TypeScript implementation.

Examples:

Usage

ZeroRuby.configure do |config|
  config.lmid_store = :active_record
end

See Also:

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(model_class: nil) ⇒ ActiveRecordStore

Returns a new instance of ActiveRecordStore.



23
24
25
# File 'lib/zero_ruby/lmid_stores/active_record_store.rb', line 23

def initialize(model_class: nil)
  @model_class = model_class || default_model_class
end

Instance Attribute Details

#model_classObject (readonly)

The model class to use for client records. Defaults to ZeroRuby::ZeroClient.



21
22
23
# File 'lib/zero_ruby/lmid_stores/active_record_store.rb', line 21

def model_class
  @model_class
end

Instance Method Details

#delete_mutation_results(args) ⇒ Object

Delete mutation results from the zero_0.mutations table.

Parameters:

  • args (Hash)

    Cleanup arguments



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/zero_ruby/lmid_stores/active_record_store.rb', line 78

def delete_mutation_results(args)
  client_group_id = args["clientGroupID"]

  sql = if args["type"] == "bulk"
    client_ids = args["clientIDs"]
    model_class.sanitize_sql_array([<<~SQL.squish, {client_group_id:}])
      DELETE FROM zero_0.mutations
      WHERE "clientGroupID" = :client_group_id
      AND "clientID" = ANY(ARRAY[#{client_ids.map { |id| model_class.connection.quote(id) }.join(",")}])
    SQL
  else
    client_id = args["clientID"]
    up_to_mutation_id = args["upToMutationID"]
    model_class.sanitize_sql_array([<<~SQL.squish, {client_group_id:, client_id:, up_to_mutation_id:}])
      DELETE FROM zero_0.mutations
      WHERE "clientGroupID" = :client_group_id
      AND "clientID" = :client_id
      AND "mutationID" <= :up_to_mutation_id
    SQL
  end

  model_class.connection.execute(sql)
end

#fetch_and_increment(client_group_id, client_id) ⇒ Integer

Atomically increment and return the last mutation ID for a client. Uses INSERT … ON CONFLICT to handle both new and existing clients in a single atomic operation, minimizing lock duration.

Parameters:

  • client_group_id (String)

    The client group ID

  • client_id (String)

    The client ID

Returns:

  • (Integer)

    The new last mutation ID (post-increment)



34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/zero_ruby/lmid_stores/active_record_store.rb', line 34

def fetch_and_increment(client_group_id, client_id)
  table = model_class.quoted_table_name
  sql = model_class.sanitize_sql_array([<<~SQL.squish, {client_group_id:, client_id:}])
    INSERT INTO #{table} ("clientGroupID", "clientID", "lastMutationID")
    VALUES (:client_group_id, :client_id, 1)
    ON CONFLICT ("clientGroupID", "clientID")
    DO UPDATE SET "lastMutationID" = #{table}."lastMutationID" + 1
    RETURNING "lastMutationID"
  SQL

  model_class.connection.select_value(sql)
end

#transaction { ... } ⇒ Object

Execute a block within an ActiveRecord transaction.

Yields:

  • The block to execute within the transaction

Returns:

  • The result of the block



51
52
53
# File 'lib/zero_ruby/lmid_stores/active_record_store.rb', line 51

def transaction(&block)
  model_class.transaction(&block)
end

#write_mutation_result(client_group_id, client_id, mutation_id, result) ⇒ Object

Write a mutation result to the zero_0.mutations table.

Parameters:

  • client_group_id (String)

    The client group ID

  • client_id (String)

    The client ID

  • mutation_id (Integer)

    The mutation ID

  • result (Hash, String)

    The mutation result. Hashes are serialized to JSON for storage.



61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/zero_ruby/lmid_stores/active_record_store.rb', line 61

def write_mutation_result(client_group_id, client_id, mutation_id, result)
  result_json = begin
    result.is_a?(String) ? result : result.to_json
  rescue JSON::GeneratorError, Encoding::UndefinedConversionError
    {error: "app", message: "Error result could not be serialized"}.to_json
  end
  sql = model_class.sanitize_sql_array([<<~SQL.squish, {client_group_id:, client_id:, mutation_id:, result: result_json}])
    INSERT INTO zero_0.mutations ("clientGroupID", "clientID", "mutationID", "result")
    VALUES (:client_group_id, :client_id, :mutation_id, :result::text::json)
  SQL

  model_class.connection.execute(sql)
end