Class: SolidObjects::ActorView

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(reference:, view_context:, authorization_context:) ⇒ ActorView

Returns a new instance of ActorView.

RBS:

  • (reference: Reference, view_context: untyped, authorization_context: untyped) -> void

Parameters:

  • reference: (Reference)
  • view_context: (Object)
  • authorization_context: (Object)


15
16
17
18
19
20
21
22
# File 'lib/solid_objects/actor_view.rb', line 15

def initialize(reference:, view_context:, authorization_context:)
  @reference = reference
  @view_context = view_context
  @authorization_context = authorization_context
  @snapshot = ActorSnapshot.new(reference)
  @component_registrations = []
  @observable_names = []
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *arguments, **keywords) ⇒ Object

RBS:

  • (Symbol, *untyped, **untyped) -> untyped

Parameters:

  • (Symbol)
  • (Object)
  • (Object)

Returns:

  • (Object)


94
95
96
97
98
# File 'lib/solid_objects/actor_view.rb', line 94

def method_missing(name, *arguments, **keywords)
  return value(name) if arguments.empty? && keywords.empty? && observable?(name)

  super
end

Instance Attribute Details

#authorization_contextObject (readonly)

Returns the value of attribute authorization_context.

Returns:

  • (Object)


107
108
109
# File 'lib/solid_objects/actor_view.rb', line 107

def authorization_context
  @authorization_context
end

#component_registrationsObject (readonly)

Returns the value of attribute component_registrations.

Returns:

  • (Object)


107
108
109
# File 'lib/solid_objects/actor_view.rb', line 107

def component_registrations
  @component_registrations
end

#observable_namesObject (readonly)

Returns the value of attribute observable_names.

Returns:

  • (Object)


107
108
109
# File 'lib/solid_objects/actor_view.rb', line 107

def observable_names
  @observable_names
end

#referenceObject (readonly)

RBS:

  • @reference: Reference

  • @view_context: untyped

  • @authorization_context: untyped

  • @snapshot: ActorSnapshot

  • @component_registrations: Array[ComponentRegistration]

  • @observable_names: Array[String]

Returns:

  • (Object)


12
13
14
# File 'lib/solid_objects/actor_view.rb', line 12

def reference
  @reference
end

#snapshotObject (readonly)

Returns the value of attribute snapshot.

Returns:

  • (Object)


107
108
109
# File 'lib/solid_objects/actor_view.rb', line 107

def snapshot
  @snapshot
end

#view_contextObject (readonly)

Returns the value of attribute view_context.

Returns:

  • (Object)


107
108
109
# File 'lib/solid_objects/actor_view.rb', line 107

def view_context
  @view_context
end

Instance Method Details

#authorize_read!(name) ⇒ void

This method returns an undefined value.

RBS:

  • (Symbol | String) -> void

Parameters:

  • (Symbol, String)


128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/solid_objects/actor_view.rb', line 128

def authorize_read!(name)
  authorized = SolidObjects.configuration.authorize_query.call(
    actor_type: reference.actor_type,
    actor_id: reference.actor_id,
    message_name: name.to_s,
    arguments: {},
    authorization_context:
  )
  return if authorized

  raise Unauthorized, "actor state query is not authorized"
end

#component(name, observes: nil, partial: nil) ⇒ Object

RBS:

  • (Symbol | String, ?observes: Symbol | String | Array[Symbol | String]?, ?partial: String?) -> untyped

Parameters:

  • (Symbol, String)
  • observes: (Symbol, String, Array[Symbol | String], nil) (defaults to: nil)
  • partial: (String, nil) (defaults to: nil)

Returns:

  • (Object)


41
42
43
44
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
# File 'lib/solid_objects/actor_view.rb', line 41

def component(name, observes: nil, partial: nil)
  component_name = normalized_component_name(name)
  return static_component(component_name, partial:) unless observes
  if partial
    raise ArgumentError,
      "reactive components resolve partials by actor and component name"
  end

  dependencies = normalized_dependencies(observes)
  validate_dependencies!(dependencies)
  ensure_unique_component!(component_name)
  refresh_path = component_path_resolver.call(view_context:)
  registration = ComponentRegistration.issue(
    reference:,
    component_name:,
    dependencies:,
    snapshot:,
    refresh_path:
  )
  rendered = ComponentRenderer.new(
    snapshot:,
    component_name:,
    dependencies:,
    view_context:,
    authorization_context:
  ).call
  component_registrations << registration
  view_context.(
    :"turbo-frame",
    rendered,
    id: DomIdentity.component(reference, component_name),
    data: {
      solid_objects_revision: "#{snapshot.instance_id}:#{snapshot.revision}"
    }
  )
end

#component_path_resolverProc

RBS:

  • () -> Proc | ComponentPathResolver

Returns:

  • (Proc)


197
198
199
200
# File 'lib/solid_objects/actor_view.rb', line 197

def component_path_resolver
  SolidObjects.configuration.component_path_resolver ||
    ComponentPathResolver.new
end

#component_tokensArray[String]

RBS:

  • () -> Array[String]

Returns:

  • (Array[String])


79
80
81
# File 'lib/solid_objects/actor_view.rb', line 79

def component_tokens
  component_registrations.map(&:token)
end

#default_partial(component_name) ⇒ String

RBS:

  • (String) -> String

