Class: SolidObjects::Client

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(mailbox: Mailbox.new) ⇒ Client

Returns a new instance of Client.

RBS:

  • (?mailbox: Mailbox) -> void

Parameters:

  • mailbox: (Mailbox) (defaults to: Mailbox.new)


10
11
12
# File 'lib/solid_objects/client.rb', line 10

def initialize(mailbox: Mailbox.new)
  @mailbox = mailbox
end

Instance Attribute Details

#mailboxObject (readonly)

Returns the value of attribute mailbox.

Returns:

  • (Object)


93
94
95
# File 'lib/solid_objects/client.rb', line 93

def mailbox
  @mailbox
end

Instance Method Details

#ask(reference, message_name, arguments, timeout:, idempotency_key: nil, authorization_context: nil) ⇒ Object

RBS:

  • (Reference, Symbol | String, Hash[Symbol | String, untyped], timeout: Numeric, ?idempotency_key: String?, ?authorization_context: untyped) -> untyped

Parameters:

  • (Reference)
  • (Symbol, String)
  • (Hash[Symbol | String, untyped])
  • timeout: (Numeric)
  • idempotency_key: (String, nil) (defaults to: nil)
  • authorization_context: (Object) (defaults to: nil)

Returns:

  • (Object)


38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/solid_objects/client.rb', line 38

def ask(reference, message_name, arguments, timeout:, idempotency_key: nil, authorization_context: nil)
  actor_class = SolidObjects.registry.fetch(reference.actor_type)
  message_symbol = message_name.to_sym
  query = actor_class.definition.queries.key?(message_symbol)
  actor_message = actor_class.definition.messages.key?(message_symbol)
  raise UnknownMessage, "unknown message #{message_name.inspect}" unless query || actor_message

  authorize!(
    query ? SolidObjects.configuration.authorize_query : SolidObjects.configuration.authorize_message,
    reference,
    message_name,
    arguments,
    authorization_context:
  )
  message_reference = mailbox.enqueue(
    reference,
    message_name,
    arguments,
    kind: "ask",
    idempotency_key:
  )
  wait_for_result(message_reference, timeout:)
end

#authorize!(hook, reference, message_name, arguments, authorization_context:) ⇒ void

This method returns an undefined value.

RBS:

  • (Proc, Reference, Symbol | String, Hash[Symbol | String, untyped], authorization_context: untyped) -> void

Parameters:

  • (Proc)
  • (Reference)
  • (Symbol, String)
  • (Hash[Symbol | String, untyped])
  • authorization_context: (Object)


96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/solid_objects/client.rb', line 96

def authorize!(hook, reference, message_name, arguments, authorization_context:)
  authorized = hook.call(
    actor_type: reference.actor_type,
    actor_id: reference.actor_id,
    message_name: message_name.to_s,
    arguments:,
    authorization_context:
  )
  return if authorized

  raise Unauthorized, "actor invocation is not authorized"
end

#authorize_destroy!(reference, authorization_context:) ⇒ void

This method returns an undefined value.

RBS:

  • (Reference, authorization_context: untyped) -> void

Parameters:

  • (Reference)
  • authorization_context: (Object)


110
111
112
113
114
115
116
117
118
119
# File 'lib/solid_objects/client.rb', line 110

def authorize_destroy!(reference, authorization_context:)
  authorized = SolidObjects.configuration.authorize_destroy.call(
    actor_type: reference.actor_type,
    actor_id: reference.actor_id,
    authorization_context:
  )
  return if authorized

  raise Unauthorized, "actor destruction is not authorized"
end

#destroy(reference, authorization_context: nil) ⇒ Boolean

RBS:

  • (Reference, ?authorization_context: untyped) -> bool

Parameters:

  • (Reference)
  • authorization_context: (Object) (defaults to: nil)

Returns:

  • (Boolean)


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
# File 'lib/solid_objects/client.rb', line 63

def destroy(reference, authorization_context: nil)
  raise ActorCallCycle, "actors cannot synchronously destroy another actor" if Context.current_actor

  SolidObjects.registry.fetch(reference.actor_type)
  authorize_destroy!(reference, authorization_context:)
  instance_id = SolidObjects.database_adapter.transaction do
    instance = Instance.lock.find_by(
      actor_type: reference.actor_type,
      actor_id: reference.actor_id
    )
    next unless instance

    instance_id = instance.id
    instance.delete
    instance_id
  end
  return false unless instance_id

  SolidObjects.instrument(
    :"actor.destroyed",
    instance_id:,
    actor_type: reference.actor_type,
    actor_id: reference.actor_id
  )
  SolidObjects.wake_up.signal
  true
end

#monotonic_nowFloat

RBS:

  • () -> Float

Returns:

  • (Float)


149
150
151
# File 'lib/solid_objects/client.rb', line 149

def monotonic_now
  ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
end

#tell(reference, message_name, arguments, available_at: nil, idempotency_key: nil, authorization_context: nil) ⇒ MessageReference

RBS:

  • (Reference, Symbol | String, Hash[Symbol | String, untyped], ?available_at: Time?, ?idempotency_key: String?, ?authorization_context: untyped) -> MessageReference

Parameters:

  • (Reference)
  • (Symbol, String)
  • (Hash[Symbol | String, untyped])
  • available_at: (Time, nil) (defaults to: nil)
  • idempotency_key: (String, nil) (defaults to: nil)
  • authorization_context: (Object) (defaults to: nil)

Returns:



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/solid_objects/client.rb', line 15

def tell(reference, message_name, arguments, available_at: nil, idempotency_key: nil, authorization_context: nil)
  actor_class = SolidObjects.registry.fetch(reference.actor_type)
  message_symbol = message_name.to_sym
  raise UnknownMessage, "unknown message #{message_name.inspect}" unless actor_class.definition.messages.key?(message_symbol)

  authorize!(
    SolidObjects.configuration.authorize_message,
    reference,
    message_name,
    arguments,
    authorization_context:
  )
  mailbox.enqueue(
    reference,
    message_name,
    arguments,
    kind: "tell",
    available_at:,
    idempotency_key:
  )
end

#wait_for_result(message_reference, timeout:) ⇒ Object

RBS:

  • (MessageReference, timeout: Numeric) -> untyped

Parameters:

Returns:

  • (Object)


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
# File 'lib/solid_objects/client.rb', line 122

def wait_for_result(message_reference, timeout:)
  deadline = monotonic_now + timeout.to_f

  loop do
    message = Message.uncached { Message.find(message_reference.id) }
    return message.result if message.completed?

    if message.dead?
      raise MessageFailed.new(
        "actor message failed permanently",
        message_id: message.id,
        details: message.error || {}
      )
    end

    remaining = deadline - monotonic_now
    raise AskTimeout, "actor request timed out after #{timeout} seconds" unless remaining.positive?

    SolidObjects.wake_up.wait(
      timeout: [ remaining, SolidObjects.configuration.ask_polling_interval ].min
    )
  end
rescue ActiveRecord::RecordNotFound
  raise ActorDestroyed, "actor was destroyed while waiting for its result"
end