scrivito_sdk 0.66.0 → 0.70.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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scrivito/blobs_controller.rb +5 -0
  3. data/app/controllers/scrivito/objs_controller.rb +2 -1
  4. data/app/controllers/scrivito/ui_controller.rb +33 -3
  5. data/app/helpers/scrivito_helper.rb +14 -20
  6. data/app/views/scrivito/objs/update.json.jbuilder +1 -1
  7. data/app/views/scrivito/ui/index.html.erb +1 -1
  8. data/app/views/scrivito/webservice/_workspace.json.jbuilder +2 -1
  9. data/config/ca-bundle.crt +1 -1
  10. data/config/precedence_routes.rb +3 -2
  11. data/lib/assets/javascripts/scrivito.js +46 -0
  12. data/lib/assets/javascripts/scrivito_ui.js +931 -501
  13. data/lib/assets/stylesheets/scrivito.css +1 -0
  14. data/lib/assets/stylesheets/scrivito_ui.css +1 -1
  15. data/lib/generators/scrivito/install/templates/app/views/page/index.html.erb +3 -3
  16. data/lib/generators/scrivito/page/page_generator.rb +3 -0
  17. data/lib/generators/scrivito/page/templates/model.erb +3 -0
  18. data/lib/generators/scrivito/widget/templates/model.erb +3 -0
  19. data/lib/generators/scrivito/widget/widget_generator.rb +3 -0
  20. data/lib/scrivito/attribute_content.rb +93 -60
  21. data/lib/scrivito/attribute_definition.rb +3 -3
  22. data/lib/scrivito/attribute_serializer.rb +2 -2
  23. data/lib/scrivito/backend/obj_data_cache.rb +22 -9
  24. data/lib/scrivito/basic_obj.rb +238 -130
  25. data/lib/scrivito/basic_widget.rb +32 -20
  26. data/lib/scrivito/binary.rb +74 -45
  27. data/lib/scrivito/binary_routing.rb +52 -0
  28. data/lib/scrivito/cache_middleware.rb +0 -5
  29. data/lib/scrivito/client_attribute_serializer.rb +134 -0
  30. data/lib/scrivito/cms_backend.rb +51 -33
  31. data/lib/scrivito/cms_data_cache.rb +1 -0
  32. data/lib/scrivito/cms_dispatch_controller.rb +1 -1
  33. data/lib/scrivito/cms_env.rb +1 -11
  34. data/lib/scrivito/cms_field_tag.rb +10 -7
  35. data/lib/scrivito/cms_rest_api/rate_limit.rb +5 -5
  36. data/lib/scrivito/cms_rest_api/request_timer.rb +27 -0
  37. data/lib/scrivito/cms_rest_api/widget_extractor.rb +8 -6
  38. data/lib/scrivito/cms_rest_api.rb +107 -54
  39. data/lib/scrivito/cms_routing.rb +13 -25
  40. data/lib/scrivito/configuration.rb +5 -2
  41. data/lib/scrivito/controller_actions.rb +68 -7
  42. data/lib/scrivito/controller_runtime.rb +8 -0
  43. data/lib/scrivito/date_attribute.rb +8 -0
  44. data/lib/scrivito/errors.rb +6 -3
  45. data/lib/scrivito/generator_helper.rb +13 -0
  46. data/lib/scrivito/image_tag.rb +24 -30
  47. data/lib/scrivito/layout_tags.rb +12 -6
  48. data/lib/scrivito/link.rb +46 -30
  49. data/lib/scrivito/log_subscriber.rb +15 -0
  50. data/lib/scrivito/membership.rb +3 -3
  51. data/lib/scrivito/membership_collection.rb +10 -10
  52. data/lib/scrivito/meta_data_collection.rb +22 -0
  53. data/lib/scrivito/model_library.rb +7 -1
  54. data/lib/scrivito/obj_collection.rb +18 -16
  55. data/lib/scrivito/obj_params_parser.rb +1 -1
  56. data/lib/scrivito/obj_search_enumerator.rb +35 -35
  57. data/lib/scrivito/page_config.rb +55 -0
  58. data/lib/scrivito/request_homepage.rb +23 -0
  59. data/lib/scrivito/routing_helper.rb +8 -8
  60. data/lib/scrivito/sdk_engine.rb +2 -3
  61. data/lib/scrivito/ui_config.rb +85 -0
  62. data/lib/scrivito/user.rb +30 -23
  63. data/lib/scrivito/user_definition.rb +48 -47
  64. data/lib/scrivito/widget_tag.rb +11 -0
  65. data/lib/scrivito/workspace.rb +93 -35
  66. data/lib/scrivito/workspace_selection_middleware.rb +8 -1
  67. data/lib/scrivito_sdk.rb +24 -24
  68. metadata +24 -33
  69. data/lib/assets/javascripts/scrivito_sdk.js +0 -57
  70. data/lib/assets/stylesheets/scrivito_sdk.css +0 -1
  71. data/lib/scrivito/client_config.rb +0 -113
  72. data/lib/scrivito/editing_context_helper.rb +0 -19
  73. data/lib/scrivito/named_link.rb +0 -39
@@ -4,11 +4,19 @@ require 'active_model/naming'
4
4
 
5
5
  module Scrivito
6
6
  #
7
- # The abstract base class for cms objects.
7
+ # The abstract base class for CMS objects.
8
8
  #
9
- # @note Please do not use {Scrivito::BasicObj} directly,
9
+ # A CMS object is a collection of properties and their values, as defined
10
+ # by its object class. These properties can be accessed in views, either
11
+ # directly as the object itself is rendered, or indirectly when other objects
12
+ # are rendered. The description of an image, for example, can be retrieved
13
+ # from within any view that requires it, e.g. a page on which the image is
14
+ # displayed.
15
+ #
16
+ # @note Please do not use {Scrivito::BasicObj} directly
10
17
  # as it is intended as an abstract class.
