module Reactor::Persistence::Base

Provides API for writing into the Content Manager. It aims to be just like ActiveRecord::Persistence, so that the difference for the developer is minimal If the method is marked as exception raising, then it should be expected also to raise Reactor::Cm::XmlRequestError when generic write/connection error occurs.

It should support all generic model callbacks, plus complete set of callbacks for release action (before/after/around).

@raise [Reactor::Cm::XmlRequestError] generic error occoured

Public Class Methods

included(base) click to toggle source
# File lib/reactor/persistence.rb, line 18
def self.included(base)
  base.extend(ClassMethods)
  base.send(:define_model_callbacks, :release)
  base.class_eval do
    before_create :sanitize_name
    before_create :trim_crul_attributes
  end
end
new(attributes = nil) { |self| ... } click to toggle source

It should excactly match ActiveRecord::Base.new in it's behavior @see ActiveRecord::Base.new

Calls superclass method
# File lib/reactor/persistence.rb, line 245
def initialize(attributes = nil, &block)
  if true ||  !self.class.send(:attribute_methods_overriden?) # FIXME !!!!
    ignored_attributes = ignore_attributes(attributes)
    # supress block hijacking!
    super(attributes) {}
    load_ignored_attributes(ignored_attributes)
    yield self if block_given?
  else
    super(attributes)
  end
end

Public Instance Methods

delete() click to toggle source

Deletes the object from the CM. No callbacks are executed. Though exceptions can be raised. Freezes the object.

# File lib/reactor/persistence.rb, line 184
def delete
  crul_obj_delete if persisted?
  @destroyed = true
  freeze
end
destroy() click to toggle source

Removes the object from the CM. Runs all the callbacks. Can raise exception. Freezes the object.

# File lib/reactor/persistence.rb, line 192
def destroy
  run_callbacks(:destroy) do
    self.delete
  end
end
destroyed?() click to toggle source

Returns true if this object has been destroyed, otherwise returns false.

# File lib/reactor/persistence.rb, line 172
def destroyed?
  @destroyed == true
end
edit(comment=nil) click to toggle source

Creates a working version of the object. Returns true on success or when the object already has a working version. Returns false when: @param comment [String] comment to leave for the next user

  1. user lacks the permissions

  2. other error occured

# File lib/reactor/persistence.rb, line 126
def edit(comment=nil)
  edit!(comment)
  return true
rescue Reactor::Cm::XmlRequestError, Reactor::NotPermitted
  return false
end
edit!(comment=nil) click to toggle source

Creates a working version of the object. Returns true on success or when the object already has a working version. Raises exceptions @param comment [String] comment to leave for the next user @raise [Reactor::NotPermitted] current user lacks required permissions

# File lib/reactor/persistence.rb, line 137
def edit!(comment=nil)
  crul_obj.edit!(comment) unless self.really_edited?
  reload
  return true
end
new_record?() click to toggle source

Returns true if this object hasn't been saved yet – that is, a record for the object doesn't exist in the data store yet; otherwise, returns false.

# File lib/reactor/persistence.rb, line 158
def new_record?
  #!destroyed? && (self.id.nil? || !self.class.exists?(self.id))
  !destroyed? && (self.id.nil? || self.path.blank?)
end
persisted?() click to toggle source

