lib/blobject.rb in blobject-0.3.3 vs lib/blobject.rb in blobject-0.3.7
- old
+ new
@@ -1,52 +1,55 @@
require 'json'
require 'yaml'
require_relative 'blobject/version'
+# Wraps a hash to provide arbitrarily nested object-style attribute access
class Blobject
# filter :to_ary else Blobject#to_ary returns a
# blobject which is not cool, especially if you are puts.
ProhibitedNames = [:to_ary]
- module Error; end
-
+ # pass an optional hash of values to preload
+ # you can also pass a block, the new Blobject will be yield
def initialize hash = {}
- @hash = hash
+ @hash = Hash.new
- @hash.keys.each do |key|
- unless key.class <= Symbol
- value = @hash.delete key
- key = key.to_sym
- @hash[key] = value
- end
+ hash.each do |key, value|
+ key = key.to_sym unless key.is_a? Symbol
+ @hash[key] = value
end
- __visit_subtree__ do |name, node|
- if node.class <= Hash
- @hash[name] = Blobject.new node
- end
+ @hash.each do |name, node|
+ @hash[name] = self.class.send(:__blobjectify__, node)
end
yield self if block_given?
end
+ def empty?
+ @hash.empty?
+ end
+
+ # delegates to the internal Hash
def inspect
@hash.inspect
end
+ # access the internal hash. be careful, this is _not_ a copy
def hash
@hash
end
+ # creates a recursive copy of the internal hash
def to_hash
h = hash.dup
- __visit_subtree__ do |name, node|
+ @hash.each do |name, node|
h[name] = node.to_hash if node.respond_to? :to_hash
end
h
end
@@ -58,16 +61,17 @@
case
# assignment in conditionals is usually a bad smell, here it helps minimize regex matching
when (name = method[/^\w+$/, 0]) && params.length == 0
# the call is an attribute reader
- return nil if frozen? and not @hash.has_key?(method)
-
+
+ return self.class.new.freeze if frozen? and not @hash.has_key?(method)
self.class.send :__define_attribute__, name
return send(method) if @hash.has_key? method
+ # close the scope for storing call chain
parent = self
nested_blobject = self.class.new
store_in_parent = lambda do
parent.send "#{name}=", nested_blobject
@@ -104,89 +108,81 @@
[/^(\w+)=$/, /^(\w+)\?$/, /^\w+$/].any? do |r|
r.match(method)
end
end
+ # compares Blobjects to Blobjects or Hashes
def == other
return @hash == other.hash if other.class <= Blobject
return @hash == other if other.class <= Hash
super
end
+ # hash-like access to the Blobject's attributes
def [] name
send name
end
+ # hash-like attribtue setter
def []= name, value
send "#{name.to_s}=", value
end
+ # freeze a Blobject to prevent it being modified
def freeze
- __visit_subtree__ { |name, node| node.freeze }
@hash.freeze
super
end
+ def freeze_r
+ self.class.send(:__freeze_r__, self)
+ freeze
+ end
+
+
+ # returns a hash which can be serialized as json.
+ # this is for use in rails controllers: `render json: blobject`
def as_json *args
return hash.as_json(*args) if hash.respond_to? :as_json
to_hash
end
+ # serialize the Blobject as a json string
def to_json *args
as_json.to_json *args
end
- def as_yaml
-
- to_hash
- end
-
+ # serialize the Blobject as a yaml string
def to_yaml
as_yaml.to_yaml
end
+ # get a Blobject from a json string
+ # if the yaml string describes an array, an array will be returned
def self.from_json json
- from_json!(json).freeze
+ __blobjectify__(JSON.parse(json))
end
- def self.from_json! json
-
- __from_hash_or_array__(JSON.parse(json))
- end
-
+ # get a Blobject from a yaml string
+ # if the yaml string describes an array, an array will be returned
def self.from_yaml yaml
- from_yaml!(yaml).freeze
+ __blobjectify__(YAML.load(yaml))
end
- def self.from_yaml! yaml
-
- __from_hash_or_array__(YAML.load(yaml))
- end
-
private
# to avoid naming collisions private method names are prefixed and suffix with double unerscores (__)
- def __visit_subtree__ &block
-
- @hash.each do |name, node|
-
- if node.class <= Array
- node.flatten.each do |node_node|
- block.call(nil, node_node, &block)
- end
- end
-
- block.call name, node, &block
- end
- end
-
+ # Used to tag and reraise errors from a Blobject
+ # Refer to "Tagging exceptions with modules" on p97 in Exceptional Ruby by Avdi Grimm
# errors from this library can be handled with rescue Blobject::Error
+ module Error; end
+
def __tag_and_raise__ e
raise e
rescue
e.extend Blobject::Error
raise e
@@ -194,23 +190,39 @@
class << self
private
- def __from_hash_or_array__ hash_or_array
-
- if hash_or_array.class <= Array
- return hash_or_array.map do |e|
- if e.class <= Hash
- Blobject.new e
- else
- e
- end
+ def __freeze_r__ object
+
+ case object
+ when Array
+ return object.each do |e|
+ e.freeze
+ __freeze_r__(e)
end
+ when Hash
+ return object.each do |k, v|
+ v.freeze
+ __freeze_r__(v)
+ end
+ when Blobject
+ object.freeze
+ __freeze_r__ object.hash
+ else
+ object.freeze
end
+ end
- Blobject.new hash_or_array
+ def __blobjectify__ object
+
+ array = object if object.is_a? Array
+ hash = object if object.is_a? Hash
+
+ return array.map{|a| __blobjectify__(a)} if array
+ return Blobject.new(hash) if hash
+ return object
end
def __define_attribute__ name
__tag_and_raise__ NameError.new("invalid attribute name #{name}") unless name =~ /^\w+$/
@@ -220,11 +232,11 @@
setter_name = (name.to_s + '=').to_sym
unless methods.include? setter_name
self.send :define_method, setter_name do |value|
begin
- value = self.class.new(value) if value.class <= Hash
+ value = self.class.send(:__blobjectify__, value) if value.is_a?(Hash) or value.is_a?(Array)
@hash[name] = value
rescue ex
__tag_and_raise__(ex)
end
@store_in_parent.call unless @store_in_parent.nil?
@@ -234,12 +246,12 @@
unless methods.include? name
self.send :define_method, name do
value = @hash[name]
- if value.nil? && !frozen?
+ if value.nil?
value = self.class.new
- @hash[name] = value
+ @hash[name] = value unless frozen?
end
value
end
end
\ No newline at end of file