scrivito_sdk 0.71.2 → 0.90.0.rc1

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.
@@ -1,18 +1,52 @@
1
1
  module Scrivito
2
+ # @api public
2
3
  #
3
4
  # This class represents a collection of meta data attributes.
4
5
  #
5
- # @api beta
6
+ # == Available meta data attributes
7
+ # All binaries have the meta attributes +content_length+ and +content_type+, in addition, PDFs
8
+ # and images have attributes specfic to their type.
6
9
  #
10
+ # If not specified otherwise, the attributes contain +string+ values.
11
+ #
12
+ # [+content_length (Number)+] Size in bytes of the given file.
13
+ # [+content_type+] MIME-type of the given file.
14
+ #
15
+ # == Available meta data attributes for PDFs
16
+ #
17
+ # [+text+] Text content of the PDF document.
18
+ #
19
+ # == Available meta data attributes for Images
20
+ #
21
+ # The meta data attributes starting with +iptc_+ or +exif_+ are extracted from the image itself
22
+ # and therefore may not be available for every image. +Iptc+ and +exif+ are both standardized
23
+ # formats supported by a wide array of software and hardware, like cameras and phones.
24
+ #
25
+ # [+width (Number)+] Width in pixels.
26
+ # [+height (Number)+] Height in pixels.
27
+ # [+exif_copyright+] Copyright of the image.
28
+ # [+exif_date_time+] The date at which the image was produced.
29
+ #
30
+ # [+iptc_keywords (Array<String>)+ ] A list of keywords associated with the image.
31
+ # [+iptc_headline+] The headline of the image.
32
+ # [+iptc_copyright+] Copyright of the image.
33
+ # [+iptc_byline+] The name of the image creator.
34
+ # [+iptc_credit+] Contains the persons or companies that should be credited.
35
+ # [+iptc_source+] The original copyright holder.
36
+ # [+iptc_profile+] The color profile of the image.
37
+ # [+iptc_city+] The city in which the image was produced.
38
+ # [+iptc_state+] The state in which the image was produced.
39
+ # [+iptc_country_name+] The country in which the image was produced.
40
+ # [+iptc_country_code+] The code of the country in which the image was produced.
7
41
  class MetaDataCollection
8
42
  def initialize(attributes)
9
43
  @attributes = ActiveSupport::HashWithIndifferentAccess.new(attributes)
10
44
  end
11
45
 
46
+ # @api public
12
47
  #
13
48
  # Find value of a meta data attribute.
14
49
  #
15
- # @api beta
16
50
  # @param name [Symbol, String] the name of the meta data attribute.
17
51
  # @return [String, Array, Fixnum, Date, nil] meta data attribute value if found or +nil+ otherwise.
18
52
  def [](name)
@@ -29,21 +29,38 @@ module Scrivito
29
29
  find_using(id_or_list, :find_by_including_deleted)
30
30
  end
31
31
 
32
- # Find the {BasicObj Obj} that has the given path.
33
- # Returns +nil+ if no matching object exists.
34
- # @param [String] path Path of the {BasicObj Obj}.
35
- # @return [Obj,NilClass]
32
+ #
33
+ # Find the {Scrivito::BasicObj Obj} that has the given +path+.
34
+ #
36
35
  # @api public
36
+ #
37
+ # @note If there is more than one {Scrivito::BasicObj Obj} with the given +path+,
38
+ # then the {Scrivito::BasicObj Obj} with the smallest +id+ will be returned.
39
+ #
40
+ # @param [String] path Path of the {Scrivito::BasicObj Obj}.
41
+ #
42
+ # @return [Scrivito::BasicObj] if an {Scrivito::BasicObj Obj} with the given +path+ exists.
43
+ # @return [NilClass] if no {Scrivito::BasicObj Obj} with the given +path+ exists.
44
+ #
37
45
  def find_by_path(path)