11
18
  # Always use {Obj} or a subclass of {Obj}.
19
+ # @see http://scrivito.com/objects-widgets-classes CMS objects, widgets, and classes
12
20
  # @api public
13
21
  #
14
22
  class BasicObj
@@ -59,27 +67,27 @@ module Scrivito
59
67
  #
60
68
  # @api public
61
69
  #
62
- # This allows you to set the different attributes types of an obj by providing a hash with the
63
- # attributes names as key and the values you want to set as values. It also considers the
64
- # defaults set via {Scrivito::AttributeContent::ClassMethods#default_for Obj.default_for}.
70
+ # This allows you to set the different attributes of an Obj by providing a hash containing the
71
+ # attribute names as keys and the corresponding values you want to set. The defaults set via
72
+ # {Scrivito::AttributeContent::ClassMethods#default_for Obj.default_for} are taken into account.
65
73
  #
66
- # @param [Hash] attributes for the new obj
67
- # @param [Hash] context in which the object creating should happen
74
+ # @param [Hash] attributes of the new obj
75
+ # @param [Hash] context in which the object is created
68
76
  # @option context [Scrivito::User] :scrivito_user current visitor
69
77
  # @return [Obj] the newly created {Scrivito::BasicObj Obj}
70
78
  #
71
79
  # @see Scrivito::AttributeContent::ClassMethods#default_for
72
80
  #
73
- # @example Reference lists have to be provided as an Array of {Scrivito::BasicObj Objs}
81
+ # @example Provide reference lists as an Array of {Scrivito::BasicObj Obj}.
74
82
  # Obj.create(:reference_list => [other_obj])
75
83
  #
76
- # @example Passing an {Scrivito::BasicObj Obj} allows you to set a reference
84
+ # @example Passing an {Scrivito::BasicObj Obj} allows you to set a reference.
77
85
  # Obj.create(:reference => other_obj)
78
86
  #
79
- # @example you can upload files by passing a ruby File object
87
+ # @example You can upload files by passing a Ruby File object.
80
88
  # Obj.create(:blob => File.new("image.png"))
81
89
  #
82
- # @example Link list can be set as an Array of {Link Links}
90
+ # @example A link list can be set as an Array of {Link Links}.
83
91
  # Obj.create(:link_list => [
84
92
  # # external link
85
93
  # Link.new(:url => "http://www.example.com", :title => "Example"),
@@ -89,34 +97,35 @@ module Scrivito
89
97
  #
90
98
  # @example Passing a {Link Link} allows you to set a link.
91
99
  # Obj.create(
92
- # external_link: Link.new(url: 'http://www.example.com', title: 'Example')
100
+ # external_link: Link.new(url: 'http://www.example.com', title: 'Example'),
93
101
  # internal_link: Link.new(obj: other_obj, title: 'Other Obj')
94
102
  # )
95
103
  #
96
- # @example Dates attributes accept Time, Date and their subclasses (DateTime for example)
104
+ # @example Date attributes accept Time, Date and their subclasses (DateTime, for example).
97
105
  # Obj.create(:date => Time.new)
98
106
  # Obj.create(:date => Date.now)
99
107
  #
100
- # @example String, html and enum can be set by passing a {String} value
108
+ # @example String, html and enum attributes can be set by passing a {String} value.
101
109
  # Obj.create(:title => "My Title")
102
110
  #
103
- # @example Arrays of {String Strings} allow you to set multi enum fields
111
+ # @example Multienum attributes can be set using an Array of {String Strings}.
104
112
  # Obj.create(:tags => ["ruby", "rails"])
105
113
  #
106
- # @example Simply pass an Array of {Scrivito::BasicWidget Widgets} to change a widget field. See {Scrivito::BasicWidget#copy Widget#copy} on how to copy a widget.
114
+ # @example Simply pass an Array of {Scrivito::BasicWidget Widgets} to populate a widget field. See {Scrivito::BasicWidget#copy Widget#copy} on how to copy a widget.
107
115
  # # Add new widgets
108
116
  # Obj.create(:widgets => [Widget.new(_obj_class: 'TitleWidget', title: 'My Title')])
109
117
  #
110
- # # Add a widget copy
118
+ # # Add a copy of a widget
111
119
  # Obj.create(:widgets => [another_obj.widgets.first.copy])
112
120
  #
113
- # # Changing a widget field
121
+ # # Change a widget field
114
122
  # obj.update(:widgets => [obj.widgets.first])
115
123
  #
116
124
  # # Clear a widget field
117
125
  # obj.update(:widgets => [])
118
126
  #
119
127
  def self.create(attributes = {}, context = {})
128
+ attributes = with_default_id_attribute(attributes)
120
129
  if obj_class = extract_obj_class_from_attributes(attributes)
121
130
  obj_class.create(attributes, context)
122
131
  else
@@ -131,8 +140,8 @@ module Scrivito
131
140
  end
132
141
 
133
142
  # Create a new {Scrivito::BasicObj Obj} instance with the given values and attributes.
134
- # Normally this method should not be used.
135
- # Instead Objs should be loaded from the cms database.
143
+ # Normally, this method should not be used.
144
+ # Instead, CMS objects should be retrieved from the CMS database.
136
145
  def initialize(attributes = {})
137
146
  update_data(ObjDataFromHash.new(attributes))
138
147
  end
@@ -153,8 +162,10 @@ module Scrivito
153
162
 
