Class: CrudComponents::Fields::Base

Inherits:
Object
  • Object
show all
Defined in:
lib/crud_components/fields/base.rb

Overview

One subclass per field flavor (one row of the README’s combination table). A field knows how it renders (which partial), how it filters (which control + how params reach SQL), and whether it sorts.

Facets declared in an ‘attribute` block override exactly one of those: :render (block), :filter (like-spec / block / false), :sort (column symbol / block / false).

Constant Summary collapse

NON_EDITABLE_COLUMNS =

── forms ──────────────────────────────────────────────────────────────Columns that exist but are never user-editable in a derived form.

%w[id created_at updated_at].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, model, options = {}, facets = {}) ⇒ Base

Returns a new instance of Base.



13
14
15
16
17
18
# File 'lib/crud_components/fields/base.rb', line 13

def initialize(name, model, options = {}, facets = {})
  @name = name.to_sym
  @model = model
  @options = options
  @facets = facets
end

Instance Attribute Details

#facetsObject (readonly)

Returns the value of attribute facets.



11
12
13
# File 'lib/crud_components/fields/base.rb', line 11

def facets
  @facets
end

#modelObject (readonly)

Returns the value of attribute model.



11
12
13
# File 'lib/crud_components/fields/base.rb', line 11

def model
  @model
end

#nameObject (readonly)

Returns the value of attribute name.



11
12
13
# File 'lib/crud_components/fields/base.rb', line 11

def name
  @name
end

#optionsObject (readonly)

Returns the value of attribute options.



11
12
13
# File 'lib/crud_components/fields/base.rb', line 11

def options
  @options
end

Instance Method Details

#apply_derived_filter(scope) ⇒ Object



152
153
154
# File 'lib/crud_components/fields/base.rb', line 152

def apply_derived_filter(scope, **)
  scope
end

#apply_filter(scope, exact: nil, geq: nil, leq: nil) ⇒ Object

‘exact`, `geq`, `leq` are the raw param values (Strings or nil).



133
134
135
136
137
138
139
140
141
# File 'lib/crud_components/fields/base.rb', line 133

def apply_filter(scope, exact: nil, geq: nil, leq: nil)
  if filter_facet
    return scope unless exact

    apply_filter_facet(scope, exact)
  else
    apply_derived_filter(scope, exact:, geq:, leq:)
  end
end

#apply_filter_facet(scope, value) ⇒ Object



143
144
145
146
147
148
149
150
# File 'lib/crud_components/fields/base.rb', line 143

def apply_filter_facet(scope, value)
  facet = filter_facet
  if facet.is_a?(Proc)
    facet.call(scope.extending(WhereLike), value)
  else
    LikeSpec.apply(scope, facet, value)
  end
end

#apply_sort(scope, dir) ⇒ Object



173
174
175
176
177
178
179
# File 'lib/crud_components/fields/base.rb', line 173

def apply_sort(scope, dir)
  case (facet = sort_facet)
  when Proc then facet.call(scope, dir)
  when Symbol then scope.reorder(model.arel_table[facet].public_send(dir))
  else scope.reorder(model.arel_table[name].public_send(dir))
  end
end

#columnObject

The DB column backing this field, if any (nil for associations and computed fields).



55
56
57
# File 'lib/crud_components/fields/base.rb', line 55

def column
  model.columns_hash[name.to_s]
end

#custom_header?Boolean

Whether this column brings its own header markup or header actions — the layout falls back to the plain human_name + sort link when it doesn’t.

Returns:

  • (Boolean)


39
# File 'lib/crud_components/fields/base.rb', line 39

def custom_header? = !header.nil? || header_actions.any?

#declared_preloadsObject