38
- find_by(:path, [path]).first.first
46
+ find_by(:path, [path]).first.min_by(&:id)
39
47
  end
40
48
 
41
- # Returns the {BasicObj Obj} that has the given permalink, or +nil+ if no matching object exists.
42
- # @param [String] permalink The permalink of the {BasicObj Obj}.
43
- # @return [Obj,NilClass]
49
+ #
50
+ # Returns the {Scrivito::BasicObj Obj} that has the given +permalink+.
51
+ #
44
52
  # @api public
53
+ #
54
+ # @note If there is more than one {Scrivito::BasicObj Obj} with the given +permalink+,
55
+ # then the {Scrivito::BasicObj Obj} with the smallest +id+ will be returned.
56
+ #
57
+ # @param [String] permalink The permalink of the {BasicObj Obj}.
58
+ #
59
+ # @return [Scrivito::BasicObj] if an {Scrivito::BasicObj Obj} with the given +permalink+ exists.
60
+ # @return [NilClass] if no {Scrivito::BasicObj Obj} with the given +permalink+ exists.
61
+ #
45
62
  def find_by_permalink(permalink)
46
- find_by(:permalink, [permalink]).first.first
63
+ find_by(:permalink, [permalink]).first.min_by(&:id)
47
64
  end
48
65
 
49
66
  # Returns an {ObjSearchEnumerator} with the given initial subquery consisting of the four arguments.
@@ -0,0 +1,40 @@
1
+ # Instances of this class represent the result of a faceted search.
2
+ # @api beta
3
+ class ObjFacetValue
4
+
5
+ def initialize(value, total, included_objs = [])
6
+ @name = value
7
+ @count = total
8
+ @included_objs = included_objs
9
+ end
10
+
11
+ # @api beta
12
+ # The value of the attribute name of this ObjFacetValue.
13
+ #
14
+ # @return [String]
15
+ def name
16
+ @name
17
+ end
18
+
19
+ # @api beta
20
+ # Total number of Objs available that have this value.
21
+ #
22
+ # Note that this refers to all Objs,
23
+ # not just the Objs included in this search.
24
+ # Also note that the count is approximate.
25
+ # @return [Integer]
26
+ def count
27
+ @count
28
+ end
29
+
30
+ # @api beta
31
+ # The Objs that were included in this search.
32
+ #
33
+ # if you did not specify +include_objs+ in your facet options,
34
+ # an empty array is returned.
35
+ # The Objs are ordered by relevance.
36
+ # @return [Array<BasicObj>]
37
+ def included_objs
38
+ @included_objs
39
+ end
40
+ end
@@ -22,23 +22,19 @@ module Scrivito
22
22
  # [+:id+] Id of an {Scrivito::BasicObj Obj}. This is a +string+ field.
23
23
  # [+:_path+] Path of an {Scrivito::BasicObj Obj}. This is a +string+ field.
24
24
  # [+:_name+] Name of an {Scrivito::BasicObj Obj}. This is a +string+ field.
25
- # [+:title+] Title of an {Scrivito::BasicObj Obj}. This is a +string+ field.
26
- # [+:body+] Body of an {Scrivito::BasicObj Obj}. This is an +html+ field. Thus, only the
27
- # +contains+ and +contains_prefix+ operators can be applied to this field.
28
25
  # [+:_obj_class+] Object class of an {Scrivito::BasicObj Obj}. This is a +string+ field.
29
26
  # [+:_permalink+] Permalink of an {Scrivito::BasicObj Obj}. This is a +string+ field.
30
27
  # [+:_last_changed+] Date of last change of an {Scrivito::BasicObj Obj}.
31
28
  # [every +_:custom_attribute_+] Custom attribute of an {Scrivito::BasicObj Obj}. Note that depending on the attribute type (e.g. an +html+ field), some operators cannot be applied.
32
29
  #
33
- # All values are stored as strings.
30
+ # == Currently available operators
34
31
  #
