scrivito_sdk 1.0.0 → 1.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +1 -0
  3. data/README.md +1 -1
  4. data/app/controllers/scrivito/legacy_redirect_controller.rb +11 -0
  5. data/app/controllers/scrivito/objs_controller.rb +15 -5
  6. data/app/controllers/scrivito/webservice_controller.rb +4 -3
  7. data/app/controllers/scrivito/workspaces_controller.rb +24 -29
  8. data/app/helpers/scrivito_helper.rb +82 -37
  9. data/app/views/scrivito/objs/binary_no_cache.json.jbuilder +1 -0
  10. data/app/views/scrivito/objs/search.json.jbuilder +5 -5
  11. data/app/views/scrivito/webservice/error.json.jbuilder +2 -2
  12. data/config/ca-bundle.crt +1 -1
  13. data/config/precedence_routes.rb +51 -48
  14. data/config/routes.rb +1 -14
  15. data/lib/assets/javascripts/scrivito_ui.js +2447 -266
  16. data/lib/assets/stylesheets/scrivito.css +1 -1
  17. data/lib/assets/stylesheets/scrivito_ui.css +1 -1
  18. data/lib/generators/scrivito/install/install_generator.rb +12 -0
  19. data/lib/generators/scrivito/install/templates/config/initializers/scrivito.rb +3 -0
  20. data/lib/scrivito/attribute_definition.rb +13 -1
  21. data/lib/scrivito/attribute_deserializer.rb +4 -4
  22. data/lib/scrivito/attribute_value_renderer.rb +79 -0
  23. data/lib/scrivito/backend/obj_data_cache.rb +15 -15
  24. data/lib/scrivito/backend/obj_data_from_rest.rb +1 -21
  25. data/lib/scrivito/backend/obj_query.rb +2 -2
  26. data/lib/scrivito/basic_obj.rb +17 -6
  27. data/lib/scrivito/binary.rb +13 -5
  28. data/lib/scrivito/cache/chainable.rb +10 -5
  29. data/lib/scrivito/cache/file_store.rb +4 -0
  30. data/lib/scrivito/cache/ram_store.rb +4 -0
  31. data/lib/scrivito/child_list_tag.rb +16 -14
  32. data/lib/scrivito/client_error.rb +10 -0
  33. data/lib/scrivito/cms_backend.rb +66 -291
  34. data/lib/scrivito/cms_data_cache.rb +7 -9
  35. data/lib/scrivito/cms_dispatch_controller.rb +1 -10
  36. data/lib/scrivito/cms_field_tag.rb +2 -1
  37. data/lib/scrivito/cms_rest_api.rb +2 -0
  38. data/lib/scrivito/cms_routing.rb +26 -22
  39. data/lib/scrivito/configuration.rb +38 -0
  40. data/lib/scrivito/connection_manager.rb +69 -0
  41. data/lib/scrivito/controller_actions.rb +2 -2
  42. data/lib/scrivito/controller_helper.rb +2 -2
  43. data/lib/scrivito/date_attribute.rb +4 -1
  44. data/lib/scrivito/deprecation.rb +5 -4
  45. data/lib/scrivito/editing_context.rb +2 -2
  46. data/lib/scrivito/errors.rb +17 -0
  47. data/lib/scrivito/image_tag.rb +2 -2
  48. data/lib/scrivito/link_parser.rb +10 -5
  49. data/lib/scrivito/meta_data_collection.rb +11 -0
  50. data/lib/scrivito/obj_collection.rb +1 -1
  51. data/lib/scrivito/obj_facet_value.rb +19 -7
  52. data/lib/scrivito/obj_params_parser.rb +43 -14
  53. data/lib/scrivito/obj_search_builder.rb +1 -2
  54. data/lib/scrivito/obj_search_enumerator/batch.rb +22 -0
  55. data/lib/scrivito/obj_search_enumerator/batch_iterator.rb +36 -0
  56. data/lib/scrivito/obj_search_enumerator/query_executor.rb +35 -0
  57. data/lib/scrivito/obj_search_enumerator.rb +218 -93
  58. data/lib/scrivito/obj_update_params_parser.rb +1 -0
  59. data/lib/scrivito/preset_routes.rb +25 -0
  60. data/lib/scrivito/revision.rb +13 -11
  61. data/lib/scrivito/route.rb +62 -0
  62. data/lib/scrivito/routing_extensions.rb +92 -0
  63. data/lib/scrivito/sdk_engine.rb +4 -0
  64. data/lib/scrivito/task.rb +77 -0
  65. data/lib/scrivito/type_computer.rb +3 -3
  66. data/lib/scrivito/ui_config.rb +4 -0
  67. data/lib/scrivito/user.rb +24 -18
  68. data/lib/scrivito/workspace/publish_checker.rb +2 -3
  69. data/lib/scrivito/workspace.rb +38 -6
  70. data/lib/scrivito/workspace_data.rb +0 -14
  71. metadata +14 -10
  72. data/lib/scrivito/content_service.rb +0 -121
  73. data/lib/scrivito/content_state.rb +0 -109
  74. data/lib/scrivito/content_state_caching.rb +0 -47
  75. data/lib/scrivito/content_state_visitor.rb +0 -19
  76. data/lib/scrivito/obj_data_from_service.rb +0 -63
  77. data/lib/scrivito/workspace_data_from_service.rb +0 -43
