lib/scrivito/basic_obj.rb in scrivito_sdk-0.50.1 vs lib/scrivito/basic_obj.rb in scrivito_sdk-0.60.0.rc1
- old
+ new
@@ -1,17 +1,35 @@
require 'json'
require 'ostruct'
require 'active_model/naming'
module Scrivito
+ #
# The abstract base class for cms objects.
#
# @note Please do not use {Scrivito::BasicObj} directly,
# as it is intended as an abstract class.
# Always use {Obj} or a subclass of {Obj}.
# @api public
+ #
class BasicObj
+ # @!parse extend Scrivito::AttributeContent::ClassMethods
+
+ PublicSystemAttributeDefinition = Class.new(AttributeDefinition)
+
+ SYSTEM_ATTRIBUTES = AttributeDefinitionCollection.new(
+ '_id' => PublicSystemAttributeDefinition.new(:_id, :string),
+ '_last_changed' => PublicSystemAttributeDefinition.new(:_last_changed, :date),
+ '_obj_class' => PublicSystemAttributeDefinition.new(:_obj_class, :string),
+ '_path' => PublicSystemAttributeDefinition.new(:_path, :string),
+ '_permalink' => PublicSystemAttributeDefinition.new(:_permalink, :string),
+
+ '_conflicts' => AttributeDefinition.new(:_conflicts, nil),
+ '_modification' => AttributeDefinition.new(:_modification, nil),
+ '_widget_pool' => AttributeDefinition.new(:_widget_pool, nil),
+ )
+
UNIQ_ATTRIBUTES = %w[
_id
_path
_permalink
].freeze
@@ -35,10 +53,11 @@
def self.reset_type_computer!
@_type_computer = nil
end
+ #
# Create a new {Scrivito::BasicObj Obj} in the cms
#
# This allows you to set the different attributes types of an obj by
# providing a hash with the attributes names as key and the values you want
# to set as values
@@ -90,17 +109,22 @@
# obj.update(:widgets => [])
#
# @api public
# @param [Hash] attributes
# @return [Obj] the newly created {Scrivito::BasicObj Obj}
- def self.create(attributes)
- attributes = with_default_obj_class(attributes)
- api_attributes, widget_properties = prepare_attributes_for_rest_api(attributes, nil)
- json = Workspace.current.api_request(:post, '/objs', obj: api_attributes)
- obj = find(json['_id'])
- CmsRestApi::WidgetExtractor.notify_persisted_widgets(obj, widget_properties)
- obj
+ #
+ def self.create(attributes = {})
+ if obj_class = extract_obj_class_from_attributes(attributes)
+ obj_class.create(attributes)
+ else
+ attributes = prepare_attributes_for_instantiation(attributes)
+ api_attributes, widget_properties = prepare_attributes_for_rest_api(attributes)
+ json = Workspace.current.api_request(:post, '/objs', obj: api_attributes)
+ obj = find(json['_id'])
+ CmsRestApi::WidgetExtractor.notify_persisted_widgets(obj, widget_properties)
+ obj
+ end
end
# Create a new {Scrivito::BasicObj Obj} instance with the given values and attributes.
# Normally this method should not be used.
# Instead Objs should be loaded from the cms database.
@@ -169,11 +193,10 @@
def self.where(field, operator, value, boost = nil)
assert_not_basic_obj('.where')
if self == ::Obj
Workspace.current.objs.where(field, operator, value, boost)
else
- assert_has_obj_class('.where')
Workspace.current.objs.where(:_obj_class, :equals, name)
.and(field, operator, value, boost)
end
end
@@ -186,11 +209,10 @@
def self.all
assert_not_basic_obj('.all')
if self == ::Obj
Workspace.current.objs.all
else
- assert_has_obj_class('.all')
find_all_by_obj_class(name)
end
end
# Returns a {ObjSearchEnumerator} of all Objs with the given +obj_class+.
@@ -399,11 +421,10 @@
# @api public
def self.homepage
root
end
- # @api private
def self.generate_widget_pool_id
SecureRandom.hex(4)
end
# returns the obj's permalink.
@@ -487,16 +508,12 @@
# Every Obj that has an attribute +blob+ of the type +binary+ is
# considered a binary
#
# @return true if this Obj represents a binary resource.
def binary?
- if obj_type = read_attribute('_obj_type')
- [:image, :generic].include?(obj_type.to_sym)
- else
- blob_attribute = obj_class.attributes['blob']
- blob_attribute && blob_attribute.type == 'binary'
- end
+ blob_attribute_definition = attribute_definitions['blob']
+ blob_attribute_definition.present? && blob_attribute_definition.type == 'binary'
end
# Returns true if this object is the root object.
# @api public
def root?
@@ -521,36 +538,36 @@
def self.sort_by_list(objs_to_be_sorted, list)
(list & objs_to_be_sorted) + (objs_to_be_sorted - list).sort_by(&:id)
end
# This should be a SET, because it's faster in this particular case.
- INTERNAL_KEYS = Set.new(%w[
+ SYSTEM_KEYS = Set.new(%w[
body
_id
_last_changed
_path
_permalink
_obj_class
title
])
- # Returns the value of an internal or external attribute specified by its name.
+ # Returns the value of an system or custom attribute specified by its name.
# Passing an invalid key will not raise an error, but return +nil+.
# @api public
def [](key)
key = key.to_s
- if INTERNAL_KEYS.include?(key)
+ if SYSTEM_KEYS.include?(key)
read_attribute(key)
else
super
end
end
def has_attribute?(key)
key = key.to_s
- if INTERNAL_KEYS.include?(key)
+ if SYSTEM_KEYS.include?(key)
true
else
super
end
end
@@ -594,18 +611,18 @@
end
end
def modification(revision=workspace.base_revision)
return Modification::UNMODIFIED unless revision
+ return read_attribute('_modification') if revision == workspace.base_revision
- obj_data_from_revision = cms_data_for_revision(revision)
-
if deleted?(revision)
Modification::DELETED
elsif new?(revision)
Modification::NEW
else # Edited
+ obj_data_from_revision = cms_data_for_revision(revision)
if obj_data_from_revision.present?
if data_from_cms == obj_data_from_revision
Modification::UNMODIFIED
else
Modification::EDITED
@@ -787,31 +804,32 @@
if container.kind_of?(BasicWidget) && !widgets[container.id]
raise ScrivitoError, 'Can not restore a widget inside a deleted widget'
end
widget_copy = widget.copy_for_restore(read_widget_pool.keys)
- field_name = widget.container_field_name
+ attribute_name = widget.container_attribute_name
current_container = widgets[container.id] || self
- current_container.update(field_name =>
- current_container[field_name].insert(widget.container_field_index, widget_copy))
+ current_container.update(attribute_name =>
+ current_container[attribute_name].insert(widget.container_attribute_index, widget_copy))
end
def mark_resolved
workspace.api_request(:put, "/objs/#{id}", obj: {_conflicts: nil})
reload
end
- def container_and_field_name_for_widget(widget_id)
- if field_name = field_name_in_data_for_widget(data_from_cms, widget_id)
- return [self, field_name]
- else
- read_widget_pool.each do |parent_widget_id, widget_data|
- if field_name = field_name_in_data_for_widget(widget_data, widget_id)
- return [widget_from_pool(parent_widget_id), field_name]
- end
+ def find_container_and_attribute_name_for_widget(widget_id)
+ if attribute_name = find_attribute_containing_widget(widget_id)
+ return [self, attribute_name]
+ end
+
+ all_widgets_from_pool.each do |container_widget|
+ if attribute_name = container_widget.find_attribute_containing_widget(widget_id)
+ return [container_widget, attribute_name]
end
end
+
[nil, nil]
end
def widget_data_from_pool(widget_id)
read_widget_pool[widget_id]
@@ -839,53 +857,28 @@
end
raise ScrivitoError.new('Could not generate a new unused widget id')
end
- #
- # Generates a +Hash+ containing all public attributes. The hash will _not_ include attributes,
- # which are read-only or used for internal purpose.
- #
- # @api private
- # @return [Hash] a hash containing all public attributes.
- #
- # @example
- # old_obj = Obj.homepage
- # attrs = old_obj.to_h
- # puts attrs
- # #=> {"_obj_class"=>"Publication", "_path"=>"/", "_id"=>"f64a68258ca15faf", "_widget_pool"=>{}}
- # old_obj.destroy
- #
- # new_obj = Obj.create(attrs)
- # puts new_obj == old_obj
- # #=> true
- #
- def to_h
- data_from_cms.to_h.except(*GENERATED_ATTRIBUTES)
- end
-
def parent_path
unless root? || path.nil?
path.gsub(/\/[^\/]+$/, '').presence || '/'
end
end
+ def as_client_json
+ data_from_cms.to_h.except(*GENERATED_ATTRIBUTES)
+ end
+
private
def cms_data_for_revision(revision)
if revision
CmsBackend.instance.find_obj_data_by(revision, "id", [id]).first.first
end
end
- def field_name_in_data_for_widget(data, widget_id)
- data.all_custom_attributes.find do |attribute_name|
- (value, type) = data.value_and_type_of(attribute_name)
- type == "widget" && value.include?(widget_id)
- end
- end
-
def read_widget_pool
read_attribute('_widget_pool')
end
def instantiate_widget(widget_id, widget_data)
@@ -938,26 +931,35 @@
def view_path(view_name)
"#{obj_class_name.underscore}/#{view_name}"
end
- class << self
+ def has_system_attribute?(attribute_name)
+ !!SYSTEM_ATTRIBUTES[attribute_name]
+ end
+ def has_public_system_attribute?(attribute_name)
+ SYSTEM_ATTRIBUTES[attribute_name].is_a?(PublicSystemAttributeDefinition)
+ end
+
+ def type_of_system_attribute(attribute_name)
+ SYSTEM_ATTRIBUTES[attribute_name].try(:type)
+ end
+
+ def value_of_system_attribute(attribute_name)
+ attribute_value = data_from_cms.value_of(attribute_name)
+ attribute_name == '_last_changed' ? DateAttribute.parse(attribute_value) : attribute_value
+ end
+
+ class << self
def assert_not_basic_obj(method_name)
if self == Scrivito::BasicObj
raise ScrivitoError, "Can not call #{method_name} on Scrivito::BasicObj."
+ " Only call it on Obj or subclasses of Obj"
end
end
- def assert_has_obj_class(method_name)
- unless Workspace.current.obj_classes[name].present?
- raise ScrivitoError, "#{name} has no corresponding ObjClass."
- + " Please use Obj.#{method_name} instead."
- end
- end
-
#
# Restores a previously deleted +Obj+.
#
# @api public
#
@@ -969,23 +971,56 @@
base_revision_path = "revisions/#{Workspace.current.base_revision_id}/objs/#{obj_id}"
obj_attributes = CmsRestApi.get(base_revision_path).merge('_id' => obj_id)
Workspace.current.api_request(:post, '/objs', obj: obj_attributes)
end
- def prepare_attributes_for_rest_api(attributes, obj)
- widget_properties = CmsRestApi::WidgetExtractor.call(attributes, obj)
- api_attributes = CmsRestApi::AttributeSerializer.convert(attributes)
- api_attributes['_widget_pool'] =
- CmsRestApi::AttributeSerializer.generate_widget_pool_changes(widget_properties)
+ def prepare_attributes_for_rest_api(obj_attributes, obj = nil)
+ widget_pool_attributes = CmsRestApi::WidgetExtractor.call(obj_attributes, obj)
+ workspace = obj ? obj.revision.workspace : Workspace.current
+ api_attributes = serialize_attributes(obj_attributes, widget_pool_attributes, workspace)
if obj
widget_pool = api_attributes['_widget_pool']
- widget_gc = WidgetGarbageCollection.new(obj, {obj => attributes}.merge(widget_properties))
+ widget_gc = WidgetGarbageCollection.new(obj,
+ {obj => obj_attributes}.merge(widget_pool_attributes))
widget_gc.widgets_to_delete.each { |widget| widget_pool[widget.id] = nil }
end
- [api_attributes, widget_properties]
+ [api_attributes, widget_pool_attributes]
end
+
+ def serialize_attributes(obj_attributes, widget_pool_attributes, workspace)
+ if workspace.uses_obj_classes
+ serialized_attributes = CmsRestApi::LegacyAttributeSerializer.convert(obj_attributes)
+ serialized_attributes['_widget_pool'] = CmsRestApi::LegacyAttributeSerializer
+ .generate_widget_pool_changes(widget_pool_attributes)
+ serialized_attributes
+ else
+ serializer = AttributeSerializer.new
+ serialized_attributes = serialize_obj_attributes(serializer, obj_attributes)
+ serialized_attributes['_widget_pool'] =
+ serialize_widget_pool_attributes(serializer, widget_pool_attributes)
+ serialized_attributes
+ end
+ end
+
+ def serialize_obj_attributes(serializer, obj_attributes)
+ serializer.serialize(obj_attributes,
+ find_attribute_definitions(obj_attributes['_obj_class']) || attribute_definitions)
+ end
+
+ def serialize_widget_pool_attributes(serializer, widget_pool_attributes)
+ {}.tap do |serialized_attributes|
+ widget_pool_attributes.each_pair do |widget, widget_attributes|
+ obj_class = widget_attributes['_obj_class']
+ serialized_attributes[widget.id] = serializer.serialize(widget_attributes,
+ find_attribute_definitions(obj_class, BasicWidget) || widget.attribute_definitions)
+ end
+ end
+ end
+
+ def find_attribute_definitions(obj_class, basic_class = self)
+ basic_class.type_computer.compute_type(obj_class).attribute_definitions if obj_class
+ end
end
end
-
end