35
- # Date values are stored in the format +YYYYMMDDHHMMSS+ in UTC. For example, 2000-01-01 00:00:00
36
- # UTC is stored as "+20000101000000+". This is relevant for string comparisons in which the
37
- # +is_less_than+ and +is_greater_than+ operators are used.
32
+ # === +contains+ and +contains_prefix+
38
33
  #
39
- # == Currently available operators
34
+ # These operators are intended for full text search of natural language texts.
35
+ # They are applicable to +string+, +stringlist+, +enum+, +multienum+ and +html+ fields.
40
36
  #
41
- # For +:contains+ and +:contains_prefix+, the examples are based on the following field value:
37
+ # For +contains+ and +contains_prefix+, the examples are based on the following field value:
42
38
  # "Behind every cloud is another cloud."
43
39
  #
44
40
  # [+:contains+] Searches for one or more whole words. Each word needs to be present.
@@ -50,7 +46,7 @@ module Scrivito
50
46
  # ✘ "behi clo" (not whole words)
51
47
  #
52
48
  # ✘ "behind everything" (second word does not match)
53
- # [+:contains_prefix+] Searches for one prefix. A whole word also counts as a prefix.
49
+ # [+:contains_prefix+] Searches for a word prefix.
54
50
  #
55
51
  # Example subquery values:
56
52
  #
@@ -58,12 +54,29 @@ module Scrivito
58
54
  #
59
55
  # ✔ "Every" (case insensitive)
60
56
  #
61
- # For +:equals+ and +:starts_with+, the examples are based on the following field value:
57
+ # === +equals+ and +starts_with+
58
+ #
59
+ # These operators are intended for programmatic comparions of string and date values.
60
+ #
61
+ # The +equals+ and +prefix+ operators have some limits with regard to string length.
62
+ # String values are only guaranteed to be considered if they are at most 1000 characters in length.
63
+ # String values of more than 1000 characters may be ignored by these operators.
64
+ #
65
+ # The +prefix+ operator also has a precision limit:
66
+ # Only prefixes of up to 20 characters are guaranteed to be matched.
67
+ # If you supply a prefix of more than 20 characters, the additional characters may be ignored.
68
+ #
69
+ # When combined with the system attribute +_path+, the operator +prefix+ has some special functionality:
70
+ # There is not precision limit, i.e. a prefix of arbitrary length may be used to match on +_path+.
71
+ # Also, prefix matching on +_path+ automatically matches entire path components,
72
+ # i.e. the prefix matching is delimited by slashes (the character +'/'+).
73
+ #
74
+ # For +equals+ and +starts_with+, the examples are based on the following field value:
62
75
  # "Some content."
63
76
  #
64
77
  # [+:equals+] The +field+ value needs to be identical to the +value+ of this subquery.
65
78
  #
66
- # Only applicable to +string+, +enum+, +multienum+ and +date+ fields.
79
+ # Applicable to +string+, +stringlist+, +enum+, +multienum+ and +date+ fields.
67
80
  #
68
81
  # Example subquery values:
69
82
  #
@@ -73,7 +86,7 @@ module Scrivito
73
86
  #
74
87
  # [+:starts_with+] The +field+ value needs to start exactly with the +value+ of this subquery.
75
88
  #
76
- # Only applicable to +string+, +enum+, +multienum+ and +date+ fields.
89
+ # Applicable to +string+, +stringlist+, +enum+ and +multienum+ fields.
77
90
  #
78
91
  # Example subquery values:
79
92
  #
@@ -83,28 +96,35 @@ module Scrivito
83
96
  #
84
97
  # ✘ "content" (not prefix of the whole value)
85
98
  #
86
- # For +:is_less_than+ and +:is_greater_than+, the examples are based on the following field value (date string):
87
- # "20000101000000"
99
+ # === +is_less_than+ and +is_greater_than+
88
100
  #
