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, exact: nil, geq: nil, leq: nil) ⇒ Object
‘exact`, `geq`, `leq` are the raw param values (Strings or nil).
- #apply_filter_facet(scope, value) ⇒ Object
- #apply_sort(scope, dir) ⇒ Object
-
#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/_<name>.
-
#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.name` and `publisher.founded_on` all sit under “Publisher”.
-
#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.
- #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
- #sort_facet ⇒ Object
-
#sortable? ⇒ Boolean
── sorting ──────────────────────────────────────────────────────────.
- #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
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 |
#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()).
241 242 243 244 245 246 247 |
# File 'lib/crud_components/fields/base.rb', line 241 def declared_preloads case (p = [:preload]) when nil then [] when Array then p else [p] end end |
#default_editable? ⇒ Boolean
196 197 198 |
# File 'lib/crud_components/fields/base.rb', line 196 def default_editable? false end |
#default_renderer ⇒ Object
79 80 81 |
# File 'lib/crud_components/fields/base.rb', line 79 def default_renderer :string end |
#derived_filter_control ⇒ Object
120 121 122 |
# File 'lib/crud_components/fields/base.rb', line 120 def derived_filter_control :text end |
#derived_filterable? ⇒ Boolean
110 111 112 |
# File 'lib/crud_components/fields/base.rb', line 110 def derived_filterable? false end |
#derived_sortable? ⇒ Boolean
169 170 171 |
# File 'lib/crud_components/fields/base.rb', line 169 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.
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?).
188 189 190 191 192 193 194 |
# File 'lib/crud_components/fields/base.rb', line 188 def editable? case [:editable] when false then false when nil then default_editable? else true end end |
#editable_permitted?(context, record = nil) ⇒ 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 = [: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_control ⇒ Object
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_facet ⇒ Object
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.
66 67 68 |
# File 'lib/crud_components/fields/base.rb', line 66 def filter_includes_null? false end |
#filterable? ⇒ Boolean
── filtering ────────────────────────────────────────────────────────
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_control ⇒ Object
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_partial ⇒ Object
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 [: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.
61 62 63 |
# File 'lib/crud_components/fields/base.rb', line 61 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.
225 226 227 |
# File 'lib/crud_components/fields/base.rb', line 225 def permit_param name end |
#permitted?(context, record = nil) ⇒ Boolean
── permissions ──────────────────────────────────────────────────────
92 93 94 |
# File 'lib/crud_components/fields/base.rb', line 92 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
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_block ⇒ Object
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) [:as] || default_renderer end |
#renderer_options ⇒ Object
87 88 89 |
# File 'lib/crud_components/fields/base.rb', line 87 def .except(:as, :if, :form_as, :label, :header, :header_actions) end |
#sort_facet ⇒ Object
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 ──────────────────────────────────────────────────────────
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 |