forest_admin_agent 1.0.0.pre.beta.26 → 1.0.0.pre.beta.28

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20188623cbbdcb4d7ce701bfda5d0c4f841d547bf6fe38612f4276182eaa437a
4
- data.tar.gz: 73f8b529249cc8be259e628f1c8e75721b59c2fb645cc544246c19130ecd1106
3
+ metadata.gz: e2ae114be4b54df0354aca036cdc18ca08ab1c78b72d6975b3002179cea93a18
4
+ data.tar.gz: fd369183191175beb32c586bd98f2c67ccd21a7438e5b41741c0a5f0667985ce
5
5
  SHA512:
6
- metadata.gz: 3d5a14eb9fa6b8d8af91a7ad6efb33927d5773b762ff82fb73574a858a7d46c252aaad3cc19a2ba026dc1ed65ec0548ba360b90b4fb7baf357a588a66fe442d1
7
- data.tar.gz: 4822e1f4b72633515287e9a5637d4a42914ae233f5baca6f912ffaa90d15db0141c6dc783dd141f23cc3fccebca79552d95a0c3ee829b469ecb6b43f1e089209
6
+ metadata.gz: 2465a1fb919eccdd5daef0183cf0bc371d312b6fa8462dfcbb6096e587376efad479184269b32617143e53d321c0208b361d99af66eabbeea299c6050dd87e3c
7
+ data.tar.gz: e8ee04c59cbc81fa1b38478e40507d94434b68e2be5009a7b6403731ac117793b9aa462c567704125a7a96264eee46123c7c915979f91ec20042fea2620dc361
@@ -15,7 +15,6 @@ module ForestAdminAgent
15
15
  def handle_request(args = {})
16
16
  build(args)
17
17
  @permissions.can?(:browse, @collection)
18
-
19
18
  filter = ForestAdminDatasourceToolkit::Components::Query::Filter.new(
20
19
  condition_tree: ConditionTreeFactory.intersect([
21
20
  @permissions.get_scope(@collection),
@@ -23,8 +22,11 @@ module ForestAdminAgent
23
22
  @collection, args
24
23
  )
25
24
  ]),
26
- page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args)
25
+ page: ForestAdminAgent::Utils::QueryStringParser.parse_pagination(args),
26
+ search: ForestAdminAgent::Utils::QueryStringParser.parse_search(@collection, args),
27
+ search_extended: ForestAdminAgent::Utils::QueryStringParser.parse_search_extended(args)
27
28
  )
29
+
28
30
  projection = ForestAdminAgent::Utils::QueryStringParser.parse_projection_with_pks(@collection, args)
29
31
  records = @collection.list(@caller, filter, projection)
30
32
 
@@ -32,12 +34,33 @@ module ForestAdminAgent
32
34
  name: args[:params]['collection_name'],
33
35
  content: JSONAPI::Serializer.serialize(
34
36
  records,
37
+ class_name: @collection.name,
35
38
  is_collection: true,
36
39
  serializer: Serializer::ForestSerializer,
37
- include: projection.relations.keys
40
+ include: projection.relations.keys,
41
+ meta: handle_search_decorator(args[:params]['search'], records)
38
42
  )
39
43
  }
40
44
  end
45
+
46
+ def handle_search_decorator(search_value, records)
47
+ decorator = { decorators: [] }
48
+ unless search_value.nil?
49
+ records.each_with_index do |entry, index|
50
+ decorator[:decorators][index] = { id: Utils::Id.pack_id(@collection, entry), search: [] }
51
+ # attributes method is defined on ActiveRecord::Base model
52
+ attributes = entry.respond_to?(:attributes) ? entry.attributes : entry
53
+
54
+ attributes.each do |field_key, field_value|
55
+ if !field_value.is_a?(Array) && field_value.to_s.downcase.include?(search_value.downcase)
56
+ decorator[:decorators][index][:search] << field_key
57
+ end
58
+ end
59
+ end
60
+ end
61
+
62
+ decorator
63
+ end
41
64
  end
