forest_admin_datasource_customizer 1.24.1 → 2.0.0

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.

Potentially problematic release.


This version of forest_admin_datasource_customizer might be problematic. Click here for more details.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b54cb38619eae9483b9adccb3eae427291437b3cc66afa11fa20cedbd597fe5
4
- data.tar.gz: 6affb1315de6da3baf01329124f6072476170a0555521265962b8edeeda7ac31
3
+ metadata.gz: 99861571fab21e188babeeae7244a3106510e8aee39a9bc3713d30f262df97d2
4
+ data.tar.gz: 6af3a7eb53f5d7b79aae3bd87c5f8925c9be6d259bb8f67c6b7a4d43e54f528a
5
5
  SHA512:
6
- metadata.gz: 702ebc6894f7e26b356908ead2fc435e8896353b0f13ea8f875bf615849194fe669a3153470e8cc33c6c591c007fd1e943ce4549b87011af01486f660312c601
7
- data.tar.gz: d9055e70afb29c141898e28c5f8fa317dd33769760a8c2acf3dd8f646d284ad3b43e9df20c2bd956d67b73a2f99ae97753fda5269e3067cf370f516eb24d28cd
6
+ metadata.gz: 300ac4146409e2cb043abf8d40b60c67190a16c18e250c7878a8bcdcf5290d57c175389f91f57f431422c0947b1941f5c7d67750a02326f447712369f9d7326f
7
+ data.tar.gz: ea5cc31eb428bce00bdcd0826919fb50d4372afde061438a800332d8cd3ed8ed3930e0a432599c84e3ef9b9a4c2a97f92b879350e1866a9b5523d0a0936e5adb
@@ -35,11 +35,8 @@ admin work on any Ruby application."
35
35
 
36
36
  spec.add_dependency "activesupport", ">= 6.1"
37
37
  spec.add_dependency "base64"
38
- spec.add_dependency "benchmark"
39
38
  spec.add_dependency "bigdecimal"
40
- spec.add_dependency "cgi"
41
39
  spec.add_dependency "csv"
42
- spec.add_dependency "logger"
43
40
  spec.add_dependency 'marcel', '~> 1.0', '>= 1.0.4'
44
41
  spec.add_dependency "mutex_m"
45
42
  spec.add_dependency "ostruct"
@@ -1,7 +1,5 @@
1
1
  module ForestAdminDatasourceCustomizer
2
2
  class CompositeDatasource
3
- attr_reader :datasources
4
-
5
3
  def initialize
6
4
  @datasources = []
7
5
  end
@@ -54,9 +52,10 @@ module ForestAdminDatasourceCustomizer
54
52
  end
55
53
 
56
54
  def add_data_source(datasource)
57
- existing_names = collections.keys
58
- datasource.collections.each_key do |name|
59
- raise ArgumentError, "Collection '#{name}' already exists" if existing_names.include?(name)
55
+ existing_names = collections.map { |c| c.respond_to?(:name) ? c.name : c.to_s }
56
+ datasource.collections.each do |c|
57
+ new_name = c.respond_to?(:name) ? c.name : c.to_s
58
+ raise ArgumentError, "Collection '#{new_name}' already exists" if existing_names.include?(new_name)
60
59
  end
61
60
 
62
61
  existing_charts = schema[:charts]
@@ -1,7 +1,7 @@
1
1
  module ForestAdminDatasourceCustomizer
2
2
  class DatasourceCustomizer
3
3
  include DSL::DatasourceHelpers
4
- attr_reader :stack, :composite_datasource
4
+ attr_reader :stack, :datasources
5
5
 
6
6
  def initialize(_db_config = {})
7
7
  @composite_datasource = ForestAdminDatasourceCustomizer::CompositeDatasource.new
@@ -61,7 +61,6 @@ module ForestAdminDatasourceCustomizer
61
61
  alias get_record_ids record_ids
62
62
  alias get_composite_record_ids composite_record_ids
63
63
  alias has_field_changed field_changed?
64
- alias form_value get_form_value
65
64
  end