@@ -210,6 +210,7 @@ module Scrivito
210
210
  self.default_image_transformation = {}
211
211
  self.choose_homepage_callback = -> (env) { Obj.root }
212
212
  self.find_user_proc = nil
213
+ self.inject_preset_routes = true
213
214
  reset_editing_auth_callback!
214
215
  end
215
216
 
@@ -231,11 +232,48 @@ module Scrivito
231
232
 
232
233
 
233
234
  # @api public
235
+ # @deprecated The legacy routing is deprecated and will be removed in the next major
236
+ # version of Scrivito.
234
237
  #
235
238
  # Scrivito changed its routing to a slug first url scheme. This configuration option
236
239
  # allows you to switch back to the old id first url scheme.
237
240
  attr_accessor :legacy_routing
238
241
 
242
+ def legacy_routing=(enabled)
243
+ if enabled
244
+ Scrivito::Deprecation.warn(%[
245
+ The legacy routing is deprecated and will be removed in the next major version of Scrivito.
246
+ Please use the new scrivito_route api to replicate the legacy routes:
247
+ scrivito_route '/', using: 'homepage', via: :all
248
+ scrivito_route '(/):id/*slug', using: 'slug_id', via: :all
249
+ scrivito_route '/*permalink', using: 'permalink', via: all
250
+ ])
251
+ end
252
+ @legacy_routing = enabled
253
+ end
254
+
255
+ # @api public
256
+ #
257
+ # The +inject_preset_routes+ configuration is enabled by default and adds the default
258
+ # Scrivito routing to your application routes. It can be disabled by setting it to
259
+ # +false+ which lets you use {RoutingExtensions#scrivito_route scrivito_route} to
260
+ # customize the routing used by Scrivito.
261
+ attr_accessor :inject_preset_routes
262
+
263
+ def scrivito_route_enabled?
264
+ inject_preset_routes == false
265
+ end
266
+
267
+ def with_scrivito_route_enabled
268
+ begin
269
+ initial_value = inject_preset_routes
270
+ self.inject_preset_routes = false
271
+ yield
272
+ ensure
273
+ self.inject_preset_routes = initial_value
274
+ end
275
+ end
276
+
239
277
  attr_accessor :choose_homepage_callback, :check_batch_size
240
278
 
241
279
  #
@@ -61,11 +61,18 @@ module Scrivito
61
61
  end
62
62
  end
63
63
 
64
+ # for testing purposes
65
+ def connect_count
66
+ @connect_count || 0
67
+ end
68
+
64
69
  private
65
70
 
66
71
  attr_accessor :connection
67
72
 
68
73
  def ensure_started(timeout)
74
+ cleanup_dead_connection
75
+
69
76
  if @connection && @connection.started?
70
77
  configure_timeout(@connection, timeout)
71
78
  else
@@ -76,13 +83,75 @@ module Scrivito
76
83
  conn.cert_store = self.class.cert_store
77
84
  end
78
85
  configure_timeout(conn, timeout)
86
+
87
+ # keep_alive_timeout is not a timeout as in "wait no longer than X",
88
+ # but specifies how long a connection will be reused if it was idle.
89
+ # therefore the 'timeout' parameter is not applied here.
90
+ # instead we use a large value to maximize connection reuse.
91
+ # (ELB uses a keep alive timeout of 60 seconds).
92
+ conn.keep_alive_timeout = 59
93
+
79
94
  retry_twice_on_socket_error do
