lib/scrivito/basic_obj.rb in scrivito_sdk-0.18.1 vs lib/scrivito/basic_obj.rb in scrivito_sdk-0.30.0.rc1
- old
+ new
@@ -1,11 +1,15 @@
require 'json'
require 'ostruct'
require 'active_model/naming'
module Scrivito
- # The CMS file class
+ # 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
UNIQ_ATTRIBUTES = %w[
_id
_path
@@ -31,20 +35,20 @@
def self.reset_type_computer!
@_type_computer = nil
end
- # Create a new {BasicObj Obj} in the cms
+ # 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
#
- # @example Reference lists have to be provided as an Array of {BasicObj Objs}
+ # @example Reference lists have to be provided as an Array of {Scrivito::BasicObj Objs}
# Obj.create(:reference_list => [other_obj])
#
- # @example Passing an {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
# Obj.create(:blob => File.new("image.png"))
#
@@ -70,11 +74,11 @@
# Obj.create(:title => "My Title")
#
# @example Arrays of {String Strings} allow you to set multi enum fields
# Obj.create(:tags => ["ruby", "rails"])
#
- # @example Simply pass an Array of {BasicWidget Widgets} to change a widget field. See {BasicWidget#clone Widget#clone} on how to clone a widget.
+ # @example Simply pass an Array of {Scrivito::BasicWidget Widgets} to change a widget field. See {Scrivito::BasicWidget#clone Widget#clone} on how to clone a widget.
# # Add new widgets
# Obj.create(:widgets => [Widget.new(_obj_class: 'TitleWidget', title: 'My Title')])
#
# # Add a widget clone
# Obj.create(:widgets => [another_obj.widgets.first.clone])
@@ -85,21 +89,21 @@
# # Clear a widget field
# obj.update(:widgets => [])
#
# @api public
# @param [Hash] attributes
- # @return [Obj] the newly created {BasicObj Obj}
+ # @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
end
- # Create a new {BasicObj Obj} instance with the given values and attributes.
+ # 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.
def initialize(attributes = {})
update_data(ObjDataFromHash.new(attributes))
end
@@ -118,11 +122,11 @@
@revision or raise "revision not set!"
end
### FINDERS ####################
- # Find a {BasicObj Obj} by its id.
+ # Find a {Scrivito::BasicObj Obj} by its id.
# If the parameter is an Array containing ids, return a list of corresponding Objs.
# @param [String, Integer, Array<String, Integer>]id_or_list
# @return [Obj, Array<Obj>]
# @api public
def self.find(id_or_list)
@@ -131,11 +135,11 @@
def self.find_by_id(id)
Workspace.current.objs.find_by_id(id)
end
- # Find a {BasicObj Obj} by its id.
+ # Find a {Scrivito::BasicObj Obj} by its id.
# If the parameter is an Array containing ids, return a list of corresponding Objs.
# The results include deleted objects as well.
# @param [String, Integer, Array<String, Integer>]id_or_list
# @return [Obj, Array<Obj>]
# @api public
@@ -145,32 +149,48 @@
# Returns a {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.
#
- # {ObjSearchEnumerator}s can be chained using one of the chainable methods (e.g. {ObjSearchEnumerator#and} and {ObjSearchEnumerator#and_not}).
+ # @note If invoked on a subclass of Obj, the result will be restricted to instances of that 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 ObjClass is "Pressrelease" and whose title contains "quarterly":
# Obj.where(:_obj_class, :equals, 'Pressrelease').and(:title, :contains, 'quarterly').take(10)
# @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 on a subclass of +Obj+ with no corresponding {ObjClass}
+ # @raise [ScrivitoError] if called directly on +BasicObj+. Use +Obj.where+ instead.
# @return [ObjSearchEnumerator]
# @api public
def self.where(field, operator, value, boost = nil)
- Workspace.current.objs.where(field, operator, value, boost)
+ 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
- # Returns a {ObjSearchEnumerator} of all {BasicObj Obj}s.
+ # 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.
# @return [ObjSearchEnumerator]
+ # @raise [ScrivitoError] if called on a subclass of +Obj+ with no corresponding {ObjClass}
+ # @raise [ScrivitoError] if called directly on +BasicObj+. Use +Obj.all+ instead.
# @api public
def self.all
- if superclass == Scrivito::BasicObj
+ 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+.
@@ -179,47 +199,49 @@
# @api public
def self.find_all_by_obj_class(obj_class)
Workspace.current.objs.find_all_by_obj_class(obj_class)
end
- # Find the {BasicObj Obj} with the given path.
+ # Find the {Scrivito::BasicObj Obj} with the given path.
# Returns +nil+ if no matching Obj exists.
- # @param [String] path Path of the {BasicObj Obj}.
+ # @param [String] path Path of the {Scrivito::BasicObj Obj}.
# @return [Obj]
# @api public
def self.find_by_path(path)
Workspace.current.objs.find_by_path(path)
end
- # Find an {BasicObj Obj} with the given name.
+ # 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.
- # @param [String] name Name of the {BasicObj Obj}.
+ # @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.
- # @param [String] name Name of the {BasicObj Obj}.
+ # @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 {BasicObj Obj} with the given permalink, or +nil+ if no matching Obj exists.
- # @param [String] permalink The permalink of the {BasicObj Obj}.
+ # 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 {BasicObj Obj} with the given permalink, or raise ResourceNotFound if no matching Obj exists.
- # @param [String] permalink The permalink of the {BasicObj Obj}.
+ # Returns the {Scrivito::BasicObj Obj} with the given permalink, or raise 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}'"
@@ -243,14 +265,14 @@
# @return [NilClass, Array<Symbol, String>]
# @api public
def self.valid_page_classes_beneath(parent_path)
end
- # Update the {BasicObj Obj} with the attributes provided.
+ # Update the {Scrivito::BasicObj Obj} with the attributes provided.
#
# For an overview of which values you can set via this method see the
- # documentation of {BasicObj.create Obj.create}.
+ # 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.
#
@@ -297,11 +319,11 @@
options = options.stringify_keys.assert_valid_keys('_path', '_id', '_permalink')
json = workspace.api_request(:post, '/objs', obj: copyable_attributes.merge(options))
self.class.find(json['_id'])
end
- # Destroys the {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
@@ -310,16 +332,16 @@
def to_param
id
end
- # return the {BasicObj Obj} that is the parent of this Obj.
+ # return the {Scrivito::BasicObj Obj} that is the parent of this Obj.
# returns +nil+ for the root Obj.
# @api public
def parent
if child_path?
- BasicObj.find_by_path(parent_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.
# @return [Array<Obj>]
@@ -332,43 +354,45 @@
end
ancestor_paths[0] = "/"
Workspace.current.objs.find_by_paths(ancestor_paths)
end
- # return a list of all child {BasicObj Obj}s.
+ # return 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 {BasicObj Obj}'s path as a String.
+ # returns the {Scrivito::BasicObj Obj}'s path as a String.
# @api public
def path
read_attribute('_path')
end
- # returns the {BasicObj Obj}'s name, i.e. the last component of the path.
+ # returns the {Scrivito::BasicObj Obj}'s name, i.e. the last component of the path.
# @api public
def name
if child_path?
path.match(/[^\/]+$/)[0]
else
""
end
end
- # Returns the root {BasicObj Obj}, i.e. the Obj with the path "/"
+ # Returns the root {Scrivito::BasicObj Obj}, i.e. the Obj with the path "/"
# @return [Obj]
# @api public
def self.root
- BasicObj.find_by_path("/") or raise ResourceNotFound,
- "Obj.root not found: There is no Obj with path '/'."
+ 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 "rake scrivito:migrate" and "rake scrivito:migrate:publish".'
end
# Returns the homepage obj. This can be overwritten in your application's +Obj+.
# Use {#homepage?} to check if an obj is the homepage.
# @return [Obj]
@@ -425,32 +449,57 @@
# @api public
def slug
(title || '').parameterize
end
- # This method determines the description that is shown in the changes list.
- # It can be overriden by a custom value.
+ #
+ # This method determines the description that is shown in the UI
+ # and defaults to {Scrivito::BasicObj#display_title}. It can be overriden by a custom value.
+ #
# @api public
+ #
def description_for_editor
- slug.presence || path
+ display_title
end
- # Returns the title of the content or the name.
- # @return [String]
+ #
+ # Calculates appropriate title for an +Obj+.
+ #
# @api public
+ #
+ # @return [String] {Scrivito::Binary#filename} if +Obj+ is +binary+ and has a +filename+.
+ # @return [String] {Scrivito::BasicObj#title} if +Obj+ has a non-empty +title+.
+ # @return [String] a placeholder +<untitled MyClass>+ otherwise.
+ #
def display_title
- self.title || name
+ (binary_title || title).presence || "<untitled #{obj_class_name}>"
end
# @api public
def title
read_attribute('title')
end
- # Returns true if image? or generic?
+ # @api public
+ # This method indicates if 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.
+ #
+ # By default every Obj that has an attribute +blob+ of the type +binary+ is
+ # considered a binary
+ #
+ # @note you can override this method to indicate that an Obj is binary,
+ # if the default behavior doesn't fit your needs
+ #
+ # @return true if this Obj represents a binary resource.
def binary?
- [:image, :generic].include?(read_attribute('_obj_type').to_sym)
+ 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
end
# Returns true if this object is the root object.
# @api public
def root?
@@ -466,13 +515,14 @@
toclist = children
toclist = toclist.reject { |toc| toc.binary? } unless args.include?(:all)
toclist
end
- # @param objs_to_be_sorted [Array<BasicObj>] unsorted list of Objs
- # @param list [Array<BasicObj>] list of Objs that defines the order
- # @return [Array<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 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+
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.
@@ -480,10 +530,11 @@
body
_id
_last_changed
_path
_permalink
+ _obj_class
title
])
# Returns the value of an internal or external attribute specified by its name.
# Passing an invalid key will not raise an error, but return +nil+.
@@ -581,23 +632,12 @@
obj
end
end
- # For a binary Obj, the content_type is equal to the content_type of its body (i.e. its data).
- # For non-binary Objs, a the default content_type is "text/html".
- # Override this method in subclasses to define a different content_type.
- # Note that only Objs with content_type "text/html"
- # will be rendered with layout and templates by the DefaultCmsController.
- # @return [String]
- # @api public
def content_type
- if binary?
- body_content_type
- else
- "text/html"
- end
+ 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.
@@ -617,46 +657,59 @@
else
read_attribute('body')
end
end
- # for binary Objs body_length equals the file size
- # for non-binary Objs body_length equals the number of characters in the body (main content)
# @api public
- def body_length
- if binary?
- blob = find_blob
- blob ? blob.length : 0
- else
- (body || "").length
- end
+ # This method is intended for Objs that represent binary resources like
+ # images or pdf documents. If this Obj represents a binary file, an instance
+ # of {Binary} is returned.
+ # The default implementation returns the attribute "blob" (if available and
+ # of type +binary+).
+ # @note You may override this method in your subclasses.
+ # @return [Binary, nil]
+ def binary
+ self[:blob] if self[:blob].is_a?(Binary)
end
- # returns an URL to retrieve the Obj's body for binary Objs.
- # returns +nil+ for non-binary Objs.
- # @return [String]
# @api public
- def body_data_url
- if binary?
- blob = find_blob
- blob.url if blob
- end
+ # This method returns the length in bytes of the binary of this obj
+ # @return [Fixnum] If no binary is set it will return 0
+ def binary_length
+ binary.try(:content_length) || 0
end
- # returns the content type of the Obj's body for binary Objs.
- # returns +nil+ for non-binary Objs.
- # @return [String]
# @api public
+ # This method returns the content type of the binary of this obj if it 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
+ # available to the public if the binary is set.
+ #
+ # See {Binary#url} for details
+ # @return [String, nil]
+ def binary_url
+ binary.try(:url)
+ end
+
+ def body_length
+ raise %(
+ The method `body_length' was removed. Please use
+ `binary_length' or `body.length' instead
+ )
+ end
+
+ def body_data_url
+ raise "The method `body_data_url' was removed. Please use `binary_url' instead"
+ end
+
def body_content_type
- if binary?
- blob = find_blob
- if blob
- blob.content_type
- else
- "application/octet-stream"
- end
- end
+ raise "The method `body_content_type' was removed. Please use `binary_content_type' instead"
end
def inspect
"<#{self.class} id=\"#{id}\" path=\"#{path}\">"
end
@@ -673,14 +726,25 @@
# for internal testing purposes only
def blob_id
find_blob.try(:id)
end
- # Reverts changes of this object.
- # After calling this method it's as if this object has been never modified in the current working copy.
- # This method does not work with +new+ or +deleted+ objects.
- # This method also does also not work for the +published+ workspace or the +rtc+ working copy.
+ #
+ # Reverts all changes made to the +Obj+ in the current workspace.
+ #
+ # @api public
+ #
+ # @note This method does not support +Obj+s, which are +new+.
+ # Please use {Scrivito::BasicObj#destroy Obj#destroy} to destroy them.
+ # @note This method does not support +Obj+s, which are +deleted+.
+ # Please use {Scrivito::BasicObj.restore Obj.restore} to restore them.
+ #
+ # @raise [ScrivitoError] If the current workspace is +published+.
+ # @raise [ScrivitoError] If the current workspace is the +rtc+ workspace.
+ # @raise [ScrivitoError] If the +Obj+ is +new+.
+ # @raise [ScrivitoError] If the +Obj+ is +deleted+.
+ #
def revert
assert_revertable
if modification == Modification::EDITED
base_revision_path = "revisions/#{workspace.base_revision_id}/objs/#{id}"
@@ -757,10 +821,16 @@
# #=> true
def to_h
data_from_cms.to_h.except(*GENERATED_ATTRIBUTES)
end
+ def parent_path
+ unless root?
+ path.gsub(/\/[^\/]+$/, '').presence || '/'
+ end
+ end
+
private
def cms_data_for_revision(revision)
if revision
CmsBackend.instance.find_obj_data_by(revision, "id", [id]).first.first
@@ -783,22 +853,16 @@
widget.id = widget_id
widget.obj = self
end
end
- def parent_path
- raise "parent_path called for root" if root?
- path.gsub(/\/[^\/]+$/, "").presence || "/"
- end
-
def as_date(value)
DateAttribute.parse(value) unless value.nil?
end
def find_blob
- blob_spec = read_attribute('blob')
- Blob.find(blob_spec["id"]) if blob_spec
+ read_attribute('blob')
end
def workspace
if revision.workspace
revision.workspace
@@ -821,16 +885,42 @@
.except(*UNIQ_ATTRIBUTES)
end
def assert_revertable
workspace.assert_revertable
- raise "revert not supported for binary objs" if binary?
if modification == Modification::NEW || modification == Modification::DELETED
raise ScrivitoError, "cannot revert changes, since obj is #{modification}."
end
end
+ def binary_title
+ binary.filename if binary? && binary
+ 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
+ #
+ # @raise [ScrivitoError] If the current workspace is +published+.
+ # @raise [ScrivitoError] If the current workspace is the +rtc+ workspace.
+ #
def restore(obj_id)
Workspace.current.assert_revertable
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)