lib/spontaneous/field/base.rb in spontaneous-0.2.0.beta1 vs lib/spontaneous/field/base.rb in spontaneous-0.2.0.beta2
- old
+ new
@@ -1,11 +1,11 @@
module Spontaneous
module Field
class Base
module ClassMethods
- def has_editor
- define_singleton_method(:editor_class) { ui_class }
+ def has_editor(js_class = ui_class)
+ define_singleton_method(:editor_class) { js_class }
end
def register(*labels)
labels = self.labels if labels.empty?
# logger.debug("Registering #{self} as #{labels.join(", ")}")
@@ -42,34 +42,75 @@
accepts.find do |pattern|
Regexp.new(pattern).match(mime_type)
end
end
+ def default_options
+ {}
+ end
+
# Provides the ability for specific field types to customize the schema values
# they return to the UI
def export(user)
{}
end
end
extend ClassMethods
attr_accessor :owner, :name, :unprocessed_value, :template_params, :version
- attr_reader :processed_values
+ attr_accessor :prototype
- alias_method :values, :processed_values
-
def initialize(params={}, default_values=true)
@processed_values = {}
deserialize(params, default_values)
+ @values = nil
end
+ def processed_values
+ @values ||= processed_values_with_fallback
+ end
+
+ alias_method :values, :processed_values
+
+ ValueHash = Spontaneous::Collections::HashWithFallback
+
+ def processed_values_with_fallback
+ return @processed_values if owner.nil? || prototype.fallback.nil?
+ fallback = owner.fields[prototype.fallback]
+ if fallback.nil?
+ logger.warn("Missing field '#{prototype.fallback}' specified as fallback for field #{owner.class}::#{name}")
+ return @processed_values
+ end
+ test = proc { |val| self.blank? }
+ ValueHash.new(fallback, test).update(@processed_values)
+ end
+
+ def [](key)
+ processed_values[key]
+ end
+
def id
[owner.id, schema_id].join("/")
end
+ # This is used exclusively to compute the filename/path of
+ # temp files.
+ #
+ # To avoid creating a deep hierarchy under /media/tmp
+ # which would be hard to cleanup we replace all dir separators
+ # with underscores.
+ #
+ # The timestamp is added because otherwise serial modifications
+ # to the same field might overwrite the value (though this is
+ # unlikely)
+ def media_id
+ ids = [owner.id, schema_id, timestamp]
+ ids.join("_").gsub(/\//, "_")
+ end
+
def writable?(user)
owner.field_writable?(user, name)
end
# If a field type needs to do some long running processing
@@ -81,14 +122,22 @@
def pending_value=(value)
values[:__pending__] = {
:value => value,
:version => version + 1,
- :timestamp => Spontaneous::Field.timestamp
+ :timestamp => timestamp
}
end
+ # A timestamp value consistent for a particular field instance
+ #
+ # This is used to solve conflicts for async updates and to
+ # tag a tempfile to a particular pending value.
+ def timestamp
+ @timestamp ||= Spontaneous::Field.timestamp
+ end
+
def pending_value
values[:__pending__]
end
def has_pending_value?
@@ -98,27 +147,43 @@
def clear_pending_value
values.delete(:__pending__)
end
def process_pending_value
- process_pending_value!
+ if (pending = process_pending_value!)
+ cleanup_pending_value!(pending)
+ end
save
end
def process_pending_value!
if has_pending_value?
+ pending = pending_value
@previous_values = values.dup
set_value!(pending_value[:value])
+ pending
end
end
+ # Ensures that this update can still run
+ def invalid_update?
+ return true if reload.nil?
+ false
+ end
+
+ # Ensures that the pending value we have hasn't been superceded by
+ # a later one.
def conflicted_update?
return false if is_valid_pending_value?
self.processed_values = @previous_values
true
end
+ # Reloads the field and compares the timestamps -- if our timestamp
+ # is the same or greater than the reloaded value then we are the
+ # most up-to-date update available. If not then we're not and
+ # should abort.
def is_valid_pending_value?
return true if @previous_values.nil?
reloaded = reload
pending = @previous_values[:__pending__] || {}
p1 = pending[:timestamp] || 0
@@ -129,10 +194,19 @@
@previous_values = reloaded.values
false
end
end
+ def cleanup_pending_value!(pending)
+ clear_pending_value
+ if pending && (v = pending[:value]) && v.is_a?(Hash)
+ if (tempfile = v[:tempfile]) && ::File.exist?(tempfile)
+ FileUtils.rm_r(::File.dirname(tempfile))
+ end
+ end
+ end
+
def reload
Spontaneous::Content.scope! do
Spontaneous::Field.find(self.id)
end
end
@@ -228,12 +302,15 @@
# attr_accessor :values
# override this to return custom values derived from (un)processed_value
# alias_method :value, :processed_value
def value(format=:html)
- processed_values[format.to_sym] || unprocessed_value
+ format = format.to_sym
+ return unprocessed_value unless processed_values.key?(format)
+ processed_values[format]
end
+
alias_method :processed_value, :value
def image?
false
end
@@ -278,15 +355,10 @@
def modified?
@modified
end
- attr_accessor :prototype
- # def prototype
- # self.class.prototype
- # end
-
def schema_id
self.prototype.schema_id
end
@@ -395,9 +467,10 @@
self.send(setter, value) if respond_to?(setter)
end
end
def processed_values=(values)
+ @values = nil
@processed_values = values
end
def set_unprocessed_value(new_value, preprocessed = false)
# initial_value should only be set once so that it can act as a test for field modification