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 = nil, 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.



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
71
72
73
74
75
76
77
# File 'lib/compony/intent.rb', line 26

def initialize(comp_name_or_cst_or_class = nil,
               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.nil?
    if name.blank? || !label.is_a?(String)
      fail('An intent created without positional arguments must be given the kwargs `name:`, `label` (String).')
    end
  elsif 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 = if !(model_or_family_name_or_cst.is_a?(String) || model_or_family_name_or_cst.is_a?(Symbol)) && @data.respond_to?(:model_name)
                              # Determine the component family from the data
                              @data.model_name.plural
                            else
                              # Determine the component family from the given argument
                              model_or_family_name_or_cst.to_s.underscore
                            end
    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.



134
135
136
137
138
139
140
141
142
# File 'lib/compony/intent.rb', line 134

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.



87
88
89
90
# File 'lib/compony/intent.rb', line 87

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

#feasibility_actionObject



123
124
125
# File 'lib/compony/intent.rb', line 123

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

#feasibility_targetObject



119
120
121
# File 'lib/compony/intent.rb', line 119

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

#feasible?Boolean

Returns whether this intent is feasible (no prevention)



128
129
130
131
# File 'lib/compony/intent.rb', line 128

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.



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

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



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

def method
  @method || :get
end

#model?Boolean

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



80
81
82
83
# File 'lib/compony/intent.rb', line 80

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



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

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.



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

def path(model = nil, *, standalone_name: nil, **path_opt_overrides)
  return @path if @path.present?
  return nil if @comp_class.nil?
  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.



149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/compony/intent.rb', line 149

def render(controller, parent_comp = nil, style: nil, **button_arg_overrides)
  # Abort if not authorized
  return nil if comp && !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 = (comp&.button_defaults || {}).merge(button_comp_opts).merge(button_arg_overrides) # overrides go right to left
  # 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