154
163
  ### FINDERS ####################
155
164
 
156
- # Find a {Scrivito::BasicObj Obj} by its id.
157
- # If the parameter is an Array containing ids, return a list of corresponding Objs.
165
+ # Find an {Scrivito::BasicObj Obj} by its id.
166
+ # If the parameter is an Array of ids, the list of corresponding objects is returned.
167
+ # @example Find several CMS objects at once:
168
+ # Obj.find(['id1', 'id2'])
158
169
  # @param [String, Integer, Array<String, Integer>]id_or_list
159
170
  # @return [Obj, Array<Obj>]
160
171
  # @api public
@@ -166,8 +177,8 @@ module Scrivito
166
177
  Workspace.current.objs.find_by_id(id)
167
178
  end
168
179
 
169
- # Find a {Scrivito::BasicObj Obj} by its id.
170
- # If the parameter is an Array containing ids, return a list of corresponding Objs.
180
+ # Find an {Scrivito::BasicObj Obj} by its id.
181
+ # If the parameter is an Array containing ids, return a list of corresponding objects.
171
182
  # The results include deleted objects as well.
172
183
  # @param [String, Integer, Array<String, Integer>]id_or_list
173
184
  # @return [Obj, Array<Obj>]
@@ -176,17 +187,26 @@ module Scrivito
176
187
  Workspace.current.objs.find_including_deleted(id_or_list)
177
188
  end
178
189
 
179
- # Returns a {ObjSearchEnumerator} with the given initial subquery consisting of the four arguments.
190
+ # Returns an {ObjSearchEnumerator} with the given initial subquery consisting of the four arguments.
180
191
  #
181
192
  # Note that +field+ and +value+ can also be arrays for searching several fields or searching for several values.
182
193
  #
183
- # @note If invoked on a subclass of Obj, the result will be restricted to instances of that subclass.
194
+ # @note If invoked on a subclass of Obj, the result will be restricted to instances of this subclass.
184
195
  #
185
196
  # {ObjSearchEnumerator}s can be chained using one of the chainable methods
186
197
  # (e.g. {ObjSearchEnumerator#and} and {ObjSearchEnumerator#and_not}).
187
198
  #
188
- # @example Look for the first 10 Objs whose obj class is "Pressrelease" and whose title contains "quarterly":
199
+ # @example Look for objects containing "Lorem", boosting headline matches:
200
+ # Obj.where(:*, :contains, 'Lorem', headline: 2).to_a
201
+ #
202
+ # @example Look for the first 10 objects whose object class is "Pressrelease" and whose title contains "quarterly":
189
203
  # Obj.where(:_obj_class, :equals, 'Pressrelease').and(:title, :contains, 'quarterly').take(10)
204
+ #
205
+ # @example Look for all objects whose class is "Item". The path should start with a defined location. Furthermore, select only items of a particular category:
206
+ # Obj.where(:_obj_class, :equals, 'Item').and(:_path, :starts_with, '/en/items/').select do |item|
207
+ # item.categories.include?(category)
208
+ # end
209
+ #
190
210
  # @param [Symbol, String, Array<Symbol, String>] field See {ObjSearchEnumerator#and} for details
191
211
  # @param [Symbol, String] operator See {ObjSearchEnumerator#and} for details
192
212
  # @param [String, Array<String>] value See {ObjSearchEnumerator#and} for details
@@ -204,8 +224,8 @@ module Scrivito
204
224
  end
205
225
  end
206
226
 
207
- # Returns a {ObjSearchEnumerator} of all {Scrivito::BasicObj Obj}s.
208
- # If invoked on a subclass of Obj, the result will be restricted to instances of that subclass.
227
+ # Returns an {ObjSearchEnumerator} of all {Scrivito::BasicObj Obj}s.
228
+ # If invoked on a subclass of Obj, the result is restricted to instances of this subclass.
209
229
  # @return [ObjSearchEnumerator]
210
230
  # @raise [ScrivitoError] if called directly on +BasicObj+. Use +Obj.all+ instead.
211
231
  # @api public
@@ -218,8 +238,8 @@ module Scrivito
218
238
  end
219
239
  end
220
240
 
221
- # Returns a {ObjSearchEnumerator} of all Objs with the given +obj_class+.
222
- # @param [String] obj_class name of the obj class.
241
+ # Returns an {ObjSearchEnumerator} of all CMS objects with the given +obj_class+.
242
+ # @param [String] obj_class name of the object class.
223
243
  # @return [ObjSearchEnumerator]
224
244
  # @api public
225
245
  def self.find_all_by_obj_class(obj_class)
@@ -236,8 +256,8 @@ module Scrivito
236
256
  end
237
257
 
238
258
  # Find an {Scrivito::BasicObj Obj} with the given name.
239
- # If several Objs with the given name exist, an arbitrary one of these Objs is chosen and returned.
240
- # If no Obj with the name exits, +nil+ is returned.
259
+ # If several objects with the given name exist, an arbitrary one is chosen and returned.
260
+ # If no Obj with this name exists, +nil+ is returned.
241
261
  # @param [String] name Name of the {Scrivito::BasicObj Obj}.
242
262
  # @return [Obj]
243
263
  # @api public
@@ -245,7 +265,7 @@ module Scrivito
245
265
  where(:_name, :equals, name).batch_size(1).first
246
266
  end
247
267
 
248
- # Returns a {ObjSearchEnumerator} of all Objs with the given name.
268
+ # Returns an {ObjSearchEnumerator} of all CMS objects with the given name.
249
269
  # @param [String] name Name of the {Scrivito::BasicObj Obj}.
250
270
  # @return [ObjSearchEnumerator]
