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