scrivito_sdk 0.71.2 → 0.90.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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