scrivito_sdk 0.50.1 → 0.60.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 (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