80
95
  conn.start
81
96
  end
97
+
98
+ increase_connect_count
82
99
  @connection = conn
83
100
  end
84
101
  end
85
102
 
103
+ def increase_connect_count
104
+ @connect_count ||= 0
105
+ @connect_count += 1
106
+ end
107
+
108
+ def cleanup_dead_connection
109
+ ensure_finished if connection_error?
110
+ end
111
+
112
+ # test if the http server closed the connection
113
+ # (i.e. due to a server-side keep alive timeout)
114
+ #
115
+ # if the server closes a connection, the socket of the connection
116
+ # is at EOF (End-Of-File). Using the connection would cause an error.
117
+ # Unfortunatly net/http does not detect this.
118
+ #
119
+ # Running into an error would be okay-ish for idempotent http requests,
120
+ # since retry is possible, but for non-idempotent requests (POST) the error
121
+ # would raise to the user.
122
+ #
123
+ # So we have to hack our way around net/http to access the socket directly.
124
+ # In order to do that, internals of net/http are accessed.
125
+ # If these internals change in future ruby versions, this method should
126
+ # simply returns `nil` (i.e. connection error are not detected)
127
+ # but not raise errors.
128
+ def connection_error?
129
+ return unless @connection
130
+
131
+ # compare: http://git.io/vltBR
132
+ socket = @connection.instance_variable_get("@socket")
133
+ return unless socket
134
+
135
+ # compare: http://git.io/vltRu
136
+ io = socket.try(:io)
137
+ return unless io && io.respond_to?(:read_nonblock)
138
+
139
+ begin
140
+ io.read_nonblock(1)
141
+
142
+ # if read_nonblock returns, there is unexpected data in the socket
143
+ # this indicates a connection problem, since http servers should never
144
+ # send data unless the client requests it.
145
+ true
146
+ rescue IO::WaitReadable, IO::WaitWritable
147
+ # these "errors" indicate the connection is still usable
148
+ false
149
+ rescue => e
150
+ # other errors indicate the connection has a problem
151
+ true
152
+ end
153
+ end
154
+
86
155
  def ensure_finished
87
156
  @connection.finish if @connection && @connection.started?
88
157
  @connection = nil
@@ -163,8 +163,8 @@ module ControllerActions
163
163
  #
164
164
  def deliver_file
165
165
  if binary = @obj.binary
166
- url = BinaryRouting.new(request, scrivito_engine).resolved_binary_obj_url(@obj, binary)
167
- redirect_to CmsRouting.match_protocol(url, request)
166
+ binary_routing = BinaryRouting.new(request, scrivito_engine)
167
+ redirect_to binary_routing.resolved_binary_obj_url(@obj, binary)
168
168
  else
169
169
  render text: 'Empty Blob', status: 404
170
170
  end
@@ -20,7 +20,7 @@ module Scrivito
20
20
  # @note +scrivito_path+ is also a helper method.
21
21
  #
22
22
  def scrivito_path(target, options = {})
23
- CmsRouting.new(request, main_app, scrivito_engine).path_or_url(target, "path", options)
23
+ CmsRouting.new(request, self, scrivito_engine).path_or_url(target, "path", options)
24
24
  end
25
25
 
26
26
  #
@@ -38,7 +38,7 @@ module Scrivito
38
38
  # @note +scrivito_url+ is also a helper method.
39
39
  #
40
40
  def scrivito_url(target, options = {})
41
- CmsRouting.new(request, main_app, scrivito_engine).path_or_url(target, "url", options)
41
+ CmsRouting.new(request, self, scrivito_engine).path_or_url(target, "url", options)
42
42
  end
43
43
 
44
44
  #
@@ -17,7 +17,10 @@ module Scrivito
17
17
 
18
18
  DateTime.iso8601(iso8601_date_time).in_time_zone
19
19
  rescue ArgumentError
20
- raise "The value is not a valid ISO 8601 date time: #{iso8601_date_time.inspect}"
20
+ raise Scrivito::ClientError.new(
21
+ "The value is not a valid ISO 8601 date time: #{iso8601_date_time.inspect}",
22
+ 400
23
+ )
21
24
  end