251
271
  # @api public
@@ -253,8 +273,8 @@ module Scrivito
253
273
  where(:_name, :equals, name)
254
274
  end
255
275
 
256
- # Returns the {Scrivito::BasicObj Obj} with the given permalink, or +nil+ if no matching Obj
257
- # exists.
276
+ # Returns the {Scrivito::BasicObj Obj} with the given permalink, or +nil+ if no
277
+ # matching Obj exists.
258
278
  # @param [String] permalink The permalink of the {Scrivito::BasicObj Obj}.
259
279
  # @return [Obj]
260
280
  # @api public
@@ -262,7 +282,7 @@ module Scrivito
262
282
  Workspace.current.objs.find_by_permalink(permalink)
263
283
  end
264
284
 
265
- # Returns the {Scrivito::BasicObj Obj} with the given permalink, or raise ResourceNotFound if
285
+ # Returns the {Scrivito::BasicObj Obj} with the given permalink, or raises ResourceNotFound if
266
286
  # no matching Obj exists.
267
287
  # @param [String] permalink The permalink of the {Scrivito::BasicObj Obj}.
268
288
  # @return [Obj]
@@ -272,19 +292,19 @@ module Scrivito
272
292
  raise ResourceNotFound, "Could not find Obj with permalink '#{permalink}'"
273
293
  end
274
294
 
275
- # Hook method to control which page classes should be available for a page with given path.
276
- # Override it to allow only certain classes or none.
295
+ # Hook method that lets you control which page classes are made available for pages with the given path.
296
+ # Override it to allow only specific classes or none at all.
277
297
  # Must return either +NilClass+, or +Array+.
278
298
  #
279
299
  # Be aware that the given argument is a parent path.
280
- # E.g. when creating a page with path +/products/shoes+ then the argument will be +/products+.
300
+ # E.g., when creating a page whose path is +/products/shoes+, the argument must be +/products+.
281
301
  # The given parent path can also be +NilClass+.
282
302
  #
283
- # If +NilClass+ is returned, then all possible classes will be available.
284
- # By default +NilClass+ is returned.
303
+ # If +NilClass+ is returned, all page classes are made available.
304
+ # By default, +NilClass+ is returned.
285
305
  #
286
- # If +Array+ is returned, then it should include the desired classes.
287
- # Only this classes will be available. Order of the classes will be preserved.
306
+ # If an +Array+ is returned, it is expected to contain the available classes.
307
+ # The order of the classes is preserved.
288
308
  #
289
309
  # @param [String, NilClass] parent_path Path of the parent obj
290
310
  # @return [NilClass, Array<Class>]
@@ -297,9 +317,9 @@ module Scrivito
297
317
  Scrivito.models.pages.to_a
298
318
  end
299
319
 
300
- # Update the {Scrivito::BasicObj Obj} with the attributes provided.
320
+ # Update the {Scrivito::BasicObj Obj} using the attributes provided.
301
321
  #
302
- # For an overview of which values you can set via this method see the
322
+ # For an overview of the values you can set via this method, see the
303
323
  # documentation of {Scrivito::BasicObj.create Obj.create}.
304
324
  #
305
325
  # Additionally, +update+ accepts a +_widget_pool+ hash in +attributes+ to modify widgets.
@@ -308,6 +328,20 @@ module Scrivito
308
328
  #
309
329
  # @api public
310
330
  # @param [Hash] attributes
331
+ # @example Update the URL of a link:
332
+ # link = obj.my_link
333
+ # link.url = "http://www.example.com"
334
+ # obj.update(my_link: link)
335
+ #
336
+ # @example Update binary attributes:
337
+ # obj.update(blob: img_obj.binary)
338
+ # obj.update(thumbnail: File.new("/path/to/small.jpg"))
339
+ #
340
+ # @example Remove the first and the last widget from a widget field:
341
+ # obj.update(
342
+ # my_widget_field: obj[:my_widget_field][1..-2]
343
+ # )
344
+ #
311
345
  # @example Move the +widget_to_move+ widget from the +left+ widget field of the +two_column_widget1+ widget to +left+ of +two_column_widget2+:
312
346
  # obj.update(
313
347
  # _widget_pool: {
@@ -317,7 +351,7 @@ module Scrivito
317
351
  # headline: "Some widgets were moved!"
318
352
  # )
319
353
  #
320
- # @example Move the +widget_to_move+ widget from the +right+ widget field of the +two_column_widget1+ widget to the top-level widget field +main_content+:
354
+ # @example Move the +widget_to_move+ widget from the +right+ widget field of the +two_column_widget1+ widget to the top-level +main_content+ widget field:
321
355
  # obj.update(
322
356
  # main_content: @obj.main_content + [widget_to_move],
323
357
  # _widget_pool: {
@@ -334,7 +368,7 @@ module Scrivito
334
368
  self
335
369
  end
336
370
 
337
- # Creates a copy of the +Obj+.
371
+ # Creates a copy of the {Scrivito::BasicObj Obj}.
338
372
  # @api public
339
373
  # @param [Hash] options
340
374
  # @option options [String,Symbol] :_path (nil) the path of the copy.
@@ -342,16 +376,19 @@ module Scrivito
342
376
  # @option options [String,Symbol] :_permalink (nil) the permalink of the copy.
343
377
  # @raise [ArgumentError] if +options+ includes invalid keys.
344
378
  # @return [Obj] the created copy
345
- # @example Copy a blog post.
379
+ # @example Copy a blog post:
346
380
  # blog_post = Obj.find_by_path('/blog/first_post')
347
381
  # blog_post.copy(_path: '/blog/second_post')
348
382
  def copy(options={})
