scrivito_sdk 0.50.1 → 0.60.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/scrivito/objs_controller.rb +25 -21
  3. data/app/controllers/scrivito/ui_controller.rb +1 -1
  4. data/app/controllers/scrivito/webservice_controller.rb +1 -1
  5. data/app/models/scrivito/content_widget.rb +1 -3
  6. data/app/views/cms/index.html.erb +3 -0
  7. data/app/views/scrivito/objs/destroy.json.jbuilder +1 -0
  8. data/app/views/scrivito/objs/show.json.jbuilder +2 -0
  9. data/app/views/scrivito/objs/update.json.jbuilder +1 -1
  10. data/app/views/scrivito/objs/{widget_modification.json.jbuilder → widget.json.jbuilder} +0 -0
  11. data/config/ca-bundle.crt +24 -219
  12. data/config/precedence_routes.rb +60 -0
  13. data/config/routes.rb +13 -46
  14. data/lib/assets/javascripts/scrivito_ui.js +360 -202
  15. data/lib/assets/stylesheets/scrivito_sdk.css +1 -1
  16. data/lib/assets/stylesheets/scrivito_ui.css +1 -1
  17. data/lib/generators/scrivito/install/templates/app/models/download.rb +1 -3
  18. data/lib/generators/scrivito/install/templates/app/models/headline_widget.rb +1 -5
  19. data/lib/generators/scrivito/install/templates/app/models/image.rb +1 -3
  20. data/lib/generators/scrivito/install/templates/app/models/image_widget.rb +1 -5
  21. data/lib/generators/scrivito/install/templates/app/models/page.rb +3 -6
  22. data/lib/generators/scrivito/install/templates/app/models/text_widget.rb +1 -5
  23. data/lib/generators/scrivito/install/templates/scrivito/migrate/install_scrivito_migration.rb +1 -25
  24. data/lib/generators/scrivito/migration/templates/migration.erb +2 -26
  25. data/lib/generators/scrivito/page/page_generator.rb +0 -11
  26. data/lib/generators/scrivito/page/templates/model.erb +3 -7
  27. data/lib/generators/scrivito/widget/widget_generator.rb +0 -11
  28. data/lib/scrivito/attribute.rb +10 -16
  29. data/lib/scrivito/attribute_collection.rb +4 -0
  30. data/lib/scrivito/attribute_content.rb +239 -161
  31. data/lib/scrivito/attribute_definition.rb +16 -10
  32. data/lib/scrivito/attribute_definition_collection.rb +7 -4
  33. data/lib/scrivito/attribute_definition_migrator.rb +1 -3
  34. data/lib/scrivito/attribute_deserializer.rb +111 -0
  35. data/lib/scrivito/attribute_serializer.rb +189 -0
  36. data/lib/scrivito/basic_obj.rb +113 -78
  37. data/lib/scrivito/basic_widget.rb +103 -37
  38. data/lib/scrivito/binary.rb +3 -3
  39. data/lib/scrivito/cache/chainable.rb +10 -4
  40. data/lib/scrivito/cache/file_store.rb +7 -4
  41. data/lib/scrivito/cache/ram_store.rb +1 -1
  42. data/lib/scrivito/class_collection.rb +2 -2
  43. data/lib/scrivito/client_config.rb +7 -6
  44. data/lib/scrivito/cms_backend.rb +2 -2
  45. data/lib/scrivito/cms_field_tag.rb +33 -25
  46. data/lib/scrivito/cms_rest_api/{attribute_serializer.rb → legacy_attribute_serializer.rb} +1 -1
  47. data/lib/scrivito/cms_routing.rb +20 -14
  48. data/lib/scrivito/configuration.rb +1 -1
  49. data/lib/scrivito/controller_actions.rb +12 -4
  50. data/lib/scrivito/date_attribute.rb +8 -0
  51. data/lib/scrivito/diff.rb +1 -1
  52. data/lib/scrivito/editing_context.rb +1 -1
  53. data/lib/scrivito/editing_context_middleware.rb +1 -1
  54. data/lib/scrivito/errors.rb +4 -0
  55. data/lib/scrivito/link_parser.rb +6 -1
  56. data/lib/scrivito/migrations/cms_backend.rb +17 -60
  57. data/lib/scrivito/migrations/migration_store.rb +0 -1
  58. data/lib/scrivito/model_library.rb +6 -6
  59. data/lib/scrivito/obj_class.rb +34 -48
  60. data/lib/scrivito/obj_class_collection.rb +21 -3
  61. data/lib/scrivito/obj_create_params_parser.rb +2 -1
  62. data/lib/scrivito/obj_data.rb +9 -103
  63. data/lib/scrivito/obj_data_from_hash.rb +19 -21
  64. data/lib/scrivito/obj_data_from_service.rb +28 -48
  65. data/lib/scrivito/obj_params_parser.rb +14 -14
  66. data/lib/scrivito/obj_search_enumerator.rb +1 -1
  67. data/lib/scrivito/obj_update_params_parser.rb +2 -2
  68. data/lib/scrivito/restriction_set.rb +1 -1
  69. data/lib/scrivito/sdk_engine.rb +2 -2
  70. data/lib/scrivito/test_request.rb +10 -0
  71. data/lib/scrivito/type_computer.rb +5 -1
  72. data/lib/scrivito/widget_collection.rb +15 -0
  73. data/lib/scrivito/widget_garbage_collection.rb +1 -1
  74. data/lib/scrivito/widget_tag.rb +2 -0
  75. data/lib/scrivito/workspace.rb +2 -1
  76. data/lib/scrivito/workspace_data_from_service.rb +1 -0
  77. data/lib/scrivito_sdk.rb +17 -1
  78. metadata +36 -34
  79. data/app/views/scrivito/objs/modification.json.jbuilder +0 -1
  80. data/config/cms_routes.rb +0 -22
  81. data/lib/generators/scrivito/page/templates/migration.erb +0 -9
  82. data/lib/generators/scrivito/widget/templates/migration.erb +0 -7