22
25
 
23
26
  def self.serialize_for_backend(attribute_value)
@@ -5,14 +5,15 @@ module Scrivito
5
5
  ActiveSupport::Deprecation.warn(message, source)
6
6
  end
7
7
 
8
- def warn_method(depricated_method_name, new_method_name = nil)
9
- warn(warn_method_message(depricated_method_name, new_method_name), caller(1))
8
+ def warn_method(deprecated_method_name, new_method_name = nil)
9
+ warn(warn_method_message(deprecated_method_name, new_method_name), caller(1))
10
10
  end
11
11
 
12
12
  private
13
13
 
14
- def warn_method_message(depricated_method_name, new_method_name = nil)
15
- message = "The method #{depricated_method_name} will be removed from the Scrivito SDK. "
14
+ def warn_method_message(deprecated_method_name, new_method_name = nil)
15
+ message = "The method #{deprecated_method_name} is deprecated and will be removed "\
16
+ "from the Scrivito SDK in the next major version. "
16
17
  message << "Please use #{new_method_name} instead" if new_method_name
17
18
  message
18
19
  end
@@ -77,8 +77,8 @@ class EditingContext
77
77
 
78
78
  def to_params
79
79
  {
80
- '_scrivito_display_mode' => display_mode,
81
- '_scrivito_workspace_id' => selected_workspace.id,
80
+ _scrivito_display_mode: display_mode,
81
+ _scrivito_workspace_id: selected_workspace.id,
82
82
  }
83
83
  end
84
84
 
@@ -4,8 +4,25 @@ module Scrivito
4
4
  class ScrivitoError < StandardError
5
5
  end
6
6
 
7
+ class ApplicationError < ScrivitoError
8
+ def http_code
9
+ 412
10
+ end
11
+
12
+ def as_json
13
+ {message: message, message_for_editor: message}
14
+ end
15
+ end
16
+
7
17
  # @api public
8
18
  class ResourceNotFound < ScrivitoError
19
+ def http_code
20
+ 404
21
+ end
22
+
23
+ def as_json
24
+ {message: message}
25
+ end
9
26
  end
10
27
 
11
28
  # @api public
@@ -37,13 +37,13 @@ class ImageTag < Struct.new(:view_context)
37
37
  BinaryRouting.new(request, scrivito_engine)
38
38
  .binary_obj_url(obj_or_widget, attribute_value, image_options)
39
39
  else
40
- path = CmsRouting.new(request, main_app, scrivito_engine, image_options)
40
+ path = CmsRouting.new(request, view_context, scrivito_engine, image_options)
41
41
  .path_or_url(attribute_value, :path)
42
42
  path unless path == CmsRouting::LINK_TO_EMPTY_BLOB
43
43
  end
44
44
  end
45
45
 
46
- delegate :request, :main_app, :scrivito_engine, to: :view_context
46
+ delegate :request, :scrivito_engine, to: :view_context
47
47
  end
48
48
 
49
49
  end
@@ -63,9 +63,9 @@ module Scrivito
63
63
  end
64
64
 
65
65
  def find_obj(uri)
66
- return nil unless application_uri?(uri) && application_route?(uri)
66
+ return nil unless application_uri?(uri)
67
67
 
68
- route_params = route(uri)
68
+ route_params = obj_route_params(uri)
69
69
 
70
70
  if id = route_params[:id]
71
71
  Obj.find(id)
@@ -75,9 +75,14 @@ module Scrivito
75
75
  rescue Scrivito::ResourceNotFound
76
76
  end
77
77
 
78
- def application_route?(uri)
79
- route_params = route(uri)
80
- route_params && route_params[:controller] == 'scrivito/cms_dispatch'
78
+ def obj_route_params(uri)
79
+ dispatch_route_for(sdk_route(uri)) || dispatch_route_for(route(uri)) || {}
80
+ end
81
+
82
+ def dispatch_route_for(route_params)
83
+ if route_params && route_params[:controller] == 'scrivito/cms_dispatch'
84
+ route_params
85
+ end
81
86
  end
82
87
 
83
88
  def sdk_route(uri)
@@ -38,6 +38,17 @@ module Scrivito
38
38
  # [+iptc_state+] The state in which the image was produced.
39
39
  # [+iptc_country_name+] The country in which the image was produced.