349
383
  options = options.stringify_keys.assert_valid_keys('_path', '_id', '_permalink')
350
- json = workspace.api_request(:post, '/objs', obj: copyable_attributes.merge(options))
384
+ attributes_for_copy = self.class.with_default_id_attribute(copyable_attributes)
385
+ attributes_for_copy = copy_binaries(attributes_for_copy)
386
+
387
+ json = workspace.api_request(:post, '/objs', obj: attributes_for_copy.merge(options))
351
388
  self.class.find(json['_id'])
352
389
  end
353
390
 
354
- # Destroys the {Scrivito::BasicObj Obj} in the current {Workspace}
391
+ # Destroys the {Scrivito::BasicObj Obj} in the current {Workspace}.
355
392
  # @api public
356
393
  def destroy
357
394
  if children.any?
@@ -364,8 +401,8 @@ module Scrivito
364
401
  id
365
402
  end
366
403
 
367
- # return the {Scrivito::BasicObj Obj} that is the parent of this Obj.
368
- # returns +nil+ for the root Obj.
404
+ # Returns the {Scrivito::BasicObj Obj} that is the parent of this Obj.
405
+ # Returns +nil+ for the root Obj.
369
406
  # @api public
370
407
  def parent
371
408
  if child_path?
@@ -373,7 +410,7 @@ module Scrivito
373
410
  end
374
411
  end
375
412
 
376
- # Returns an Array of all the ancestor objects, starting at the root and ending at this object's parent.
413
+ # Returns an Array of all the ancestor objects, starting at the root and ending at the parent of this object.
377
414
  # @return [Array<Obj>]
378
415
  # @api public
379
416
  def ancestors
@@ -386,7 +423,7 @@ module Scrivito
386
423
  Workspace.current.objs.find_by_paths(ancestor_paths)
387
424
  end
388
425
 
389
- # return a list of all child {Scrivito::BasicObj Obj}s.
426
+ # Returns a list of all child {Scrivito::BasicObj Obj}s.
390
427
  # @return [Array<Obj>]
391
428
  # @api public
392
429
  def children
@@ -397,13 +434,13 @@ module Scrivito
397
434
 
398
435
  ### ATTRIBUTES #################
399
436
 
400
- # returns the {Scrivito::BasicObj Obj}'s path as a String.
437
+ # Returns the path of the {Scrivito::BasicObj Obj} as a String.
401
438
  # @api public
402
439
  def path
403
440
  read_attribute('_path')
404
441
  end
405
442
 
406
- # returns the {Scrivito::BasicObj Obj}'s name, i.e. the last component of the path.
443
+ # Returns the name of the {Scrivito::BasicObj Obj}. The name is the last component of a path.
407
444
  # @api public
408
445
  def name
409
446
  if child_path?
@@ -413,7 +450,7 @@ module Scrivito
413
450
  end
414
451
  end
415
452
 
416
- # Returns the root {Scrivito::BasicObj Obj}, i.e. the Obj with the path "/"
453
+ # Returns the root {Scrivito::BasicObj Obj}, the object whose path is "/".
417
454
  # @return [Obj]
418
455
  # @api public
419
456
  def self.root
@@ -423,27 +460,19 @@ module Scrivito
423
460
  'Try "bundle exec rake scrivito:migrate scrivito:migrate:publish".'
424
461
  end
425
462
 
426
- # Returns the homepage obj. This can be overridden in your application's +Obj+.
427
- # Use {#homepage?} to check if an obj is the homepage.
428
- # @return [Obj]
429
- # @api public
430
- def self.homepage
431
- root
432
- end
433
-
434
463
  def self.generate_widget_pool_id
435
464
  SecureRandom.hex(4)
436
465
  end
437
466
 
438
- # returns the obj's permalink.
467
+ # Returns the permalink of the {Scrivito::BasicObj Obj}.
439
468
  # @api public
440
469
  def permalink
441
470
  read_attribute('_permalink')
442
471
  end
443
472
 
444
- # This method determines the controller that should be invoked when the +Obj+ is requested.
445
- # By default a controller matching the Obj's obj_class will be used.
446
- # If the controller does not exist, the CmsController will be used as a fallback.
473
+ # This method determines the controller to be invoked when the +Obj+ is requested.
474
+ # By default, a controller matching the obj_class of the Obj is used.
475
+ # If the controller does not exist, the CmsController is used as a fallback.
447
476
  # Override this method to force a different controller to be used.
448
477
  # @return [String]
449
478
  # @api public
@@ -451,7 +480,7 @@ module Scrivito
451
480
  obj_class_name
452
481
  end
453
482
 
454
- # This method determines the action that should be invoked when the +Obj+ is requested.
483
+ # This method determines the action to be invoked when the +Obj+ is requested.
455
484
  # The default action is 'index'.
456
485
  # Override this method to force a different action to be used.
457
486
  # @return [String]
@@ -460,15 +489,9 @@ module Scrivito
460
489
  "index"
461
490
  end
462
491
 
463
- # Returns true if the current obj is the {.homepage} obj.
464
- # @api public
465
- def homepage?
466
- self == self.class.homepage
467
- end
468
-
469
492
  # This method is used to calculate a part of a URL of this Obj.
470
493
  #
471
- # The routing schema: <code><em><obj.id></em>/<em><obj.slug></em></code>
494
+ # The routing schema: <code><em><obj.slug></em>-<em><obj.id></em></code>
472
495
  #
473
496
  # The default is {http://apidock.com/rails/ActiveSupport/Inflector/parameterize parameterize}
474
497
  # on +obj.title+.
@@ -481,8 +504,8 @@ module Scrivito
481
504
  end
