Class: Compony::Intent

Inherits:
Object
  • Object
show all
Defined in:
lib/compony/intent.rb

Overview

An Intent is a a gateway to a component, along with relevant context, such as the comp and family, perhaps a resource, standalone name, feasibility etc. The class provides tooling used by various Compony helpers used to point to other components in some way. Note: The arguments label and style are not part of the button: hash, because they are processed by the Intent before affecting the button.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(comp_name_or_cst_or_class, model_or_family_name_or_cst = nil, standalone_name: nil, name: nil, label: nil, style: nil, button: {}, path: nil, method: nil, data: nil, data_class: nil, feasibility_target: nil, feasibility_action: nil) ⇒ Intent

Returns a new instance of Intent.

Parameters:

  • comp_name_or_cst_or_class (String, Symbol, Class)

    The component that should be loaded, for instance ShowForAll, 'ShowForAll' or :show_for_all, or can also pass a component class (such as Components::Users::Show)

  • model_or_family_name_or_cst (String, Symbol, ApplicationRecord) (defaults to: nil)

    Either the family that contains the requested component, or an instance implementing model_name from which the family name is auto-generated. Examples: Users, 'Users', :users, User.first

  • standalone_name (Symbol) (defaults to: nil)

    If given, will override the standalone name for all path calls for this intent instance.

  • name (Symbol) (defaults to: nil)

    If given, will override the name of this intent. Defaults to component and family name joined by underscore.

  • label (String, Hash) (defaults to: nil)

    If given, will be used for generating the label. If Hash, is given as options to #label.

  • style (Symbol) (defaults to: nil)

    If given, sets the button style to use and uses the default one otherwise.

  • button (Hash) (defaults to: {})

    Parameters that will be given as-is to the button component initializer.

  • path (String, Hash) (defaults to: nil)

    If given, will be used for generating the path. If Hash, is given as options to #path.

  • data (ApplicationRecord, Object) (defaults to: nil)

    If given, the target component will be instanciated with this argument. Omit if your second pos arg is a model.

  • data_class (Class) (defaults to: nil)

    If given, the target component will be instanciated with this argument.

  • feasibility_target (ApplicationRecord) (defaults to: nil)

    If given, will override the feasibility target (prevention framework)

  • feasibility_action (ApplicationRecord) (defaults to: nil)

    If given, will override the feasibility action (prevention framework)



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
# File 'lib/compony/intent.rb', line 26

def initialize(comp_name_or_cst_or_class,
               model_or_family_name_or_cst = nil,
               standalone_name: nil,
               name: nil,
               label: nil,
               style: nil,
               button: {},
               path: nil,
               method: nil,
               data: nil,
               data_class: nil,
               feasibility_target: nil,
               feasibility_action: nil)
  # Check for model / data
  @data = data
  @data ||= model_or_family_name_or_cst if model_or_family_name_or_cst.respond_to?(:model_name)
  @data_class = data_class

  # Figure out comp_class
  if comp_name_or_cst_or_class.is_a?(Class) && (comp_name_or_cst_or_class <= Compony::Component)
    # A class was given as the first argument
    @comp_class = comp_name_or_cst_or_class
  else
    # Build the constant from the first two arguments
    family_underscore_str = @data.respond_to?(:model_name) ? @data.model_name.plural : model_or_family_name_or_cst.to_s.underscore
    constant_str = "::Components::#{family_underscore_str.camelize}::#{comp_name_or_cst_or_class.to_s.camelize}"
    @comp_class = constant_str.constantize
  end

  # Store further arguments
  @name = name&.to_sym
  @standalone_name = standalone_name
  @label = label.is_a?(String) ? label : nil
  @label_opts = label.is_a?(Hash) ? label : {}
  @style = style&.to_sym
  @button_opts = button
  @path = path.is_a?(String) ? path : nil
  @path_opts = path.is_a?(Hash) ? path : {}
  @method = method&.to_sym
  @feasibility_target = feasibility_target
  @feasibility_action = feasibility_action
end

Instance Attribute Details

#comp_classObject (readonly)



7
8
9
# File 'lib/compony/intent.rb', line 7

def comp_class
  @comp_class
end

#dataObject (readonly)



8
9
10
# File 'lib/compony/intent.rb', line 8

def data
  @data
end

Instance Method Details

#button_comp_optsObject

Returns the options that are given to the initializer when creating a button from this intent.



121
122
123
124
125
126
127
128
129
# File 'lib/compony/intent.rb', line 121

