scrivito_sdk 0.66.0 → 0.70.0.rc1

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