forest_admin_datasource_active_record 1.35.0 → 1.35.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 04e8352c5b26a43ce466ea857586a38f235d32b38b252c4c37e51bef43e2f4f4
4
- data.tar.gz: ffcfe8fd892725000f743ac15323015a35d06238680fb80b2c2467ee45b16afc
3
+ metadata.gz: df0d3da32f3e5533efcd50c768ed24a103f7b442e956b48c7a359c07b3d3c721
4
+ data.tar.gz: 1af868e9ed17dc80de89648eb262c9fe960a2e35af5aa2515d7d802bd129557f
5
5
  SHA512:
6
- metadata.gz: c8995e225a4300c5a25737a74715c35ddf991c4fdf8bef82722d25b89836cd2e2a1e42c35aabf905aa3bf538413e6e164e18f12f0effcb6a4211bfd2390c0c09
7
- data.tar.gz: ad3209330ba9e93b06d98fafc26648b06eda6f80309f5b3373d5ff2c0fe28609430c63d9a3f93d75500edde838bae1fc476d43a2cca6009ac2868c9a86b0f1a6
6
+ metadata.gz: 415b589ca9918863682926543313e9c2917baf7736eb4333e876c9aafa9873580ddc6b4c15e9e9c64e7fdefbcea2a4617d9b9212479652d75d1855a277b82615
7
+ data.tar.gz: 7acfbd16b68262aca27a07e52cf2c76d1145f6e0fffc53d923a11a295475c171014d81c615469f6f738f79752475c7475a1fdc5cd36d16595b9beeb92ad58848
@@ -13,6 +13,7 @@ module ForestAdminDatasourceActiveRecord
13
13
  # root keeps all its selected columns (attributes + FKs); a related record is restricted to
14
14
  # its projected columns, matching the JOINed hydration
15
15
  hash = path.empty? || projection.nil? ? base_attributes(object) : projected_columns(object, projection)
16
+ hash = normalize_polymorphic_types(object.class, hash)
16
17
 
17
18
  serialize_associations(object, projection, hash, path) if projection
18
19
 
@@ -65,6 +66,7 @@ module ForestAdminDatasourceActiveRecord
65
66
 
66
67
  hash = {}
67
68
  projection.columns.each { |column| hash[column] = object[meta[:columns][column]] }
69
+ hash = normalize_polymorphic_types(target_model(relation_path), hash)
68
70
  projection.relations.each_key do |nested_name|
69
71
  hash[nested_name] = hash_joined_relation(projection.relations[nested_name], relation_path + [nested_name])
70
72
  end
@@ -72,8 +74,43 @@ module ForestAdminDatasourceActiveRecord
72
74
  hash
73
75
  end
74
76
 
77
+ def normalize_polymorphic_types(model_class, hash)
78
+ return hash if model_class.nil?
79
+
80
+ polymorphic_belongs_to(model_class).each do |association|
81
+ stored = hash[association.foreign_type]
82
+ next if stored.nil?
83
+
84
+ hash = hash.merge(association.foreign_type => model_class.polymorphic_class_for(stored).name)
85
+ rescue NameError => e
86
+ warn_unable(association.name, model_class, e)
87
+ end
88
+ hash
89
+ end
90
+
91
+ # Target model of a JOINed relation path (only belongs_to / has_one :through are ever JOINed).
92
+ def target_model(relation_path)
93
+ relation_path.reduce(object.class) do |model, name|
94
+ model&.reflect_on_association(name.to_sym)&.klass
95
+ end
96
+ rescue NameError
97
+ nil
98
+ end
99
+
75
100
  private
76
101
 
102
+ def polymorphic_belongs_to(model_class)
103
+ (@polymorphic_belongs_to ||= {})[model_class] ||=
104
+ model_class.reflect_on_all_associations(:belongs_to).select(&:polymorphic?)
105
+ end
106
+
107
+ def warn_unable(name, model_class, error)
108
+ ActiveSupport::Logger.new($stdout).warn(
109
+ "[ForestAdmin] Unable to normalize polymorphic type of '#{name}' " \
110
+ "in model '#{model_class.name}': #{error.message}. Keeping the stored value."
111
+ )
112
+ end
113
+
77
114
  def joined_relation?(relation_path)
78
115
  !joined_relations.nil? && joined_relations.key?(relation_path.join('.'))
79
116
  end
@@ -229,12 +229,13 @@ module ForestAdminDatasourceActiveRecord
229
229
  def split_relations
230
230
  join_tree = {}
231
231
  preload_tree = {}
