Class: Hibiki::Rails::Generators::ScaffoldSchema
- Inherits:
-
Object
- Object
- Hibiki::Rails::Generators::ScaffoldSchema
- Defined in:
- lib/generators/hibiki/rails/scaffold_schema.rb
Overview
The column list a scaffold generator hands its templates, plus every list derived from it.
Two builders, because a scaffold has two genuinely different sources of truth and neither covers both commands:
from_attributes `hibiki:rails:scaffold Book title:string` — the
migration has NOT run when the controller generator
is invoked, so the schema does not exist yet.
from_model `hibiki:rails:scaffold_controller Book` with no
field list — the only source is the real schema.
Explicit attributes always win, matching Rails' own precedence.
But ORDER and FACTS are separate questions, and only order has to come
from the arguments. from_attributes takes a model: for the case
where both sources exist — scaffold_controller Book title:string ...
against a migrated app, which is exactly what the field-order notice
recommends — and reads every fact from the schema while the argument
list decides the sequence. Without it, taking that advice silently cost
the validators behind live_errors, a number field's min/max, and the
label column a belongs_to's select displays.
Constant Summary collapse
- TIMESTAMPS =
%w[created_at updated_at].freeze
- REFUSALS =
Attribute shapes that cannot be a ReactiveForm signal, a Data member or a collection_select. They are refused out loud rather than emitted half-working: a silently dropped column is the §10 failure class, where everything looks generated and one field is quietly missing.
{ rich_text?: "rich text needs its own editor and cannot be a form signal", attachment?: "Active Storage attachments are not form signals", attachments?: "Active Storage attachments are not form signals", password_digest?: "a password digest must not travel over a channel", token?: "a token must not travel over a channel", polymorphic?: "a polymorphic belongs_to has no single collection to select from" }.freeze
Instance Attribute Summary collapse
-
#columns ⇒ Object
readonly
Returns the value of attribute columns.
-
#skipped ⇒ Object
readonly
Returns the value of attribute skipped.
-
#unmatched ⇒ Object
readonly
Returns the value of attribute unmatched.
Class Method Summary collapse
-
.from_attributes(attributes, timestamps: true, model: nil) ⇒ Object
modelis nil whenever there is nothing to read: no constant, or no table yet — which is EVERYhibiki:rails:scaffoldrun, since it invokes the controller generator before its own migration. - .from_model(model) ⇒ Object
- .refusal_for(attr) ⇒ Object
Instance Method Summary collapse
- #attribute_names ⇒ Object
- #belongs_tos ⇒ Object
- #booleans ⇒ Object
- #empty? ⇒ Boolean
- #filterable ⇒ Object
-
#initialize(columns:, skipped: [], unmatched: [], introspection: nil, timestamps: true, introspected: false, schema_ordered: false) ⇒ ScaffoldSchema
constructor
A new instance of ScaffoldSchema.
-
#introspected? ⇒ Boolean
Two independent facts, and the merge is the case that separates them:
introspected?says the FACTS came from a live model, whileschema_ordered?says the schema also decided the ORDER — which is the only thing the field-order notice has anything to say about. - #live_errors ⇒ Object
-
#primary_boolean ⇒ Object
One grouped COUNT covers both numbers, so the counts sentence gets a boolean breakdown for free.
-
#row_members ⇒ Object
A belongs_to contributes two members: the foreign key the form binds to, and the label the row partial prints.
- #schema_ordered? ⇒ Boolean
- #searchable ⇒ Object
- #searchable? ⇒ Boolean
-
#sortable ⇒ Object
:id first because it is the default sort and always exists; :created_at last because "newest" is the sort a reader reaches for that no declared column provides.
- #timestamps? ⇒ Boolean
-
#unmirrored_uniqueness ⇒ Object
§5.15: unique indexes with no validator mirroring them, as column-name lists.
Constructor Details
#initialize(columns:, skipped: [], unmatched: [], introspection: nil, timestamps: true, introspected: false, schema_ordered: false) ⇒ ScaffoldSchema
Returns a new instance of ScaffoldSchema.
477 478 479 480 481 482 483 484 485 486 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 477 def initialize(columns:, skipped: [], unmatched: [], introspection: nil, timestamps: true, introspected: false, schema_ordered: false) @columns = columns @skipped = skipped @unmatched = unmatched @introspection = introspection @timestamps = @introspected = introspected @schema_ordered = schema_ordered end |
Instance Attribute Details
#columns ⇒ Object (readonly)
Returns the value of attribute columns.
404 405 406 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 404 def columns @columns end |
#skipped ⇒ Object (readonly)
Returns the value of attribute skipped.
404 405 406 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 404 def skipped @skipped end |
#unmatched ⇒ Object (readonly)
Returns the value of attribute unmatched.
404 405 406 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 404 def unmatched @unmatched end |
Class Method Details
.from_attributes(attributes, timestamps: true, model: nil) ⇒ Object
model is nil whenever there is nothing to read: no constant, or
no table yet — which is EVERY hibiki:rails:scaffold run, since it
invokes the controller generator before its own migration. That
case is this method's original behaviour, unchanged.
413 414 415 416 417 418 419 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 413 def from_attributes(attributes, timestamps: true, model: nil) introspection = ModelIntrospection.new(model) if model supported, refused = attributes.partition { refusal_for(it).nil? } return from_arguments_only(supported, refused, ) unless introspection merge(supported, refused, introspection) end |
.from_model(model) ⇒ Object
421 422 423 424 425 426 427 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 421 def from_model(model) introspection = ModelIntrospection.new(model) new(columns: introspection.columns, skipped: introspection.skipped, introspection: introspection, timestamps: introspection., introspected: true, schema_ordered: true) end |
.refusal_for(attr) ⇒ Object
407 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 407 def refusal_for(attr) = REFUSALS.find { |query, _| attr.public_send(query) }&.last |
Instance Method Details
#attribute_names ⇒ Object
501 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 501 def attribute_names = columns.map(&:name) |
#belongs_tos ⇒ Object
499 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 499 def belongs_tos = columns.select(&:belongs_to?) |
#booleans ⇒ Object
500 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 500 def booleans = columns.select(&:filterable?) |
#empty? ⇒ Boolean
495 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 495 def empty? = columns.empty? |
#filterable ⇒ Object
498 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 498 def filterable = columns.select(&:filterable?).map(&:name) |
#introspected? ⇒ Boolean
Two independent facts, and the merge is the case that separates them:
introspected? says the FACTS came from a live model, while
schema_ordered? says the schema also decided the ORDER — which is
the only thing the field-order notice has anything to say about.
492 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 492 def introspected? = @introspected |
#live_errors ⇒ Object
524 525 526 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 524 def live_errors columns.filter_map { |column| [column.name, column.live_error_clause] if column.live_error_clause } end |
#primary_boolean ⇒ Object
One grouped COUNT covers both numbers, so the counts sentence gets a boolean breakdown for free. With no boolean column there is nothing to group by and the sentence loses its trailing clause.
516 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 516 def primary_boolean = booleans.first |
#row_members ⇒ Object
A belongs_to contributes two members: the foreign key the form binds to, and the label the row partial prints.
520 521 522 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 520 def row_members [:id, *columns.flat_map { it.belongs_to? ? [it.name, it.display_reader] : [it.name] }] end |
#schema_ordered? ⇒ Boolean
493 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 493 def schema_ordered? = @schema_ordered |
#searchable ⇒ Object
497 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 497 def searchable = columns.select(&:searchable?).map(&:name) |
#searchable? ⇒ Boolean
502 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 502 def searchable? = searchable.any? |
#sortable ⇒ Object
:id first because it is the default sort and always exists; :created_at last because "newest" is the sort a reader reaches for that no declared column provides. :updated_at is deliberately absent — it moves on every write, so ordering by it makes the list reshuffle under the reader.
509 510 511 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 509 def sortable [:id, *columns.select(&:sortable?).map(&:name), *(:created_at if )] end |
#timestamps? ⇒ Boolean
494 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 494 def = @timestamps |
#unmirrored_uniqueness ⇒ Object
§5.15: unique indexes with no validator mirroring them, as column-name
lists. The model is authoritative wherever there is one — it knows
composite indexes, which no argument list can express — and
title:string:uniq is the fallback for the path with no model to ask.
Read off the attribute rather than the column for the same reason
refusal_for is: it is a question about what was TYPED.
534 535 536 537 |
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 534 def unmirrored_uniqueness @unmirrored_uniqueness ||= @introspection&.unmirrored_unique_indexes(columns.map(&:name)) || columns.select { it.attr.has_uniq_index? }.map { [it.name] } end |