Class: Kabk::RelationHydrator
- Inherits:
-
Object
- Object
- Kabk::RelationHydrator
- Defined in:
- lib/kabk/relation_hydrator.rb
Overview
Hydrates <field>_display for relation fields to prevent N+1 queries.
Class Method Summary collapse
- .format_dates!(resource, record_hashes) ⇒ Object
-
.hydrate(resource, records) ⇒ Array<Hash>
Hydrates a collection of records in-memory or via SQL joins/eager loading.
Class Method Details
.format_dates!(resource, record_hashes) ⇒ Object
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 |
# File 'lib/kabk/relation_hydrator.rb', line 71 def self.format_dates!(resource, record_hashes) date_fields = resource.fields.select { |f| %w[date datetime].include?(f.type) }.map(&:name).map(&:to_sym) record_hashes.each do |rh| # Include standard audit/concurrency fields if present and not explicitly defined as fields [:created_at, :updated_at].each do |ts| if rh[ts].respond_to?(:iso8601) rh[ts] = rh[ts].iso8601 elsif rh[ts].is_a?(Time) || rh[ts].is_a?(Date) || rh[ts].is_a?(DateTime) # Fallback formatting if it responds to strftime rh[ts] = rh[ts].strftime("%Y-%m-%dT%H:%M:%S.%LZ") end end date_fields.each do |df| val = rh[df] if val.respond_to?(:iso8601) rh[df] = val.iso8601 elsif val.is_a?(Time) || val.is_a?(Date) || val.is_a?(DateTime) rh[df] = val.strftime("%Y-%m-%dT%H:%M:%S.%LZ") end end end end |
.hydrate(resource, records) ⇒ Array<Hash>
Hydrates a collection of records in-memory or via SQL joins/eager loading
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/kabk/relation_hydrator.rb', line 12 def self.hydrate(resource, records) return [] if records.empty? # Convert all records to hashes immediately if they are models record_hashes = records.map { |r| r.is_a?(Hash) ? r.dup : r.values } relation_fields = resource.fields.select { |f| f.type == "relation" && f.relation } relation_fields.each do |field| = field.relation target_resource = Registry.instance.get(["resource"] || [:resource]) next unless target_resource target_model = target_resource.model_class value_field = (["value_field"] || [:value_field]).to_sym label_field = (["label_field"] || [:label_field]).to_sym display_key = (["display_key"] || [:display_key] || "#{field.name}_display").to_sym cardinality = ["cardinality"] || [:cardinality] || "many_to_one" field_sym = field.name.to_sym # Extract all keys to load keys_to_load = record_hashes.flat_map do |rh| val = rh[field_sym] if val.is_a?(String) && cardinality == "many_to_many" # In case it's stored as JSON or comma-separated # Attempt to parse if it looks like JSON array (SQLite) val = JSON.parse(val) rescue val.split(",") if val.is_a?(String) end val end.flatten.compact.uniq next if keys_to_load.empty? # Load relation map # E.g. SELECT id, full_name FROM users WHERE id IN (...) target_data = target_model.where(value_field => keys_to_load).select(value_field, label_field).all target_map = target_data.each_with_object({}) do |row, map| map[row[value_field]] = row[label_field] map[row[value_field].to_s] = row[label_field] # allow string keys too end # Map back to records record_hashes.each do |rh| val = rh[field_sym] if cardinality == "many_to_many" arr = val.is_a?(Array) ? val : (JSON.parse(val.to_s) rescue val.to_s.split(",")) rh[display_key] = arr.map { |v| target_map[v] }.compact if arr else rh[display_key] = target_map[val] if val end end end # Finally, ensure all dates are ISO8601 formatted per spec format_dates!(resource, record_hashes) record_hashes end |