66
65
  end
67
66
  end
@@ -17,22 +17,16 @@ module ForestAdminDatasourceCustomizer
17
17
 
18
18
  MARKER_NAME = '__null_marker'.freeze
19
19
  def self.with_null_marker(projection)
20
- seen = Set.new(projection)
21
20
  new_projection = Projection.new(projection)
22
-
23
21
  projection.each do |path|
24
22
  parts = path.split(':')
25
23
 
26
24
  parts.slice(1, parts.size).each_with_index do |_item, index|
27
- marker = "#{parts.slice(0, index + 1).join(":")}:#{MARKER_NAME}"
28
- next if seen.include?(marker)
29
-
30
- seen << marker
31
- new_projection << marker
25
+ new_projection << "#{parts.slice(0, index + 1).join(":")}:#{MARKER_NAME}"
32
26
  end
33
27
  end
34
28
 
35
- new_projection
29
+ new_projection.uniq
36
30
  end
37
31
 
38
32
  def self.flatten(records, projection)
@@ -240,36 +240,30 @@ module ForestAdminDatasourceCustomizer
240
240
 
241
241
  def re_project_relation_in_place(caller, records, name, projection)
242
242
  field_schema = schema[:fields][name]
243
+
243
244
  return if field_schema.type == 'PolymorphicManyToOne'
244
245
 
246
+ association = datasource.get_collection(field_schema.foreign_collection)
247
+
245
248
  if !@relations[name]
246
- association = datasource.get_collection(field_schema.foreign_collection)
247
249
  association.re_project_in_place(caller, records.filter_map { |r| r[name] }, projection)
248
250
  elsif field_schema.type == 'ManyToOne'
249
- assign_many_to_one_records(caller, records, name, field_schema, projection)
250
- elsif ['OneToOne', 'OneToMany'].include?(field_schema.type)
251
- assign_to_one_or_many_records(caller, records, name, field_schema, projection)
252
- end
253
- end
251
+ ids = records.filter_map { |record| record[field_schema.foreign_key] }.uniq
252
+ sub_filter = Filter.new(condition_tree: ConditionTreeLeaf.new(field_schema.foreign_key_target, 'In', ids))
253
+ sub_records = association.list(caller, sub_filter, projection.union([field_schema.foreign_key_target]))
254
254
 
255
- def assign_many_to_one_records(caller, records, name, field_schema, projection)
256
- association = datasource.get_collection(field_schema.foreign_collection)
257
- ids = records.each_with_object(Set.new) { |record, set| set << record[field_schema.foreign_key] }.delete(nil).to_a
258
- sub_filter = Filter.new(condition_tree: ConditionTreeLeaf.new(field_schema.foreign_key_target, 'In', ids))
259
- sub_records = association.list(caller, sub_filter, projection.union([field_schema.foreign_key_target]))
260
- sub_records_by_key = sub_records.to_h { |sr| [sr[field_schema.foreign_key_target], sr] }
261
-
262
- records.each { |record| record[name] = sub_records_by_key[record[field_schema.foreign_key]] }
263
- end
264
-
265
- def assign_to_one_or_many_records(caller, records, name, field_schema, projection)
266
- association = datasource.get_collection(field_schema.foreign_collection)
267
- ids = records.each_with_object(Set.new) { |record, set| set << record[field_schema.origin_key_target] }.delete(nil).to_a
268
- sub_filter = Filter.new(condition_tree: ConditionTreeLeaf.new(field_schema.origin_key, 'In', ids))
269
- sub_records = association.list(caller, sub_filter, projection.union([field_schema.origin_key]))
270
- sub_records_by_key = sub_records.to_h { |sr| [sr[field_schema.origin_key], sr] }
255
+ records.each do |record|
256
+ record[name] = sub_records.find { |sr| sr[field_schema.foreign_key_target] == record[field_schema.foreign_key] }
257
+ end
258
+ elsif ['OneToOne', 'OneToMany'].include?(field_schema.type)
259
+ ids = records.filter_map { |record| record[field_schema.origin_key_target] }.uniq
260
+ sub_filter = Filter.new(condition_tree: ConditionTreeLeaf.new(field_schema.origin_key, 'In', ids))
261
+ sub_records = association.list(caller, sub_filter, projection.union([field_schema.origin_key]))
271
262
 