482
505
 
483
506
  #
484
- # This method determines the description that is shown in the UI
485
- # and defaults to {Scrivito::BasicObj#display_title}. It can be overridden by a custom value.
507
+ # This method determines the description shown in the UI.
508
+ # It defaults to {Scrivito::BasicObj#display_title}. It can be overridden by a custom value.
486
509
  #
487
510
  # @api public
488
511
  #
@@ -505,7 +528,7 @@ module Scrivito
505
528
 
506
529
  # The alt description of an +Obj+ used for {ScrivitoHelper#scrivito_image_tag}.
507
530
  #
508
- # By default this method returns the +title+ of this +Obj+.
531
+ # By default, this method returns the +title+ of the +Obj+.
509
532
  #
510
533
  # You can customize this part by overriding {#alt_description}.
511
534
  #
@@ -521,11 +544,11 @@ module Scrivito
521
544
  end
522
545
 
523
546
  # @api public
524
- # This method indicates if the Obj represents binary data. Binaries are
547
+ # This method indicates whether the Obj represents binary data. Binaries are
525
548
  # handled differently in that they are not rendered using the normal layout
526
549
  # but sent as a file. Examples of binary resources are Images or PDFs.
527
550
  #
528
- # Every Obj that has an attribute +blob+ of the type +binary+ is
551
+ # Every Obj that has a +blob+ attribute of the +binary+ type is
529
552
  # considered a binary
530
553
  #
531
554
  # @return true if this Obj represents a binary resource.
@@ -535,15 +558,15 @@ module Scrivito
535
558
  end
536
559
 
537
560
  #
538
- # When delivering binary Objs, this method decides whether the image transformations should be
539
- # applied by default.
561
+ # When delivering binary objects, this method decides whether the image transformations should
562
+ # be applied by default.
540
563
  #
541
- # @api beta
564
+ # @api public
542
565
  #
543
- # By default this method returns +false+.
544
- # Override in subclasses to fit your needs.
566
+ # By default, this method returns +false+.
567
+ # Override it in subclasses to fit your needs.
545
568
  #
546
- # @note Only relevant for binary Objs
569
+ # @note Only relevant for binary objects
547
570
  # @see Scrivito::Configuration.default_image_transformation=
548
571
  # @see Scrivito::Binary#transform
549
572
  #
@@ -551,14 +574,14 @@ module Scrivito
551
574
  false
552
575
  end
553
576
 
554
- # Returns true if this object is the root object.
577
+ # Returns true if this Obj is the root Obj.
555
578
  # @api public
556
579
  def root?
557
580
  path == "/"
558
581
  end
559
582
 
560
583
  # Returns a list of children excluding the binary? ones unless :all is specfied.
561
- # This is mainly used for navigations.
584
+ # This is useful for creating navigations.
562
585
  # @return [Array<Obj>]
563
586
  # @api public
564
587
  def toclist(*args)
@@ -568,15 +591,15 @@ module Scrivito
568
591
  toclist
569
592
  end
570
593
 
571
- # @param objs_to_be_sorted [Array<Scrivito::BasicObj>] unsorted list of Objs
572
- # @param list [Array<Scrivito::BasicObj>] list of Objs that defines the order
573
- # @return [Array<Scrivito::BasicObj>] a sorted list of Objs. Any objs present in
574
- # +objs_to_be_sorted+ but not in +list+ are appended at the end, sorted by +Obj#id+
594
+ # @param objs_to_be_sorted [Array<Scrivito::BasicObj>] unsorted list of CMS objects
595
+ # @param list [Array<Scrivito::BasicObj>] list of objects that defines the order
596
+ # @return [Array<Scrivito::BasicObj>] a sorted list of objects. Objects present in
597
+ # +objs_to_be_sorted+ but not in +list+ are appended to the end, sorted by +Obj#id+
575
598
  def self.sort_by_list(objs_to_be_sorted, list)
576
599
  (list & objs_to_be_sorted) + (objs_to_be_sorted - list).sort_by(&:id)
577
600
  end
578
601
 
579
- # This should be a SET, because it's faster in this particular case.
602
+ # This should be a Set, because it's faster in this particular case.
580
603
  SYSTEM_KEYS = Set.new(%w[
581
604
  body
582
605
  _id
@@ -587,8 +610,8 @@ module Scrivito
587
610
  title
588
611
  ])
589
612
 
590
- # Returns the value of an system or custom attribute specified by its name.
591
- # Passing an invalid key will not raise an error, but return +nil+.
613
+ # Returns the value of a system or custom attribute specified by its name.
614
+ # Passing an invalid key will not raise an error but return +nil+.
592
615
  # @api public
593
616
  def [](key)
594
617
  key = key.to_s
@@ -609,8 +632,8 @@ module Scrivito
609
632
  end
610
633
  end
611
634
 
612
- # Reloads the attributes of this object from the database.
613
- # Notice that the ruby class of this Obj instance will NOT change,
635
+ # Reloads the attributes of this Obj from the database.
636
+ # Note that the Ruby class of this Obj instance will NOT change,
614
637
  # even if the obj_class in the database has changed.
615
638
  # @api public
616
639
  def reload
@@ -652,10 +675,10 @@ module Scrivito
652
675
  end
653
676
  end
654
677
 
655
- # similar to modification, but faster if you are only interested in
678
+ # Similar to modification but faster if you are only interested in
656
679
  # "new" and "deleted".
657
- # this method sometimes does not return a string, but an instance of
658
- # ObjData instead. this indicates that the modification is either
680
+ # This method sometimes does not return a string, but an instance of
681
+ # ObjData instead. This indicates that the modification is either
659
682
  # UNMODIFIED or EDITED. Which one it is can be determined by comparing