Stolen from Rails 3. Returns if the record is persisted, i.e. stored in database (it's not a new record and it was not destroyed.) @note Code should not be changed without large modifications to the module.

# File lib/reactor/persistence.rb, line 167
def persisted?
  !(new_record? || destroyed?)
end
really_edited?() click to toggle source

Equivalent to Obj#edited?

# File lib/reactor/persistence.rb, line 275
def really_edited?
  self.edited?
end
reasons_for_incomplete_state() click to toggle source

Returns an array of errors

# File lib/reactor/persistence.rb, line 280
def reasons_for_incomplete_state
  crul_obj.get('reasonsForIncompleteState') || []
end
release(comment=nil) click to toggle source

Releases the object. Returns true on success, false when one of the following occurs:

  1. user lacks the permissions

  2. the object has already been released

  3. object is invalid

  4. other error occoured

@param comment [String] comment to leave for the next user

# File lib/reactor/persistence.rb, line 34
def release(comment=nil)
  return release!(comment)
rescue Reactor::Cm::XmlRequestError, ActiveRecord::RecordInvalid, Reactor::NotPermitted, Reactor::AlreadyReleased
  return false
end
release!(comment=nil) click to toggle source

Releases the object. Returns true on succes, can raise exceptions @param comment [String] comment to leave for the next user @raise [Reactor::AlreadyReleased] @raise [ActiveRecord::RecordInvalid] validations failed @raise [Reactor::NotPermitted] current user lacks required permissions

# File lib/reactor/persistence.rb, line 76
def release!(comment=nil)
  run_callbacks(:release) do
    raise(Reactor::AlreadyReleased) unless self.really_edited?
    crul_obj.release!(comment)
    reload
  end
  return true
end
reload(options = nil) click to toggle source

Reloads object attributes. Invalidates caches. Does not call any other reload methods (neither from RailsConnector nor from ActiveRecord) but tries to mimmic their behaviour.

# File lib/reactor/persistence.rb, line 201
def reload(options = nil)
  RailsConnector::AbstractObj.uncached do
    #super # Throws RecordNotFound when changing obj_class
    # AR reload
    clear_aggregation_cache
    clear_association_cache
    fresh_object = RailsConnector::AbstractObj.find(self.id, options)
    @attributes = fresh_object.instance_variable_get('@attributes')
    @attributes_cache = {}
    # RC reload
    @attr_values = nil
    @attr_defs = nil
    @attr_dict = nil
    @obj_class_definition = nil
    @object_with_meta_data = nil
    # meta reload
    @editor = nil
    @object_with_meta_data = nil
    self
  end
end
resolve_refs() click to toggle source

Resolves references in any of the html fields. Returns true on success, or false when:

  1. user lacks the permissions

  2. generic error occoured

# File lib/reactor/persistence.rb, line 227
def resolve_refs
  resolve_refs!
  return true
rescue Reactor::Cm::XmlRequestError, Reactor::NotPermitted
  return false
end
resolve_refs!() click to toggle source

Resolves references in any of the html fields. Returns true on success, raises exceptions. @raise [Reactor::NotPermitted] current user lacks required permissions

# File lib/reactor/persistence.rb, line 237
def resolve_refs!
  crul_obj.resolve_refs!
  return true
end
revert(comment=nil) click to toggle source

Removes the working version of the object, if it exists @param comment [String] comment to leave for the next user @return [true]

# File lib/reactor/persistence.rb, line 56
def revert(comment=nil)
  return revert!(comment)
end
revert!(comment=nil) click to toggle source

Removes the working version of the object, if it exists @param comment [String] comment to leave for the next user @return [true] @note There is no difference between revert and revert!

# File lib/reactor/persistence.rb, line 65
def revert!(comment=nil)
  crul_obj.revert!(comment)
  reload
  return true
end
super_objects() click to toggle source

Return an array of RailsConnector::AbstractObj that contain a link to this file. @raise [Reactor::Cm::XmlRequestError] generic error occoured

# File lib/reactor/persistence.rb, line 152
def super_objects
  RailsConnector::AbstractObj.where(:obj_id => crul_obj.get('superObjects')).to_a
end
take(comment=nil) click to toggle source

Makes the current user the editor of the object. Returns true when user is already the editor or take succeded, false when one of the following occurs:

  1. user lacks the permissions

  2. the object has not beed edited

  3. other error occured

@param comment [String] comment to leave for the next user

# File lib/reactor/persistence.rb, line 100
def take(comment=nil)
  take!(comment)
  return true
rescue Reactor::Cm::XmlRequestError, Reactor::NotPermitted, Reactor::NoWorkingVersion
  return false
end
take!(comment=nil) click to toggle source

Makes the current user the editor of the object. Returns true when user is already the editor or take succeded. Raises exceptions @param comment [String] comment to leave for the next user @raise [Reactor::NoWorkingVersion] there is no working version of the object @raise [Reactor::NotPermitted] current user lacks required permissions

# File lib/reactor/persistence.rb, line 112
def take!(comment=nil)
  raise(Reactor::NoWorkingVersion) unless self.really_edited?
  # TODO: refactor the if condition
  crul_obj.take!(comment) if (crul_obj.editor != Reactor::Configuration::xml_access[:username])
  # neccessary to recalculate #editor
  reload
  return true
end
unrelease(comment=nil) click to toggle source

Unreleases the object. Returns true on success, false when one of the following occurs:

  1. user lacks the permissions

  2. the object is not released

  3. object is invalid

  4. other error occoured

# File lib/reactor/persistence.rb, line 46
def unrelease(comment=nil)
  return unrelease!(comment)
rescue Reactor::Cm::XmlRequestError, ActiveRecord::RecordInvalid, Reactor::NotPermitted
  return false
end
unrelease!(comment=nil) click to toggle source

Unreleases the object. Returns true on succes, can raise exceptions @param comment [String] comment to leave for the next user

# File lib/reactor/persistence.rb, line 87
def unrelease!(comment=nil)
  crul_obj.unrelease!(comment)
  reload
  return true
end

Protected Instance Methods

changed_linklists() click to toggle source
# File lib/reactor/persistence.rb, line 310
def changed_linklists
  custom_attrs =
    self.singleton_class.send(:instance_variable_get, '@_o_allowed_attrs') ||
    self.class.send(:instance_variable_get, '@_o_allowed_attrs') ||
    []

  custom_attrs.select do |attr|
    self.send(:attribute_type, attr) == :linklist && self.send(:[],attr.to_sym).try(:changed?)
  end
end
crul_attributes() click to toggle source
# File lib/reactor/persistence.rb, line 321
def crul_attributes
  @__crul_attributes || {}
end
crul_attributes_set?() click to toggle source
# File lib/reactor/persistence.rb, line 302
def crul_attributes_set?
  !crul_attributes.empty? || uploaded?
end
crul_obj() click to toggle source
# File lib/reactor/persistence.rb, line 325
def crul_obj
  @crul_obj ||= Reactor::Cm::Obj.load(obj_id)
end
crul_obj_delete() click to toggle source
# File lib/reactor/persistence.rb, line 329
def crul_obj_delete
  crul_obj.delete!
end
crul_obj_save() click to toggle source
# File lib/reactor/persistence.rb, line 333
def crul_obj_save
  attrs, _ = crul_attributes.partition do |field, (value, options)|
    self.send(:attribute_type, field) != :linklist
  end
  linklists = changed_linklists

  new_links = {}.tap do |result|
    linklists.map do |field|
      result[field] = Reactor::Attributes::LinkListFromAccessor.new(self, field).call.map do |l|
        {:link_id => l.id, :title => l.title, :destination_url => (l.internal? ? l.destination_object.path : l.url), :target => l.target}
      end
    end
  end

  links_modified = !linklists.empty?

  crul_obj.composite_save(attrs, [], [], [], links_modified) do |attrs, links_to_add, links_to_remove, links_to_set|

    links_to_add.clear
    links_to_remove.clear
    links_to_set.clear

    copy = RailsConnector::AbstractObj.uncached { RailsConnector::AbstractObj.find(self.id) }

    linklists.each do |linklist|
      original_link_ids = Reactor::Attributes::LinkListFromAttrValues.new(copy, linklist).call.map(&:id)
      i = 0
      common = [original_link_ids.length,
                new_links[linklist].length].min

      # replace existing links
      while i < common
        link = new_links[linklist][i]
        link[:link_id] = link_id = original_link_ids[i]

        links_to_set << [link_id, link]
        i += 1
      end

      # add appended links
      while i < new_links[linklist].length
        link = new_links[linklist][i]

        links_to_add << [linklist, link]
        i += 1
      end

      # remove trailing links
      while i < original_link_ids.length
        links_to_remove << original_link_ids[i]
        i += 1
      end
    end
  end

  self.class.connection.clear_query_cache
end
prevent_resolve_refs() click to toggle source
# File lib/reactor/persistence.rb, line 285
def prevent_resolve_refs
  @prevent_resolve_refs = true
end
prevent_resolve_refs?() click to toggle source
# File lib/reactor/persistence.rb, line 289
def prevent_resolve_refs?
  @prevent_resolve_refs == true
end
sanitize_name() click to toggle source
# File lib/reactor/persistence.rb, line 293
def sanitize_name
  return unless self.name.present?

  sanitized_name = self.class.send(:sanitize_name, self.name)
  if sanitized_name != self.name
    self.name = sanitized_name
  end
end

Private Instance Methods

copy(new_parent, recursive = false, new_name = nil) click to toggle source

TODO: test it & make it public Copy an Obj to another tree, returnes ID of the new Object @param [String, Obj, Integer] new_parent path, id, or instance of target where to copy @param [true, false] recursive set to true to also copy the underlying subtree @param [String] new_name gives the object new name

# File lib/reactor/persistence.rb, line 398
def copy(new_parent, recursive = false, new_name = nil)
  self.id = crul_obj.copy(RailsConnector::AbstractObj.path_from_anything(new_parent), recursive, new_name)
  #self.reload
  resolve_refs #?
  self.id
end
create() click to toggle source
# File lib/reactor/persistence.rb, line 413
def create
  run_callbacks(:create) do
    c_name  = self.name
    c_parent= self.class.path_from_anything(self.parent_obj_id)
    c_objcl = self.obj_class
    crul_obj_create(c_name, c_parent, c_objcl)
    self.id = @crul_obj.obj_id
    crul_obj_save if crul_attributes_set? || crul_links_changed?
    self.reload # ?
    self.id
  end
end
crul_obj_create(name, parent, klass) click to toggle source
# File lib/reactor/persistence.rb, line 409
def crul_obj_create(name, parent, klass)
  @crul_obj = Reactor::Cm::Obj.create(name, parent, klass)
end
ignore_attributes(attributes) click to toggle source
# File lib/reactor/persistence.rb, line 450
def ignore_attributes(attributes)
  return {} if attributes.nil?

  obj_class = attributes.delete(:obj_class)
  parent    = attributes.delete(:parent)
  {:obj_class => obj_class, :parent => parent}
end
load_ignored_attributes(attributes) click to toggle source
# File lib/reactor/persistence.rb, line 460
def load_ignored_attributes(attributes)
  return if attributes.nil?

  set_parent(attributes[:parent]) if attributes[:parent].present?
  set_obj_class(attributes[:obj_class]) if attributes[:obj_class].present?
end
rollback_active_record_state!() { || ... } click to toggle source

disables active record transactions

# File lib/reactor/persistence.rb, line 484
def rollback_active_record_state!
  yield
end
set_obj_class(obj_class) click to toggle source
# File lib/reactor/persistence.rb, line 473
def set_obj_class(obj_class)
  self.obj_class = obj_class
end
set_parent(parent_something) click to toggle source
# File lib/reactor/persistence.rb, line 467
def set_parent(parent_something)
  parent_obj = self.class.obj_from_anything(parent_something)
  self.parent_obj_id = parent_obj.id
  crul_set(:parent, parent_obj.path, {}) if persisted?
end
trim_crul_attributes() click to toggle source
# File lib/reactor/persistence.rb, line 405
def trim_crul_attributes
  crul_attributes.delete_if {|attr, options| [:name, :objClass].include?(attr) }
end
update() click to toggle source
# File lib/reactor/persistence.rb, line 434
def update
   run_callbacks(:update) do
     crul_obj_save if crul_attributes_set? || crul_links_changed?
     self.reload
     self.id
   end
end
with_transaction_returning_status() { || ... } click to toggle source

disables active record transactions

# File lib/reactor/persistence.rb, line 479
def with_transaction_returning_status
  yield
end