lib/ztk/dsl/base.rb

Summary

Maintainability
A
0 mins
Test Coverage
module ZTK::DSL

  # Generic Domain-specific Language Interface
  #
  # This module allows you to easily add attributes and relationships to classes
  # to create a custom DSL in no time.
  #
  # You can then access these classes in manners similar to what *ActiveRecord*
  # provides for relationships.  You can easily link classes together; load
  # stored objects from Ruby rb files (think Opscode Chef DSL).
  #
  # I intend the interface to act like ActiveRecord for the programmer and a
  # nice DSL for the end user.  It's not meant to be a database; more like
  # a soft dataset in memory; extremely fast but highly volitale.  As always
  # you can never have your cake and eat it too.
  #
  # You specify the "schema" in the classes itself; there is no data storage at
  # this time, but I do plan to add support for loading/saving *datasets* to
  # disk.  Keep in mind since you do not specify type constrants in Ruby, one
  # can assign any object to an attribute.
  #
  # At this time if you do not specify an ID; one is auto generated.
  #
  # If you wish to create objects in a nested fashion the outer most object must
  # be started using the class name initializer.  Once inside the block you can
  # start using the relationship names and do not need to call any further
  # class initializers.
  #
  # You can also instantiate classes separately and associate them after the
  # fact.  That is not shown in this example.
  #
  # @example Building infrastructure DSL objects
  #
  #     class Network < ZTK::DSL::Base
  #       has_many :servers
  #
  #       attribute :name
  #       attribute :gw
  #       attribute :network
  #       attribute :netmask
  #     end
  #
  #     class Server < ZTK::DSL::Base
  #       belongs_to :network
  #
  #       attribute :name
  #     end
  #
  #     Network.new do
  #       id :leet_net
  #       name "leet-net"
  #       gw "7.3.3.1"
  #       network "7.3.3.0"
  #       netmask "255.255.255.0"
  #
  #       server do
  #         name "leet-server"
  #       end
  #
  #       server do
  #         id :my_server
  #         name "my-server"
  #       end
  #
  #       server do
  #         name "dev-server"
  #       end
  #     end
  #
  #     Network.count
  #     Network.all
  #     Network.find(:leet_net)
  #
  #     Server.count
  #     Server.all
  #     Server.find(:my_server)
  #
  # @author Zachary Patten <zpatten AT jovelabs DOT io>
  class Base
    include(ZTK::DSL::Core)

    # @api private
    def self.inherited(base)
      base.send(:extend, ZTK::DSL::Base::ClassMethods)
      base.instance_eval do
        attribute :id
      end
    end

    # @api private
    def self.included(base)
      # NOOP
    end

    # @api private
    def self.extended(base)
      # NOOP
    end

    def initialize(id=nil, &block)
      self.id = (id || self.class.next_id)
      self.class.dataset << self

      do_block(&block)
    end

    def do_block(&block)
      if block_given?
        if (block.arity < 1)
          instance_eval(&block)
        else
          block.call(self)
        end
      end

      if (self.class.dataset.count{ |data| (data.id == self.id) } > 1)
        raise StandardError, "Primary key '#{self.id}' already exists!"
      end
    end

    # Instance Inspect
    #
    # Inspect the DSL object's instance, returning a concise string
    # representation of the instance.
    #
    # @return [String] A concise string representation of the instance.
    def inspect
      details = Array.new
      details << "attributes=#{attributes.inspect}" if attributes.count > 0
      details << "has_many_references=#{@has_many_references.count}" if @has_many_references
      details << "belongs_to_references=#{@belongs_to_references.count}" if @belongs_to_references
      "#<#{self.class.to_s} id=#{self.id.inspect} #{details.join(', ')}>"
    end

    # @author Zachary Patten <zpatten AT jovelabs DOT io>
    module ClassMethods

      # Class Inspect
      #
      # Inspect the DSL object's class, returning a concise string
      # representation of the class.
      #
      # @return [String] A concise string representation of the class.
      def inspect
        details = Array.new
        details << "count=#{self.all.count}" if self.all.count > 0
        "#<#{self.to_s} #{details.join(', ')}>"
      end

    end

  end

end