660
683
  # the returned ObjData.
661
684
  def quick_modification(revision)
@@ -669,7 +692,7 @@ module Scrivito
669
692
 
670
693
  if data_for_comparison.present?
671
694
  if modification_attr == 'deleted'
672
- # Obj exists in comparison revision, but not in current
695
+ # Obj exists in comparison revision but not in current
673
696
  Modification::DELETED
674
697
  else
675
698
  # Obj exists in both revisions, leave the actual comparions
@@ -681,7 +704,7 @@ module Scrivito
681
704
  # Obj does not exist in either revision
682
705
  Modification::UNMODIFIED
683
706
  else
684
- # Obj exists in current, but not in comparision revision
707
+ # Obj exists in current but not in comparision revision
685
708
  Modification::NEW
686
709
  end
687
710
  end
@@ -707,16 +730,16 @@ module Scrivito
707
730
  end
708
731
  alias mime_type content_type
709
732
 
710
- # returns the extension (the part after the last dot) from the Obj's name.
711
- # returns an empty string if no extension is present in the Obj's name.
733
+ # Returns the name extension of the Obj (the part after the last dot).
734
+ # Returns an empty string if the name of the Obj doesn't have an extension.
712
735
  # @return [String]
713
736
  # @api public
714
737
  def file_extension
715
738
  File.extname(name)[1..-1] || ""
716
739
  end
717
740
 
718
- # Returns the body (main content) of the Obj for non-binary Objs.
719
- # Returns +nil+ for binary Objs.
741
+ # Returns the body (main content) of the Obj for non-binary objects.
742
+ # Returns +nil+ for binary objects.
720
743
  # @return [String]
721
744
  # @api public
722
745
  def body
@@ -728,11 +751,11 @@ module Scrivito
728
751
  end
729
752
 
730
753
  # @api public
731
- # This method is intended for Objs that represent binary resources like
732
- # images or pdf documents. If this Obj represents a binary file, an instance
754
+ # This method is intended for CMS objects that represent binary resources like
755
+ # images or PDF documents. If the Obj represents a binary file, an instance
733
756
  # of {Binary} is returned.
734
757
  #
735
- # This method returns the attribute +blob+ if it is of the type +binary+.
758
+ # This method returns the +blob+ attribute if its type is +binary+.
736
759
  #
737
760
  # @return [Binary, nil]
738
761
  def binary
@@ -740,21 +763,21 @@ module Scrivito
740
763
  end
741
764
 
742
765
  # @api public
743
- # This method returns the length in bytes of the binary of this obj
744
- # @return [Fixnum] If no binary is set it will return 0
766
+ # This method returns the byte length of the binary of the Obj.
767
+ # @return [Fixnum] If no binary is set, 0 is returned.
745
768
  def binary_length
746
769
  binary.try(:content_length) || 0
747
770
  end
748
771
 
749
772
  # @api public
750
- # This method returns the content type of the binary of this obj if it is set.
773
+ # This method returns the content type of the binary of this Obj if the binary is set.
751
774
  # @return [String, nil]
752
775
  def binary_content_type
753
776
  binary.try(:content_type)
754
777
  end
755
778
 
756
779
  # @api public
757
- # This method returns the url under which the content of this binary is
780
+ # This method returns the URL under which the content of this binary is
758
781
  # available to the public if the binary is set.
759
782
  #
760
783
  # See {Binary#url} for details
@@ -796,7 +819,7 @@ module Scrivito
796
819
  end
797
820
 
798
821
  # @api public
799
- # Allows accessing the {Scrivito::BasicWidget Widgets} of this Obj
822
+ # Allows accessing the {Scrivito::BasicWidget Widgets} of this Obj.
800
823
  #
801
824
  # @example Access a widget by its id
802
825
  # obj.widgets['widget_id']
@@ -816,9 +839,9 @@ module Scrivito
816
839
  #
817
840
  # @api public
818
841
  #
819
- # @note This method does not support +Obj+s, which are +new+.
842
+ # @note This method does not support +Obj+s that are +new+.
820
843
  # Please use {Scrivito::BasicObj#destroy Obj#destroy} to destroy them.
821
- # @note This method does not support +Obj+s, which are +deleted+.
844
+ # @note This method does not support +Obj+s that are +deleted+.
822
845
  # Please use {Scrivito::BasicObj.restore Obj.restore} to restore them.
823
846
  #
824
847
  # @raise [ScrivitoError] If the current workspace is +published+.
@@ -916,10 +939,6 @@ module Scrivito
916
939
  ParentPath.of(path) unless root?
917
940
  end
918
941
 
919
- def as_client_json
920
- data_from_cms.to_h.except(*GENERATED_ATTRIBUTES)
921
- end
922
-
923
942
  def outdated?
924
943
  return false if workspace.published?
925
944
 
@@ -932,8 +951,64 @@ module Scrivito
932
951
  cms_data_for_revision(base_revision) != cms_data_for_revision(published_revision)
933
952
  end
934
953
 
954
+ def transfer_modifications_to(target_workspace)
955
+ return unless modification
956
+ if in_revision(target_workspace.revision).try(:modification)
957
+ raise TransferModificationsError, "Already modified in workspace #{target_workspace.id}"
958
+ end
959
+ copy_modifications_to(target_workspace)
960
+ reset_modifications
961
+ end
962
+
935
963
  private
936
964
 
