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/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/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.



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

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:

  • Name of the method you want to call for authentication

API:

  • description



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:

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

  • 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

  • (defaults to: nil)

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

  • (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

  • (defaults to: nil)

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

  • (defaults to: nil)

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

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

See Also:

API:

  • description



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/compony.rb', line 142

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.



49
50
51
52
53
# File 'lib/compony.rb', line 49

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:

API:

  • description



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

API:

  • description



193
194
195
# File 'lib/compony.rb', line 193

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:

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

  • 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

API:

  • description



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

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:

API:

  • description



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

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

.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:

  • 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

API:

  • description



183
184
185
186
187
188
189
# File 'lib/compony.rb', line 183

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:

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

API:

  • description



224
225
226
227
228
229
230
231
232
# File 'lib/compony.rb', line 224

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.



57
58
59
# File 'lib/compony.rb', line 57

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:

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

API:

  • description



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:

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

  • 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

  • Positional arguments passed to the Rails helper

  • 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.

API:

  • description



79
80
81
82
83
84
85
86
87
# File 'lib/compony.rb', line 79

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)

See Also:

API:

  • description



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

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:

  • Name of the component the action points to.

  • Name of the family the action points to.

  • (defaults to: nil)

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

See Also:

API:

  • description



126
127
128
# File 'lib/compony.rb', line 126

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

API:

  • description



175
176
177
# File 'lib/compony.rb', line 175

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:

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

  • Within this block, all omitted button options point to keys_to_overwrite

API:

  • description



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'lib/compony.rb', line 201

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