forest_admin_datasource_active_record 1.45.1 → 1.45.2

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: fb2d630600116a2cc26631fa350213ec683295247c0633a8ead5e03ffa0222d5
4
- data.tar.gz: 6d8715c1b02954bea75edf654ab5d757ee3b3853778b45c4533bf4ef81be3075
3
+ metadata.gz: dc1197c09a580dd96b9ad3c9966560332df92ce4ffe97922e6d57967c883b4c9
4
+ data.tar.gz: 8e8de31895904e2c9bd2820f23148d2cc8e8357d4667fd97b55502ce0d56dc47
5
5
  SHA512:
6
- metadata.gz: 64fa06775c9f9366de9c6e3002c36803db9d94e5368b6be87d673cec289fa5d81489d424fc96017d59f35da28d61e8a83af4b9608bfd857a0354eba1a6f15f09
7
- data.tar.gz: 298f5e132565b05b3f460ed8c27b2145680145088c2534ec4cbcc31622fe6ba089514cc566fad82c9f731cff0628337b56f6aa2914f0376ab476279d5c60e711
6
+ metadata.gz: 45d04f5fbc9f9dcb16933f007a1fd3e240df3534f970cce7173c071bfe1e557f3e79d9f22f2e7860fb1a58b9298ef739218b3dd83ef5c117a1f3845268eae58b
7
+ data.tar.gz: 734ba95d343b8a85daadd567379f2428d3bb16a6950f3cdeaa751a9a40e7fba9756c27d7395a071450a171cecdee1443edb197b4fdc24b7913633576471aec47
@@ -39,6 +39,7 @@ admin work on any Ruby application."
39
39
  spec.add_dependency "bigdecimal"
40
40
  spec.add_dependency "cgi"
41
41
  spec.add_dependency "csv"
42
+ spec.add_dependency "forest_admin_datasource_toolkit", ">= #{ForestAdminDatasourceActiveRecord::VERSION}"
42
43
  spec.add_dependency "logger"
43
44
  spec.add_dependency "mutex_m"
44
45
  spec.add_dependency "ostruct"
@@ -28,7 +28,8 @@ module ForestAdminDatasourceActiveRecord
28
28
  query = Utils::Query.new(self, projection, filter)
29
29
  records = query.get
30
30
 
31
- records.map { |record| Utils::ActiveRecordSerializer.new(record, query.joined_relations).to_hash(projection) }
31
+ serializer = Utils::ActiveRecordSerializer.new(projection, query.joined_relations)
32
+ records.map { |record| serializer.serialize(record) }
32
33
  end
33
34
 
34
35
  def aggregate(_caller, filter, aggregation, limit = nil)
@@ -37,7 +38,7 @@ module ForestAdminDatasourceActiveRecord
37
38
 
38
39
  def create(_caller, data)
39
40
  Utils::ErrorHandler.handle_errors(:create) do
40
- Utils::ActiveRecordSerializer.new(@model.create!(data)).to_hash(ProjectionFactory.all(self))
41
+ Utils::ActiveRecordSerializer.new(ProjectionFactory.all(self)).serialize(@model.create!(data))
41
42
  end
42
43
  end
43
44
 
@@ -2,73 +2,85 @@ module ForestAdminDatasourceActiveRecord
2
2
  module Utils
3
3
  # Relations in `joined_relations` (see Query#collect_joined_selects) are hydrated from the flat
4
4
  # row's aliased columns; every other relation is read from its preloaded ActiveRecord association.
5
- ActiveRecordSerializer = Struct.new(:object, :joined_relations) do
6
- def to_hash(projection)
7
- hash_object(object, projection)
5
+ class ActiveRecordSerializer
6
+ ONE_ASSOCIATIONS = %i[has_one belongs_to].freeze
7
+ MANY_ASSOCIATIONS = %i[has_many has_and_belongs_to_many].freeze
8
+
9
+ def initialize(projection, joined_relations = nil)
10
+ @tree = ForestAdminDatasourceToolkit::Components::Query::ProjectionTree.build(projection || ForestAdminDatasourceToolkit::Components::Query::Projection.new)
11
+ @joined_relations = joined_relations || {}
12
+ @join_aliases = @joined_relations.values.flat_map { |meta| meta[:columns].values }.uniq
13
+ @polymorphic_belongs_to = {}
14
+ @target_models = {}
8
15
  end
9
16
 
10
- def hash_object(object, projection = nil, path: [])
17
+ def serialize(object)
18
+ hash_object(object, object, @tree)
19
+ end
20
+
21
+ def target_model(root_model, relation_path)
22
+ @target_models.fetch([root_model, relation_path]) do |key|
23
+ @target_models[key] = resolve_target_model(root_model, relation_path)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def hash_object(root, object, tree, path: [])
11
30
  return if object.nil?
12
31
 
13
32
  # root keeps all its selected columns (attributes + FKs); a related record is restricted to
14
33
  # its projected columns, matching the JOINed hydration
15
- hash = path.empty? || projection.nil? ? base_attributes(object) : projected_columns(object, projection)
34
+ hash = path.empty? ? base_attributes(object) : projected_columns(object, tree)
16
35
  hash = normalize_polymorphic_types(object.class, hash)
17
36
 
18
- serialize_associations(object, projection, hash, path) if projection
37
+ serialize_associations(root, object, tree, hash, path)
19
38
 
20
39
  hash
21
40
  end
22
41
 
23
42
  def base_attributes(object)
