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)


112
113
114
115
116
# File 'lib/solid_objects/actor_view.rb', line 112

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)


125
126
127
# File 'lib/solid_objects/actor_view.rb', line 125

def authorization_context
  @authorization_context
end

#component_registrationsObject (readonly)

Returns the value of attribute component_registrations.

Returns:

  • (Object)


125
126
127
# File 'lib/solid_objects/actor_view.rb', line 125

def component_registrations
  @component_registrations
end

#observable_namesObject (readonly)

Returns the value of attribute observable_names.

Returns:

  • (Object)


125
126
127
# File 'lib/solid_objects/actor_view.rb', line 125

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)


125
126
127
# File 'lib/solid_objects/actor_view.rb', line 125

def snapshot
  @snapshot
end

#view_contextObject (readonly)

Returns the value of attribute view_context.

Returns:

  • (Object)


125
126
127
# File 'lib/solid_objects/actor_view.rb', line 125

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)


146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/solid_objects/actor_view.rb', line 146

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, key: nil, locals: {}, refresh: :replace) ⇒ Object

RBS:

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

Parameters:

  • (Symbol, String)
  • observes: (Symbol, String, Array[Symbol | String], nil) (defaults to: nil)
  • partial: (String, nil) (defaults to: nil)
  • key: (Object) (defaults to: nil)
  • locals: (Hash[untyped, untyped]) (defaults to: {})
  • refresh: (String, Symbol) (defaults to: :replace)

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
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/solid_objects/actor_view.rb', line 41

def component(
  name,
  observes: nil,
  partial: nil,
  key: nil,
  locals: {},
  refresh: :replace
)
  component_name = normalized_component_name(name)
  unless observes
    validate_static_options!(key:, locals:, refresh:)
    return static_component(component_name, partial:)
  end
  if partial
    raise ArgumentError,
      "reactive components resolve partials by actor and component name"
  end

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

#component_path_resolverProc

RBS:

  • () -> Proc | ComponentPathResolver

Returns:

  • (Proc)


224
225
226
227
# File 'lib/solid_objects/actor_view.rb', line 224

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

#component_tokensArray[String]

RBS:

  • () -> Array[String]

Returns:

  • (Array[String])


92
93
94
# File 'lib/solid_objects/actor_view.rb', line 92

def component_tokens
  component_registrations.map(&:token)
end

#default_partial(component_name) ⇒ String

RBS:

  • (String) -> String

Parameters:

  • (String)

Returns:

  • (String)


230
231
232
233
234
235
# File 'lib/solid_objects/actor_view.rb', line 230

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)


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

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!(registration) ⇒ void

This method returns an undefined value.

RBS:

  • (ComponentRegistration) -> void

Parameters:



205
206
207
208
209
210
211
212
213
# File 'lib/solid_objects/actor_view.rb', line 205

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

  raise ArgumentError,
    "component #{registration.component_name.inspect} with key " \
      "#{registration.component_key.inspect} is already rendered in this solid_object scope"
end

#morph_components?Boolean

RBS:

  • () -> bool

Returns:

  • (Boolean)


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

def morph_components?
  component_registrations.any?(&:morph?)
end

#normalized_component_name(name) ⇒ String

RBS:

  • (Symbol | String) -> String

Parameters:

  • (Symbol, String)

Returns:

  • (String)


174
175
176
177
178
179
180
181
# File 'lib/solid_objects/actor_view.rb', line 174

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])


184
185
186
187
188
189
190
191
# File 'lib/solid_objects/actor_view.rb', line 184

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)


160
161
162
# File 'lib/solid_objects/actor_view.rb', line 160

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)


119
120
121
# File 'lib/solid_objects/actor_view.rb', line 119

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

#scalar_observable_namesArray[String]

RBS:

  • () -> Array[String]

Returns:

  • (Array[String])


97
98
99
# File 'lib/solid_objects/actor_view.rb', line 97

def scalar_observable_names
  observable_names.dup
end

#stateState

RBS:

  • () -> State

Returns:



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

def state
  snapshot.actor.state
end

#static_component(component_name, partial:) ⇒ Object

RBS:

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

Parameters:

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

Returns:

  • (Object)


132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/solid_objects/actor_view.rb', line 132

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])


194
195
196
197
198
199
200
201
202
# File 'lib/solid_objects/actor_view.rb', line 194

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

#validate_static_options!(key:, locals:, refresh:) ⇒ void

This method returns an undefined value.

RBS:

  • (key: untyped, locals: Hash[untyped, untyped], refresh: String | Symbol) -> void

Parameters:

  • key: (Object)
  • locals: (Hash[untyped, untyped])
  • refresh: (String, Symbol)


216
217
218
219
220
221
# File 'lib/solid_objects/actor_view.rb', line 216

def validate_static_options!(key:, locals:, refresh:)
  return if key.nil? && locals.empty? && refresh.to_s == "replace"

  raise ArgumentError,
    "key, locals, and refresh require an observable component"
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