def button_comp_opts
  return {
    label:,
    href:   feasible? ? path : nil,
    method:,
    class:  feasible? ? nil : 'disabled',
    title:  feasible? ? nil : feasibility_target.full_feasibility_messages(feasibility_action).presence
  }.deep_merge(@button_opts)
end

#compObject

Instanciates the component and returns the instance. If data and/or data_class were specified when instantiating this intent, they are passed. All given arguments will be given to the component's initializer, also overriding data and data_class if present.



77
78
79
# File 'lib/compony/intent.rb', line 77

def comp(*, **)
  return @comp ||= @comp_class.new(*, data: @data, data_class: @data_class, **)
end

#feasibility_actionObject



110
111
112
# File 'lib/compony/intent.rb', line 110

def feasibility_action
  @feasibility_action.presence || comp_class.comp_name.to_sym
end

#feasibility_targetObject



106
107
108
# File 'lib/compony/intent.rb', line 106

def feasibility_target
  @feasibility_target.presence || model? ? @data : nil
end

#feasible?Boolean

Returns whether this intent is feasible (no prevention)

Returns:

  • (Boolean)


115
116
117
118
# File 'lib/compony/intent.rb', line 115

def feasible?
  return true if feasibility_target.blank? || feasibility_action.blank?
  return feasibility_target.feasible?(feasibility_action)
end

#label(model = nil, **label_opt_overrides) ⇒ Object

Returns the label of buttons produced by this intent.



97
98
99
100
# File 'lib/compony/intent.rb', line 97

def label(model = nil, *, **label_opt_overrides)
  label_opts = @label_opts.deep_merge(label_opt_overrides)
  @label.presence || comp.label(model || (model? ? @data : nil), *, **label_opts)
end

#methodObject



102
103
104
# File 'lib/compony/intent.rb', line 102

def method
  @method || :get
end

#model?Boolean

Returns true for things like User.first, but false for things like :users or User

Returns:

  • (Boolean)


70
71
72
73
# File 'lib/compony/intent.rb', line 70

def model?
  @model = @data.respond_to?(:model_name) && !@data.is_a?(Class) if @model.nil?
  return @model
end

#nameObject

Returns a name for this intent, consisting of comp and family name. Can be overriden in the constructor. Example: :show_users, :destroy_sessions



92
93
94
# File 'lib/compony/intent.rb', line 92

def name
  @name.presence || :"#{comp_class.comp_name}_#{comp_class.family_name}"
end

#path(model = nil, standalone_name: nil, **path_opt_overrides) ⇒ Object

Returns the path to the component. Additional arguments are passed to the component's path block, which typically passes them to the Rails path helper.

Parameters:

  • model (ApplicationRecord) (defaults to: nil)

    If given and non-nil, will override the model passed to the component's path block

  • standalone_name (Symbol) (defaults to: nil)

    If given and non-nil, will override the standalone_name passed to the component's path block



85
86
87
88
# File 'lib/compony/intent.rb', line 85

def path(model = nil, *, standalone_name: nil, **path_opt_overrides)
  path_opts = @path_opts.deep_merge(path_opt_overrides)
  comp.path(model || (model? ? @data : nil), standalone_name: standalone_name || @standalone_name, **path_opts)
end

#render(controller, parent_comp = nil, style: nil, **button_arg_overrides) ⇒ Object

Renders this intent into a button defined by style.

Parameters:

  • controller (ApplicationController)

    The controller from the request context, needed to render the button.

  • parent_comp (Compony::Component) (defaults to: nil)

    If called from within a component, pass the component to inform the button that it is nested within.

  • style (Symbol) (defaults to: nil)

    If present, overrides the class of the generated button component, defaults to Compony#default_button_style.

  • button_arg_overrides (Hash)

    Any further kwargs are passed to the button component's initializer.



136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/compony/intent.rb', line 136

def render(controller, parent_comp = nil, style: nil, **button_arg_overrides)
  # Abort if not authorized
  return nil unless comp.standalone_access_permitted_for?(controller, standalone_name: @standalone_name, verb: method)
  # Prepare opts
  button_comp_class ||= Compony.button_component_class(*[style || @style].compact)
  button_opts = button_comp_opts.merge(button_arg_overrides)
  # Perform render
  if parent_comp
    return parent_comp.sub_comp(button_comp_class, **button_opts).render(controller)
  else
    button_comp_class.new(**button_opts).render(controller)
  end
end