lib/cockpit/core/definition.rb in cockpit-0.1.1 vs lib/cockpit/core/definition.rb in cockpit-0.2.0
- old
+ new
@@ -1,93 +1,204 @@
module Cockpit
- # This class defines default properties for a setting object, based on the DSL
- class Definition
- # keys is the nested keys associated with child values
- attr_accessor :key, :value, :keys, :nested, :parent, :attributes, :type
-
- def initialize(key, *args, &block)
- process(key, *args, &block)
- end
-
- def process(key, *args, &block)
- self.key = key.to_s
- if args.length >= 1
- if args.last.is_a?(Hash)
- self.attributes = args.pop
+ class Settings
+ # This class defines default properties for a setting object, based on the DSL
+ class Definition
+
+ class << self
+ def define!(options = {}, &block)
+ DefinedBy::DSL(&block).map do |key, value, dsl_block|
+ Cockpit::Settings::Definition.new(key, value, &dsl_block)
+ end
+ end
+ end
+
+ # keys is the nested keys associated with child values
+ attr_reader :key, :value
+ attr_reader :attributes, :type, :callbacks, :validation
+ attr_reader :nested
+
+ def initialize(key, *args, &block)
+ @key = key.to_s
+ @attributes = {}
+
+ if block_given?
+ @value = self.class.define!(&block)
+ @nested = true
else
- self.attributes = {}
+ args = args.pop
+ if args.is_a?(Array)
+ if args.last.is_a?(Hash)
+ @attributes.merge!(args.pop)
+ end
+ if args.last.is_a?(Class)
+ @type = args.pop
+ end
+
+ args = args.pop if (args.length == 1)
+ end
+
+ if attributes.has_key?(:default)
+ @value = attributes.delete(:default)
+ else
+ if args.is_a?(Class)
+ @type = args
+ else
+ @value = args
+ end
+ end
+
+ @validation = attributes.delete(:if)
+ @callbacks = {
+ :before => attributes.delete(:before),
+ :after => attributes.delete(:after)
+ }
+
+ @type ||= @value.class
+ @nested = false
end
- else
- self.attributes ||= {}
end
- if block_given?
- self.value ||= []
- self.nested = true
- instance_eval(&block)
- else
- self.value = *args.first
- self.nested = false
+
+ def each(&block)
+ iterate(:each, &block)
end
- end
-
- def [](key)
- if attributes.has_key?(key.to_sym)
- attributes[key.to_sym]
- elsif attributes.has_key?(key.to_s)
- attributes[key.to_s]
- else
- method_missing(key)
+
+ def map(&block)
+ iterate(:map, &block)
end
- end
-
- def nested?
- self.nested == true
- end
-
- def keys(separator = ".")
- if nested?
- value.inject({key => self}) do |hash, definition|
- sub_definition = definition.keys.keys.inject({}) do |sub_hash, sub_key|
- sub_hash["#{key}#{separator}#{sub_key}"] = definition.keys[sub_key]
- sub_hash
+
+ def iterate(method, &block)
+ keys.send(method) do |key|
+ case block.arity
+ when 1
+ yield(key)
+ when 2
+ yield(key, value_for(key))
end
- hash.merge(sub_definition)
end
- else
- {key => self}
end
- end
-
- def method_missing(method, *args, &block)
- method = method.to_s.gsub("=", "").to_sym
- if args.blank? && !block_given?
- result = self.value.detect do |definition|
- definition.key == method.to_s
+
+ def keys
+ @keys ||= get_keys(false, :keys)
+ end
+
+ def all_keys
+ @all_keys ||= get_keys(true, :all_keys)
+ end
+
+ def child(key)
+ flatten[key.to_s]
+ end
+
+ def value_for(key)
+ child(key).value rescue nil
+ end
+
+ def [](key)
+ value_for(key)
+ end
+
+ # map of nested key to definition
+ def flatten(separator = ".")
+ unless @flattened
+ if nested?
+ @flattened = value.inject({key => self}) do |hash, definition|
+ sub_definition = definition.keys.inject({}) do |sub_hash, sub_key|
+ sub_hash["#{key}#{separator}#{sub_key}"] = definition.child(sub_key)
+ sub_hash
+ end
+ hash.merge(sub_definition)
+ end
+ else
+ @flattened = {key => self}
+ end
end
- result ? result.value : nil
- else
- old_value = self.value.detect { |definition| definition.key == method.to_s }
- if old_value
- old_value.process(method, *args, &block)
- else
- self.value << Cockpit::Definition.new(method, *args, &block)
+
+ @flattened
+ end
+
+ def to_hash
+ flatten.inject({}) do |hash, key, definition|
+ hash[key] = definition.value unless definition.nested?
+ hash
end
end
- end
-
- class << self
- # top-level declaration are the first keys in the chain
- def define!(*args, &block)
- @definitions = []
- instance_eval(&block) if block_given?
- definitions = @definitions
- @definitions = nil
- definitions
+
+ def to_tree
+ {key => nested? ? value.map(&:to_tree) : value}
end
- def method_missing(method, *args, &block)
- method = method.to_s.gsub("=", "").to_sym
- @definitions << Cockpit::Definition.new(method, *args, &block)
+ def nested?
+ self.nested == true
+ end
+
+ # callbacks
+ def with_callbacks(record, new_value, &block)
+ validate(record, new_value) do
+ callback(:before, record, new_value)
+ yield(new_value)
+ callback(:after, record, new_value)
+ end
+ end
+
+ def validate(record, new_value, &block)
+ yield if execute(validation, record, new_value)
+ end
+
+ def callback(name, record, new_value)
+ execute(callbacks[name], record, new_value)
+ end
+
+ def execute(executable, record, new_value)
+ return true unless executable
+
+ case executable
+ when String, Symbol
+ if record.respond_to?(executable)
+ case record.method(executable).arity
+ when 0
+ record.send(executable)
+ when 1
+ record.send(executable, key)
+ when 2
+ record.send(executable, key, new_value)
+ end
+ end
+ when Proc
+ case executable.arity
+ when 0, -1
+ if record
+ record.instance_eval(&executable)
+ else
+ executable.call
+ end
+ when 1
+ if record
+ record.instance_exec(key, &executable)
+ else
+ executable.call(key)
+ end
+ when 2
+ if record
+ record.instance_exec(key, new_value, &executable)
+ else
+ executable.call(key, new_value)
+ end
+ end
+ end
+ end
+
+ protected
+ def get_keys(include_self, method)
+ if nested?
+ @keys = include_self ? [key] : []
+ @keys += value.map(&method).flatten.map {|key| "#{self.key}.#{key}"}
+ else
+ @keys = [key]
+ end
+ end
+
+ def call_proc(proc, record)
+
end
end
end
end