Parameters:

  • (String)

Returns:

  • (String)


203
204
205
206
207
208
# File 'lib/solid_objects/actor_view.rb', line 203

def default_partial(component_name)
  actor_name = snapshot.actor_class.name
  raise InvalidActor, "anonymous actors must provide an explicit component partial" unless actor_name

  "actors/#{actor_name.underscore}/#{component_name}"
end

#display_value(value) ⇒ String

RBS:

  • (untyped) -> String

Parameters:

  • (Object)

Returns:

  • (String)


147
148
149
150
151
152
153
# File 'lib/solid_objects/actor_view.rb', line 147

def display_value(value)
  return value if value.is_a?(String)
  return value.to_s if value.is_a?(Numeric) || value == true || value == false
  return "" if value.nil?

  JSON.generate(value)
end

#ensure_unique_component!(component_name) ⇒ void

This method returns an undefined value.

RBS:

  • (String) -> void

Parameters:

  • (String)


187
188
189
190
191
192
193
194
# File 'lib/solid_objects/actor_view.rb', line 187

def ensure_unique_component!(component_name)
  return unless component_registrations.any? do |registration|
    registration.component_name == component_name
  end

  raise ArgumentError,
    "component #{component_name.inspect} is already rendered in this solid_object scope"
end

#normalized_component_name(name) ⇒ String

RBS:

  • (Symbol | String) -> String

Parameters:

  • (Symbol, String)

Returns:

  • (String)


156
157
158
159
160
161
162
163
# File 'lib/solid_objects/actor_view.rb', line 156

def normalized_component_name(name)
  component_name = name.to_s
  unless component_name.match?(/\A[a-zA-Z0-9_]+\z/)
    raise ArgumentError, "component names may contain only letters, digits, and underscores"
  end

  component_name
end

#normalized_dependencies(observes) ⇒ Array[String]

RBS:

  • (Symbol | String | Array[Symbol | String]) -> Array[String]

Parameters:

  • (Symbol, String, Array[Symbol | String])

Returns:

  • (Array[String])


166
167
168
169
170
171
172
173
# File 'lib/solid_objects/actor_view.rb', line 166

def normalized_dependencies(observes)
  dependencies = Array(observes).map(&:to_s).uniq
  if dependencies.empty?
    raise ArgumentError, "reactive components require at least one observable"
  end

  dependencies
end

#observable?(name) ⇒ Boolean

RBS:

  • (Symbol | String) -> bool

Parameters:

  • (Symbol, String)

Returns:

  • (Boolean)


142
143
144
# File 'lib/solid_objects/actor_view.rb', line 142

def observable?(name)
  snapshot.actor_class.definition.observables.key?(name.to_sym)
end

#respond_to_missing?(name, include_private = false) ⇒ Boolean

RBS:

  • (Symbol, bool) -> bool

Parameters:

  • (Symbol)
  • (Boolean)

Returns:

  • (Boolean)


101
102
103
# File 'lib/solid_objects/actor_view.rb', line 101

def respond_to_missing?(name, include_private = false)
  observable?(name) || super
end

#scalar_observable_namesArray[String]

RBS:

  • () -> Array[String]

Returns:

  • (Array[String])


84
85
86
# File 'lib/solid_objects/actor_view.rb', line 84

def scalar_observable_names
  observable_names.dup
end

#stateState

RBS:

  • () -> State

Returns:



89
90
91
# File 'lib/solid_objects/actor_view.rb', line 89

def state
  snapshot.actor.state
end

#static_component(component_name, partial:) ⇒ Object

RBS:

  • (String, partial: String?) -> untyped

Parameters:

  • (String)
  • partial: (String, nil)

Returns:

  • (Object)


114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/solid_objects/actor_view.rb', line 114

def static_component(component_name, partial:)
  authorize_read!(component_name)
  rendered = view_context.render(
    partial: partial || default_partial(component_name),
    locals: { actor: self }
  )
  view_context.(
    :div,
    rendered,
    id: DomIdentity.component(reference, component_name)
  )
end

#validate_dependencies!(dependencies) ⇒ void

This method returns an undefined value.

RBS:

  • (Array[String]) -> void

Parameters:

  • (Array[String])


176
177
178
179
180
181
182
183
184
# File 'lib/solid_objects/actor_view.rb', line 176

def validate_dependencies!(dependencies)
  unknown = dependencies.find do |dependency|
    !snapshot.actor_class.definition.observables.key?(dependency.to_sym)
  end
  return unless unknown

  raise UnknownComponentDependency,
    "unknown observable dependency #{unknown.inspect} for #{reference.actor_type}"
end

#value(name) ⇒ Object

RBS:

  • (Symbol | String) -> untyped

Parameters:

  • (Symbol, String)

Returns:

  • (Object)


25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/solid_objects/actor_view.rb', line 25

def value(name)
  observable_name = name.to_sym
  handler = snapshot.actor_class.definition.observables[observable_name]
  raise UnknownMessage, "unknown observable #{name.inspect}" unless handler

  authorize_read!(observable_name)
  value = snapshot.observable_value(observable_name)
  observable_names << observable_name.to_s unless observable_names.include?(observable_name.to_s)
  view_context.(
    :span,
    display_value(value),
    id: DomIdentity.observable(reference, observable_name)
  )
end