272
- records.each { |record| record[name] = sub_records_by_key[record[field_schema.origin_key_target]] }
263
+ records.each do |record|
264
+ record[name] = sub_records.find { |sr| sr[field_schema.origin_key] == record[field_schema.origin_key_target] }
265
+ end
266
+ end
273
267
  end
274
268
  end
275
269
  end
@@ -47,6 +47,9 @@ module ForestAdminDatasourceCustomizer
47
47
  end
48
48
 
49
49
  def rename_collection(current_name, new_name)
50
+ # Check collection exists
51
+ collection = get_collection(current_name)
52
+
50
53
  return unless current_name != new_name
51
54
 
52
55
  # Check new name is not already used
@@ -61,6 +64,17 @@ module ForestAdminDatasourceCustomizer
61
64
  "Cannot rename a collection twice: #{@to_child_name[current_name]}->#{current_name}->#{new_name}"
62
65
  end
63
66
 
67
+ polymorphic_relations = %w[PolymorphicOneToOne PolymorphicOneToMany]
68
+ collection.schema[:fields].each do |field_name, field_schema|
69
+ next unless polymorphic_relations.include?(field_schema.type)
70
+
71
+ reverse_relation_name = Utils::Collection.get_inverse_relation(get_collection(current_name), field_name)
72
+
73
+ raise Exceptions::ForestException,
74
+ "Cannot rename collection #{current_name} because it's a target of a polymorphic relation " \
75
+ "'#{field_schema.foreign_collection}.#{reverse_relation_name}'"
76
+ end
77
+
64
78
  @from_child_name[current_name] = new_name
65
79
  @to_child_name[new_name] = current_name
66
80
 
@@ -9,46 +9,21 @@ module ForestAdminDatasourceCustomizer
9
9
  datasource.get_collection_name(super)
10
10
  end
11
11
 
12
- def list(caller, filter = nil, projection = nil)
13
- refined_filter = refine_filter(caller, filter)
14
- records = @child_collection.list(caller, refined_filter, projection)
15
-
16
- transform_records_polymorphic_values(records)
17
- end
18
-
19
- def create(caller, data)
20
- transformed_data = transform_data_polymorphic_values(data)
21
- result = @child_collection.create(caller, transformed_data)
22
-
23
- transform_record_polymorphic_values(result)
24
- end
25
-
26
- def update(caller, filter, patch)
27
- refined_filter = refine_filter(caller, filter)
28
- transformed_patch = transform_data_polymorphic_values(patch)
29
-
30
- @child_collection.update(caller, refined_filter, transformed_patch)
31
- end
32
-
33
- def refine_filter(_caller, filter)
34
- return filter unless filter&.condition_tree
35
-
36
- type_fields = polymorphic_type_fields
12
+ def refine_schema(sub_schema)
13
+ fields = {}
37
14
 
38
- transformed_tree = filter.condition_tree.replace_leafs do |leaf|
39
- if type_fields.include?(leaf.field)
40
- transformed_value = transform_polymorphic_value(leaf.value)
41
- if transformed_value == leaf.value
42
- leaf
43
- else
44
- leaf.override(value: transformed_value)
15
+ sub_schema[:fields].each do |name, old_schema|
16
+ if old_schema.type != 'Column' && old_schema.type != 'PolymorphicManyToOne'
17
+ old_schema.foreign_collection = datasource.get_collection_name(old_schema.foreign_collection)
18
+ if old_schema.type == 'ManyToMany'
19
+ old_schema.through_collection = datasource.get_collection_name(old_schema.through_collection)
45
20
  end
46
- else
47
- leaf
48
21
  end
22
+
23
+ fields[name] = old_schema
49
24
  end
50
25
 
51
- filter.override(condition_tree: transformed_tree)
26
+ sub_schema
52
27
  end