89
- # [+:is_less_than+] Matches if the field string value is less than the subquery string value.
101
+ # These operators are intended for comparions on +date+ attributes and on numerical metadata, for example the width of an image.
90
102
  #
91
- # Only applicable to +string+, +enum+, +multienum+ and +date+ fields.
103
+ # For +is_less_than+ and +is_greater_than+, the examples are based on the following date value:
104
+ # +Time.new(2000,01,01,00,00,00)+
92
105
  #
93
- # Example subquery values:
106
+ # [+:is_less_than+] Matches if the field value is less than the subquery string value.
94
107
  #
95
- # "19991231235959" (is less than "20000101000000")
108
+ # Example subquery values:
96
109
  #
97
- # "20000101000000" (equal, not less than)
110
+ # +Time.new(1999,12,31,23,59,59)+ (is less than)
98
111
  #
99
- # [+:is_greater_than+] Matches if the field string value is greater than the subquery string value.
112
+ # +Time.new(2000,01,01,00,00,00)+ (equal, not less than)
100
113
  #
101
- # Only applicable to +string+, +enum+, +multienum+ and +date+ fields.
114
+ # [+:is_greater_than+] Matches if the field value is greater than the subquery string value.
102
115
  #
103
116
  # Example subquery values:
104
117
  #
105
- # ✔ "20000101000001" (is greater than "20000101000000")
118
+ # ✔ +Time.new(2000,01,01,00,00,01)+ (is greater than)
119
+ #
120
+ # ✘ +Time.new(2000,01,01,00,00,00)+ (equal, not greater than)
106
121
  #
107
- # "20000101000000" (equal, not greater than)
122
+ # == Matching +multienum+ and +stringlist+
123
+ #
124
+ # Attributes of type +multienum+ and +stringlist+ contain an array of strings.
125
+ # Each of these strings is searched individually.
126
+ # A search query matches a +multienum+ or +stringlist+, if at least one string in the list matches.
127
+ # Example: A query using the operator +:equals+ and the value +"Eggs"+ matches an Obj containing +["Spam","Eggs"]+ in a +stringlist+ or +multienum+ attribute.
108
128
  #
109
129
  # @api public
110
130
  class ObjSearchEnumerator
@@ -128,7 +148,7 @@ module Scrivito
128
148
  # @param [Symbol, String, Array<Symbol, String>] field Name(s) of the field(s) to be searched.
129
149
  # For arrays, the subquery matches if one or more of these fields meet this criterion.
130
150
  # @param [Symbol, String] operator See "Currently available operators" above.
131
- # @param [String, Array<String>] value The value(s) to compare with the field value(s) using the
151
+ # @param [String, Date, Time, Array<String, Date, Time>] value The value(s) to compare with the field value(s) using the
132
152
  # +operator+ of this subquery. For arrays, the subquery matches if the condition is met for
133
153
  # one or more of the array elements.
134
154
  # @param [Hash] boost A hash where the keys are field names and their values are boosting
@@ -164,7 +184,7 @@ module Scrivito
164
184
  # @param [Symbol, String] operator Only applicable to subqueries in which the +equals+,
165
185
  # +starts_with+, +is_greater_than+ or +is_less_than+ operator is used.
166
186
  # (See "Currently available operators" above).
167
- # @param [String, Array<String>] value The value(s) to compare with the field value(s) using the
187
+ # @param [String, Date, Time, Array<String, Date, Time>] value The value(s) to compare with the field value(s) using the
168
188
  # +operator+ of this subquery. For arrays, the subquery matches if the condition is met for
169
189
  # one or more of the array elements.
170
190
  # @return [Scrivito::ObjSearchEnumerator]
@@ -184,6 +204,12 @@ module Scrivito
184
204
  end
185
205
 
186
206
  # Orders the results by +field_name+.
207
+ #
208
+ # Applicable to the attribute types +string+, +enum+ and +date+.
209
+ #
210
+ # There is a precision limit when sorting string values:
211
+ # Only the first 50 characters of a string are guaranteed to be considered when sorting search results.
212
+ #
187
213
  # @param [Symbol, String] field_name This parameter specifies the field by which the hits are
