Module: Compony

Defined in:
lib/compony.rb,
lib/compony/engine.rb,
lib/compony/version.rb,
lib/compony/component.rb,
lib/compony/model_mixin.rb,
lib/compony/view_helpers.rb,
lib/compony/components/new.rb,
lib/compony/components/edit.rb,
lib/compony/components/form.rb,
lib/compony/request_context.rb,
lib/compony/controller_mixin.rb,
lib/compony/model_fields/url.rb,
lib/compony/components/button.rb,
lib/compony/model_fields/base.rb,
lib/compony/model_fields/date.rb,
lib/compony/model_fields/text.rb,
lib/compony/model_fields/time.rb,
lib/compony/components/destroy.rb,
lib/compony/model_fields/color.rb,
lib/compony/model_fields/email.rb,
lib/compony/model_fields/float.rb,
lib/compony/model_fields/phone.rb,
lib/compony/model_fields/string.rb,
lib/compony/components/with_form.rb,
lib/compony/model_fields/boolean.rb,
lib/compony/model_fields/decimal.rb,
lib/compony/model_fields/integer.rb,
lib/compony/model_fields/currency.rb,
lib/compony/model_fields/datetime.rb,
lib/compony/method_accessible_hash.rb,
lib/compony/model_fields/rich_text.rb,
lib/compony/model_fields/attachment.rb,
lib/compony/model_fields/percentage.rb,
lib/compony/model_fields/anchormodel.rb,
lib/compony/model_fields/association.rb,
lib/compony/component_mixins/resourceful.rb,
lib/compony/component_mixins/default/labelling.rb,
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

Root module, containing confguration and pure helpers. For the setters, create an initializer config/initializers/compony.rb and call them from there.

Defined Under Namespace

Modules: ComponentMixins, Components, ControllerMixin, ModelFields, ModelMixin, Version, ViewHelpers Classes: Component, Engine, MethodAccessibleHash, RequestContext

Class Method Summary collapse

Class Method Details

.authentication_before_actionObject

Getter for the name of the Rails before_action that enforces authentication.



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

def self.authentication_before_action
  @authentication_before_action
end

.authentication_before_action=(authentication_before_action) ⇒ Object

Setter for the name of the Rails before_action that should be called to ensure that users are authenticated before accessing the component. For instance, implement a method def enforce_authentication in your ApplicationController. In the method, make sure the user has a session and redirect to the login page if they don't.
The action must be accessible by ComponyController and the easiest way to achieve this is to implement the action in your ApplicationController. If this is never called, authentication is disabled.

Parameters:

  • authentication_before_action (Symbol)

    Name of the method you want to call for authentication



39
40
41
# File 'lib/compony.rb', line 39

def self.authentication_before_action=(authentication_before_action)
  @authentication_before_action = authentication_before_action.to_sym
end

.button(comp_name_or_cst, model_or_family_name_or_cst, label_opts: nil, params: nil, feasibility_action: nil, feasibility_target: nil, method: nil, **override_kwargs) ⇒ Object

Given a component and a family/model, this instanciates and returns a button component.

