Module: InlineForms::SchemaPreview
- Defined in:
- lib/inline_forms/schema_preview.rb
Overview
Cheap preview of a SchemaIntent, WITHOUT running a migration or booting a second app. The trick (see the staging doc): declare the proposed attribute as a virtual typed attribute on a throwaway subclass of the target model, so it reads/writes in memory with no column, and render the stock inline forms against an AttributeList that has the proposed row spliced in at the requested position. The real class and its table are untouched.
obj, list = InlineForms::SchemaPreview.build(intent, existing_record)
# render "inline_forms/show" with @object = obj,
# @inline_forms_attribute_list = list (the controller already supports
# this per-request override — see attr_writer :inline_forms_attribute_list)
Scope of the stab: scalar attributes preview fully. Relation dropdowns (backed by a foreign key + association) and uploader/rich_text fields need model-side wiring the preview does not synthesize; #supported? reports this so a caller can fall back to a "available after apply" placeholder.
Constant Summary collapse
- COLUMN_TYPE_TO_VIRTUAL =
Map an AR column type (from the engine's form-element maps) to an ActiveModel::Type symbol for a virtual attribute.
{ string: :string, text: :string, integer: :integer, decimal: :decimal, float: :float, boolean: :boolean, date: :date, time: :time, datetime: :datetime, timestamp: :datetime }.freeze
- PREVIEW_UNSUPPORTED_FORM_ELEMENTS =
Form elements the GUI must not offer as "add a scalar field":
- :header / :info are not real fields — :header is a display-only separator (adding one via addto would create a pointless string column), :info is read-only over an existing column.
- uploaders need a mounted CarrierWave uploader; :money_field a monetize declaration; :devise_password_field a Devise model; :pdf_link a route — a virtual attribute alone cannot faithfully preview them.
%i[ header info image_field audio_field file_field multi_image_field simple_file_field pdf_link money_field devise_password_field ].freeze
Class Method Summary collapse
-
.attribute_list_for(base_class, intent) ⇒ Object
The AttributeList the preview should render: the model's current list with the proposed row inserted at --after/--before (falling back to append).
-
.build(intent, base_record = nil) ⇒ Object
Returns [preview_object, attribute_list].
-
.preview_class_for(base_class, intent) ⇒ Object
A throwaway subclass with the proposed attribute declared virtual (when supported), masquerading as the base class so partials/routes resolve.
-
.supported?(intent) ⇒ Boolean
False when the intent's form element needs model-side wiring the preview cannot synthesize (relations, uploaders, rich_text).
-
.supported_form_elements ⇒ Object
The sorted list of form elements the GUI offers (those that preview via a virtual attribute): scalar text/number/date/boolean/choice elements.
- .virtual_type(form_element) ⇒ Object
Class Method Details
.attribute_list_for(base_class, intent) ⇒ Object
The AttributeList the preview should render: the model's current list with the proposed row inserted at --after/--before (falling back to append).
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/inline_forms/schema_preview.rb', line 87 def attribute_list_for(base_class, intent) current = base_class.new.inline_forms_attribute_list list = InlineForms::AttributeList.wrap(current.map(&:dup)) opts = { values: intent.values, disabled: intent.disabled }.compact # Value-bearing choice elements need a values hash or their _edit/_show # helper raises. Mirror the addto generator: inject a placeholder the user # edits after apply (so preview and apply produce the same row). if opts[:values].nil? && InlineForms::VALUE_BEARING_FORM_ELEMENTS.include?(intent.form_element) opts[:values] = InlineForms::PLACEHOLDER_VALUES end if intent.after && list.include_attribute?(intent.after) list.insert_after(intent.after, intent.attribute, intent.form_element, **opts) elsif intent.before && list.include_attribute?(intent.before) list.insert_before(intent.before, intent.attribute, intent.form_element, **opts) else list.field(intent.attribute, intent.form_element, **opts) end list end |
.build(intent, base_record = nil) ⇒ Object
Returns [preview_object, attribute_list]. base_record seeds the preview
object's existing attributes when given (so the rest of the form shows
real data); otherwise a blank instance is used.
61 62 63 64 65 66 67 68 69 |
# File 'lib/inline_forms/schema_preview.rb', line 61 def build(intent, base_record = nil) base_class = intent.model_class list = attribute_list_for(base_class, intent) preview = preview_class_for(base_class, intent) object = base_record ? preview.new(base_record.attributes) : preview.new object.inline_forms_attribute_list = list [ object, list ] end |
.preview_class_for(base_class, intent) ⇒ Object
A throwaway subclass with the proposed attribute declared virtual (when supported), masquerading as the base class so partials/routes resolve.
111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/inline_forms/schema_preview.rb', line 111 def preview_class_for(base_class, intent) type = virtual_type(intent.form_element) klass = Class.new(base_class) klass.attribute(intent.attribute, type) if type base_name = base_class.name base_model_name = base_class.model_name klass.define_singleton_method(:name) { base_name } klass.define_singleton_method(:model_name) { base_model_name } klass end |
.supported?(intent) ⇒ Boolean
False when the intent's form element needs model-side wiring the preview cannot synthesize (relations, uploaders, rich_text).
73 74 75 |
# File 'lib/inline_forms/schema_preview.rb', line 73 def supported?(intent) !virtual_type(intent.form_element).nil? end |
.supported_form_elements ⇒ Object
The sorted list of form elements the GUI offers (those that preview via a virtual attribute): scalar text/number/date/boolean/choice elements.
79 80 81 82 83 |
# File 'lib/inline_forms/schema_preview.rb', line 79 def supported_form_elements candidates = InlineForms::SPECIAL_COLUMN_TYPES.keys + InlineForms::DEFAULT_FORM_ELEMENTS.values candidates.uniq.select { |fe| !virtual_type(fe).nil? }.sort end |
.virtual_type(form_element) ⇒ Object
123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/inline_forms/schema_preview.rb', line 123 def virtual_type(form_element) fe = form_element.to_sym return nil if PREVIEW_UNSUPPORTED_FORM_ELEMENTS.include?(fe) column_type = InlineForms::SPECIAL_COLUMN_TYPES[fe] || (InlineForms::DEFAULT_FORM_ELEMENTS.value?(fe) ? :string : nil) return nil if column_type.nil? || column_type == :no_migration || column_type == :belongs_to COLUMN_TYPE_TO_VIRTUAL[column_type] end |