188
214
  # sorted (e.g. +:_path+).
189
215
  # @return [Scrivito::ObjSearchEnumerator]
@@ -206,7 +232,12 @@ module Scrivito
206
232
  end
207
233
 
208
234
  # Number of search results to be returned by each of the internal search requests.
209
- # The default is +10+. The server may reduce large batches to a reasonable size.
235
+ # The default is +10+.
236
+ #
237
+ # Scrivito makes a best effort to return the given number of search results,
238
+ # but may under certain circumstances return larger or smaller batches due to technical
239
+ # reasons.
240
+ #
210
241
  # @param [Integer] size A value in the range from +1+ to +100+.
211
242
  # @return [Scrivito::ObjSearchEnumerator]
212
243
  # @api public
@@ -259,6 +290,11 @@ module Scrivito
259
290
  end
260
291
 
261
292
  # The total number of hits.
293
+ #
294
+ # This number is an approximation. Scrivito makes a best effort to deliver
295
+ # the exact number of hits. But due to technical reasons, the returned number may differ
296
+ # from the actual number under certain circumstances.
297
+ #
262
298
  # @return [Integer]
263
299
  # @api public
264
300
  def size
@@ -280,7 +316,9 @@ module Scrivito
280
316
  # Loads a single batch of search results from the backend.
281
317
  # @return [Array] of {Scrivito::BasicObj Obj}.
282
318
  # Usually returns +batch_size+ results if available,
283
- # but may occasionally return fewer than +batch_size+ results (due to rate limit, for example).
319
+ # but may occasionally return more or fewer than +batch_size+ results
320
+ # (due to technical reasons). If you need an exact number of hits, use
321
+ # methods from +Enumerable+, for example +take+.
284
322
  # @api public
285
323
  def load_batch
286
324
  next_batch = fetch_next_batch(options[:offset] || 0)
@@ -294,6 +332,79 @@ module Scrivito
294
332
  # @api public
295
333
  alias_method :count, :size
296
334
 
335
+ # @api public beta
336
+ # Perform a faceted search over an attribute to retrieve structured results for individual values of this attribute.
337
+ #
338
+ # Applicable to attribute types +string+, +stringlist+, +enum+, +multienum+.
339
+ #
340
+ # Please note that there is a precision limit for faceting:
341
+ # Only the first 50 characters of a string are guaranteed to be considered for faceting.
342
+ # If two string values have the same first 50 characters, they may be grouped into the same facet value.
343
+ #
344
+ # @param [String] attribute_name the name of an attribute
345
+ # @param [Hash] options the options to facet a request with.
346
+ # @option options [Integer] :limit maximum number of unique values to return. Defaults to 20.
347
+ # @option options [Integer] :include_objs number of Objs to fetch for each unique value. Defaults to 0.
348
+ #
349
+ # @return [Array<Scrivito::ObjFacetValue>]
350
+ # A list of unique values that were found for the given attribute name. The list is
351
+ # ordered by frequency, i.e. values occurring more frequently come first.
352
+ #
353
+ # @example Faceted request: colors of _big_ balloons.
354
+ # facets = Balloon.where(:size, :equals, "big").facet("color")
355
+ #
356
+ # # Big balloons come in 3 colors:
357
+ # facets.count #=> 3
358
+ #
359
+ # # There are 3 big red balloons:
360
+ # red_balloons = facets.first
361
+ # red_balloons.name #=> "red"
362
+ # red_balloons.count #=> 3
363
+ #
364
+ # # There are 2 big green balloons:
365
+ # green_balloons = facets.second
366
+ # green_balloons.name #=> "green"
367
+ # green_balloons.count #=> 2
368
+ #
369
+ # # There is 1 big blue balloon:
370
+ # blue_balloons = facets.third
371
+ # blue_balloons.name #=> "blue"
372
+ # blue_balloons.count #=> 1
373
+ #
374
+ # @example Faceted request with limit: at most 2 colors of big balloons.
375
+ # facets = Balloon.where(:size, :equals, "big").facet("color", limit: 2)
376
+ #
377
+ # # Although there are 3 different colors of big balloons,
378
+ # # only the first 2 colors will be taken into account.
379
+ # facets.count # => 2
380
+ #
381
+ # @example Faceted request with included Objs.
382
+ # facets = Balloon.where(:size, :equals, "big").facet("color", include_objs: 2)
383
+ #
384
+ # facets.each do |facet|
385
+ # facet.included_objs.each do |obj|
386
+ # puts "#{obj.size} #{obj.color} #{obj.class}"
387
+ # end
388
+ # end
389
+ #
390
+ # # If there are 3 big red balloons, 2 big green balloons and 1 big blue balloon,
391
+ # # then this will produce:
392
+ #
393
+ # "big red Balloon"
394
+ # "big red Balloon"
395
+ # "big green Balloon"
396
+ # "big green Balloon"
397
+ # "big blue Balloon"
398
+ #
399
+ # @raise [Scrivito::ClientError] If the maximum number of results has been exceeded.
400
+ # The maximum number of results is limited to 100 with respect to the facets themselves and the included objs.
401
+ #
402
+ def facet(attribute_name, options = {})
403
+ facets_params = [{ attribute: attribute_name }.merge!(options)]
404
+ result = get_facet_value_objs(facets_params, [attribute_name])
405
+ result[attribute_name]
406
+ end
407
+
297
408
  private