40
40
  # [+iptc_country_code+] The code of the country in which the image was produced.
41
+ #
42
+ # == Searching for meta data
43
+ #
44
+ # Meta data is taken into account when searching. See the
45
+ # {Scrivito::ObjSearchEnumerator ObjSearchEnumerator} for details.
46
+ #
47
+ # @example Search for all JPEGs
48
+ # Image.where('blob:content_type', :equals, 'image/jpeg')
49
+ #
50
+ # @example Search for wide images
51
+ # Image.where('blob:width', :is_greater_than, 1024)
41
52
  class MetaDataCollection
42
53
  def initialize(attributes)
43
54
  @attributes = ActiveSupport::HashWithIndifferentAccess.new(attributes)
@@ -85,7 +85,7 @@ module Scrivito
85
85
  # @return [ObjSearchEnumerator]
86
86
  # @api public
87
87
  def all
88
- ObjSearchEnumerator.new(workspace).batch_size(1000)
88
+ ObjSearchEnumerator.new(workspace, 1000)
89
89
  end
90
90
 
91
91
  # Returns an {ObjSearchEnumerator} of all {BasicObj Obj}s with the given +obj_class+.
@@ -1,6 +1,9 @@
1
1
  module Scrivito
2
+ #
2
3
  # Instances of this class represent the result of a faceted search.
3
- # @api beta
4
+ #
5
+ # @api public
6
+ #
4
7
  class ObjFacetValue
5
8
 
6
9
  def initialize(value, total, included_objs = [])
@@ -9,32 +12,41 @@ module Scrivito
9
12
  @included_objs = included_objs
10
13
  end
11
14
 
12
- # @api beta
15
+ #
13
16
  # The value of the attribute name of this ObjFacetValue.
14
17
  #
18
+ # @api public
15
19
  # @return [String]
20
+ #
16
21
  def name
17
22
  @name
18
23
  end
19
24
 
20
- # @api beta
25
+ #
21
26
  # Total number of Objs available that have this value.
22
27
  #
23
- # Note that this refers to all Objs,
24
- # not just the Objs included in this search.
28
+ # @api public
29
+ #
30
+ # Note that this refers to all Objs, not just the Objs included in this search.
25
31
  # Also note that the count is approximate.
32
+ #
26
33
  # @return [Integer]
34
+ #
27
35
  def count
28
36
  @count
29
37
  end
30
38
 
31
- # @api beta
39
+ #
32
40
  # The Objs that were included in this search.
33
41
  #
34
- # if you did not specify +include_objs+ in your facet options,
42
+ # @api public
43
+ #
44
+ # If you did not specify +include_objs+ in your facet options,
35
45
  # an empty array is returned.
36
46
  # The Objs are ordered by relevance.
47
+ #
37
48
  # @return [Array<BasicObj>]
49
+ #
38
50
  def included_objs
39
51
  @included_objs
40
52
  end
@@ -28,26 +28,55 @@ module Scrivito
28
28
  def parse_widgetlist_params(params)
29
29
  params.map do |widget_id_or_params|
30
30
  if widget_id_or_params.is_a?(Hash)
31
- action, widget_params = widget_id_or_params.flatten
32
- case action
33
- when 'create' then Widget.new(widget_params, context.slice(:scrivito_user))
34
- when 'copy'
35
- widget_id = widget_params['widget_id']
36
- widget = Workspace.find(widget_params['workspace_id'])
37
- .objs.find(widget_params['obj_id'])
38
- .widgets[widget_id]
39
- raise ResourceNotFound, "Could not find Widget with id #{widget_id}" unless widget
40
- widget.copy
41
- else raise UnknownWidgetAction
42
- end
31
+ parse_widgetlist_params_with_action(widget_id_or_params)
43
32
  else
44
- obj.widgets[widget_id_or_params]
33
+ widget = obj.widgets[widget_id_or_params]
34
+ raise_widget_not_found_error(widget_id_or_params) unless widget
35
+ widget
45
36
  end
46
37
  end
47
38
  end
48
39
 