@@ -3,36 +3,42 @@ module Scrivito
3
3
  #
4
4
  # This class represents a definition of an attribute.
5
5
  #
6
- # @api private
6
+ # @api public
7
7
  #
8
8
  class AttributeDefinition
9
9
  #
10
10
  # @!attribute [r] name
11
- # @api private
12
- # @return [Symbol] the name of the attribute.
11
+ # @api public
12
+ # @return [String] the name of the attribute.
13
13
  #
14
14
  # @!attribute [r] type
15
- # @api private
16
- # @return [Symbol] the type of the attribute.
15
+ # @api public
16
+ # @return [String] the type of the attribute.
17
17
  #
18
18
  attr_reader :name, :type
19
19
 
20
20
  def initialize(name, type, options = {})
21
- @name, @type, @options = name, type, options
21
+ @name, @type, @options = name.to_s, type.to_s, options.with_indifferent_access
22
22
  end
23
23
 
24
24
  #
25
25
  # Allowed values for an attribute.
26
26
  #
27
- # @api private
28
- # @return [[String], nil] allowed values if type is +enum+ or +multienum+ or +nil+ otherwise.
29
- # If no values have been specified, then an empty array will be returned.
27
+ # @api public
28
+ # @return [Array<String>] allowed values if type is +enum+ or +multienum+ or an empty array
29
+ # otherwise. If no values have been specified, then an empty array will be returned.
30
30
  #
31
31
  def values
32
- if type == :enum || type == :multienum
32
+ if type == 'enum' || type == 'multienum'
33
33
  @options[:values] || []
34
+ else
35
+ []
34
36
  end
35
37
  end
38
+
39
+ def widgetlist?
40
+ type == 'widgetlist'
41
+ end
36
42
  end
37
43
 
38
44
  end
@@ -3,11 +3,14 @@ module Scrivito
3
3
  #
4
4
  # This class represents a collection of attribute definitions.
5
5
  #
6
- # @api private
6
+ # @api public
7
7
  #
8
8
  class AttributeDefinitionCollection
9
9
  include Enumerable
10
10
 
11
+ # Compatibility for Object#blank?
12
+ delegate :empty?, to: :attribute_definitions
13
+
11
14
  def initialize(attribute_definitions)
12
15
  @attribute_definitions = attribute_definitions
13
16
  end
@@ -15,7 +18,7 @@ class AttributeDefinitionCollection
15
18
  #
16
19
  # Iterates over the attribute definitions.
17
20
  #
18
- # @api private
21
+ # @api public
19
22
  # @yieldparam [Scrivito:AttributeDefinition] attribute_definition
20
23
  #
21
24
  def each(&block)
@@ -25,12 +28,12 @@ class AttributeDefinitionCollection
25
28
  #
26
29
  # Find definition of an attribute.
27
30
  #
28
- # @api private
31
+ # @api public
29
32
  # @param name [Symbol, String] the name of the attribute.
30
33
  # @return [Scrivito::AttributeDefinition, nil] attribute definition if found or +nil+ otherwise.
31
34
  #
32
35
  def [](name)
33
- attribute_definitions[name.to_sym]
36
+ attribute_definitions[name.to_s]
34
37
  end
35
38
 
36
39
  private
@@ -30,9 +30,7 @@ class AttributeDefinitionMigrator
30
30
  with_padding do