298
409
 
299
410
  attr_reader :options
@@ -314,6 +425,62 @@ module Scrivito
314
425
  end
315
426
  end
316
427
 
428
+ def create_facet_value_objs(facet_arrays, obj_collection)
429
+ result = []
430
+ facet_arrays.map do |facet|
431
+ included_objs = []
432
+ if included_ids = get_objs_facet_ids(facet)
433
+ obj_collection.each do |basic_obj|
434
+ if included_ids.include? basic_obj.id
435
+ included_objs << basic_obj unless included_objs.include? basic_obj
436
+ end
437
+ end
438
+ end
439
+ result << ObjFacetValue.new(facet["value"], facet["total"], included_objs)
440
+ end
441
+ result
442
+ end
443
+
444
+ def get_all_facets_ids(facet_array)
445
+ result = []
446
+ facet_array.map do |facet|
447
+ result += get_objs_facet_ids(facet)
448
+ end
449
+ result
450
+ end
451
+
452
+ def get_objs_facet_ids(facet)
453
+ result = []
454
+ if included_ids = facet["results"]
455
+ included_ids.each { |obj| result << obj["id"] }
456
+ end
457
+ result
458
+ end
459
+
460
+ def get_facet_value_objs(facets_params, attributes_list = [])
461
+ result = attributes_list.each_with_object({}) { |v,h| h[v] = [] }
462
+ included_objs_ids = []
463
+ params = { facets: facets_params }
464
+
465
+ if query
466
+ offset = options[:offset] || 0
467
+ params.merge! search_dsl(offset)
468
+ end
469
+
470
+ params.reverse_merge!(size: 0)
471
+
472
+ request_result = CmsBackend.instance.search_objs(workspace, params)
473
+ request_result['facets'].each do |facets_array|
474
+ included_objs_ids += get_all_facets_ids(facets_array)
475
+ end
476
+
477
+ obj_collection = Scrivito::BasicObj.find(included_objs_ids)
478
+ request_result['facets'].each_with_index do |facets_array, index|
479
+ result[result.keys[index]] += create_facet_value_objs(facets_array, obj_collection)
480
+ end
481
+ result
482
+ end
483
+
317
484
  def operator_mapping(operator)
318
485
  case operator.to_sym
319
486
  when :contains