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