42
65
  end
43
66
  end
@@ -50,6 +50,7 @@ module ForestAdminAgent
50
50
  content: JSONAPI::Serializer.serialize(
51
51
  records,
52
52
  is_collection: true,
53
+ class_name: @child_collection.name,
53
54
  serializer: Serializer::ForestSerializer,
54
55
  include: projection.relations.keys
55
56
  )
@@ -34,6 +34,7 @@ module ForestAdminAgent
34
34
  name: args[:params]['collection_name'],
35
35
  content: JSONAPI::Serializer.serialize(
36
36
  records[0],
37
+ class_name: @collection.name,
37
38
  is_collection: false,
38
39
  serializer: Serializer::ForestSerializer,
39
40
  include: projection.relations.keys
@@ -25,6 +25,7 @@ module ForestAdminAgent
25
25
  content: JSONAPI::Serializer.serialize(
26
26
  record,
27
27
  is_collection: false,
28
+ class_name: @collection.name,
28
29
  serializer: Serializer::ForestSerializer
29
30
  )
30
31
  }
@@ -33,6 +33,7 @@ module ForestAdminAgent
33
33
  content: JSONAPI::Serializer.serialize(
34
34
  records[0],
35
35
  is_collection: false,
36
+ class_name: @collection.name,
36
37
  serializer: Serializer::ForestSerializer
37
38
  )
38
39
  }
@@ -20,8 +20,17 @@ module ForestAdminAgent
20
20
  end
21
21
 
22
22
  def type
23
- class_name = object.class.name
24
- @@class_names[class_name] ||= class_name.demodulize.underscore.freeze
23
+ class_name = @options[:class_name]
24
+ @@class_names[class_name] ||= class_name
25
+ end
26
+
27
+ def id
28
+ forest_collection = ForestAdminAgent::Facades::Container.datasource.get_collection(@options[:class_name])
29
+ primary_keys = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(forest_collection)
30
+ id = []
31
+ primary_keys.each { |key| id << @object[key] }
32
+
33
+ id.join('|')
25
34
  end
26
35
 
27
36
  def format_name(attribute_name)
@@ -41,7 +50,7 @@ module ForestAdminAgent
41
50
  end
42
51
 
43
52
  def attributes
44
- forest_collection = ForestAdminAgent::Facades::Container.datasource.get_collection(object.class.name.demodulize.underscore)
53
+ forest_collection = ForestAdminAgent::Facades::Container.datasource.get_collection(@options[:class_name])
45
54
  fields = forest_collection.schema[:fields].select { |_field_name, field| field.type == 'Column' }
46
55
  fields.each { |field_name, _field| add_attribute(field_name) }
47
56
  return {} if attributes_map.nil?
@@ -61,11 +70,7 @@ module ForestAdminAgent
61
70
  instance_eval(&attr_or_block)
62
71
  else
63
72
  # Default behavior, call a method by the name of the attribute.
64
- begin
65
- object.try(attr_or_block)
66
- rescue
67
- nil
68
- end
73
+ object[attr_or_block]
69
74
  end
70
75
  end
71
76
 
@@ -104,7 +109,8 @@ module ForestAdminAgent
104
109
  end
105
110
 
106
111
  def relationships
107
- forest_collection = ForestAdminAgent::Facades::Container.datasource.get_collection(object.class.name.demodulize.underscore)
112
+ datasource = ForestAdminAgent::Facades::Container.datasource
113
+ forest_collection = datasource.get_collection(@options[:class_name])
108
114
  relations_to_many = forest_collection.schema[:fields].select { |_field_name, field| field.type == 'OneToMany' || field.type == 'ManyToMany' }
109
115
  relations_to_one = forest_collection.schema[:fields].select { |_field_name, field| field.type == 'OneToOne' || field.type == 'ManyToOne' }
110
116
 
@@ -124,10 +130,13 @@ module ForestAdminAgent
124
130
  end