Parameters:

  • comp_name_or_cst (String, Symbol)

    The component that should be loaded, for instance ShowForAll, 'ShowForAll' or :show_for_all

  • model_or_family_name_or_cst (String, Symbol, ApplicationRecord)

    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

  • label_opts (Hash) (defaults to: nil)

    Options hash that will be passed to the label method (see Compony::ComponentMixins::Default::Labelling#label)

  • params (Hash) (defaults to: nil)

    GET parameters to be inclued into the path this button points to. Special case: e.g. format: :pdf -> some.url/foo/bar.pdf

  • feasibility_action (Symbol) (defaults to: nil)

    Name of the feasibility action that should be checked for this button, defaults to the component name

  • feasibility_target (Symbol) (defaults to: nil)

    Name of the feasibility target (subject) that the feasibility should be checked on, defaults to the model if given

  • override_kwargs (Hash)

    Override button options, see options for Compony::Components::Button

See Also:



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/compony.rb', line 168

def self.button(comp_name_or_cst,
                model_or_family_name_or_cst,
                label_opts: nil,
                params: nil,
                feasibility_action: nil,
                feasibility_target: nil,
                method: nil,
                **override_kwargs)
  label_opts ||= button_defaults[:label_opts] || {}
  params ||= button_defaults[:params] || {}
  model = model_or_family_name_or_cst.respond_to?(:model_name) ? model_or_family_name_or_cst : nil
  target_comp_instance = Compony.comp_class_for!(comp_name_or_cst, model_or_family_name_or_cst).new(data: model)
  feasibility_action ||= button_defaults[:feasibility_action] || comp_name_or_cst.to_s.underscore.to_sym
  feasibility_target ||= button_defaults[:feasibility_target] || model
  options = {
    label:   target_comp_instance.label(model, **label_opts),
    icon:    target_comp_instance.icon,
    color:   target_comp_instance.color,
    path:    Compony.path(target_comp_instance.comp_name, target_comp_instance.family_name, model, **params),
    method:,
    visible: ->(controller) { target_comp_instance.standalone_access_permitted_for?(controller, verb: method) }
  }
  if feasibility_target
    options.merge!({
                     enabled: feasibility_target.feasible?(feasibility_action),
                     title:   feasibility_target.full_feasibility_messages(feasibility_action).presence
                   })
  end
  options.merge!(override_kwargs.symbolize_keys)
  return Compony.button_component_class.new(**options.symbolize_keys)
end

.button_component_classObject

Getter for the global button component class.



63
64
65
66
67
# File 'lib/compony.rb', line 63

def self.button_component_class
  @button_component_class ||= Components::Button
  @button_component_class = const_get(@button_component_class) if @button_component_class.is_a?(String)
  return @button_component_class
end

.button_component_class=(button_component_class) ⇒ Object

Setter for the global button component class. This allows you to implement a custom button component and have all Compony button helpers use your custom button component instead of Compony::Components::Button.

Parameters:



15
16
17
# File 'lib/compony.rb', line 15

def self.button_component_class=(button_component_class)
  @button_component_class = button_component_class
end

.button_defaultsObject

TODO:

document params

Getter for current button defaults



219
220
221
# File 'lib/compony.rb', line 219

def self.button_defaults
  RequestStore.store[:button_defaults] || {}
end

.comp_class_for(comp_name_or_cst, model_or_family_name_or_cst) ⇒ Object

Given a component and a family/model, this returns the matching component class if any, or nil if the component does not exist.

Parameters:

  • comp_name_or_cst (String, Symbol)

    The component that should be loaded, for instance ShowForAll, 'ShowForAll' or :show_for_all

  • model_or_family_name_or_cst (String, Symbol, ApplicationRecord)

    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



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

def self.comp_class_for(comp_name_or_cst, model_or_family_name_or_cst)
  family_cst_str = family_name_for(model_or_family_name_or_cst).camelize
  comp_cst_str = comp_name_or_cst.to_s.camelize
  return nil unless ::Components.const_defined?(family_cst_str)
  family_constant = ::Components.const_get(family_cst_str)
  return nil unless family_constant.const_defined?(comp_cst_str)
  return family_constant.const_get(comp_cst_str)
end

.comp_class_for!(comp_name_or_cst, model_or_family_name_or_cst) ⇒ Object

Same as Compony#comp_class_for but fails if none found

See Also:



131
132
133
134
135
# File 'lib/compony.rb', line 131

def self.comp_class_for!(comp_name_or_cst, model_or_family_name_or_cst)
  comp_class_for(comp_name_or_cst, model_or_family_name_or_cst) || fail(
    "No component found for [#{comp_name_or_cst.inspect}, #{model_or_family_name_or_cst.inspect}]"
  )
end

.content_after_root_comp(&block) ⇒ Object

Setter for a content block that runs after the root component gets rendered (standalone only). Usage is the same as content. The block runs after render, i.e. after the last content block.



52
53
54
55
# File 'lib/compony.rb', line 52

def self.content_after_root_comp(&block)
  fail('`Compony.content_after` requires a block.') unless block_given?
  @content_after_root_comp_block = block
end

.content_after_root_comp_blockObject

Getter for content_after_root_comp_block



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

def self.content_after_root_comp_block
  @content_after_root_comp_block
end

.content_before_root_comp(&block) ⇒ Object

Setter for a content block that runs before the root component gets rendered (standalone only). Usage is the same as content. The block runs between before_render and render, i.e. before the first content block.



45
46
47
48
# File 'lib/compony.rb', line 45

def self.content_before_root_comp(&block)
  fail('`Compony.content_before` requires a block.') unless block_given?
  @content_before_root_comp_block = block
end

.content_before_root_comp_blockObject

Getter for content_before_root_comp_block



83
84
85
# File 'lib/compony.rb', line 83

def self.content_before_root_comp_block
  @content_before_root_comp_block
end

.family_name_for(model_or_family_name_or_cst) ⇒ Object

Given a family name or a model-like class, this returns the suitable family name as String.

Parameters:

  • model_or_family_name_or_cst (String, Symbol, ApplicationRecord)

    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



209
210
211
212
213
214
215
# File 'lib/compony.rb', line 209

def self.family_name_for(model_or_family_name_or_cst)
  if model_or_family_name_or_cst.respond_to?(:model_name)
    return model_or_family_name_or_cst.model_name.plural
  else
    return model_or_family_name_or_cst.to_s.underscore
  end
end

.model_field_class_for(constant) ⇒ Object

Goes through model_field_namespaces and returns the first hit for the given constant

Parameters:

  • constant (Constant)

    The constant that is searched, e.g. RichText -> would return e.g. Compony::ModelFields::RichText



250
251
252
253
254
255
256
257
258
# File 'lib/compony.rb', line 250

def self.model_field_class_for(constant)
  model_field_namespaces.each do |model_field_namespace|
    model_field_namespace = model_field_namespace.constantize if model_field_namespace.is_a?(::String)
    if model_field_namespace.const_defined?(constant, false)
      return model_field_namespace.const_get(constant, false)
    end
  end
  fail("No `model_field_namespace` implements ...::#{constant}. Configured namespaces: #{Compony.model_field_namespaces.inspect}")
end

.model_field_namespacesObject

Getter for the global field namespaces.



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

def self.model_field_namespaces
  return @model_field_namespaces ||= ['Compony::ModelFields']
end

.model_field_namespaces=(model_field_namespaces) ⇒ Object

Setter for the global field namespaces. This allows you to implement custom Fields, be it new ones or overrides for existing Compony model fields. Must give an array of strings of namespaces that contain field classes named after the field type. The array is queried in order, if the first namespace does not contain the class we're looking for, the next is considered and so on. The classes defined in the namespace must inherit from Compony::ModelFields::Base

Parameters:

  • model_field_namespaces (Array)

    Array of strings, the names of the namespaces in the order they should be searched



26
27
28
# File 'lib/compony.rb', line 26

def self.model_field_namespaces=(model_field_namespaces)
  @model_field_namespaces = model_field_namespaces
end

.path(comp_name_or_cst, model_or_family_name_or_cst, *args_for_path_helper, **kwargs_for_path_helper) ⇒ Object

Generates a Rails path to a component. Examples: Compony.path(:index, :users), Compony.path(:show, User.first)

Parameters:

  • comp_name_or_cst (String, Symbol)

    The component that should be loaded, for instance ShowForAll, 'ShowForAll' or :show_for_all

  • model_or_family_name_or_cst (String, Symbol, ApplicationRecord)

    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

  • args_for_path_helper (Array)

    Positional arguments passed to the Rails helper

  • kwargs_for_path_helper (Hash)

    Named arguments passed to the Rails helper. If a model is given to model_or_family_name_or_cst, the param id defaults to the passed model's ID.



105
106
107
108
109
110
111
112
113
# File 'lib/compony.rb', line 105

def self.path(comp_name_or_cst, model_or_family_name_or_cst, *args_for_path_helper, **kwargs_for_path_helper)
  # Extract model if any, to get the ID
  kwargs_for_path_helper.merge!(id: model_or_family_name_or_cst.id) if model_or_family_name_or_cst.respond_to?(:model_name)
  return Rails.application.routes.url_helpers.send(
    "#{path_helper_name(comp_name_or_cst, model_or_family_name_or_cst)}_path",
    *args_for_path_helper,
    **kwargs_for_path_helper
  )
end

.path_helper_nameObject

Given a component and a family, this returns the name of the Rails URL helper returning the path to this component.
The parameters are the same as for rails_action_name.
Example usage: send("#{path_helper_name(:index, :users)}_url)



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

def self.path_helper_name(...)
  "#{rails_action_name(...)}_comp"
end

.rails_action_name(comp_name_or_cst, model_or_family_name_or_cst, name = nil) ⇒ Object

Given a component and a family, this returns the name of the ComponyController action for this component.
Optionally can pass a name for extra standalone configs.

Parameters:

  • comp_name_or_cst (String, Symbol)

    Name of the component the action points to.

  • model_or_family_name_or_cst (String, Symbol)

    Name of the family the action points to.

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

    If referring to an extra standalone entrypoint, specify its name using this param.

See Also:



152
153
154
# File 'lib/compony.rb', line 152

def self.rails_action_name(comp_name_or_cst, model_or_family_name_or_cst, name = nil)
  [name.presence, comp_name_or_cst.to_s.underscore, family_name_for(model_or_family_name_or_cst)].compact.join('_')
end

.root_compObject

Returns the current root component, if any



201
202
203
# File 'lib/compony.rb', line 201

def self.root_comp
  RequestStore.store[:compony_root_comp]
end

.with_button_defaults(**keys_to_overwrite, &block) ⇒ Object

Overwrites the keys of the current button defaults by the ones provided during the execution of a given block and restores them afterwords. This method is useful when the same set of options is to be given to a multitude of buttons.

Parameters:

  • keys_to_overwrite (Hash)

    Options that should be given to the buttons within the block, with their values

  • block (Block)

    Within this block, all omitted button options point to keys_to_overwrite



227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
# File 'lib/compony.rb', line 227

def self.with_button_defaults(**keys_to_overwrite, &block)
  # Lazy initialize butto_defaults store if it hasn't been yet
  RequestStore.store[:button_defaults] ||= {}
  keys_to_overwrite.transform_keys!(&:to_sym)
  old_values = {}
  newly_defined_keys = keys_to_overwrite.keys - RequestStore.store[:button_defaults].keys
  keys_to_overwrite.each do |key, new_value|
    # Assign new value
    old_values[key] = RequestStore.store[:button_defaults][key]
    RequestStore.store[:button_defaults][key] = new_value
  end
  return_value = block.call
  # Restore previous value
  keys_to_overwrite.each do |key, _new_value|
    RequestStore.store[:button_defaults][key] = old_values[key]
  end
  # Undefine keys that were not there previously
  newly_defined_keys.each { |key| RequestStore.store[:button_defaults].delete(key) }
  return return_value
end