lib/scrivito/basic_obj.rb in scrivito_sdk-0.66.0 vs lib/scrivito/basic_obj.rb in scrivito_sdk-0.70.0.rc1
- old
+ new
@@ -2,15 +2,23 @@
require 'ostruct'
require 'active_model/naming'
module Scrivito
#
- # The abstract base class for cms objects.
+ # The abstract base class for CMS objects.
#
- # @note Please do not use {Scrivito::BasicObj} directly,
+ # A CMS object is a collection of properties and their values, as defined
+ # by its object class. These properties can be accessed in views, either
+ # directly as the object itself is rendered, or indirectly when other objects
+ # are rendered. The description of an image, for example, can be retrieved
+ # from within any view that requires it, e.g. a page on which the image is
+ # displayed.
+ #
+ # @note Please do not use {Scrivito::BasicObj} directly
# as it is intended as an abstract class.
# Always use {Obj} or a subclass of {Obj}.
+ # @see http://scrivito.com/objects-widgets-classes CMS objects, widgets, and classes
# @api public
#
class BasicObj
PublicSystemAttributeDefinition = Class.new(AttributeDefinition)
@@ -57,68 +65,69 @@
#
# Create a new {Scrivito::BasicObj Obj} in the CMS.
#
# @api public
#
- # 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. It also considers the
- # defaults set via {Scrivito::AttributeContent::ClassMethods#default_for Obj.default_for}.
+ # This allows you to set the different attributes of an Obj by providing a hash containing the
+ # attribute names as keys and the corresponding values you want to set. The defaults set via
+ # {Scrivito::AttributeContent::ClassMethods#default_for Obj.default_for} are taken into account.
#
- # @param [Hash] attributes for the new obj
- # @param [Hash] context in which the object creating should happen
+ # @param [Hash] attributes of the new obj
+ # @param [Hash] context in which the object is created
# @option context [Scrivito::User] :scrivito_user current visitor
# @return [Obj] the newly created {Scrivito::BasicObj Obj}
#
# @see Scrivito::AttributeContent::ClassMethods#default_for
#
- # @example Reference lists have to be provided as an Array of {Scrivito::BasicObj Objs}
+ # @example Provide reference lists as an Array of {Scrivito::BasicObj Obj}.
# Obj.create(:reference_list => [other_obj])
#
- # @example Passing an {Scrivito::BasicObj Obj} allows you to set a reference
+ # @example Passing an {Scrivito::BasicObj Obj} allows you to set a reference.
# Obj.create(:reference => other_obj)
#
- # @example you can upload files by passing a ruby File object
+ # @example You can upload files by passing a Ruby File object.
# Obj.create(:blob => File.new("image.png"))
#
- # @example Link list can be set as an Array of {Link Links}
+ # @example A link list can be set as an Array of {Link Links}.
# Obj.create(:link_list => [
# # external link
# Link.new(:url => "http://www.example.com", :title => "Example"),
# # internal link
# Link.new(:obj => other_obj, :title => "Other Obj")
# ])
#
# @example Passing a {Link Link} allows you to set a link.
# Obj.create(
- # external_link: Link.new(url: 'http://www.example.com', title: 'Example')
+ # external_link: Link.new(url: 'http://www.example.com', title: 'Example'),
# internal_link: Link.new(obj: other_obj, title: 'Other Obj')
# )
#
- # @example Dates attributes accept Time, Date and their subclasses (DateTime for example)
+ # @example Date attributes accept Time, Date and their subclasses (DateTime, for example).
# Obj.create(:date => Time.new)
# Obj.create(:date => Date.now)
#
- # @example String, html and enum can be set by passing a {String} value
+ # @example String, html and enum attributes can be set by passing a {String} value.
# Obj.create(:title => "My Title")
#
- # @example Arrays of {String Strings} allow you to set multi enum fields
+ # @example Multienum attributes can be set using an Array of {String Strings}.
# Obj.create(:tags => ["ruby", "rails"])
#
- # @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.
+ # @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.
# # Add new widgets
# Obj.create(:widgets => [Widget.new(_obj_class: 'TitleWidget', title: 'My Title')])
#
- # # Add a widget copy
+ # # Add a copy of a widget
# Obj.create(:widgets => [another_obj.widgets.first.copy])
#
- # # Changing a widget field
+ # # Change a widget field
# obj.update(:widgets => [obj.widgets.first])
#
# # Clear a widget field
# obj.update(:widgets => [])
#
def self.create(attributes = {}, context = {})
+ attributes = with_default_id_attribute(attributes)
if obj_class = extract_obj_class_from_attributes(attributes)
obj_class.create(attributes, context)
else
attributes = build_attributes_with_defaults(attributes, context)
attributes = prepare_attributes_for_instantiation(attributes)
@@ -129,12 +138,12 @@
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.
+ # Normally, this method should not be used.
+ # Instead, CMS objects should be retrieved from the CMS database.
def initialize(attributes = {})
update_data(ObjDataFromHash.new(attributes))
end
# @api public
@@ -151,12 +160,14 @@
@revision or raise "revision not set!"
end
### FINDERS ####################
- # Find a {Scrivito::BasicObj Obj} by its id.
- # If the parameter is an Array containing ids, return a list of corresponding Objs.
+ # Find an {Scrivito::BasicObj Obj} by its id.
+ # If the parameter is an Array of ids, the list of corresponding objects is returned.
+ # @example Find several CMS objects at once:
+ # Obj.find(['id1', 'id2'])
# @param [String, Integer, Array<String, Integer>]id_or_list
# @return [Obj, Array<Obj>]
# @api public
def self.find(id_or_list)
Workspace.current.objs.find(id_or_list)
@@ -164,31 +175,40 @@
def self.find_by_id(id)
Workspace.current.objs.find_by_id(id)
end
- # Find a {Scrivito::BasicObj Obj} by its id.
- # If the parameter is an Array containing ids, return a list of corresponding Objs.
+ # Find an {Scrivito::BasicObj Obj} by its id.
+ # If the parameter is an Array containing ids, return a list of corresponding objects.
# The results include deleted objects as well.
# @param [String, Integer, Array<String, Integer>]id_or_list
# @return [Obj, Array<Obj>]
# @api public
def self.find_including_deleted(id_or_list)
Workspace.current.objs.find_including_deleted(id_or_list)
end
- # Returns a {ObjSearchEnumerator} with the given initial subquery consisting of the four arguments.
+ # Returns an {ObjSearchEnumerator} with the given initial subquery consisting of the four arguments.
#
# Note that +field+ and +value+ can also be arrays for searching several fields or searching for several values.
#
- # @note If invoked on a subclass of Obj, the result will be restricted to instances of that subclass.
+ # @note If invoked on a subclass of Obj, the result will be restricted to instances of this subclass.
#
# {ObjSearchEnumerator}s can be chained using one of the chainable methods
# (e.g. {ObjSearchEnumerator#and} and {ObjSearchEnumerator#and_not}).
#
- # @example Look for the first 10 Objs whose obj class is "Pressrelease" and whose title contains "quarterly":
+ # @example Look for objects containing "Lorem", boosting headline matches:
+ # Obj.where(:*, :contains, 'Lorem', headline: 2).to_a
+ #
+ # @example Look for the first 10 objects whose object class is "Pressrelease" and whose title contains "quarterly":
# Obj.where(:_obj_class, :equals, 'Pressrelease').and(:title, :contains, 'quarterly').take(10)
+ #
+ # @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:
+ # Obj.where(:_obj_class, :equals, 'Item').and(:_path, :starts_with, '/en/items/').select do |item|
+ # item.categories.include?(category)
+ # end
+ #
# @param [Symbol, String, Array<Symbol, String>] field See {ObjSearchEnumerator#and} for details
# @param [Symbol, String] operator See {ObjSearchEnumerator#and} for details
# @param [String, Array<String>] value See {ObjSearchEnumerator#and} for details
# @param [Hash] boost See {ObjSearchEnumerator#and} for details
# @raise [ScrivitoError] if called directly on +BasicObj+. Use +Obj.where+ instead.
@@ -202,12 +222,12 @@
Workspace.current.objs.where(:_obj_class, :equals, name)
.and(field, operator, value, boost)
end
end
- # Returns a {ObjSearchEnumerator} of all {Scrivito::BasicObj Obj}s.
- # If invoked on a subclass of Obj, the result will be restricted to instances of that subclass.
+ # Returns an {ObjSearchEnumerator} of all {Scrivito::BasicObj Obj}s.
+ # If invoked on a subclass of Obj, the result is restricted to instances of this subclass.
# @return [ObjSearchEnumerator]
# @raise [ScrivitoError] if called directly on +BasicObj+. Use +Obj.all+ instead.
# @api public
def self.all
assert_not_basic_obj('.all')
@@ -216,12 +236,12 @@
else
find_all_by_obj_class(name)
end
end
- # Returns a {ObjSearchEnumerator} of all Objs with the given +obj_class+.
- # @param [String] obj_class name of the obj class.
+ # Returns an {ObjSearchEnumerator} of all CMS objects with the given +obj_class+.
+ # @param [String] obj_class name of the object class.
# @return [ObjSearchEnumerator]
# @api public
def self.find_all_by_obj_class(obj_class)
Workspace.current.objs.find_all_by_obj_class(obj_class)
end
@@ -234,59 +254,59 @@
def self.find_by_path(path)
Workspace.current.objs.find_by_path(path)
end
# Find an {Scrivito::BasicObj Obj} with the given name.
- # If several Objs with the given name exist, an arbitrary one of these Objs is chosen and returned.
- # If no Obj with the name exits, +nil+ is returned.
+ # If several objects with the given name exist, an arbitrary one is chosen and returned.
+ # If no Obj with this name exists, +nil+ is returned.
# @param [String] name Name of the {Scrivito::BasicObj Obj}.
# @return [Obj]
# @api public
def self.find_by_name(name)
where(:_name, :equals, name).batch_size(1).first
end
- # Returns a {ObjSearchEnumerator} of all Objs with the given name.
+ # Returns an {ObjSearchEnumerator} of all CMS objects with the given name.
# @param [String] name Name of the {Scrivito::BasicObj Obj}.
# @return [ObjSearchEnumerator]
# @api public
def self.find_all_by_name(name)
where(:_name, :equals, name)
end
- # Returns the {Scrivito::BasicObj Obj} with the given permalink, or +nil+ if no matching Obj
- # exists.
+ # Returns the {Scrivito::BasicObj Obj} with the given permalink, or +nil+ if no
+ # matching Obj exists.
# @param [String] permalink The permalink of the {Scrivito::BasicObj Obj}.
# @return [Obj]
# @api public
def self.find_by_permalink(permalink)
Workspace.current.objs.find_by_permalink(permalink)
end
- # Returns the {Scrivito::BasicObj Obj} with the given permalink, or raise ResourceNotFound if
+ # Returns the {Scrivito::BasicObj Obj} with the given permalink, or raises ResourceNotFound if
# no matching Obj exists.
# @param [String] permalink The permalink of the {Scrivito::BasicObj Obj}.
# @return [Obj]
# @api public
def self.find_by_permalink!(permalink)
find_by_permalink(permalink) or
raise ResourceNotFound, "Could not find Obj with permalink '#{permalink}'"
end
- # Hook method to control which page classes should be available for a page with given path.
- # Override it to allow only certain classes or none.
+ # Hook method that lets you control which page classes are made available for pages with the given path.
+ # Override it to allow only specific classes or none at all.
# Must return either +NilClass+, or +Array+.
#
# Be aware that the given argument is a parent path.
- # E.g. when creating a page with path +/products/shoes+ then the argument will be +/products+.
+ # E.g., when creating a page whose path is +/products/shoes+, the argument must be +/products+.
# The given parent path can also be +NilClass+.
#
- # If +NilClass+ is returned, then all possible classes will be available.
- # By default +NilClass+ is returned.
+ # If +NilClass+ is returned, all page classes are made available.
+ # By default, +NilClass+ is returned.
#
- # If +Array+ is returned, then it should include the desired classes.
- # Only this classes will be available. Order of the classes will be preserved.
+ # If an +Array+ is returned, it is expected to contain the available classes.
+ # The order of the classes is preserved.
#
# @param [String, NilClass] parent_path Path of the parent obj
# @return [NilClass, Array<Class>]
# @api public
def self.valid_page_classes_beneath(parent_path)
@@ -295,31 +315,45 @@
def self.valid_page_ruby_classes_beneath(parent_path)
assert_classes(valid_page_classes_beneath(parent_path), '.valid_page_classes_beneath') ||
Scrivito.models.pages.to_a
end
- # Update the {Scrivito::BasicObj Obj} with the attributes provided.
+ # Update the {Scrivito::BasicObj Obj} using the attributes provided.
#
- # For an overview of which values you can set via this method see the
+ # For an overview of the values you can set via this method, see the
# documentation of {Scrivito::BasicObj.create Obj.create}.
#
# Additionally, +update+ accepts a +_widget_pool+ hash in +attributes+ to modify widgets.
# The keys of +_widget_pool+ are widget instances, the values are the modified attributes of
# these particular widgets.
#
# @api public
# @param [Hash] attributes
+ # @example Update the URL of a link:
+ # link = obj.my_link
+ # link.url = "http://www.example.com"
+ # obj.update(my_link: link)
+ #
+ # @example Update binary attributes:
+ # obj.update(blob: img_obj.binary)
+ # obj.update(thumbnail: File.new("/path/to/small.jpg"))
+ #
+ # @example Remove the first and the last widget from a widget field:
+ # obj.update(
+ # my_widget_field: obj[:my_widget_field][1..-2]
+ # )
+ #
# @example Move the +widget_to_move+ widget from the +left+ widget field of the +two_column_widget1+ widget to +left+ of +two_column_widget2+:
# obj.update(
# _widget_pool: {
# two_column_widget1 => {left: two_column_widget1.left - [widget_to_move]},
# two_column_widget2 => {left: two_column_widget2.left + [widget_to_move]}
# },
# headline: "Some widgets were moved!"
# )
#
- # @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+:
+ # @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:
# obj.update(
# main_content: @obj.main_content + [widget_to_move],
# _widget_pool: {
# two_column_widget1 => {
# right: two_column_widget1.right - [widget_to_move]
@@ -332,28 +366,31 @@
reload_data
CmsRestApi::WidgetExtractor.notify_persisted_widgets(self, widget_properties)
self
end
- # Creates a copy of the +Obj+.
+ # Creates a copy of the {Scrivito::BasicObj Obj}.
# @api public
# @param [Hash] options
# @option options [String,Symbol] :_path (nil) the path of the copy.
# @option options [String,Symbol] :_id (nil) the id of the copy.
# @option options [String,Symbol] :_permalink (nil) the permalink of the copy.
# @raise [ArgumentError] if +options+ includes invalid keys.
# @return [Obj] the created copy
- # @example Copy a blog post.
+ # @example Copy a blog post:
# blog_post = Obj.find_by_path('/blog/first_post')
# blog_post.copy(_path: '/blog/second_post')
def copy(options={})
options = options.stringify_keys.assert_valid_keys('_path', '_id', '_permalink')
- json = workspace.api_request(:post, '/objs', obj: copyable_attributes.merge(options))
+ attributes_for_copy = self.class.with_default_id_attribute(copyable_attributes)
+ attributes_for_copy = copy_binaries(attributes_for_copy)
+
+ json = workspace.api_request(:post, '/objs', obj: attributes_for_copy.merge(options))
self.class.find(json['_id'])
end
- # Destroys the {Scrivito::BasicObj Obj} in the current {Workspace}
+ # Destroys the {Scrivito::BasicObj Obj} in the current {Workspace}.
# @api public
def destroy
if children.any?
raise ClientError.new(I18n.t('scrivito.errors.models.basic_obj.has_children'), 412)
end
@@ -362,20 +399,20 @@
def to_param
id
end
- # return the {Scrivito::BasicObj Obj} that is the parent of this Obj.
- # returns +nil+ for the root Obj.
+ # Returns the {Scrivito::BasicObj Obj} that is the parent of this Obj.
+ # Returns +nil+ for the root Obj.
# @api public
def parent
if child_path?
workspace.objs.find_by_path(parent_path)
end
end
- # Returns an Array of all the ancestor objects, starting at the root and ending at this object's parent.
+ # Returns an Array of all the ancestor objects, starting at the root and ending at the parent of this object.
# @return [Array<Obj>]
# @api public
def ancestors
return [] unless child_path?
@@ -384,93 +421,79 @@
end
ancestor_paths[0] = "/"
Workspace.current.objs.find_by_paths(ancestor_paths)
end
- # return a list of all child {Scrivito::BasicObj Obj}s.
+ # Returns a list of all child {Scrivito::BasicObj Obj}s.
# @return [Array<Obj>]
# @api public
def children
return [] unless path
workspace.objs.find_by_parent_path(path)
end
### ATTRIBUTES #################
- # returns the {Scrivito::BasicObj Obj}'s path as a String.
+ # Returns the path of the {Scrivito::BasicObj Obj} as a String.
# @api public
def path
read_attribute('_path')
end
- # returns the {Scrivito::BasicObj Obj}'s name, i.e. the last component of the path.
+ # Returns the name of the {Scrivito::BasicObj Obj}. The name is the last component of a path.
# @api public
def name
if child_path?
path.match(/[^\/]+$/)[0]
else
""
end
end
- # Returns the root {Scrivito::BasicObj Obj}, i.e. the Obj with the path "/"
+ # Returns the root {Scrivito::BasicObj Obj}, the object whose path is "/".
# @return [Obj]
# @api public
def self.root
BasicObj.find_by_path('/') or raise ResourceNotFound,
'"Obj.root" not found: There is no "Obj" with path "/". '\
'Maybe you forgot the migration when setting up your Scrivito application? '\
'Try "bundle exec rake scrivito:migrate scrivito:migrate:publish".'
end
- # Returns the homepage obj. This can be overridden in your application's +Obj+.
- # Use {#homepage?} to check if an obj is the homepage.
- # @return [Obj]
- # @api public
- def self.homepage
- root
- end
-
def self.generate_widget_pool_id
SecureRandom.hex(4)
end
- # returns the obj's permalink.
+ # Returns the permalink of the {Scrivito::BasicObj Obj}.
# @api public
def permalink
read_attribute('_permalink')
end
- # This method determines the controller that should be invoked when the +Obj+ is requested.
- # By default a controller matching the Obj's obj_class will be used.
- # If the controller does not exist, the CmsController will be used as a fallback.
+ # This method determines the controller to be invoked when the +Obj+ is requested.
+ # By default, a controller matching the obj_class of the Obj is used.
+ # If the controller does not exist, the CmsController is used as a fallback.
# Override this method to force a different controller to be used.
# @return [String]
# @api public
def controller_name
obj_class_name
end
- # This method determines the action that should be invoked when the +Obj+ is requested.
+ # This method determines the action to be invoked when the +Obj+ is requested.
# The default action is 'index'.
# Override this method to force a different action to be used.
# @return [String]
# @api public
def controller_action_name
"index"
end
- # Returns true if the current obj is the {.homepage} obj.
- # @api public
- def homepage?
- self == self.class.homepage
- end
-
# This method is used to calculate a part of a URL of this Obj.
#
- # The routing schema: <code><em><obj.id></em>/<em><obj.slug></em></code>
+ # The routing schema: <code><em><obj.slug></em>-<em><obj.id></em></code>
#
# The default is {http://apidock.com/rails/ActiveSupport/Inflector/parameterize parameterize}
# on +obj.title+.
#
# You can customize this part by overriding {#slug}.
@@ -479,12 +502,12 @@
def slug
(title || '').parameterize
end
#
- # This method determines the description that is shown in the UI
- # and defaults to {Scrivito::BasicObj#display_title}. It can be overridden by a custom value.
+ # This method determines the description shown in the UI.
+ # It defaults to {Scrivito::BasicObj#display_title}. It can be overridden by a custom value.
#
# @api public
#
def description_for_editor
display_title
@@ -503,11 +526,11 @@
(binary_title || title).presence || self.class.description_for_editor
end
# The alt description of an +Obj+ used for {ScrivitoHelper#scrivito_image_tag}.
#
- # By default this method returns the +title+ of this +Obj+.
+ # By default, this method returns the +title+ of the +Obj+.
#
# You can customize this part by overriding {#alt_description}.
#
# @return [String]
# @api public
@@ -519,78 +542,78 @@
def title
read_attribute('title')
end
# @api public
- # This method indicates if the Obj represents binary data. Binaries are
+ # This method indicates whether the Obj represents binary data. Binaries are
# handled differently in that they are not rendered using the normal layout
# but sent as a file. Examples of binary resources are Images or PDFs.
#
- # Every Obj that has an attribute +blob+ of the type +binary+ is
+ # Every Obj that has a +blob+ attribute of the +binary+ type is
# considered a binary
#
# @return true if this Obj represents a binary resource.
def binary?
blob_attribute_definition = attribute_definitions['blob']
blob_attribute_definition.present? && blob_attribute_definition.type == 'binary'
end
#
- # When delivering binary Objs, this method decides whether the image transformations should be
- # applied by default.
+ # When delivering binary objects, this method decides whether the image transformations should
+ # be applied by default.
#
- # @api beta
+ # @api public
#
- # By default this method returns +false+.
- # Override in subclasses to fit your needs.
+ # By default, this method returns +false+.
+ # Override it in subclasses to fit your needs.
#
- # @note Only relevant for binary Objs
+ # @note Only relevant for binary objects
# @see Scrivito::Configuration.default_image_transformation=
# @see Scrivito::Binary#transform
#
def apply_image_transformation?
false
end
- # Returns true if this object is the root object.
+ # Returns true if this Obj is the root Obj.
# @api public
def root?
path == "/"
end
# Returns a list of children excluding the binary? ones unless :all is specfied.
- # This is mainly used for navigations.
+ # This is useful for creating navigations.
# @return [Array<Obj>]
# @api public
def toclist(*args)
return [] if binary?
toclist = children
toclist = toclist.reject { |toc| toc.binary? } unless args.include?(:all)
toclist
end
- # @param objs_to_be_sorted [Array<Scrivito::BasicObj>] unsorted list of Objs
- # @param list [Array<Scrivito::BasicObj>] list of Objs that defines the order
- # @return [Array<Scrivito::BasicObj>] a sorted list of Objs. Any objs present in
- # +objs_to_be_sorted+ but not in +list+ are appended at the end, sorted by +Obj#id+
+ # @param objs_to_be_sorted [Array<Scrivito::BasicObj>] unsorted list of CMS objects
+ # @param list [Array<Scrivito::BasicObj>] list of objects that defines the order
+ # @return [Array<Scrivito::BasicObj>] a sorted list of objects. Objects present in
+ # +objs_to_be_sorted+ but not in +list+ are appended to the end, sorted by +Obj#id+
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.
+ # This should be a Set, because it's faster in this particular case.
SYSTEM_KEYS = Set.new(%w[
body
_id
_last_changed
_path
_permalink
_obj_class
title
])
- # 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+.
+ # Returns the value of a 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 SYSTEM_KEYS.include?(key)
read_attribute(key)
@@ -607,12 +630,12 @@
else
super
end
end
- # Reloads the attributes of this object from the database.
- # Notice that the ruby class of this Obj instance will NOT change,
+ # Reloads the attributes of this Obj from the database.
+ # Note that the Ruby class of this Obj instance will NOT change,
# even if the obj_class in the database has changed.
# @api public
def reload
workspace.reload
reload_data
@@ -650,14 +673,14 @@
else
quick_modification
end
end
- # similar to modification, but faster if you are only interested in
+ # Similar to modification but faster if you are only interested in
# "new" and "deleted".
- # this method sometimes does not return a string, but an instance of
- # ObjData instead. this indicates that the modification is either
+ # This method sometimes does not return a string, but an instance of
+ # ObjData instead. This indicates that the modification is either
# UNMODIFIED or EDITED. Which one it is can be determined by comparing
# the returned ObjData.
def quick_modification(revision)
return Modification::UNMODIFIED unless revision
@@ -667,11 +690,11 @@
data_for_comparison = cms_data_for_revision(revision)
if data_for_comparison.present?
if modification_attr == 'deleted'
- # Obj exists in comparison revision, but not in current
+ # Obj exists in comparison revision but not in current
Modification::DELETED
else
# Obj exists in both revisions, leave the actual comparions
# up to the caller
data_for_comparison
@@ -679,11 +702,11 @@
else
if modification_attr == "deleted"
# Obj does not exist in either revision
Modification::UNMODIFIED
else
- # Obj exists in current, but not in comparision revision
+ # Obj exists in current but not in comparision revision
Modification::NEW
end
end
end
@@ -705,20 +728,20 @@
def content_type
raise "The method `content_type' and `mime_type' were removed. Please use `binary_content_type' instead"
end
alias mime_type content_type
- # returns the extension (the part after the last dot) from the Obj's name.
- # returns an empty string if no extension is present in the Obj's name.
+ # Returns the name extension of the Obj (the part after the last dot).
+ # Returns an empty string if the name of the Obj doesn't have an extension.
# @return [String]
# @api public
def file_extension
File.extname(name)[1..-1] || ""
end
- # Returns the body (main content) of the Obj for non-binary Objs.
- # Returns +nil+ for binary Objs.
+ # Returns the body (main content) of the Obj for non-binary objects.
+ # Returns +nil+ for binary objects.
# @return [String]
# @api public
def body
if binary?
nil
@@ -726,37 +749,37 @@
read_attribute('body')
end
end
# @api public
- # This method is intended for Objs that represent binary resources like
- # images or pdf documents. If this Obj represents a binary file, an instance
+ # This method is intended for CMS objects that represent binary resources like
+ # images or PDF documents. If the Obj represents a binary file, an instance
# of {Binary} is returned.
#
- # This method returns the attribute +blob+ if it is of the type +binary+.
+ # This method returns the +blob+ attribute if its type is +binary+.
#
# @return [Binary, nil]
def binary
self[:blob] if self[:blob].is_a?(Binary)
end
# @api public
- # This method returns the length in bytes of the binary of this obj
- # @return [Fixnum] If no binary is set it will return 0
+ # This method returns the byte length of the binary of the Obj.
+ # @return [Fixnum] If no binary is set, 0 is returned.
def binary_length
binary.try(:content_length) || 0
end
# @api public
- # This method returns the content type of the binary of this obj if it is set.
+ # This method returns the content type of the binary of this Obj if the binary is set.
# @return [String, nil]
def binary_content_type
binary.try(:content_type)
end
# @api public
- # This method returns the url under which the content of this binary is
+ # This method returns the URL under which the content of this binary is
# available to the public if the binary is set.
#
# See {Binary#url} for details
# @return [String, nil]
def binary_url
@@ -794,11 +817,11 @@
widget_data = widget_data_from_pool(widget_id)
instantiate_widget(widget_id, widget_data) if widget_data
end
# @api public
- # Allows accessing the {Scrivito::BasicWidget Widgets} of this Obj
+ # Allows accessing the {Scrivito::BasicWidget Widgets} of this Obj.
#
# @example Access a widget by its id
# obj.widgets['widget_id']
#
# @return [Scrivito::WidgetCollection]
@@ -814,13 +837,13 @@
#
# Reverts all changes made to the +Obj+ in the current workspace.
#
# @api public
#
- # @note This method does not support +Obj+s, which are +new+.
+ # @note This method does not support +Obj+s that are +new+.
# Please use {Scrivito::BasicObj#destroy Obj#destroy} to destroy them.
- # @note This method does not support +Obj+s, which are +deleted+.
+ # @note This method does not support +Obj+s that are +deleted+.
# Please use {Scrivito::BasicObj.restore Obj.restore} to restore them.
#
# @raise [ScrivitoError] If the current workspace is +published+.
# @raise [ScrivitoError] If the +Obj+ is +new+.
# @raise [ScrivitoError] If the +Obj+ is +deleted+.
@@ -914,14 +937,10 @@
def parent_path
ParentPath.of(path) unless root?
end
- def as_client_json
- data_from_cms.to_h.except(*GENERATED_ATTRIBUTES)
- end
-
def outdated?
return false if workspace.published?
base_revision = workspace.base_revision
published_revision = Workspace.published.revision
@@ -930,12 +949,68 @@
return true if has_conflict?
cms_data_for_revision(base_revision) != cms_data_for_revision(published_revision)
end
+ def transfer_modifications_to(target_workspace)
+ return unless modification
+ if in_revision(target_workspace.revision).try(:modification)
+ raise TransferModificationsError, "Already modified in workspace #{target_workspace.id}"
+ end
+ copy_modifications_to(target_workspace)
+ reset_modifications
+ end
+
private
+ def copy_modifications_to(target_workspace)
+ case
+ when new? then create_in(target_workspace)
+ when deleted? then destroy_in(target_workspace)
+ else update_in(target_workspace)
+ end
+ end
+
+ def create_in(target_workspace)
+ target_workspace.api_request(:post, '/objs', obj: get_attributes)
+ end
+
+ def update_in(target_workspace)
+ update_attributes = fill_in_missing_attributes_as_nil(copyable_attributes)
+ target_workspace.api_request(:put, "/objs/#{id}", obj: update_attributes)
+ end
+
+ def destroy_in(target_workspace)
+ target_workspace.api_request(:delete, "/objs/#{id}")
+ end
+
+ def fill_in_missing_attributes_as_nil(attributes, type_computer=Obj.type_computer)
+ obj_class = type_computer.compute_type_without_fallback(attributes['_obj_class'])
+ missing_attributes = obj_class.attribute_definitions.map(&:name) - attributes.keys
+
+ if attributes['_widget_pool']
+ attributes['_widget_pool'].each do |id, widget_attributes|
+ attributes['_widget_pool'][id] = fill_in_missing_attributes_as_nil(
+ widget_attributes, Widget.type_computer)
+ end
+ end
+
+ missing_attributes.each do |attribute_name|
+ attributes[attribute_name] = nil
+ end
+
+ attributes
+ end
+
+ def reset_modifications
+ case
+ when new? then destroy
+ when deleted? then self.class.restore(id)
+ else revert
+ end
+ end
+
def cms_data_for_revision(revision)
return nil unless revision
result = CmsBackend.instance.find_obj_data_by(revision, "id", [id])
obj_data = result.first.first
@@ -979,15 +1054,40 @@
def prepare_attributes_for_rest_api(attributes)
self.class.prepare_attributes_for_rest_api(attributes, self)
end
def copyable_attributes
+ get_attributes.except(*GENERATED_ATTRIBUTES).except(*UNIQ_ATTRIBUTES)
+ end
+
+ def get_attributes
workspace.api_request(:get, "/objs/#{id}")
- .except(*GENERATED_ATTRIBUTES)
- .except(*UNIQ_ATTRIBUTES)
end
+ def copy_binaries(attributes)
+ attribute_defintions = self.class.find_attribute_definitions(obj_class_name)
+ destination_obj_id = attributes.fetch(:_id)
+
+ Hash[attributes.map do |name, value|
+ if value && attribute_defintions[name].try(:type) == 'binary'
+ binary = self[name]
+ [name, ['binary', copy_binary(destination_obj_id, binary)]]
+ else
+ [name, value]
+ end
+ end]
+ end
+
+ def copy_binary(destination_obj_id, binary)
+ normalized_id = CmsRestApi.normalize_path_component(binary.id)
+ CmsRestApi.put("/blobs/#{normalized_id}/copy", {
+ destination_obj_id: destination_obj_id,
+ filename: binary.filename,
+ content_type: binary.content_type,
+ })
+ end
+
def assert_revertable
workspace.assert_revertable
if modification == Modification::NEW || modification == Modification::DELETED
raise ScrivitoError, "cannot revert changes, since obj is #{modification}."
end
@@ -1037,27 +1137,31 @@
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(obj_attributes, obj = nil)
- widget_pool_attributes = CmsRestApi::WidgetExtractor.call(obj_attributes, obj)
+ def prepare_attributes_for_rest_api(attributes, obj = nil)
+ widget_pool_attributes, obj_attributes = CmsRestApi::WidgetExtractor.call(attributes, obj)
+ obj_id = obj ? obj.id : obj_attributes.fetch(:_id)
workspace = obj ? obj.revision.workspace : Workspace.current
- api_attributes = serialize_attributes(obj_attributes, widget_pool_attributes, workspace)
+ api_attributes = serialize_attributes(
+ obj_attributes, widget_pool_attributes, workspace, obj_id
+ )
+
if obj
widget_pool = api_attributes['_widget_pool']
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_pool_attributes]
end
- def serialize_attributes(obj_attributes, widget_pool_attributes, workspace)
- serializer = AttributeSerializer.new(obj_attributes['_obj_class'] || name)
+ def serialize_attributes(obj_attributes, widget_pool_attributes, workspace, obj_id)
+ serializer = AttributeSerializer.new(obj_attributes['_obj_class'] || name, obj_id)
serialized_attributes = serialize_obj_attributes(serializer, obj_attributes)
serialized_attributes['_widget_pool'] =
serialize_widget_pool_attributes(serializer, widget_pool_attributes)
serialized_attributes
end
@@ -1077,9 +1181,13 @@
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
+
+ def with_default_id_attribute(attributes)
+ attributes.reverse_merge(_id: SecureRandom.hex(8))
end
end
end
end