232
- used_tables = Set[@collection.model.table_name] | @filter_joined_tables
232
+ used_joins = { @collection.model.table_name => :root }
233
+ @filter_joined_tables.each { |table| used_joins[table] ||= :filter } # never reuse a filter/sort join
233
234
 
234
235
  @projection.relations.each do |relation_name, sub_projection|
235
- tables = joinable_tables(@collection, relation_name, sub_projection, used_tables)
236
- if tables
237
- used_tables |= tables
236
+ joins = joinable_joins(@collection, relation_name, sub_projection, used_joins)
237
+ if joins
238
+ used_joins.merge!(joins)
238
239
  join_tree[relation_name.to_sym] = format_relation_projection(sub_projection)
239
240
  collect_joined_selects(@collection, relation_name, sub_projection, [relation_name])
240
241
  else
@@ -275,22 +276,28 @@ module ForestAdminDatasourceActiveRecord
275
276
  end
276
277
 
277
278
  # Set of tables the subtree adds via JOIN, or nil if any relation in it can't be safely joined.
278
- def joinable_tables(collection, relation_name, sub_projection, used_tables)
279
- target = joinable_target(collection, relation_name, used_tables)
279
+ def joinable_joins(collection, relation_name, sub_projection, used_joins)
280
+ target = joinable_target(collection, relation_name)
280
281
  return nil if target.nil?
281
282
 
282
- tables = Set[target.model.table_name] | through_tables(collection, relation_name)
283
+ joins = join_signatures(collection, relation_name)
284
+ return nil if joins.nil? || conflicting?(joins, used_joins)
285
+
283
286
  sub_projection.relations.each do |nested_name, nested_projection|
284
- nested = joinable_tables(target, nested_name, nested_projection, used_tables | tables)
287
+ nested = joinable_joins(target, nested_name, nested_projection, used_joins.merge(joins))
285
288
  return nil if nested.nil?
286
289
 
287
- tables |= nested
290
+ joins = joins.merge(nested)
288
291
  end
289
- tables
292
+ joins
293
+ end
294
+
295
+ def conflicting?(new_joins, used_joins)
296
+ new_joins.any? { |table, signature| used_joins.key?(table) && used_joins[table] != signature }
290
297
  end
291
298
 
292
299
  # The target collection when this hop is safe to collapse into a JOIN, else nil (-> preload).
293
- def joinable_target(collection, relation_name, used_tables)
300
+ def joinable_target(collection, relation_name)
294
301
  relation_schema = collection.schema[:fields][relation_name]
295
302
  return unless relation_schema.respond_to?(:foreign_collection)
296
303
 
@@ -308,19 +315,29 @@ module ForestAdminDatasourceActiveRecord
308
315
  target = local_ar_collection(collection.datasource, relation_schema.foreign_collection)
309
316
  return if target.nil? || !target.model.default_scopes.empty? # same risk as a scoped association
310
317
  return unless same_database?(collection.model, target.model)
311
- return if used_tables.include?(target.model.table_name) # a table joined twice would be aliased by AR
312
- return if through_tables(collection, relation_name).intersect?(used_tables)
313
318
 
314
319
  target
315
320
  end
316
321
 
317
- def through_tables(collection, relation_name)
318
- through = collection.model.reflect_on_association(relation_name.to_sym)&.through_reflection
319
- return Set[] unless through
320
-
321
- Set[through.table_name]
322
+ def join_signatures(collection, relation_name)
323
+ reflection = collection.model.reflect_on_association(relation_name.to_sym)
324
+ if reflection.through_reflection?
325
+ { reflection.through_reflection.klass.table_name => signature(reflection.through_reflection),
326
+ reflection.klass.table_name => signature(reflection.source_reflection) }
327
+ else
328
+ { reflection.klass.table_name => signature(reflection) }
329
+ end
322
330
  rescue StandardError
323
- Set[]
331
+ nil
332
+ end
333
+
334
+ def signature(reflection)
335
+ "#{reflection.active_record.table_name}.#{Array(reflection.foreign_key).join(",")}" \
336
+ "->#{reflection.klass.table_name}.#{Array(join_key(reflection)).join(",")}"
337
+ end
338
+
339
+ def join_key(reflection)
340
+ reflection.respond_to?(:join_primary_key) ? reflection.join_primary_key : reflection.association_primary_key
324
341
  end
325
342
 
326
343
  def belongs_to_chain_through?(reflection)
@@ -1,3 +1,3 @@
1
1
  module ForestAdminDatasourceActiveRecord
2
- VERSION = "1.35.0"
2
+ VERSION = "1.35.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_datasource_active_record
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.35.0
4
+ version: 1.35.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthieu