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