31
31
  say %{[warning] Model "#{model_class}" has no corresponding ObjClass in published workspace.
32
32
  Ignore this message if it is an abstract class and should not have an ObjClass by design.
33
- Otherwise:
34
- - Is there a pending migration which will create an ObjClass for "#{model_class}"? Please run and publish this migration.
35
- - Or maybe "#{model_class}" is not used anymore and file #{relative_path} can be removed?
33
+ Or maybe "#{model_class}" is not used anymore and file #{relative_path} can be removed?
36
34
  }, :yellow
37
35
  end
38
36
  say_status :skipping, relative_path
@@ -0,0 +1,111 @@
1
+ module Scrivito
2
+
3
+ class AttributeDeserializer < Struct.new(:model, :workspace)
4
+ def deserialize(attribute_value, attribute_definition)
5
+ case attribute_definition.type
6
+ when 'binary' then deserialize_binary_value(attribute_value)
7
+ when 'date' then deserialize_date_value(attribute_value)
8
+ when 'enum' then deserialize_enum_value(attribute_value, attribute_definition)
9
+ when 'html' then deserialize_html_value(attribute_value)
10
+ when 'link' then deserialize_link_value(attribute_value)
11
+ when 'linklist' then deserialize_linklist_value(attribute_value)
12
+ when 'multienum' then deserialize_multienum_value(attribute_value, attribute_definition)
13
+ when 'reference' then deserialize_reference_value(attribute_value)
14
+ when 'referencelist' then deserialize_referencelist_value(attribute_value)
15
+ when 'widget' then deserialize_legacy_widget_value(attribute_value, attribute_definition)
16
+ when 'widgetlist' then deserialize_widgetlist_value(attribute_value, attribute_definition)
17
+ else attribute_value
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def deserialize_binary_value(attribute_value)
24
+ if attribute_value && (id = attribute_value['id'])
25
+ Binary.new(id, workspace.published?)
26
+ end
27
+ end
28
+
29
+ def deserialize_date_value(attribute_value)
30
+ DateAttribute.parse(attribute_value) if attribute_value
31
+ end
32
+
33
+ def deserialize_enum_value(attribute_value, attribute_definition)
34
+ attribute_value if attribute_definition.values.include?(attribute_value)
35
+ end
36
+
37
+ def deserialize_html_value(attribute_value)
38
+ StringTagging.tag_as_html(attribute_value)
39
+ end
40
+
41
+ def deserialize_link_value(attribute_value)
42
+ return unless attribute_value
43
+ if attribute_value['destination']
44
+ deserialize_internal_link(attribute_value)
45
+ else
46
+ deserialize_external_link(attribute_value)
47
+ end
48
+ end
49
+
50
+ def deserialize_linklist_value(link_definitions)
51
+ if link_definitions.present?
52
+ link_definitions = link_definitions.map(&:with_indifferent_access)
53
+
54
+ object_ids = link_definitions.map { |link_data| link_data[:destination] }.compact.uniq
55
+ objects = object_ids.empty? ? [] : workspace.objs.find(object_ids)
56
+ link_definitions.each_with_object([]) do |link_data, links|
57
+ obj = objects.detect { |o| o && o.id == link_data[:destination] }
58
+ link = Link.new(link_data.merge(obj: obj))
59
+ links << link if link.resolved?
60
+ end
61
+ else
62
+ []
63
+ end
64
+ end
65
+
66
+ def deserialize_multienum_value(attribute_value, attribute_definition)
67
+ attribute_value & attribute_definition.values
68
+ end
69
+
70
+ def deserialize_reference_value(attribute_value)
71
+ workspace.objs.find([attribute_value]).first
72
+ end
73
+
74
+ def deserialize_referencelist_value(attribute_value)
75
+ workspace.objs.find(attribute_value).compact
76
+ end
77
+
78
+ def deserialize_internal_link(attribute_value)
79
+ Link.new(attribute_value.slice('title', 'query', 'fragment', 'target').symbolize_keys
80
+ .merge(obj: workspace.objs.find(attribute_value['destination'])))
81
+ rescue ResourceNotFound
82
+ end
83
+
84
+ def deserialize_external_link(attribute_value)
85
+ Link.new(attribute_value.slice('url', 'title', 'target').symbolize_keys)
86
+ end
87
+
88
+ def deserialize_legacy_widget_value(widget_ids, attribute_definition)
89
+ if workspace.uses_obj_classes
90
+ deserialize_widgetlist_value(widget_ids, attribute_definition)
91
+ end
92
+ end
93
+
94
+ def deserialize_widgetlist_value(widget_ids, attribute_definition)
95
+ if widget_ids
96
+ widget_ids.map { |widget_id| deserialize_widget_value(widget_id, attribute_definition) }
97
+ else
98
+ []
99
+ end
100
+ end
101
+
102
+ def deserialize_widget_value(widget_id, attribute_definition)
103
+ model.widget_from_pool(widget_id).tap do |widget|
104
+ raise ScrivitoError, "Widget with ID #{widget_id} not found!" unless widget
105
+ widget.container = model
106
+ widget.container_attribute_name = attribute_definition.name
107
+ end
108
+ end
109
+ end
110
+
111
+ end
@@ -0,0 +1,189 @@
1
+ module Scrivito
2
+
3
+ class AttributeSerializer
4
+ def serialize(attributes, attribute_definitions)
5
+ return attributes if attributes.blank?
6
+ serialized_attributes = attributes.map do |attribute_name, attribute_value|
7
+ serialize_attribute(attribute_name, attribute_value, attribute_definitions)
8
+ end
9
+ Hash[serialized_attributes]
10
+ end
11
+
12
+ private
13
+
14
+ def serialize_attribute(attribute_name, attribute_value, attribute_definitions)
15
+ attribute_name = attribute_name.to_s
16
+ if attribute_name.starts_with?('_')
17
+ serialize_system_attribute(attribute_name, attribute_value)
18
+ else
19
+ attribute_definition = attribute_definitions[attribute_name]
20
+ serialize_custom_attribute(attribute_name, attribute_value, attribute_definition)
21
+ end
22
+ end
23
+
24
+ def serialize_system_attribute(attribute_name, attribute_value)
25
+ if %w[
26
+ _id
27
+ _obj_class
28
+ _path
29
+ _permalink
30
+ ].include?(attribute_name)
31
+ [attribute_name, attribute_value.to_s]
32
+ else
33
+ raise ScrivitoError, "Unknown attribute '#{attribute_name}'"
34
+ end
35
+ end
36
+
37
+ def serialize_custom_attribute(attribute_name, attribute_value, attribute_definition)
38
+ raise ScrivitoError, "Unknown attribute '#{attribute_name}'" unless attribute_definition
39
+ attribute_value = case attribute_type = attribute_definition.type
40
+ when 'binary' then serialize_binary_value(attribute_value, attribute_definition)
41
+ when 'date' then serialize_date_value(attribute_value, attribute_definition)
42
+ when 'enum' then serialize_enum_value(attribute_value, attribute_definition)
43
+ when 'html' then serialize_html_value(attribute_value)
44
+ when 'link' then serialize_link_value(attribute_value, attribute_definition)
45
+ when 'linklist' then serialize_linklist_value(attribute_value, attribute_definition)
46
+ when 'multienum' then serialize_multienum_value(attribute_value, attribute_definition)
47
+ when 'reference' then serialize_reference_value(attribute_value)
48
+ when 'referencelist' then serialize_referencelist_value(attribute_value, attribute_definition)
49
+ when 'string' then serialize_string_value(attribute_value)
50
+ when 'stringlist' then serialize_stringlist_value(attribute_value, attribute_definition)
51
+ when 'widget' then serialize_widget_value(attribute_value, attribute_definition)
52
+ when 'widgetlist' then serialize_widgetlist_value(attribute_value, attribute_definition)
53
+ end
54
+ [attribute_name, [serialize_attribute_type(attribute_type), attribute_value]]
55
+ end
56
+
57
+ def serialize_attribute_type(attribute_type)
58
+ case attribute_type
59
+ when 'enum' then 'string'
60
+ when 'multienum' then 'stringlist'
61
+ else attribute_type
62
+ end
63
+ end
64
+
65
+ def serialize_binary_value(attribute_value, attribute_definition)
66
+ return unless attribute_value
67
+ case attribute_value
68
+ when File then CmsRestApi.upload_file(attribute_value)
69
+ when UploadedBinary then attribute_value.params
70
+ else
71
+ raise_validation_error(attribute_definition.name,
72
+ 'an instance of File, Scrivito::UploadedBinary or nil', attribute_value)
73
+ end
74
+ end
75
+
76
+ def serialize_date_value(attribute_value, attribute_definition)
77
+ return unless attribute_value
78
+ attribute_value = DateAttribute.serialize(attribute_value)
79
+ unless attribute_value
80
+ raise_validation_error(attribute_definition.name, 'an instance of Date or Time',
81
+ attribute_value)
82
+ end
83
+ attribute_value
84
+ end
85
+
86
+ def serialize_enum_value(attribute_value, attribute_definition)
87
+ return unless attribute_value
88
+ attribute_value = attribute_value.to_s
89
+ attribute_values = attribute_definition.values
90
+ if attribute_values.include?(attribute_value)
91
+ attribute_value
92
+ else
93
+ raise_validation_error(attribute_definition.name, format_array(attribute_values),
94
+ attribute_value)
95
+ end
96
+ end
97
+
98
+ def serialize_html_value(attribute_value)
99
+ attribute_value.to_s
100
+ end
101
+
102
+ def serialize_link_value(attribute_value, attribute_definition)
103
+ return unless attribute_value
104
+ if attribute_value.is_a?(Link)
105
+ attribute_value.to_cms_api_linklist_params
106
+ else
107
+ raise_validation_error(attribute_definition.name, 'an instance of Scrivito::Link',
108
+ attribute_value)
109
+ end
110
+ end
111
+
112
+ def serialize_linklist_value(attribute_value, attribute_definition)
113
+ if attribute_value.is_a?(Enumerable) && attribute_value.all? { |item| item.is_a?(Link) }
114
+ attribute_value.map(&:to_cms_api_linklist_params)
115
+ else
116
+ raise_validation_error(attribute_definition.name,
117
+ 'Enumerable containing instances of Scrivito::Link', attribute_value)
118
+ end
119
+ end
120
+
121
+ def serialize_multienum_value(attribute_value, attribute_definition)
122
+ attribute_value = attribute_value.map(&:to_s)
123
+ attribute_values = attribute_definition.values
124
+ forbidden_values = attribute_value - attribute_values
125
+ if forbidden_values.any?
126
+ raise_validation_error(attribute_definition.name, format_array(attribute_values),
127
+ attribute_value)
128
+ end
129
+ attribute_value
130
+ end
131
+
132
+ def serialize_reference_value(attribute_value)
133
+ return unless attribute_value
134
+ attribute_value.is_a?(BasicObj) ? attribute_value.id : attribute_value.to_s
135
+ end
136
+
137
+ def serialize_referencelist_value(attribute_value, attribute_definition)
138
+ if attribute_value.is_a?(Enumerable)
139
+ attribute_value.map(&method(:serialize_reference_value))
140
+ else
141
+ raise_validation_error(attribute_definition.name,
142
+ 'Enumerable containing instances of String or Scrivito::BasicWidget', attribute_value)
143
+ end
144
+ end
145
+
146
+ def serialize_string_value(attribute_value)
147
+ attribute_value.to_s
148
+ end
149
+
150
+ def serialize_stringlist_value(attribute_value, attribute_definition)
151
+ if attribute_value.is_a?(Enumerable)
152
+ attrubute_value.map(&:to_s)
153
+ else
154
+ raise_validation_error(attribute_definition.name, 'Enumerable containing instances of String',
155
+ attribute_value)
156
+ end
157
+ end
158
+
159
+ def serialize_widget_value(attribute_value, attribute_definition)
160
+ return unless attribute_value
161
+ if attribute_value.is_a?(BasicWidget)
162
+ attribute_value.id
163
+ else
164
+ raise_validation_error(attribute_definition.name, 'an instance of Scrivito::BasicWidget',
165
+ attribute_value)
166
+ end
167
+ end
168
+
169
+ def serialize_widgetlist_value(attribute_value, attribute_definition)
170
+ if attribute_value.is_a?(Enumerable) && attribute_value.all? { |item| item.is_a?(BasicWidget) }
171
+ attribute_value.map(&:id)
172
+ else
173
+ raise_validation_error(attribute_definition.name,
174
+ 'Enumerable containing instances of Scrivito::BasicWidget', attribute_value)
175
+ end
176
+ end
177
+
178
+ def raise_validation_error(attribute_name, expected_value, actual_value)
179
+ error_message = "Unexpected value #{actual_value.inspect} for attribute '#{attribute_name}'."\
180
+ " Expected: #{expected_value}."
181
+ raise ClientError.new(error_message, 412)
182
+ end
183
+
184
+ def format_array(values)
185
+ values.map(&:inspect).to_sentence(two_words_connector: ' or ', last_word_connector: ' or ')
186
+ end
187
+ end
188
+
189
+ end
@@ -3,13 +3,31 @@ require 'ostruct'
3
3
  require 'active_model/naming'
