Module: Compony::ComponentMixins::Default::Standalone

Extended by:
ActiveSupport::Concern
Defined in:
lib/compony/component_mixins/default/standalone.rb,
lib/compony/component_mixins/default/standalone/verb_dsl.rb,
lib/compony/component_mixins/default/standalone/standalone_dsl.rb,
lib/compony/component_mixins/default/standalone/resourceful_verb_dsl.rb

Overview

This contains all default component logic concerning standalone functionality. The module is included in every component.

Defined Under Namespace

Classes: ResourcefulVerbDsl, StandaloneDsl, VerbDsl

Instance Method Summary collapse

Instance Method Details

#clear_standalone!Object (protected)

Undoes previous standalone calls



122
123
124
# File 'lib/compony/component_mixins/default/standalone.rb', line 122

def clear_standalone!
  @standalone_configs = {}
end

#on_standalone_access(verb_config, controller) ⇒ Object

Called by compony_controller when a request is issued. This is the entrypoint where a request enters the Component world.

Parameters:

  • verb_config (Hash)

    The config generated by Compony::ComponentMixins::Default::Standalone::VerbDsl#to_conf

  • controller (ComponyController)

    The controller instance that calls this method.

See Also:



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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
# File 'lib/compony/component_mixins/default/standalone.rb', line 21

def on_standalone_access(verb_config, controller)
  # Register as root comp
  if parent_comp.nil?
    fail "#{inspect} is attempting to become root component, but #{root_comp.inspect} is already root." if Compony.root_comp.present?
    RequestStore.store[:compony_root_comp] = self
  end

  # Prepare the request context in which the innermost DSL calls will be executed
  request_context = RequestContext.new(self, controller)

  ###===---
  # Dispatch request to component. Empty Dslblend base objects are used to provide multiple contexts to the authorize and respond blocks.
  # Lifecycle is (see also "doc/Resourceful Lifecycle.pdf"):
  #   - load data (optional, speficied ResourcefulVerbDsl, by convention, should default to the implementation in Resourceful)
  #     - after_load_data (optional, specified in Resourceful)
  #   - assign_attributes (optional, speficied ResourcefulVerbDsl, by convention, should default to the implementation in Resourceful)
  #     - after_assign_attributes (optional, specified in Resourceful)
  #   - authorize
  #   - store_data (optional, speficied ResourcefulVerbDsl, by convention, should default to the implementation in Resourceful)
  #   - respond (typically either redirect or render standalone, specified in VerbDsl), which defaults to render_standalone, performing:
  #     - before_render
  #     - render (unless before_render already redirected)
  ###===---

  if verb_config.load_data_block
    request_context.evaluate_with_backfire(&verb_config.load_data_block)
    if global_after_load_data_block
      request_context.evaluate_with_backfire(&global_after_load_data_block)
    end
  end

  if verb_config.assign_attributes_block
    request_context.evaluate_with_backfire(&verb_config.assign_attributes_block)
    if global_after_assign_attributes_block
      request_context.evaluate_with_backfire(&global_after_assign_attributes_block)
    end
  end

  # TODO: Make much prettier, providing message, action, subject and conditions
  fail CanCan::AccessDenied, [inspect, verb_config.authorize_block.inspect].join(', ') unless request_context.evaluate(&verb_config.authorize_block)

  if verb_config.store_data_block
    request_context.evaluate_with_backfire(&verb_config.store_data_block)
  end

  # Check if there is a specific respond block for the format.
  # If there isn't, fallback to the nil respond block, which defaults to `render_standalone`.
  respond_block = verb_config.respond_blocks[controller.request.format.symbol] || verb_config.respond_blocks[nil]
  request_context.evaluate(&respond_block)
end

#render_standalone(controller, status: nil, standalone_name: nil) ⇒ Object

Renders the component using the controller passed to it upon instanciation (calls the controller's render) Do not overwrite



89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/compony/component_mixins/default/standalone.rb', line 89

def render_standalone(controller, status: nil, standalone_name: nil)
  # Start the render process. This produces a nil value if before_render has already produced a response, e.g. a redirect.
  rendered_html = render(controller, standalone: true)
  if rendered_html.present? # If nil, a response body was already produced in the controller and we take no action here (would have DoubleRenderError)
    opts = { html: rendered_html, layout: @standalone_configs[standalone_name].layout }
    opts[:status] = status if status.present?
    controller.respond_to do |format|
      # Form posts trigger format types turbo stream and then html, turbo stream wins.
      # For this reason, Rails prefers stream, in which case the layout is disabled, regardless of the option.
      # To mitigate this, we use respond_to to force a HTML-only response.
      format.html { controller.render(**opts) }
    end
  end
end

#standalone(name = nil, *args, **nargs, &block) ⇒ Object (protected)

DSL method This can be called multiple times to make a component listen to multiple paths. Each standalone config (path) has a name, the default being nil. To have a component listen to multiple paths, call standalone again and provide a name, e.g.: standalone(:autocomplete, path: 'foo/bar/autocomplete') The kwarg parameter path is handled analog to the Rails route path

Parameters:

  • name (Symbol, nil) (defaults to: nil)

    The name of the standalone config, defaults to nil. Only provide if you add additional configs.



111
112
113
114
115
116
117
118
119
# File 'lib/compony/component_mixins/default/standalone.rb', line 111

def standalone(name = nil, *args, **nargs, &block)
  block = proc {} unless block_given? # If called without a block, must default to an empty block to provide a binding to the DSL.
  name = name&.to_sym # nil name is the most common case
  if @standalone_configs[name]
    @standalone_configs[name].deep_merge! StandaloneDsl.new(self, name, *args, provide_defaults: false, **nargs).to_conf(&block)
  else
    @standalone_configs[name] = Compony::MethodAccessibleHash.new(StandaloneDsl.new(self, name, *args, provide_defaults: true, **nargs).to_conf(&block))
  end
end

#standalone_access_permitted_for?(controller, standalone_name: nil, verb: nil) ⇒ Boolean

Call this on a standalone component to find out whether default GET access is permitted for the current user. This is useful to hide/disable buttons leading to components a user may not press. For resourceful components, before calling this, you must have loaded date beforehand, for instance in one of the following ways:

  • when called standalone (via request to the component), the load data step must be completed
  • when called to check for permission only, e.g. to display a button to it, initialize the component by passing the :data keyword to new By default, this checks the authorization to access the main standalone entrypoint (with name nil) and HTTP verb GET.

Returns:

  • (Boolean)


78
79
80
81
82
83
84
85
# File 'lib/compony/component_mixins/default/standalone.rb', line 78

def standalone_access_permitted_for?(controller, standalone_name: nil, verb: nil)
  verb ||= :get
  standalone_name = standalone_name&.to_sym
  verb = verb.to_sym
  standalone_config = standalone_configs[standalone_name] || fail("#{inspect} does not provide the standalone config #{standalone_config.inspect}.")
  verb = standalone_config.verbs[verb] || fail("#{inspect} standalone config #{standalone_config.inspect} does not provide verb #{verb.inspect}.")
  return RequestContext.new(self, controller).evaluate(&verb.authorize_block)
end