The ‘preload:` option as an array of includes-specs (a nested hash kept intact, unlike Array()).



241
242
243
244
245
246
247
# File 'lib/crud_components/fields/base.rb', line 241

def declared_preloads
  case (p = options[:preload])
  when nil then []
  when Array then p
  else [p]
  end
end

#default_editable?Boolean

Returns:

  • (Boolean)


196
197
198
# File 'lib/crud_components/fields/base.rb', line 196

def default_editable?
  false
end

#default_rendererObject



79
80
81
# File 'lib/crud_components/fields/base.rb', line 79

def default_renderer
  :string
end

#derived_filter_controlObject



120
121
122
# File 'lib/crud_components/fields/base.rb', line 120

def derived_filter_control
  :text
end

#derived_filterable?Boolean

Returns:

  • (Boolean)


110
111
112
# File 'lib/crud_components/fields/base.rb', line 110

def derived_filterable?
  false
end

#derived_sortable?Boolean

Returns:

  • (Boolean)


169
170
171
# File 'lib/crud_components/fields/base.rb', line 169

def derived_sortable?
  false
end

#eager_loadObject

── loading ──────────────────────────────────────────────────────────Includes-specs (symbols/nested hashes for ActiveRecord#includes) to eager-load when this column is shown. Base contributes the per-attribute ‘preload:` — associations a render block / custom renderer reaches on the listed model. Association fields override to also nest the target’s identity_preloads under the association name.



235
236
237
# File 'lib/crud_components/fields/base.rb', line 235

def eager_load
  declared_preloads
end

#editable?Boolean

Whether this field appears as an input in a derived form. ‘editable:` overrides; a symbol/Proc means “editable, subject to a can? check” (see editable_permitted?).

Returns:

  • (Boolean)


188
189
190
191
192
193
194
# File 'lib/crud_components/fields/base.rb', line 188

def editable?
  case options[:editable]
  when false then false
  when nil then default_editable?
  else true
  end
end

#editable_permitted?(context, record = nil) ⇒ Boolean

Returns:

  • (Boolean)


200
201
202
203
204
205
206
207
208
# File 'lib/crud_components/fields/base.rb', line 200

def editable_permitted?(context, record = nil)
  condition = options[:editable]
  return true unless condition.is_a?(Symbol) || condition.is_a?(Proc)

  # recordless: false — a record-dependent `editable:` can't be granted by
  # the class-level permit list (no record there); deny by default and let
  # the per-record form check decide where a record is present.
  Permission.permitted?(condition, model, context, record, recordless: false)
end

#filter_choices(_query = nil) ⇒ Object



124
125
126
# File 'lib/crud_components/fields/base.rb', line 124

def filter_choices(_query = nil)
  nil
end

#filter_controlObject

Which filter control partial to render: :text, :select, :boolean, :number_range or :date_range.



116
117
118
# File 'lib/crud_components/fields/base.rb', line 116

def filter_control
  filter_facet ? :text : derived_filter_control
end

#filter_facetObject



105
106
107
108
# File 'lib/crud_components/fields/base.rb', line 105

def filter_facet
  facets[:filter].is_a?(Proc) || facets[:filter].is_a?(Array) ||
    facets[:filter].is_a?(Hash) || facets[:filter].is_a?(Symbol) ? facets[:filter] : nil
end

#filter_includes_null?Boolean

Whether this field’s filter offers a “not set” (IS NULL) choice.

Returns:

  • (Boolean)


66
67
68
# File 'lib/crud_components/fields/base.rb', line 66

def filter_includes_null?
  false
end

#filterable?Boolean

── filtering ────────────────────────────────────────────────────────

Returns:

  • (Boolean)


97
98
99
100
101
102
103
# File 'lib/crud_components/fields/base.rb', line 97

def filterable?
  return false if facets[:filter] == false
  return false if CrudComponents::RESERVED_PARAMS.include?(name.to_s)
  return true if filter_facet

  derived_filterable?
end

#form_controlObject

The form-input flavor; nil = no form representation (json, computed).



211
212
213
# File 'lib/crud_components/fields/base.rb', line 211

def form_control
  nil
end

#form_partialObject

The form-input partial to render: crud_components/form_fields/_<name>. Defaults to the field’s form_control type; override per field with ‘form_as:` (mirrors `as:` for the read-only/display renderer). The partial receives the simple_form builder `f`, the `field`, and `form`.



219
220
221
# File 'lib/crud_components/fields/base.rb', line 219