24
- return object.attributes if join_aliases.empty?
43
+ return object.attributes if @join_aliases.empty?
25
44
 
26
- object.attributes.except(*join_aliases)
45
+ object.attributes.except(*@join_aliases)
27
46
  end
28
47
 
29
- def projected_columns(object, projection)
30
- projection.columns.to_h { |column| [column, object[column]] }
48
+ def projected_columns(object, tree)
49
+ tree.columns.to_h { |column| [column, object[column]] }
31
50
  end
32
51
 
33
- def serialize_associations(object, projection, hash, path)
34
- one_associations = %i[has_one belongs_to]
35
- many_associations = %i[has_many has_and_belongs_to_many]
36
-
37
- projection.relations.each_key do |association_name|
52
+ def serialize_associations(root, object, tree, hash, path)
53
+ tree.relations.each do |association_name, sub_tree|
38
54
  relation_path = path + [association_name]
39
55
 
40
- if joined_relation?(relation_path)
41
- hash[association_name] = hash_joined_relation(projection.relations[association_name], relation_path)
56
+ if @joined_relations.key?(relation_path.join('.'))
57
+ hash[association_name] = hash_joined_relation(root, sub_tree, relation_path)
42
58
  next
43
59
  end
44
60
 
45
61
  association = object.class.reflect_on_association(association_name.to_sym)
46
62
  next if association.nil?
47
63
 
48
- if one_associations.include?(association.macro)
49
- hash[association_name] = hash_object(
50
- object.send(association_name),
51
- projection.relations[association_name],
52
- path: relation_path
53
- )
54
- elsif many_associations.include?(association.macro)
64
+ if ONE_ASSOCIATIONS.include?(association.macro)
65
+ hash[association_name] = hash_object(root, object.send(association_name), sub_tree, path: relation_path)
66
+ elsif MANY_ASSOCIATIONS.include?(association.macro)
55
67
  hash[association_name] = object.send(association_name).map do |item|
56
- hash_object(item, projection.relations[association_name], path: relation_path)
68
+ hash_object(root, item, sub_tree, path: relation_path)
57
69
  end
58
70
  end
59
71
  end
60
72
  end
61
73
 
62
74
  # Reads a JOINed relation's columns from the aliases on the root object (not a nested one).
63
- def hash_joined_relation(projection, relation_path)
64
- meta = joined_relations[relation_path.join('.')]
65
- return nil if object[meta[:pk_alias]].nil?
75
+ def hash_joined_relation(root, tree, relation_path)
76
+ meta = @joined_relations[relation_path.join('.')]
77
+ return nil if root[meta[:pk_alias]].nil?
66
78
 
67
79
  hash = {}
68
- projection.columns.each { |column| hash[column] = object[meta[:columns][column]] }
69
- hash = normalize_polymorphic_types(target_model(relation_path), hash)
70
- projection.relations.each_key do |nested_name|
71
- hash[nested_name] = hash_joined_relation(projection.relations[nested_name], relation_path + [nested_name])
80
+ tree.columns.each { |column| hash[column] = root[meta[:columns][column]] }
81
+ hash = normalize_polymorphic_types(target_model(root.class, relation_path), hash)
82
+ tree.relations.each do |nested_name, nested_tree|
83
+ hash[nested_name] = hash_joined_relation(root, nested_tree, relation_path + [nested_name])
72
84
  end
73
85
 
74
86
  hash
@@ -88,19 +100,16 @@ module ForestAdminDatasourceActiveRecord
88
100
  hash
89
101
  end
90
102
 
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|
103
+ def resolve_target_model(root_model, relation_path)
104
+ relation_path.reduce(root_model) do |model, name|
94
105
  model&.reflect_on_association(name.to_sym)&.klass
95
106
  end
96
107
  rescue NameError
97
108
  nil
98
109
  end
99
110
 
100
- private
101
-
102
111
  def polymorphic_belongs_to(model_class)
103
- (@polymorphic_belongs_to ||= {})[model_class] ||=
112
+ @polymorphic_belongs_to[model_class] ||=
104
113
  model_class.reflect_on_all_associations(:belongs_to).select(&:polymorphic?)
105
114
  end
106
115
 
@@ -110,14 +119,6 @@ module ForestAdminDatasourceActiveRecord
110
119
  "in model '#{model_class.name}': #{error.message}. Keeping the stored value."
111
120
  )
112
121
  end
113
-
114
- def joined_relation?(relation_path)
115
- !joined_relations.nil? && joined_relations.key?(relation_path.join('.'))
116
- end
117
-
118
- def join_aliases
119
- @join_aliases ||= (joined_relations || {}).values.flat_map { |meta| meta[:columns].values }.uniq
120
- end
121
122
  end
122
123
  end
123
124
  end
@@ -1,3 +1,3 @@
1
1
  module ForestAdminDatasourceActiveRecord
2
- VERSION = "1.45.1"
2
+ VERSION = "1.45.2"
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.45.1
4
+ version: 1.45.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthieu
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2026-10-10 00:00:00.000000000 Z
12
+ date: 2026-10-14 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
@@ -109,6 +109,20 @@ dependencies:
109
109
  - - ">="
110
110
  - !ruby/object:Gem::Version
111
111
  version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: forest_admin_datasource_toolkit
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: 1.45.2
119
+ type: :runtime
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: 1.45.2
112
126
  - !ruby/object:Gem::Dependency
113
127
  name: logger
114
128
  requirement: !ruby/object:Gem::Requirement