4
4
 
5
5
  module Scrivito
6
+ #
6
7
  # The abstract base class for cms objects.
7
8
  #
8
9
  # @note Please do not use {Scrivito::BasicObj} directly,
9
10
  # as it is intended as an abstract class.
10
11
  # Always use {Obj} or a subclass of {Obj}.
11
12
  # @api public
13
+ #
12
14
  class BasicObj
15
+ # @!parse extend Scrivito::AttributeContent::ClassMethods
16
+
17
+ PublicSystemAttributeDefinition = Class.new(AttributeDefinition)
18
+
19
+ SYSTEM_ATTRIBUTES = AttributeDefinitionCollection.new(
20
+ '_id' => PublicSystemAttributeDefinition.new(:_id, :string),
21
+ '_last_changed' => PublicSystemAttributeDefinition.new(:_last_changed, :date),
22
+ '_obj_class' => PublicSystemAttributeDefinition.new(:_obj_class, :string),
23
+ '_path' => PublicSystemAttributeDefinition.new(:_path, :string),
24
+ '_permalink' => PublicSystemAttributeDefinition.new(:_permalink, :string),
25
+
26
+ '_conflicts' => AttributeDefinition.new(:_conflicts, nil),
27
+ '_modification' => AttributeDefinition.new(:_modification, nil),
28
+ '_widget_pool' => AttributeDefinition.new(:_widget_pool, nil),
29
+ )
30
+
13
31
  UNIQ_ATTRIBUTES = %w[
14
32
  _id
15
33
  _path
@@ -37,6 +55,7 @@ module Scrivito
37
55
  @_type_computer = nil
38
56
  end
39
57
 
58
+ #
40
59
  # Create a new {Scrivito::BasicObj Obj} in the cms
41
60
  #
42
61
  # This allows you to set the different attributes types of an obj by
@@ -92,13 +111,18 @@ module Scrivito
92
111
  # @api public
93
112
  # @param [Hash] attributes
94
113
  # @return [Obj] the newly created {Scrivito::BasicObj Obj}
95
- def self.create(attributes)
96
- attributes = with_default_obj_class(attributes)
97
- api_attributes, widget_properties = prepare_attributes_for_rest_api(attributes, nil)
98
- json = Workspace.current.api_request(:post, '/objs', obj: api_attributes)
99
- obj = find(json['_id'])
100
- CmsRestApi::WidgetExtractor.notify_persisted_widgets(obj, widget_properties)
101
- obj
114
+ #
115
+ def self.create(attributes = {})
116
+ if obj_class = extract_obj_class_from_attributes(attributes)
117
+ obj_class.create(attributes)
118
+ else
119
+ attributes = prepare_attributes_for_instantiation(attributes)
120
+ api_attributes, widget_properties = prepare_attributes_for_rest_api(attributes)
121
+ json = Workspace.current.api_request(:post, '/objs', obj: api_attributes)
122
+ obj = find(json['_id'])
123
+ CmsRestApi::WidgetExtractor.notify_persisted_widgets(obj, widget_properties)
124
+ obj
125
+ end
102
126
  end
103
127
 
104
128
  # Create a new {Scrivito::BasicObj Obj} instance with the given values and attributes.
@@ -171,7 +195,6 @@ module Scrivito
171
195
  if self == ::Obj
172
196
  Workspace.current.objs.where(field, operator, value, boost)
173
197
  else
174
- assert_has_obj_class('.where')
175
198
  Workspace.current.objs.where(:_obj_class, :equals, name)
176
199
  .and(field, operator, value, boost)
177
200
  end
@@ -188,7 +211,6 @@ module Scrivito
188
211
  if self == ::Obj
189
212
  Workspace.current.objs.all
190
213
  else
191
- assert_has_obj_class('.all')
192
214
  find_all_by_obj_class(name)
193
215
  end
194
216
  end
@@ -401,7 +423,6 @@ module Scrivito
401
423
  root
402
424
  end
403
425
 
404
- # @api private
405
426
  def self.generate_widget_pool_id
406
427
  SecureRandom.hex(4)
407
428
  end
@@ -489,12 +510,8 @@ module Scrivito
489
510
  #
490
511
  # @return true if this Obj represents a binary resource.
491
512
  def binary?
492
- if obj_type = read_attribute('_obj_type')
493
- [:image, :generic].include?(obj_type.to_sym)
494
- else
495
- blob_attribute = obj_class.attributes['blob']
496
- blob_attribute && blob_attribute.type == 'binary'
497
- end
513
+ blob_attribute_definition = attribute_definitions['blob']
514
+ blob_attribute_definition.present? && blob_attribute_definition.type == 'binary'
498
515
  end
499
516
 
500
517
  # Returns true if this object is the root object.
@@ -523,7 +540,7 @@ module Scrivito
523
540
  end
524
541
 
525
542
  # This should be a SET, because it's faster in this particular case.
526
- INTERNAL_KEYS = Set.new(%w[
543
+ SYSTEM_KEYS = Set.new(%w[
527
544
  body
528
545
  _id
529
546
  _last_changed
@@ -533,12 +550,12 @@ module Scrivito
533
550
  title
534
551
  ])
535
552
 
536
- # Returns the value of an internal or external attribute specified by its name.
553
+ # Returns the value of an system or custom attribute specified by its name.
537
554
  # Passing an invalid key will not raise an error, but return +nil+.
538
555
  # @api public
539
556
  def [](key)
540
557
  key = key.to_s
541
- if INTERNAL_KEYS.include?(key)
558
+ if SYSTEM_KEYS.include?(key)
542
559
  read_attribute(key)
543
560
  else
544
561
  super
@@ -548,7 +565,7 @@ module Scrivito
548
565
  def has_attribute?(key)
549
566
  key = key.to_s
550
567
 
551
- if INTERNAL_KEYS.include?(key)
568
+ if SYSTEM_KEYS.include?(key)
552
569
  true
553
570
  else
554
571
  super
@@ -596,14 +613,14 @@ module Scrivito
596
613
 
597
614
  def modification(revision=workspace.base_revision)
598
615
  return Modification::UNMODIFIED unless revision
599
-
600
- obj_data_from_revision = cms_data_for_revision(revision)
616
+ return read_attribute('_modification') if revision == workspace.base_revision
601
617
 
602
618
  if deleted?(revision)
603
619
  Modification::DELETED
604
620
  elsif new?(revision)
605
621
  Modification::NEW
606
622
  else # Edited
623
+ obj_data_from_revision = cms_data_for_revision(revision)
607
624
  if obj_data_from_revision.present?
608
625
  if data_from_cms == obj_data_from_revision
609
626
  Modification::UNMODIFIED
@@ -789,10 +806,10 @@ module Scrivito
789
806
  end
790
807
 
791
808
  widget_copy = widget.copy_for_restore(read_widget_pool.keys)
792
- field_name = widget.container_field_name
809
+ attribute_name = widget.container_attribute_name
793
810
  current_container = widgets[container.id] || self
794
- current_container.update(field_name =>
795
- current_container[field_name].insert(widget.container_field_index, widget_copy))
811
+ current_container.update(attribute_name =>
812
+ current_container[attribute_name].insert(widget.container_attribute_index, widget_copy))
796
813
  end
797
814
 
798
815
  def mark_resolved
@@ -800,16 +817,17 @@ module Scrivito
800
817
  reload
801
818
  end
802
819
 
803
- def container_and_field_name_for_widget(widget_id)
804
- if field_name = field_name_in_data_for_widget(data_from_cms, widget_id)
805
- return [self, field_name]
806
- else
807
- read_widget_pool.each do |parent_widget_id, widget_data|
808
- if field_name = field_name_in_data_for_widget(widget_data, widget_id)
809
- return [widget_from_pool(parent_widget_id), field_name]
810
- end
820
+ def find_container_and_attribute_name_for_widget(widget_id)
821
+ if attribute_name = find_attribute_containing_widget(widget_id)
822
+ return [self, attribute_name]
823
+ end
824
+
825
+ all_widgets_from_pool.each do |container_widget|
826
+ if attribute_name = container_widget.find_attribute_containing_widget(widget_id)
827
+ return [container_widget, attribute_name]
811
828
  end
812
829
  end
830
+
813
831
  [nil, nil]
814
832
  end
815
833
 
@@ -841,34 +859,16 @@ module Scrivito
841
859
  raise ScrivitoError.new('Could not generate a new unused widget id')
842
860
  end
843
861
 
844
- #
845
- # Generates a +Hash+ containing all public attributes. The hash will _not_ include attributes,
846
- # which are read-only or used for internal purpose.
847
- #
848
- # @api private
849
- # @return [Hash] a hash containing all public attributes.
850
- #
851
- # @example
852
- # old_obj = Obj.homepage
853
- # attrs = old_obj.to_h
854
- # puts attrs
855
- # #=> {"_obj_class"=>"Publication", "_path"=>"/", "_id"=>"f64a68258ca15faf", "_widget_pool"=>{}}
856
- # old_obj.destroy
857
- #
858
- # new_obj = Obj.create(attrs)
859
- # puts new_obj == old_obj
860
- # #=> true
861
- #
862
- def to_h
863
- data_from_cms.to_h.except(*GENERATED_ATTRIBUTES)
864
- end
865
-
866
862
  def parent_path
867
863
  unless root? || path.nil?
868
864
  path.gsub(/\/[^\/]+$/, '').presence || '/'
869
865
  end
870
866
  end
871
867
 
868
+ def as_client_json
869
+ data_from_cms.to_h.except(*GENERATED_ATTRIBUTES)
870
+ end
871
+
872
872
  private
873
873
 
874
874
  def cms_data_for_revision(revision)
@@ -877,13 +877,6 @@ module Scrivito
877
877
  end
878
878
  end
879
879
 
880
- def field_name_in_data_for_widget(data, widget_id)
881
- data.all_custom_attributes.find do |attribute_name|
882
- (value, type) = data.value_and_type_of(attribute_name)
883
- type == "widget" && value.include?(widget_id)
884
- end
885
- end
886
-
887
880
  def read_widget_pool
888
881
  read_attribute('_widget_pool')
889
882
  end
@@ -940,8 +933,24 @@ module Scrivito
940
933
  "#{obj_class_name.underscore}/#{view_name}"
941
934
  end
942
935
 
943
- class << self
936
+ def has_system_attribute?(attribute_name)
937
+ !!SYSTEM_ATTRIBUTES[attribute_name]
938
+ end
939
+
940
+ def has_public_system_attribute?(attribute_name)
941
+ SYSTEM_ATTRIBUTES[attribute_name].is_a?(PublicSystemAttributeDefinition)
942
+ end
944
943
 
944
+ def type_of_system_attribute(attribute_name)
945
+ SYSTEM_ATTRIBUTES[attribute_name].try(:type)
946
+ end
947
+
948
+ def value_of_system_attribute(attribute_name)
949
+ attribute_value = data_from_cms.value_of(attribute_name)
950
+ attribute_name == '_last_changed' ? DateAttribute.parse(attribute_value) : attribute_value
951
+ end
952
+
953
+ class << self
945
954
  def assert_not_basic_obj(method_name)
946
955
  if self == Scrivito::BasicObj
947
956
  raise ScrivitoError, "Can not call #{method_name} on Scrivito::BasicObj."
@@ -949,13 +958,6 @@ module Scrivito
949
958
  end
950
959
  end
951
960
 
952
- def assert_has_obj_class(method_name)
953
- unless Workspace.current.obj_classes[name].present?
954
- raise ScrivitoError, "#{name} has no corresponding ObjClass."
955
- + " Please use Obj.#{method_name} instead."
956
- end
957
- end
958
-
959
961
  #
960
962
  # Restores a previously deleted +Obj+.
961
963
  #
@@ -971,21 +973,54 @@ module Scrivito
971
973
  Workspace.current.api_request(:post, '/objs', obj: obj_attributes)
972
974
  end
973
975
 
974
- def prepare_attributes_for_rest_api(attributes, obj)
975
- widget_properties = CmsRestApi::WidgetExtractor.call(attributes, obj)
976
- api_attributes = CmsRestApi::AttributeSerializer.convert(attributes)
977
- api_attributes['_widget_pool'] =
978
- CmsRestApi::AttributeSerializer.generate_widget_pool_changes(widget_properties)
976
+ def prepare_attributes_for_rest_api(obj_attributes, obj = nil)
977
+ widget_pool_attributes = CmsRestApi::WidgetExtractor.call(obj_attributes, obj)
978
+ workspace = obj ? obj.revision.workspace : Workspace.current
979
+ api_attributes = serialize_attributes(obj_attributes, widget_pool_attributes, workspace)
979
980
 
980
981
  if obj
981
982
  widget_pool = api_attributes['_widget_pool']
982
- widget_gc = WidgetGarbageCollection.new(obj, {obj => attributes}.merge(widget_properties))
983
+ widget_gc = WidgetGarbageCollection.new(obj,
984
+ {obj => obj_attributes}.merge(widget_pool_attributes))
983
985
  widget_gc.widgets_to_delete.each { |widget| widget_pool[widget.id] = nil }
984
986
  end
985
987
 
986
- [api_attributes, widget_properties]
988
+ [api_attributes, widget_pool_attributes]
989
+ end
990
+
991
+ def serialize_attributes(obj_attributes, widget_pool_attributes, workspace)
992
+ if workspace.uses_obj_classes
993
+ serialized_attributes = CmsRestApi::LegacyAttributeSerializer.convert(obj_attributes)
994
+ serialized_attributes['_widget_pool'] = CmsRestApi::LegacyAttributeSerializer
995
+ .generate_widget_pool_changes(widget_pool_attributes)
996
+ serialized_attributes
997
+ else
998
+ serializer = AttributeSerializer.new
999
+ serialized_attributes = serialize_obj_attributes(serializer, obj_attributes)
1000
+ serialized_attributes['_widget_pool'] =
1001
+ serialize_widget_pool_attributes(serializer, widget_pool_attributes)
1002
+ serialized_attributes
1003
+ end
1004
+ end
1005
+
1006
+ def serialize_obj_attributes(serializer, obj_attributes)
1007
+ serializer.serialize(obj_attributes,
1008
+ find_attribute_definitions(obj_attributes['_obj_class']) || attribute_definitions)
1009
+ end
1010
+
1011
+ def serialize_widget_pool_attributes(serializer, widget_pool_attributes)
1012
+ {}.tap do |serialized_attributes|
1013
+ widget_pool_attributes.each_pair do |widget, widget_attributes|
1014
+ obj_class = widget_attributes['_obj_class']
1015
+ serialized_attributes[widget.id] = serializer.serialize(widget_attributes,
1016
+ find_attribute_definitions(obj_class, BasicWidget) || widget.attribute_definitions)
1017
+ end
1018
+ end
1019
+ end
1020
+
1021
+ def find_attribute_definitions(obj_class, basic_class = self)
1022
+ basic_class.type_computer.compute_type(obj_class).attribute_definitions if obj_class
987
1023
  end
988
1024
  end
989
1025
  end
990
-
991
1026
  end