965
+ def copy_modifications_to(target_workspace)
966
+ case
967
+ when new? then create_in(target_workspace)
968
+ when deleted? then destroy_in(target_workspace)
969
+ else update_in(target_workspace)
970
+ end
971
+ end
972
+
973
+ def create_in(target_workspace)
974
+ target_workspace.api_request(:post, '/objs', obj: get_attributes)
975
+ end
976
+
977
+ def update_in(target_workspace)
978
+ update_attributes = fill_in_missing_attributes_as_nil(copyable_attributes)
979
+ target_workspace.api_request(:put, "/objs/#{id}", obj: update_attributes)
980
+ end
981
+
982
+ def destroy_in(target_workspace)
983
+ target_workspace.api_request(:delete, "/objs/#{id}")
984
+ end
985
+
986
+ def fill_in_missing_attributes_as_nil(attributes, type_computer=Obj.type_computer)
987
+ obj_class = type_computer.compute_type_without_fallback(attributes['_obj_class'])
988
+ missing_attributes = obj_class.attribute_definitions.map(&:name) - attributes.keys
989
+
990
+ if attributes['_widget_pool']
991
+ attributes['_widget_pool'].each do |id, widget_attributes|
992
+ attributes['_widget_pool'][id] = fill_in_missing_attributes_as_nil(
993
+ widget_attributes, Widget.type_computer)
994
+ end
995
+ end
996
+
997
+ missing_attributes.each do |attribute_name|
998
+ attributes[attribute_name] = nil
999
+ end
1000
+
1001
+ attributes
1002
+ end
1003
+
1004
+ def reset_modifications
1005
+ case
1006
+ when new? then destroy
1007
+ when deleted? then self.class.restore(id)
1008
+ else revert
1009
+ end
1010
+ end
1011
+
937
1012
  def cms_data_for_revision(revision)
938
1013
  return nil unless revision
939
1014
 
@@ -981,9 +1056,34 @@ module Scrivito
981
1056
  end
982
1057
 
983
1058
  def copyable_attributes
1059
+ get_attributes.except(*GENERATED_ATTRIBUTES).except(*UNIQ_ATTRIBUTES)
1060
+ end
1061
+
1062
+ def get_attributes
984
1063
  workspace.api_request(:get, "/objs/#{id}")
985
- .except(*GENERATED_ATTRIBUTES)
986
- .except(*UNIQ_ATTRIBUTES)
1064
+ end
1065
+
1066
+ def copy_binaries(attributes)
1067
+ attribute_defintions = self.class.find_attribute_definitions(obj_class_name)
1068
+ destination_obj_id = attributes.fetch(:_id)
1069
+
1070
+ Hash[attributes.map do |name, value|
1071
+ if value && attribute_defintions[name].try(:type) == 'binary'
1072
+ binary = self[name]
1073
+ [name, ['binary', copy_binary(destination_obj_id, binary)]]
1074
+ else
1075
+ [name, value]
1076
+ end
1077
+ end]
1078
+ end
1079
+
1080
+ def copy_binary(destination_obj_id, binary)
1081
+ normalized_id = CmsRestApi.normalize_path_component(binary.id)
1082
+ CmsRestApi.put("/blobs/#{normalized_id}/copy", {
1083
+ destination_obj_id: destination_obj_id,
1084
+ filename: binary.filename,
1085
+ content_type: binary.content_type,
1086
+ })
987
1087
  end
988
1088
 
989
1089
  def assert_revertable
@@ -1039,10 +1139,14 @@ module Scrivito
1039
1139
  Workspace.current.api_request(:post, '/objs', obj: obj_attributes)
1040
1140
  end
1041
1141
 
1042
- def prepare_attributes_for_rest_api(obj_attributes, obj = nil)
1043
- widget_pool_attributes = CmsRestApi::WidgetExtractor.call(obj_attributes, obj)
1142
+ def prepare_attributes_for_rest_api(attributes, obj = nil)
1143
+ widget_pool_attributes, obj_attributes = CmsRestApi::WidgetExtractor.call(attributes, obj)
1144
+ obj_id = obj ? obj.id : obj_attributes.fetch(:_id)
1044
1145
  workspace = obj ? obj.revision.workspace : Workspace.current
1045
- api_attributes = serialize_attributes(obj_attributes, widget_pool_attributes, workspace)
1146
+
1147
+ api_attributes = serialize_attributes(
1148
+ obj_attributes, widget_pool_attributes, workspace, obj_id
1149
+ )
1046
1150
 
1047
1151
  if obj
1048
1152
  widget_pool = api_attributes['_widget_pool']
@@ -1054,8 +1158,8 @@ module Scrivito
1054
1158
  [api_attributes, widget_pool_attributes]
1055
1159
  end
1056
1160
 
1057
- def serialize_attributes(obj_attributes, widget_pool_attributes, workspace)
1058
- serializer = AttributeSerializer.new(obj_attributes['_obj_class'] || name)
1161
+ def serialize_attributes(obj_attributes, widget_pool_attributes, workspace, obj_id)
1162
+ serializer = AttributeSerializer.new(obj_attributes['_obj_class'] || name, obj_id)
1059
1163
  serialized_attributes = serialize_obj_attributes(serializer, obj_attributes)
1060
1164
  serialized_attributes['_widget_pool'] =
1061
1165
  serialize_widget_pool_attributes(serializer, widget_pool_attributes)
@@ -1080,6 +1184,10 @@ module Scrivito
1080
1184
  def find_attribute_definitions(obj_class, basic_class = self)
1081
1185
  basic_class.type_computer.compute_type(obj_class).attribute_definitions if obj_class
1082
1186
  end
1187
+
1188
+ def with_default_id_attribute(attributes)
1189
+ attributes.reverse_merge(_id: SecureRandom.hex(8))
1190
+ end
1083
1191
  end
1084
1192
  end
1085
1193
  end