53
28
 
54
29
  # rubocop:disable Lint/UselessMethodDefinition
@@ -56,128 +31,6 @@ module ForestAdminDatasourceCustomizer
56
31
  super
57
32
  end
58
33
  # rubocop:enable Lint/UselessMethodDefinition
59
-
60
- private
61
-
62
- def polymorphic_type_fields
63
- type_fields = []
64
- child_schema = @child_collection.schema
65
- return type_fields unless child_schema && child_schema[:fields]
66
-
67
- child_schema[:fields].each_value do |field_schema|
68
- case field_schema.type
69
- when 'PolymorphicManyToOne'
70
- type_fields << field_schema.foreign_key_type_field
71
- end
72
- end
73
- type_fields
74
- end
75
-
76
- def transform_data_polymorphic_values(data)
77
- return data unless data
78
-
79
- type_fields = polymorphic_type_fields
80
- transformed_data = data.dup
81
-
82
- type_fields.each do |type_field|
83
- next unless transformed_data.key?(type_field)
84
-
85
- original_value = transformed_data[type_field]
86
- transformed_value = reverse_collection_name(original_value)
87
- transformed_data[type_field] = transformed_value if transformed_value != original_value
88
- end
89
-
90
- transformed_data
91
- end
92
-
93
- def transform_polymorphic_value(value)
94
- return value unless value
95
-
96
- # Handle both single values and arrays (for IN/NOT_IN operators)
97
- if value.is_a?(Array)
98
- value.map { |v| reverse_collection_name(v) }
99
- else
100
- reverse_collection_name(value)
101
- end
102
- end
103
-
104
- # convert new collection name back to old name for db queries
105
- def reverse_collection_name(collection_name)
106
- to_child_name = datasource.instance_variable_get(:@to_child_name)
107
- to_child_name[collection_name] || collection_name
108
- end
109
-
110
- # convert old collection name to new name for returned data
111
- def forward_collection_name(collection_name)
112
- from_child_name = datasource.instance_variable_get(:@from_child_name)
113
- from_child_name[collection_name] || collection_name
114
- end
115
-
116
- def transform_records_polymorphic_values(records)
117
- return records unless records.is_a?(Array)
118
-
119
- type_fields = polymorphic_type_fields
120
- return records if type_fields.empty?
121
-
122
- records.map do |record|
123
- transform_record_polymorphic_values(record)
124
- end
125
- end
126
-
127
- def transform_record_polymorphic_values(record)
128
- return record unless record.is_a?(Hash)
129
-
130
- type_fields = polymorphic_type_fields
131
- return record if type_fields.empty?
132
-
133
- transformed_record = record.dup
134
-
135
- type_fields.each do |type_field|
136
- next unless transformed_record.key?(type_field)
137
-
138
- old_value = transformed_record[type_field]
139
- new_value = forward_collection_name(old_value)
140
- transformed_record[type_field] = new_value if new_value != old_value
141
- end
142
-
143
- transformed_record
144
- end
145
-
146
- protected
147
-
148
- def refine_schema(sub_schema)
149
- current_collection_name = @child_collection.name
150
-
151
- sub_schema[:fields].each_value do |old_schema|
152
- case old_schema.type
153
- when 'PolymorphicOneToOne', 'PolymorphicOneToMany'
154
- refine_polymorphic_one_schema(old_schema, current_collection_name)
155
- when 'PolymorphicManyToOne'
156
- refine_polymorphic_many_schema(old_schema)
157
- when 'ManyToOne', 'OneToMany', 'OneToOne'
158
- old_schema.foreign_collection = datasource.get_collection_name(old_schema.foreign_collection)
159
- when 'ManyToMany'
160
- old_schema.foreign_collection = datasource.get_collection_name(old_schema.foreign_collection)
161
- old_schema.through_collection = datasource.get_collection_name(old_schema.through_collection)
162
- end
163
- end
164
-
165
- sub_schema
166
- end
167
-
168
- def refine_polymorphic_one_schema(schema, current_collection_name)
169
- if schema.origin_type_value == current_collection_name
170
- schema.origin_type_value = datasource.get_collection_name(current_collection_name)
171
- end
172
- schema.foreign_collection = datasource.get_collection_name(schema.foreign_collection)
173
- end
174
-
175
- def refine_polymorphic_many_schema(schema)
176
- schema.foreign_collections = schema.foreign_collections.map { |fc| datasource.get_collection_name(fc) }
177
- schema.foreign_key_targets = schema.foreign_key_targets.transform_keys do |key|
178
- datasource.get_collection_name(key)
179
- end
180
- end
181
34
  end
