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
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/kabk/relation_hydrator.rb', line 82 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 %i[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 70 71 72 73 74 75 76 77 78 79 80 |
# 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" && val.is_a?(String) # Handle JSON or comma-separated string values. val = begin JSON.parse(val) rescue StandardError val.split(",") end end val end.flatten.compact.uniq next if keys_to_load.empty? # Load relation map. # Example: 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 = if val.is_a?(Array) val else begin JSON.parse(val.to_s) rescue StandardError val.to_s.split(",") end end rh[display_key] = arr.map { |v| target_map[v] }.compact if arr elsif val rh[display_key] = target_map[val] end end end # Format dates to ISO8601 per standard protocol. format_dates!(resource, record_hashes) record_hashes end |