Module: CrudComponents::Helpers

Defined in:
lib/crud_components/helpers.rb

Overview

The everyday view API, included into ActionView by the engine. Every helper builds a presenter and renders a partial you can override via the host app’s view path (‘app/views/crud_components/…`).

Instance Method Summary collapse

Instance Method Details

#crud_actions(subject, fieldset: nil) ⇒ ActiveSupport::SafeBuffer

The action buttons for a record (row actions) or a model class (collection actions) — for manual placement when you render with ‘actions: false`.

Parameters:

  • subject (ActiveRecord::Base, Class)

    a record (row actions) or the model class (collection actions). A relation is rejected — collection actions are model-level (‘can?(:new, Book)`), so pass the class.

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

    which fieldset’s actions to use; defaults to ‘:index` (collection) or `:show` (row).

Returns:

  • (ActiveSupport::SafeBuffer)

    the rendered HTML.

Raises:

  • (ArgumentError)

    if given a relation.



150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/crud_components/helpers.rb', line 150

def crud_actions(subject, fieldset: nil)
  if subject.is_a?(ActiveRecord::Relation)
    raise ArgumentError,
          'crud_actions takes a record (row actions) or a model class (collection ' \
          "actions), not a relation — pass `#{subject.klass}`, not a scope."
  end
  model = subject.is_a?(Class) ? subject : subject.class
  structure = Structure.for(model)
  kind = subject.is_a?(Class) ? :collection : :row
  resolved_fieldset = structure.fieldset(fieldset || (kind == :collection ? :index : :show))
  presenter = Presenters::Actions.new(view: self, subject: subject, structure: structure,
                                      actions: structure.fieldset_actions(resolved_fieldset, on: kind))
  render 'crud_components/actions', actions: presenter
end

#crud_association_index_path(owner, field) ⇒ String?

The index a has_many cell links to: nested under the owner, else the target’s filtered index, else nil.

Parameters:

Returns:

  • (String, nil)

    the path, or nil when none resolves.



244
245
246
# File 'lib/crud_components/helpers.rb', line 244

def crud_association_index_path(owner, field)
  RouteResolver.collection_index_path(self, field.target, owner, field.name)
end

#crud_association_label(field, record) ⇒ String

The label for an associated record in an association column: a per-column ‘label:` callable (`attribute :order, label: ->(o) { o.full_title(short: true) }`) when given, else the target’s default #crud_label. Used by the association / association_list renderers so a column can re-title the associated record for its context while keeping the nil-safe link.

Parameters:

Returns:

  • (String)


182
183
184
185
# File 'lib/crud_components/helpers.rb', line 182

def crud_association_label(field, record)
  callable = field.options[:label]
  callable.respond_to?(:call) ? callable.call(record) : crud_label(record)
end

#crud_collection(records, fieldset: nil, layout: :table, query: :auto, param_prefix: nil, actions: true, group_by: nil, extra_columns: nil, picker: false, picked_columns: :auto) ⇒ ActiveSupport::SafeBuffer

A set of records as a table (or any layout partial you point ‘layout:` at).

Parameters:

  • records (ActiveRecord::Relation)

    the rows to render. Pass a scope, not a model class, so your authorization and any pre-scoping apply before the gem renders (e.g. ‘Book.accessible_by(current_ability)`).

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

    which declared fieldset to use; defaults to ‘:index` (or every column when the model declares nothing).

  • layout (Symbol) (defaults to: :table)

    the layout partial under ‘crud_components/layouts/` — `:table` ships; add your own (e.g. `:cards`) and pass its name.

  • query (Symbol, CrudComponents::Query) (defaults to: :auto)

    query mode: ‘:auto` (default) reads the request params and filters/sorts; `:static` renders no filter row or sort links (params ignored); a Query is manual mode (records already filtered). See Presenters::Collection.

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

    namespaces this collection’s params so two auto collections can share one page.

  • actions (Boolean) (defaults to: true)

    render the actions column + toolbar (false to place them yourself with #crud_actions).

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

    a column, belongs_to or enum to group rows under collapsible headers.

  • extra_columns (Array<CrudComponents::DynamicColumn>, nil) (defaults to: nil)

    user-defined columns whose data lives outside the model’s table (custom properties from a separate store, JSONB, an API). Appended after the declared columns and subject to the same ‘if:` permission gate; filter/sort only when the column supplies those facets.

  • picker (Boolean) (defaults to: false)

    render the column-picker gear in the header row (default false). The gear stays put regardless of ‘picked_columns:`, so it survives across ephemeral and persisted selections alike.

  • picked_columns (Symbol, Array<Symbol>) (defaults to: :auto)

    which columns to show when the picker is on: ‘:auto` (default) reads the `?cols=` submit; an `Array` shows exactly those columns and **never reads the param** — the backend resolved it (from a persisted preference, or from the param via CrudComponents.selected_columns). A forged/stale name can only hide or reorder, never reveal a column the `if:` gate forbids.

Returns:

  • (ActiveSupport::SafeBuffer)

    the rendered HTML.



40
41
42
43
44
45
46
47
# File 'lib/crud_components/helpers.rb', line 40

def crud_collection(records, fieldset: nil, layout: :table, query: :auto, param_prefix: nil,
                    actions: true, group_by: nil, extra_columns: nil, picker: false, picked_columns: :auto)
  presenter = Presenters::Collection.new(view: self, records: records, fieldset: fieldset,
                                         query: query, layout: layout, param_prefix: param_prefix,
                                         actions: actions, group_by: group_by, extra_columns: extra_columns,
                                         picker: picker, picked_columns: picked_columns)
  render "crud_components/layouts/#{presenter.layout}", collection: presenter
end

#crud_column_picker(subject, fieldset: nil, extra_columns: nil, picked_columns: :auto, url: nil, param_prefix: nil) ⇒ ActiveSupport::SafeBuffer

A standalone column picker — the same gear-and-checklist the table renders in its header, but placed wherever you like (e.g. above a ‘crud_record` detail view). It submits `?cols[]=` to `url` (the current page by default), so a `crud_collection`/`crud_record` on the target page picks it up via `picked_columns:` or the param directly. Persist the choice with CrudComponents.selected_columns.

Parameters:

  • subject (ActiveRecord::Relation, Class, ActiveRecord::Base)

    anything the columns belong to — a scope, the model class, or a record.

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

    which fieldset’s fields to offer (e.g. ‘:show`).

  • extra_columns (Array<CrudComponents::DynamicColumn>, nil) (defaults to: nil)

    dynamic columns to include in the choices.

  • picked_columns (Symbol, Array<Symbol>) (defaults to: :auto)

    which boxes are pre-ticked: ‘:auto` (default) reflects the current `?cols=`; an `Array` pre-ticks that exact selection (no param read).

  • url (String, nil) (defaults to: nil)

    where the picker form submits; defaults to the current path.

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

    namespaces the ‘?cols=` param.

Returns:

  • (ActiveSupport::SafeBuffer)

    the rendered HTML.



95
96
97
98
99
100
101
102
103
104
# File 'lib/crud_components/helpers.rb', line 95

def crud_column_picker(subject, fieldset: nil, extra_columns: nil, picked_columns: :auto, url: nil, param_prefix: nil)
  relation = if subject.respond_to?(:klass) then subject
             elsif subject.is_a?(Class) then subject.all
             else subject.class.all
             end
  presenter = Presenters::Collection.new(view: self, records: relation, fieldset: fieldset, query: :static,
                                         extra_columns: extra_columns, picker: true, picked_columns: picked_columns,
                                         param_prefix: param_prefix, actions: false)
  render 'crud_components/column_picker', collection: presenter, url: (url || request.path)
end

#crud_components_stylesObject

Inline the gem’s stylesheet (the column-picker float styles) as a <style> tag — drop ‘<%= crud_components_styles %>` once in your layout <head>. This is the pipeline-agnostic way to load it: it needs no asset compilation, so it works the same under cssbundling/sass, importmap, sprockets or propshaft. Hosts whose pipeline serves engine assets can instead link the same file with `stylesheet_link_tag “crud_components”`.



254
255
256
257
# File 'lib/crud_components/helpers.rb', line 254

def crud_components_styles
  nonce = content_security_policy_nonce if respond_to?(:content_security_policy_nonce)
  tag.style(CrudComponents.bundled_css.html_safe, type: 'text/css', nonce: nonce)
end

#crud_file_icon(filename) ⇒ String

A Bootstrap-icon name (no library prefix — pair with css.icon_prefix) for a filename, by extension: config.file_icons, else config.file_fallback_icon. Used by the attachment renderer’s icon fallback; override that partial to customize.

Parameters:

  • filename (String, #to_s)

    the file name (or path) to derive from.

Returns:

  • (String)

    the icon name.



224
225
226
227
228
# File 'lib/crud_components/helpers.rb', line 224

def crud_file_icon(filename)
  config = CrudComponents.config
  ext = File.extname(filename.to_s).delete('.').downcase
  config.file_icons.fetch(ext, config.file_fallback_icon)
end

#crud_filter(model, fieldset: nil, query: nil, param_prefix: nil, layout: :filter) ⇒ ActiveSupport::SafeBuffer

A standalone labelled filter form (modal / sidebar) — separate from the inline filter row a table renders.

Parameters:

  • model (Class)

    the ActiveRecord model whose fields drive the form.

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

    which fieldset’s filterable fields to offer.

  • query (CrudComponents::Query, nil) (defaults to: nil)

    reuse an existing query’s values; nil reads the request params.

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

    namespaces the form’s params.

  • layout (Symbol) (defaults to: :filter)

    the partial under ‘crud_components/` (`:filter` ships).

Returns:

  • (ActiveSupport::SafeBuffer)

    the rendered HTML.



116
117
118
119
120
# File 'lib/crud_components/helpers.rb', line 116

def crud_filter(model, fieldset: nil, query: nil, param_prefix: nil, layout: :filter)
  presenter = Presenters::Filter.new(view: self, model: model, fieldset: fieldset,
                                     query: query, param_prefix: param_prefix)
  render "crud_components/#{layout}", filter: presenter
end

#crud_form(record, fieldset: nil, action: nil, url: nil, method: nil, layout: :form) ⇒ ActiveSupport::SafeBuffer

A derived create/edit form. The gem renders; your controller saves using the matching permit list (CrudComponents.permitted_attributes), so the form and strong-params can’t drift.

Parameters:

  • record (ActiveRecord::Base)

    a new or persisted instance.

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

    which fieldset’s fields to render; defaults to the form fieldset for the action.

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

    ‘:new`/`:edit`; inferred from the record when nil.

  • url (String, nil) (defaults to: nil)

    the form action URL; inferred from the record when nil.

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

    the HTTP verb; inferred when nil.

  • layout (Symbol) (defaults to: :form)

    the partial under ‘crud_components/` (`:form` ships).

Returns:

  • (ActiveSupport::SafeBuffer)

    the rendered HTML.



134
135
136
137
138
# File 'lib/crud_components/helpers.rb', line 134

def crud_form(record, fieldset: nil, action: nil, url: nil, method: nil, layout: :form)
  presenter = Presenters::Form.new(view: self, record: record, fieldset: fieldset,
                                   action: action, url: url, method: method)
  render "crud_components/#{layout}", form: presenter
end

#crud_label(record) ⇒ String

The display label for a record — its declared ‘label`, else a humanized guess.

Parameters:

  • record (ActiveRecord::Base)

Returns:

  • (String)


170
171
172
# File 'lib/crud_components/helpers.rb', line 170

def crud_label(record)
  Structure.for(record.class).label_for(record, self)
end

#crud_model_icon(subject, **html_options) ⇒ ActiveSupport::SafeBuffer?

The icon markup badging a model — an ‘<i>` tag (paired with `css.icon_prefix`) for the model’s Structure#icon, or nil when the model has no icon (undeclared and unmapped, with no ‘model_fallback_icon`). Pass a record, a model class or a relation. Extra html options merge onto the tag; `class:` adds to the icon classes.

<%= crud_model_icon(@book) %>                  # <i class="bi bi-book" aria-hidden="true">
<%= crud_model_icon(Publisher, class: 'me-1') %>

Parameters:

  • subject (ActiveRecord::Base, Class, ActiveRecord::Relation)

Returns:

  • (ActiveSupport::SafeBuffer, nil)


198
199
200
201
202
203
204
205
# File 'lib/crud_components/helpers.rb', line 198

def crud_model_icon(subject, **html_options)
  name = crud_model_icon_name(subject)
  return nil unless name

  extra = Array(html_options.delete(:class))
  classes = ["#{CrudComponents.config.css.icon_prefix}#{name}", *extra].join(' ')
  tag.i('', class: classes, aria: { hidden: true }, **html_options)
end

#crud_model_icon_name(subject) ⇒ String?

The bare icon name (no library prefix) for a model — Structure#icon for the model behind a record / class / relation, or nil. Use when you need the name rather than the ‘<i>` tag #crud_model_icon builds.

Parameters:

  • subject (ActiveRecord::Base, Class, ActiveRecord::Relation)

Returns:

  • (String, nil)


212
213
214
215
# File 'lib/crud_components/helpers.rb', line 212

def crud_model_icon_name(subject)
  model = subject.respond_to?(:klass) ? subject.klass : subject.is_a?(Class) ? subject : subject.class
  Structure.for(model).icon
end

#crud_record(record, fieldset: nil, actions: true, layout: :record, picked_columns: :auto, param_prefix: nil, extra_columns: nil) ⇒ ActiveSupport::SafeBuffer

One record as a definition list (or any layout partial you point ‘layout:` at). Extend by creating your own partial — e.g. `crud_components/_card` — and passing `layout: :card`.

Parameters:

  • record (ActiveRecord::Base)

    the record to show.

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

    which fieldset to use; defaults to ‘:show`.

  • actions (Boolean) (defaults to: true)

    render the row actions (false to place them with #crud_actions).

  • layout (Symbol) (defaults to: :record)

    the partial under ‘crud_components/` (`:record` ships).

  • picked_columns (Symbol, Array<Symbol>) (defaults to: :auto)

    narrow/order the fields shown. A detail view has no inline gear of its own, so pass an ‘Array` you resolved (e.g. from a standalone #crud_column_picker on the page, via CrudComponents.selected_columns). `:auto` (default) means “don’t narrow” —with no gear here a stray ‘?cols=` is ignored.

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

    namespaces the ‘?cols=` param this view reads (match it to the picker’s ‘param_prefix:`).

  • extra_columns (Array<CrudComponents::DynamicColumn>, nil) (defaults to: nil)

    user-defined columns whose data lives outside the model’s table, shown as extra rows (same as #crud_collection‘s `extra_columns:`, for a detail view).

Returns:

  • (ActiveSupport::SafeBuffer)

    the rendered HTML.



69
70
71
72
73
74
75
# File 'lib/crud_components/helpers.rb', line 69

def crud_record(record, fieldset: nil, actions: true, layout: :record, picked_columns: :auto,
                param_prefix: nil, extra_columns: nil)
  presenter = Presenters::Record.new(view: self, record: record, fieldset: fieldset, actions: actions,
                                     picked_columns: picked_columns, param_prefix: param_prefix,
                                     extra_columns: extra_columns)
  render "crud_components/#{layout}", record_presenter: presenter
end

#crud_record_path(record, owner: nil) ⇒ String?

The canonical path to a record (its ‘show`, resolved by RouteResolver).

Parameters:

  • record (ActiveRecord::Base)
  • owner (ActiveRecord::Base, nil) (defaults to: nil)

    the owner, for a nested route.

Returns:

  • (String, nil)

    the path, or nil when none resolves.



234
235
236
237
# File 'lib/crud_components/helpers.rb', line 234

def crud_record_path(record, owner: nil)
  found = RouteResolver.record_path(self, record, owner: owner)
  found&.first
end