def form_partial
  options[:form_as] || form_control
end

#group_labelObject

Column-picker grouping: the heading this column sits under (a path column groups under the association(s) it reaches through), or nil for an own column. ‘picker_label` is the label shown within that group.



44
# File 'lib/crud_components/fields/base.rb', line 44

def group_label = nil

#group_modelObject

The model the column-picker groups this column under (Pipedrive-style): its own model for a plain column, the associated model for an association or path column — so ‘publisher`, `publisher.name` and `publisher.founded_on` all sit under “Publisher”.



51
# File 'lib/crud_components/fields/base.rb', line 51

def group_model = model

#headerObject

── column header (issue #4) ───────────────────────────────────────────A column may own its ‘<th>`: a custom `header:` (a String rendered as-is —mark it html_safe for markup — or a view-context block, e.g. a link), and `header_actions:` — plain Action objects rendered in the header. A `:selection` action there acts on the ticked rows (it submits the shared select-form); any other action renders as a link/button. Available on every field flavor: a DynamicColumn passes them through its options, a declared `attribute` takes them as options too.



34
# File 'lib/crud_components/fields/base.rb', line 34

def header = options[:header]

#header_actionsObject



35
# File 'lib/crud_components/fields/base.rb', line 35

def header_actions = Array(options[:header_actions])

#human_nameObject



20
21
22
23
24
# File 'lib/crud_components/fields/base.rb', line 20

def human_name
  return options[:label] if options[:label].is_a?(String)

  model.human_attribute_name(name)
end

#nullable?Boolean

Whether the backing column permits NULL — gates the “not set” filter choice and the 3-state form control for nullable boolean/enum fields.

Returns:

  • (Boolean)


61
62
63
# File 'lib/crud_components/fields/base.rb', line 61

def nullable?
  !!column&.null
end

#permit_paramObject

What this field contributes to a strong-params permit list — a symbol or a nested hash; collected by Structure#permitted_params.



225
226
227
# File 'lib/crud_components/fields/base.rb', line 225

def permit_param
  name
end

#permitted?(context, record = nil) ⇒ Boolean

── permissions ──────────────────────────────────────────────────────

Returns:

  • (Boolean)


92
93
94
# File 'lib/crud_components/fields/base.rb', line 92

def permitted?(context, record = nil)
  Permission.permitted?(options[:if], model, context, record)
end

#picker_labelObject



45
# File 'lib/crud_components/fields/base.rb', line 45

def picker_label = human_name

#range_filter?Boolean

Returns:

  • (Boolean)


128
129
130
# File 'lib/crud_components/fields/base.rb', line 128

def range_filter?
  filter_control == :number_range || filter_control == :date_range
end

#render_blockObject



83
84
85
# File 'lib/crud_components/fields/base.rb', line 83

def render_block
  facets[:render]
end

#renderer(_record = nil) ⇒ Object

── rendering ────────────────────────────────────────────────────────



75
76
77
# File 'lib/crud_components/fields/base.rb', line 75

def renderer(_record = nil)
  options[:as] || default_renderer
end

#renderer_optionsObject



87
88
89
# File 'lib/crud_components/fields/base.rb', line 87

def renderer_options
  options.except(:as, :if, :form_as, :label, :header, :header_actions)
end

#sort_facetObject



165
166
167
# File 'lib/crud_components/fields/base.rb', line 165

def sort_facet
  facets[:sort].is_a?(Proc) || facets[:sort].is_a?(Symbol) ? facets[:sort] : nil
end

#sortable?Boolean

── sorting ──────────────────────────────────────────────────────────

Returns:

  • (Boolean)


157
158
159
160
161
162
163
# File 'lib/crud_components/fields/base.rb', line 157

def sortable?
  return false if facets[:sort] == false
  return false if CrudComponents::RESERVED_PARAMS.include?(name.to_s)
  return true if sort_facet

  derived_sortable?
end

#value(record) ⇒ Object



70
71
72
# File 'lib/crud_components/fields/base.rb', line 70

def value(record)
  record.public_send(name)
end