40
+ def parse_widgetlist_params_with_action(params)
41
+ action, widget_params = params.flatten
42
+ case action
43
+ when 'create' then Widget.new(widget_params, context.slice(:scrivito_user))
44
+ when 'copy'
45
+ widget_id = widget_params['widget_id']
46
+ widget = Workspace.find(widget_params['workspace_id'])
47
+ .objs.find(widget_params['obj_id'])
48
+ .widgets[widget_id]
49
+ raise_widget_not_found_error(widget_id) unless widget
50
+ widget.copy
51
+ else raise UnknownWidgetAction
52
+ end
53
+ end
54
+
49
55
  def parse_binary_params(params)
50
- UploadedBinary.new(params) if params
56
+ return unless params
57
+
58
+ if params[:copy]
59
+ parse_copy_binary_params(params[:copy])
60
+ else
61
+ UploadedBinary.new(params)
62
+ end
63
+ end
64
+
65
+ def parse_copy_binary_params(params)
66
+ obj = Obj.find(params[:obj_id])
67
+
68
+ if widget_id = params[:widget_id]
69
+ widget = obj.widgets[widget_id]
70
+ raise_widget_not_found_error(widget_id) unless widget
71
+ end
72
+
73
+ if binary = (widget || obj)[params[:attribute_name]]
74
+ binary.copy(filename: params[:filename], content_type: params[:content_type])
75
+ end
76
+ end
77
+
78
+ def raise_widget_not_found_error(widget_id)
79
+ raise ResourceNotFound, "Could not find Widget with id #{widget_id}"
51
80
  end
52
81
  end
53
82
  end
@@ -40,8 +40,7 @@ class ObjSearchBuilder < Struct.new(:query)
40
40
  end
41
41
 
42
42
  def set_order
43
- enumerator.order(query[:order]) if query[:order]
44
- enumerator.reverse_order if query[:reverse_order]
43
+ enumerator.order(Hash[*query[:order]]) if query[:order]
45
44
  end
46
45
 
47
46
  def set_batch_size
@@ -0,0 +1,22 @@
1
+ module Scrivito
2
+ class ObjSearchEnumerator
3
+ class Batch
4
+ def initialize(objs, total, continuation, facets, previously_fetched_ids)
5
+ @objs = filter_duplicates(objs, previously_fetched_ids)
6
+ @total = total
7
+ @continuation = continuation
8
+ @facets = facets
9
+ end
10
+
11
+ attr_reader :objs, :total, :continuation, :facets
12
+
13
+ private
14
+
15
+ def filter_duplicates(objs, previously_fetched_ids)
16
+ objs.reject do |obj|
17
+ previously_fetched_ids.include?(obj.id)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,36 @@
1
+ module Scrivito
2
+ class ObjSearchEnumerator
3
+ class BatchIterator
4
+ def initialize(workspace, query, initial_batch)
5
+ @query = query
6
+ @executor = ObjSearchEnumerator::QueryExecutor.new(workspace)
7
+ @all_fetched_ids = Set.new
8
+ @initial_batch = initial_batch
9
+ end
10
+
11
+ attr_reader :total
12
+
13
+ def each
14
+ current_batch = initial_batch || fetch_batch
15
+
16
+ loop do
17
+ yield current_batch
18
+ break unless current_batch.continuation
19
+ current_batch = fetch_batch(current_batch.continuation)
20
+ end
21
+
22
+ @total = @all_fetched_ids.size
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :query, :executor, :initial_batch
28
+
29
+ def fetch_batch(continuation=nil)
30
+ batch = executor.call(query, continuation, @all_fetched_ids)
31
+ @all_fetched_ids += batch.objs.map(&:id)
32
+ batch
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ module Scrivito
2
+ class ObjSearchEnumerator
3
+ class QueryExecutor
4
+ def initialize(workspace)
5
+ @workspace = workspace
6
+ end
7
+
8
+ def call(query, continuation=nil, fetched_ids=[])
9
+ query['continuation'] = continuation if continuation
10
+ response = CmsBackend.instance.search_objs(workspace, query)
11
+
12
+ ObjSearchEnumerator::Batch.new(
13
+ build_objs(response),
14
+ response['total'].to_i,
15
+ response['continuation'],
16
+ response['facets'],
17
+ fetched_ids
18
+ )
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :workspace
24
+
25
+ def build_objs(response)
26
+ obj_ids = response['results'].map { |result| result['id'] }
27
+ if obj_ids.present?
28
+ workspace.objs.find_including_deleted(obj_ids)
29
+ else
30
+ []
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end