125
131
 
126
132
  object = has_one_relationship(attribute_name, attr_data)
127
- if object.nil?
133
+ if object.nil? || object.empty?
128
134
  data[formatted_attribute_name]['data'] = nil
129
135
  else
130
- related_object_serializer = ForestSerializer.new(object, @options)
136
+ relation = datasource.get_collection(@options[:class_name]).schema[:fields][attribute_name.to_s]
137
+ options = @options.clone
138
+ options[:class_name] = datasource.get_collection(relation.foreign_collection).name
139
+ related_object_serializer = ForestSerializer.new(object, options)
131
140
  data[formatted_attribute_name]['data'] = {
132
141
  'type' => related_object_serializer.type.to_s,
133
142
  'id' => related_object_serializer.id.to_s,
@@ -152,6 +161,9 @@ module ForestAdminAgent
152
161
  if @_include_linkages.include?(formatted_attribute_name) || attr_data[:options][:include_data]
153
162
  data[formatted_attribute_name]['data'] = []
154
163
  objects = has_many_relationship(attribute_name, attr_data) || []
164
+ relation = datasource.get_collection(@options[:class_name]).schema[:fields][attribute_name.to_s]
165
+ options = @options.clone
166
+ options[:class_name] = datasource.get_collection(relation.foreign_collection).name
155
167
  objects.each do |obj|
156
168
  related_object_serializer = JSONAPI::Serializer.find_serializer(obj, @options)
157
169
  data[formatted_attribute_name]['data'] << {
@@ -48,7 +48,7 @@ module ForestAdminAgent
48
48
  end
49
49
 
50
50
  # We're finding relationships for compound documents, so skip anything that doesn't exist.
51
- next if object.nil?
51
+ next if object.nil? || object.empty?
52
52
 
53
53
  # Full linkage: a request for comments.author MUST automatically include comments
54
54
  # in the response.
@@ -58,6 +58,8 @@ module ForestAdminAgent
58
58
  # If it is not set, that indicates that this is an inner path and not a leaf and will
59
59
  # be followed by the recursion below.
60
60
  objects.each do |obj|
61
+ relation = ForestAdminAgent::Facades::Container.datasource.get_collection(options[:class_name]).schema[:fields][attribute_name]
62
+ relation_class_name = ForestAdminAgent::Facades::Container.datasource.get_collection(relation.foreign_collection).name
61
63
  obj_serializer = JSONAPI::Serializer.find_serializer(obj, options)
62
64
  # Use keys of ['posts', '1'] for the results to enforce uniqueness.
63
65
  # Spec: A compound document MUST NOT include more than one resource object for each
@@ -82,7 +84,8 @@ module ForestAdminAgent
82
84
  # so merge the include_linkages each time we see it to load all the relevant linkages.
83
85
  current_child_includes += (results[key] && results[key][:include_linkages]) || []
84
86
  current_child_includes.uniq!
85
- results[key] = { object: obj, include_linkages: current_child_includes }
87
+
88
+ results[key] = { object: obj, include_linkages: current_child_includes, class_name: relation_class_name }
86
89
  end
87
90
  end
88
91
 
@@ -96,6 +99,123 @@ module ForestAdminAgent
96
99
  end
97
100
  nil
98
101
  end
102
+
103
+ def self.serialize(objects, options = {})
104
+ # Normalize option strings to symbols.
105
+ options[:is_collection] = options.delete('is_collection') || options[:is_collection] || false
106
+ options[:include] = options.delete('include') || options[:include]
107
+ options[:serializer] = options.delete('serializer') || options[:serializer]
108
+ options[:namespace] = options.delete('namespace') || options[:namespace]
109
+ options[:context] = options.delete('context') || options[:context] || {}
110
+ options[:skip_collection_check] = options.delete('skip_collection_check') || options[:skip_collection_check] || false
111
+ options[:base_url] = options.delete('base_url') || options[:base_url]
112
+ options[:jsonapi] = options.delete('jsonapi') || options[:jsonapi]
113
+ options[:meta] = options.delete('meta') || options[:meta]
114
+ options[:links] = options.delete('links') || options[:links]
115
+ options[:fields] = options.delete('fields') || options[:fields] || {}
116
+
117
+ # Deprecated: use serialize_errors method instead
118
+ options[:errors] = options.delete('errors') || options[:errors]
119
+
120
+ # Normalize includes.
121
+ includes = options[:include]
122
+ includes = (includes.is_a?(String) ? includes.split(',') : includes).uniq if includes
123
+
124
+ # Transforms input so that the comma-separated fields are separate symbols in array
125
+ # and keys are stringified
126
+ # Example:
127
+ # {posts: 'title,author,long_comments'} => {'posts' => [:title, :author, :long_comments]}
128
+ # {posts: ['title', 'author', 'long_comments'} => {'posts' => [:title, :author, :long_comments]}
129
+ #
130
+ fields = {}
131
+ # Normalize fields to accept a comma-separated string or an array of strings.
132
+ options[:fields].map do |type, whitelisted_fields|
133
+ whitelisted_fields = [whitelisted_fields] if whitelisted_fields.is_a?(Symbol)
134
+ whitelisted_fields = whitelisted_fields.split(',') if whitelisted_fields.is_a?(String)
135
+ fields[type.to_s] = whitelisted_fields.map(&:to_sym)
136
+ end
137
+
138
+ # An internal-only structure that is passed through serializers as they are created.
139
+ passthrough_options = {
140
+ context: options[:context],
141
+ serializer: options[:serializer],
142
+ namespace: options[:namespace],
143
+ include: includes,
144
+ fields: fields,
145
+ base_url: options[:base_url],
146
+ class_name: options[:class_name]
147
+ }
148
+
149
+ if !options[:skip_collection_check] && options[:is_collection] && !objects.respond_to?(:each)
150
+ raise JSONAPI::Serializer::AmbiguousCollectionError.new(
151
+ 'Attempted to serialize a single object as a collection.')
152
+ end
153
+
154
+ # Automatically include linkage data for any relation that is also included.
155
+ if includes
156
+ include_linkages = includes.map { |key| key.to_s.split('.').first }
157
+ passthrough_options[:include_linkages] = include_linkages
158
+ end
159
+
160
+ # Spec: Primary data MUST be either:
161
+ # - a single resource object or null, for requests that target single resources.
162
+ # - an array of resource objects or an empty array ([]), for resource collections.
163
+ # http://jsonapi.org/format/#document-structure-top-level
164
+ if options[:is_collection] && !objects.any?
165
+ primary_data = []
166
+ elsif !options[:is_collection] && objects.nil?
167
+ primary_data = nil
168
+ elsif options[:is_collection]
169
+ # Have object collection.
170
+ primary_data = serialize_primary_multi(objects, passthrough_options)
171
+ else
172
+ # Duck-typing check for a collection being passed without is_collection true.
173
+ # We always must be told if serializing a collection because the JSON:API spec distinguishes
174
+ # how to serialize null single resources vs. empty collections.
175
+ if !options[:skip_collection_check] && objects.is_a?(Array)
176
+ raise JSONAPI::Serializer::AmbiguousCollectionError.new(
177
+ 'Must provide `is_collection: true` to `serialize` when serializing collections.')
178
+ end
179
+ # Have single object.
180
+ primary_data = serialize_primary(objects, passthrough_options)
181
+ end
182
+ result = {
183
+ 'data' => primary_data,
184
+ }
185
+ result['jsonapi'] = options[:jsonapi] if options[:jsonapi]
186
+ result['meta'] = options[:meta] if options[:meta]
187
+ result['links'] = options[:links] if options[:links]
188
+ result['errors'] = options[:errors] if options[:errors]
189
+
190
+ # If 'include' relationships are given, recursively find and include each object.
191
+ if includes
192
+ relationship_data = {}
193
+ inclusion_tree = parse_relationship_paths(includes)
194
+
195
+ # Given all the primary objects (either the single root object or collection of objects),
196
+ # recursively search and find related associations that were specified as includes.
197
+ objects = options[:is_collection] ? objects.to_a : [objects]
198
+ objects.compact.each do |obj|
199
+ # Use the mutability of relationship_data as the return datastructure to take advantage
200
+ # of the internal special merging logic.
201
+ find_recursive_relationships(obj, inclusion_tree, relationship_data, passthrough_options)
202
+ end
203
+
204
+ result['included'] = relationship_data.map do |_, data|
205
+ included_passthrough_options = {}
206
+ included_passthrough_options[:base_url] = passthrough_options[:base_url]
207
+ included_passthrough_options[:context] = passthrough_options[:context]
208
+ included_passthrough_options[:fields] = passthrough_options[:fields]
209
+ included_passthrough_options[:serializer] = find_serializer_class(data[:object], options)
210
+ included_passthrough_options[:namespace] = passthrough_options[:namespace]
211
+ included_passthrough_options[:include_linkages] = data[:include_linkages]
212
+ included_passthrough_options[:class_name] = data[:class_name]
213
+
214
+ serialize_primary(data[:object], included_passthrough_options)
215
+ end
216
+ end
217
+ result
218
+ end
99
219
  end
100
220
  end
101
221
  end
@@ -3,6 +3,19 @@ module ForestAdminAgent
3
3
  class Id
4
4
  include ForestAdminDatasourceToolkit::Utils
5
5
  include ForestAdminDatasourceToolkit
6
+
7
+ def self.pack_ids(schema, records)
8
+ records.map { |packed_id| pack_id(schema, packed_id) }
9
+ end
10
+
11
+ def self.pack_id(schema, record)
12
+ pk_names = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(schema)
13
+
14
+ raise Exceptions::ForestException, 'This collection has no primary key' if pk_names.empty?
15
+
16
+ pk_names.map { |pk| record[pk].to_s }.join('|')
17
+ end
18
+
6
19
  def self.unpack_id(collection, packed_id, with_key: false)
7
20
  primary_keys = ForestAdminDatasourceToolkit::Utils::Schema.primary_keys(collection)
8
21
  primary_key_values = packed_id.to_s.split('|')
@@ -85,6 +85,23 @@ module ForestAdminAgent
85
85
 
86
86
  Page.new(offset: offset, limit: items_per_pages.to_i)
87
87
  end
88
+
89
+ def self.parse_search(collection, args)
90
+ search = args.dig(:params, :data, :attributes, :all_records_subset_query, :search) || args.dig(:params, :search)
91
+
92
+ raise ForestException, 'Collection is not searchable' if search && !collection.is_searchable?
93
+
94
+ search
95
+ end
96
+
97
+ def self.parse_search_extended(args)
98
+ extended = args.dig(:params, :data, :attributes, :all_records_subset_query,
99
+ :searchExtended) || args.dig(:params, :searchExtended)
100
+
101
+ return false if extended.nil?
102
+
103
+ extended != '0'
104
+ end
88
105
  end
89
106
  end
90
107
  end
@@ -7,7 +7,7 @@ module ForestAdminAgent
7
7
  class SchemaEmitter
8
8
  LIANA_NAME = "forest-rails"
9
9
 
10
- LIANA_VERSION = "1.0.0-beta.26"
10
+ LIANA_VERSION = "1.0.0-beta.28"
11
11
 
12
12
  def self.get_serialized_schema(datasource)
13
13
  schema_path = Facades::Container.cache(:schema_path)
@@ -1,3 +1,3 @@
1
1
  module ForestAdminAgent
2
- VERSION = "1.0.0-beta.26"
2
+ VERSION = "1.0.0-beta.28"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.pre.beta.26
4
+ version: 1.0.0.pre.beta.28
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: 2024-01-02 00:00:00.000000000 Z
12
+ date: 2024-01-19 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport