Class: Hibiki::Rails::Generators::ModelIntrospection

Inherits:
Object
  • Object
show all
Defined in:
lib/generators/hibiki/rails/scaffold_schema.rb

Overview

Reads a live ActiveRecord model into ScaffoldColumns.

Its own class because introspection is the path with real steps — ignoring the framework's own columns, pairing foreign keys back to their reflections, resolving a label column through the association, and reading validators. The attribute path is a one-line map by comparison, which is why only this half is worth naming.

Constant Summary collapse

LABEL_CANDIDATES =

What a belongs_to's label column is likely to be called, in preference order. Only reachable on this path: no argument list can state what column of another table a select should display.

%w[name title label email].freeze

Instance Method Summary collapse

Constructor Details

#initialize(model) ⇒ ModelIntrospection

Returns a new instance of ModelIntrospection.



212
213
214
215
216
217
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 212

def initialize(model)
  @model = model
  belongs_tos = model.reflect_on_all_associations(:belongs_to)
  @polymorphic = belongs_tos.select(&:polymorphic?)
  @by_foreign_key = (belongs_tos - @polymorphic).index_by { it.foreign_key.to_s }
end

Instance Method Details

#column_for(attr) ⇒ Object

The introspected column an argument names, or nil when the model has no such column.

A reference is matched by its ASSOCIATION name first, because that is how a belongs_to is declared (author:references, never author_id:references) and because belongs_to :author, foreign_key: :writer_id is precisely the case where the association and its column disagree — the argument can only name the association, and only the reflection knows the column.



270
271
272
273
274
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 270

def column_for(attr)
  return by_association[attr.name.to_sym] || by_name[attr.column_name.to_sym] if attr.reference?

  by_name[attr.column_name.to_sym]
end

#columnsObject



219
220
221
222
223
224
225
226
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 219

def columns
  @columns ||= @model.columns_hash.each_value.filter_map do |column|
    next if ignored.include?(column.name)

    reflection = @by_foreign_key[column.name]
    reflection ? reference_column(reflection) : scalar_column(column)
  end
end

#polymorphic?(attr) ⇒ Boolean

A polymorphic belongs_to is refused whether the ARGUMENT says so (imageable:references{polymorphic}) or only the model does. Without this the plain imageable:references spelling would match nothing — both its columns are ignored — and fall through to the argument's own answer, emitting a collection_select over a table that does not exist.

Returns:

  • (Boolean)


282
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 282

def polymorphic?(attr) = @polymorphic.any? { it.name.to_s == attr.name }

#skippedObject



228
229
230
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 228

def skipped
  @polymorphic.map { [it.name.to_s, ScaffoldSchema::REFUSALS[:polymorphic?]] }
end

#timestamps?Boolean

Returns:

  • (Boolean)


232
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 232

def timestamps? = @model.columns_hash.key?("created_at")

#unmirrored_unique_indexes(names) ⇒ Object

Unique indexes the model does not mirror with a validator of its own, as column-name lists.

A database constraint is not a validator, and ReactiveForm#commit can only mirror what the model checks — so a duplicate raises RecordNotUnique instead of returning false, which on the graph thread is a dev-log line and a form that quietly saves nothing.

Restricted to indexes at least one of whose columns this form actually writes: an index nothing here can violate is not this generator's business.

A CONDITIONAL uniqueness validator counts as mirrored, unlike everywhere else in this class. Elsewhere the question is "can this be checked before a round trip", where a condition the form cannot evaluate disqualifies it; here it is "does the model know about this constraint at all", and a deliberate if: is an answer.



251
252
253
254
255
256
257
258
259
# File 'lib/generators/hibiki/rails/scaffold_schema.rb', line 251

def unmirrored_unique_indexes(names)
  @model.connection.indexes(@model.table_name)
        .select { unmirrored?(it, names) }
        .map { it.columns.map(&:to_sym) }
rescue StandardError
  # Introspecting indexes is the one thing here that needs more than
  # the schema cache. A generator must not fail over a notice.
  []
end