182
35
  end
183
36
  end
@@ -20,9 +20,8 @@ module ForestAdminDatasourceCustomizer
20
20
  # form do
21
21
  # field :amount, type: :number, required: true
22
22
  # field :reason, type: :string,
23
- # # You can use either form_value (DSL-style) or get_form_value (legacy)
24
- # required: ->(ctx) { ctx.form_value(:amount).to_i > 1000 },
25
- # if_condition: ->(ctx) { ctx.form_value(:amount).to_i > 500 }
23
+ # required: ->(ctx) { ctx.get_form_value('amount').to_i > 1000 },
24
+ # if_condition: ->(ctx) { ctx.get_form_value('amount').to_i > 500 }
26
25
  # end
27
26
  class FormBuilder
28
27
  attr_reader :fields
@@ -1,3 +1,3 @@
1
1
  module ForestAdminDatasourceCustomizer
2
- VERSION = "1.24.1"
2
+ VERSION = "2.0.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_datasource_customizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.24.1
4
+ version: 2.0.0
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-02-10 00:00:00.000000000 Z
12
+ date: 2025-12-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -39,20 +39,6 @@ dependencies:
39
39
  - - ">="
40
40
  - !ruby/object:Gem::Version
41
41
  version: '0'
42
- - !ruby/object:Gem::Dependency
43
- name: benchmark
44
- requirement: !ruby/object:Gem::Requirement
45
- requirements:
46
- - - ">="
47
- - !ruby/object:Gem::Version
48
- version: '0'
49
- type: :runtime
50
- prerelease: false
51
- version_requirements: !ruby/object:Gem::Requirement
52
- requirements:
53
- - - ">="
54
- - !ruby/object:Gem::Version
55
- version: '0'
56
42
  - !ruby/object:Gem::Dependency
57
43
  name: bigdecimal
58
44
  requirement: !ruby/object:Gem::Requirement
@@ -67,20 +53,6 @@ dependencies:
67
53
  - - ">="
68
54
  - !ruby/object:Gem::Version
69
55
  version: '0'
70
- - !ruby/object:Gem::Dependency
71
- name: cgi
72
- requirement: !ruby/object:Gem::Requirement
73
- requirements:
74
- - - ">="
75
- - !ruby/object:Gem::Version
76
- version: '0'
77
- type: :runtime
78
- prerelease: false
79
- version_requirements: !ruby/object:Gem::Requirement
80
- requirements:
81
- - - ">="
82
- - !ruby/object:Gem::Version
83
- version: '0'
84
56
  - !ruby/object:Gem::Dependency
85
57
  name: csv
86
58
  requirement: !ruby/object:Gem::Requirement
@@ -95,20 +67,6 @@ dependencies:
95
67
  - - ">="
96
68
  - !ruby/object:Gem::Version
97
69
  version: '0'
98
- - !ruby/object:Gem::Dependency
99
- name: logger
100
- requirement: !ruby/object:Gem::Requirement
101
- requirements:
102
- - - ">="
103
- - !ruby/object:Gem::Version
104
- version: '0'
105
- type: :runtime
106
- prerelease: false
107
- version_requirements: !ruby/object:Gem::Requirement
108
- requirements:
109
- - - ">="
110
- - !ruby/object:Gem::Version
111
- version: '0'
112
70
  - !ruby/object:Gem::Dependency
113
71
  name: marcel
114
72
  requirement: !ruby/object:Gem::Requirement