Class: CrudComponents::Fields::Base
- Inherits:
-
Object
- Object
- CrudComponents::Fields::Base
- 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).
Direct Known Subclasses
AttachmentField, BelongsToField, BooleanField, ComputedField, DateField, EnumField, HasManyField, JsonField, NumericField, StringField
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
-
#facets ⇒ Object
readonly
Returns the value of attribute facets.
-
#model ⇒ Object
readonly
Returns the value of attribute model.
-
#name ⇒ Object
readonly
Returns the value of attribute name.
-
#options ⇒ Object
readonly
Returns the value of attribute options.
Instance Method Summary collapse
- #apply_derived_filter(scope) ⇒ Object
-
#apply_filter(scope, value: nil, geq: nil, leq: nil) ⇒ Object
The raw param values (Strings or nil):
valueis the bare?field=,geqandleqthe range bounds. - #apply_filter_facet(scope, value) ⇒ Object
-
#apply_sort(scope, dir) ⇒ Object
An explicit
?sort=must win over any order a prior stage set (a search backend's relevance rank, a default scope). -
#column ⇒ Object
The DB column backing this field, if any (nil for associations and computed fields).
-
#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.
-
#declared_preloads ⇒ Object
The
preload:option as an array of includes-specs (a nested hash kept intact, unlike Array()). - #default_editable? ⇒ Boolean
- #default_renderer ⇒ Object
- #derived_filter_control ⇒ Object
- #derived_filterable? ⇒ Boolean
- #derived_sortable? ⇒ Boolean
-
#eager_load ⇒ Object
── loading ────────────────────────────────────────────────────────── Includes-specs (symbols/nested hashes for ActiveRecord#includes) to eager-load when this column is shown.
-
#editable? ⇒ Boolean
Whether this field appears as an input in a derived form.
- #editable_permitted?(context, record = nil) ⇒ Boolean
- #filter_choices(query = nil) ⇒ Object
-
#filter_control ⇒ Object
Which filter control partial to render: :text, :select, :boolean, :number_range or :date_range.
- #filter_facet ⇒ Object
-
#filter_includes_null? ⇒ Boolean
Whether this field's filter offers a "not set" (IS NULL) choice.
-
#filterable? ⇒ Boolean
── filtering ────────────────────────────────────────────────────────.
-
#form_control ⇒ Object
The form-input flavor; nil = no form representation (json, computed).
-
#form_partial ⇒ Object
The form-input partial to render: crud_components/form_fields/_
. -
#group_label ⇒ Object
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.
-
#group_model ⇒ Object
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.nameandpublisher.founded_onall sit under "Publisher". -
#header ⇒ Object
── column header (issue #4) ─────────────────────────────────────────── A column may own its
<th>: a customheader:(a String rendered as-is — mark it html_safe for markup — or a view-context block, e.g. a link), andheader_actions:— plain Action objects rendered in the header. - #header_actions ⇒ Object
- #human_name ⇒ Object
-
#initialize(name, model, options = {}, facets = {}) ⇒ Base
constructor
A new instance of Base.
-
#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.
-
#permit_param ⇒ Object
What this field contributes to a strong-params permit list — a symbol or a nested hash; collected by Structure#permitted_params.
-
#permitted?(context, record = nil) ⇒ Boolean
── permissions ──────────────────────────────────────────────────────.
- #picker_label ⇒ Object
- #range_filter? ⇒ Boolean
- #render_block ⇒ Object
-
#renderer(_record = nil) ⇒ Object
── rendering ────────────────────────────────────────────────────────.
- #renderer_options ⇒ Object
-
#search_spec_entry ⇒ Object
How this field feeds the default ?q= ("search what you see"): its own string/text column, or nil for fields with no free-text column behind them (numbers, dates, enums, attachments, computed).
- #sort_facet ⇒ Object
-
#sortable? ⇒ Boolean
── sorting ──────────────────────────────────────────────────────────.
-
#typed_filter ⇒ Object
The internal TypedFilter for a
filter:block that declares keyword params (eq:/geq:/leq:/contains:) — its value type comes fromfilter_as:oras:, so it renders the matching control and receives cast values. - #value(record) ⇒ Object
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, = {}, facets = {}) @name = name.to_sym @model = model @options = @facets = facets end |
Instance Attribute Details
#facets ⇒ Object (readonly)
Returns the value of attribute facets.
11 12 13 |
# File 'lib/crud_components/fields/base.rb', line 11 def facets @facets end |
#model ⇒ Object (readonly)
Returns the value of attribute model.
11 12 13 |
# File 'lib/crud_components/fields/base.rb', line 11 def model @model end |
#name ⇒ Object (readonly)
Returns the value of attribute name.
11 12 13 |
# File 'lib/crud_components/fields/base.rb', line 11 def name @name end |
#options ⇒ Object (readonly)
Returns the value of attribute options.
11 12 13 |
# File 'lib/crud_components/fields/base.rb', line 11 def @options end |
Instance Method Details
#apply_derived_filter(scope) ⇒ Object
176 177 178 |
# File 'lib/crud_components/fields/base.rb', line 176 def apply_derived_filter(scope, **) scope end |
#apply_filter(scope, value: nil, geq: nil, leq: nil) ⇒ Object
The raw param values (Strings or nil): value is the bare ?field=, geq
and leq the range bounds. How a field reads value is up to it — an
exact match for a number/enum, a substring for text.
155 156 157 158 159 160 161 162 163 164 165 |
# File 'lib/crud_components/fields/base.rb', line 155 def apply_filter(scope, value: nil, geq: nil, leq: nil) if typed_filter typed_filter.apply(scope, value:, geq:, leq:) elsif filter_facet return scope unless value apply_filter_facet(scope, value) else apply_derived_filter(scope, value:, geq:, leq:) end end |
#apply_filter_facet(scope, value) ⇒ Object
167 168 169 170 171 172 173 174 |
# File 'lib/crud_components/fields/base.rb', line 167 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
An explicit ?sort= must win over any order a prior stage set (a search
backend's relevance rank, a default scope). The Symbol/derived branches
already .reorder; a Proc facet is handed a scope whose prior order is
cleared, so a block using the obvious .order(...) overrides the rank too
rather than appending to it (see issue #23). A block may still .reorder
itself — that composes fine on an already-cleared scope.
203 204 205 206 207 208 209 |
# File 'lib/crud_components/fields/base.rb', line 203 def apply_sort(scope, dir) case (facet = sort_facet) when Proc then facet.call(scope.reorder(nil), 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 |
#column ⇒ Object
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.
39 |
# File 'lib/crud_components/fields/base.rb', line 39 def custom_header? = !header.nil? || header_actions.any? |
#declared_preloads ⇒ Object
The preload: option as an array of includes-specs (a nested hash kept
intact, unlike Array()).
271 272 273 274 275 276 277 |
# File 'lib/crud_components/fields/base.rb', line 271 def declared_preloads case (p = [:preload]) when nil then [] when Array then p else [p] end end |
#default_editable? ⇒ Boolean
226 227 228 |
# File 'lib/crud_components/fields/base.rb', line 226 def default_editable? false end |
#default_renderer ⇒ Object
87 88 89 |
# File 'lib/crud_components/fields/base.rb', line 87 def default_renderer :string end |
#derived_filter_control ⇒ Object
140 141 142 |
# File 'lib/crud_components/fields/base.rb', line 140 def derived_filter_control :text end |
#derived_filterable? ⇒ Boolean
128 129 130 |
# File 'lib/crud_components/fields/base.rb', line 128 def derived_filterable? false end |
#derived_sortable? ⇒ Boolean
193 194 195 |
# File 'lib/crud_components/fields/base.rb', line 193 def derived_sortable? false end |
#eager_load ⇒ Object
── 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.
265 266 267 |
# File 'lib/crud_components/fields/base.rb', line 265 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?).
218 219 220 221 222 223 224 |
# File 'lib/crud_components/fields/base.rb', line 218 def editable? case [:editable] when false then false when nil then default_editable? else true end end |
#editable_permitted?(context, record = nil) ⇒ Boolean
230 231 232 233 234 235 236 237 238 |
# File 'lib/crud_components/fields/base.rb', line 230 def editable_permitted?(context, record = nil) condition = [: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
144 145 146 |
# File 'lib/crud_components/fields/base.rb', line 144 def filter_choices(query = nil) typed_filter&.filter_choices(query) end |
#filter_control ⇒ Object
Which filter control partial to render: :text, :select, :boolean, :number_range or :date_range.
134 135 136 137 138 |
# File 'lib/crud_components/fields/base.rb', line 134 def filter_control return typed_filter.control if typed_filter filter_facet ? :text : derived_filter_control end |
#filter_facet ⇒ Object
113 114 115 116 |
# File 'lib/crud_components/fields/base.rb', line 113 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.
74 75 76 |
# File 'lib/crud_components/fields/base.rb', line 74 def filter_includes_null? false end |
#filterable? ⇒ Boolean
── filtering ────────────────────────────────────────────────────────
105 106 107 108 109 110 111 |
# File 'lib/crud_components/fields/base.rb', line 105 def filterable? return false if facets[:filter] == false return false if CrudComponents::RESERVED_PARAMS.include?(name.to_s) return true if typed_filter || filter_facet derived_filterable? end |
#form_control ⇒ Object
The form-input flavor; nil = no form representation (json, computed).
241 242 243 |
# File 'lib/crud_components/fields/base.rb', line 241 def form_control nil end |
#form_partial ⇒ Object
The form-input partial to render: crud_components/form_fields/_form_as: (mirrors as: for the read-only/display renderer). The
partial receives the simple_form builder f, the field, and form.
249 250 251 |
# File 'lib/crud_components/fields/base.rb', line 249 def form_partial [:form_as] || form_control end |
#group_label ⇒ Object
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_model ⇒ Object
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 |
#header ⇒ Object
── 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 = [:header] |
#header_actions ⇒ Object
35 |
# File 'lib/crud_components/fields/base.rb', line 35 def header_actions = Array([:header_actions]) |
#human_name ⇒ Object
20 21 22 23 24 |
# File 'lib/crud_components/fields/base.rb', line 20 def human_name return [:label] if [: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.
69 70 71 |
# File 'lib/crud_components/fields/base.rb', line 69 def nullable? !!column&.null end |
#permit_param ⇒ Object
What this field contributes to a strong-params permit list — a symbol or a nested hash; collected by Structure#permitted_params.
255 256 257 |
# File 'lib/crud_components/fields/base.rb', line 255 def permit_param name end |
#permitted?(context, record = nil) ⇒ Boolean
── permissions ──────────────────────────────────────────────────────
100 101 102 |
# File 'lib/crud_components/fields/base.rb', line 100 def permitted?(context, record = nil) Permission.permitted?([:if], model, context, record) end |
#picker_label ⇒ Object
45 |
# File 'lib/crud_components/fields/base.rb', line 45 def picker_label = human_name |
#range_filter? ⇒ Boolean
148 149 150 |
# File 'lib/crud_components/fields/base.rb', line 148 def range_filter? filter_control == :number_range || filter_control == :date_range end |
#render_block ⇒ Object
91 92 93 |
# File 'lib/crud_components/fields/base.rb', line 91 def render_block facets[:render] end |
#renderer(_record = nil) ⇒ Object
── rendering ────────────────────────────────────────────────────────
83 84 85 |
# File 'lib/crud_components/fields/base.rb', line 83 def renderer(_record = nil) [:as] || default_renderer end |
#renderer_options ⇒ Object
95 96 97 |
# File 'lib/crud_components/fields/base.rb', line 95 def .except(:as, :if, :form_as, :label, :header, :header_actions, :filter_as, :filter_choices) end |
#search_spec_entry ⇒ Object
How this field feeds the default ?q= ("search what you see"): its own string/text column, or nil for fields with no free-text column behind them (numbers, dates, enums, attachments, computed). Associations override this to contribute their target's label.
63 64 65 |
# File 'lib/crud_components/fields/base.rb', line 63 def search_spec_entry name if column && %i[string text].include?(column.type) end |
#sort_facet ⇒ Object
189 190 191 |
# File 'lib/crud_components/fields/base.rb', line 189 def sort_facet facets[:sort].is_a?(Proc) || facets[:sort].is_a?(Symbol) ? facets[:sort] : nil end |
#sortable? ⇒ Boolean
── sorting ──────────────────────────────────────────────────────────
181 182 183 184 185 186 187 |
# File 'lib/crud_components/fields/base.rb', line 181 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 |
#typed_filter ⇒ Object
The internal TypedFilter for a filter: block that declares keyword params
(eq:/geq:/leq:/contains:) — its value type comes from filter_as: or as:,
so it renders the matching control and receives cast values. A positional
->(scope, value) block has none (plain text filter); nil for everyone else.
122 123 124 125 126 |
# File 'lib/crud_components/fields/base.rb', line 122 def typed_filter return @typed_filter if defined?(@typed_filter) @typed_filter = build_typed_filter end |
#value(record) ⇒ Object
78 79 80 |
# File 'lib/crud_components/fields/base.rb', line 78 def value(record) record.public_send(name) end |