lib/rumx/bean.rb in rumx-0.0.8 vs lib/rumx/bean.rb in rumx-0.1.0
- old
+ new
@@ -1,87 +1,84 @@
+require 'monitor'
+
module Rumx
# Defines a Rumx bean that allows access to the defined attributes and operations.
# All public instance methods are prefixed with "bean_" to try to avoid collisions.
module Bean
module ClassMethods
+ # options
+ # type => :list
+ # list_type - type of each list element
+ # max_size - the max size the list can be indexed for setting. Can be an integer or
+ # a symbol that represents an attribute or method of the bean. Defaults to the
+ # current size of the list.
def bean_reader(name, type, description, options={})
- bean_add_attribute(Attribute.new(name, type, description, true, false, options))
+ bean_add_attribute(name, type, description, true, false, options)
end
- # options
- # max_size - the max size the list can be indexed for setting. Can be an integer or
- # a symbol that represents an attribute or method of the bean. Defaults to the
- # current size of the list.
def bean_list_reader(name, type, description, options={})
- bean_add_list_attribute(Attribute.new(name, type, description, true, false, options))
+ raise "bean_list_reader no longer used, instead use 'bean_reader :#{name}, :list, #{description.inspect}, #{options.merge(:list_type => type).inspect}'"
end
def bean_attr_reader(name, type, description, options={})
attr_reader(name)
bean_reader(name, type, description, options)
end
def bean_list_attr_reader(name, type, description, options={})
- attr_reader(name)
- bean_list_reader(name, type, description, options)
+ raise "bean_list_attr_reader no longer used, instead use 'bean_attr_reader :#{name}, :list, #{description.inspect}, #{options.merge(:list_type => type).inspect}'"
end
def bean_writer(name, type, description, options={})
- bean_add_attribute(Attribute.new(name, type, description, false, true, options))
+ bean_add_attribute(name, type, description, false, true, options)
end
def bean_list_writer(name, type, description, options={})
- bean_add_list_attribute(Attribute.new(name, type, description, false, true, options))
+ raise "bean_list_writer no longer used, instead use 'bean_writer :#{name}, :list, #{description.inspect}, #{options.merge(:list_type => type).inspect}'"
end
def bean_attr_writer(name, type, description, options={})
attr_writer(name)
bean_writer(name, type, description, options)
end
def bean_list_attr_writer(name, type, description, options={})
- attr_writer(name)
- bean_list_writer(name, type, description, options)
+ raise "bean_list_attr_writer no longer used, instead use 'bean_attr_writer :#{name}, :list, #{description.inspect}, #{options.merge(:list_type => type).inspect}'"
end
def bean_accessor(name, type, description, options={})
- bean_add_attribute(Attribute.new(name, type, description, true, true, options))
+ bean_add_attribute(name, type, description, true, true, options)
end
def bean_list_accessor(name, type, description, options={})
- bean_add_list_attribute(Attribute.new(name, type, description, true, true, options))
+ raise "bean_list_accessor no longer used, instead use 'bean_accessor :#{name}, :list, #{description.inspect}, #{options.merge(:list_type => type).inspect}'"
end
def bean_attr_accessor(name, type, description, options={})
attr_accessor(name)
bean_accessor(name, type, description, options)
end
def bean_list_attr_accessor(name, type, description, options={})
- attr_accessor(name)
- bean_list_accessor(name, type, description, options)
+ raise "bean_list_attr_accessor no longer used, instead use 'bean_attr_accessor :#{name}, :list, #{description.inspect}, #{options.merge(:list_type => type).inspect}'"
end
def bean_embed(name, description)
- # We're going to ignore description (for now)
- bean_embeds_local << name.to_sym
+ raise "bean_embed no longer used, instead use 'bean_reader :#{name}, :bean, #{description.inspect}'"
end
def bean_attr_embed(name, description)
- attr_reader(name)
- bean_embed(name, description)
+ raise "bean_attr_embed no longer used, instead use 'bean_attr_reader :#{name}, :bean, #{description.inspect}'"
end
def bean_embed_list(name, description)
- # We're going to ignore description (for now)
- bean_embed_lists_local << name.to_sym
+ raise "bean_embed_list no longer used, instead use 'bean_attr_reader :#{name}, :list, #{description.inspect}, :list_type => :bean'"
end
def bean_attr_embed_list(name, description)
- attr_reader(name)
- bean_embed_list(name, description)
+ raise "bean_attr_embed_list no longer used, instead use 'bean_attr_reader :#{name}, :list, #{description.inspect}, :list_type => :bean'"
end
#bean_operation :my_operation, :string, 'My operation', [
# [ :arg_int, :int, 'An int argument' ],
# [ :arg_float, :float, 'A float argument' ],
@@ -97,42 +94,36 @@
#######
# private - TODO: Local helper methods, how should I designate them as private or just nodoc them?
#######
- def bean_add_attribute(attribute)
- bean_attributes_local << attribute
+ def bean_add_attribute(name, type_name, description, allow_read, allow_write, options)
+ # Dummy up the things that are defined like attributes but are really beans
+ if type_name == :bean
+ bean_embeds_local[name.to_sym] = nil
+ elsif type_name == :list && options[:list_type] == :bean
+ bean_embeds_local[name.to_sym] = ListBean
+ elsif type_name == :hash && options[:hash_type] == :bean
+ bean_embeds_local[name.to_sym] = HashBean
+ else
+ type = Type.find(type_name)
+ bean_attributes_local << type.create_attribute(name, description, allow_read, allow_write, options)
+ end
end
- def bean_add_list_attribute(attribute)
- bean_list_attributes_local << attribute
- end
-
def bean_attributes
attributes = []
self.ancestors.reverse_each do |mod|
attributes += mod.bean_attributes_local if mod.include?(Rumx::Bean)
end
return attributes
end
- def bean_list_attributes
- attributes = []
- self.ancestors.reverse_each do |mod|
- attributes += mod.bean_list_attributes_local if mod.include?(Rumx::Bean)
- end
- return attributes
- end
-
def bean_attributes_local
@attributes ||= []
end
- def bean_list_attributes_local
- @list_attributes ||= []
- end
-
def bean_operations
operations = []
self.ancestors.reverse_each do |mod|
operations += mod.bean_operations_local if mod.include?(Rumx::Bean)
end
@@ -142,33 +133,22 @@
def bean_operations_local
@operations ||= []
end
def bean_embeds
- embeds = []
+ embeds = {}
+ # Merge in all the module embeds that are beans
self.ancestors.reverse_each do |mod|
- embeds += mod.bean_embeds_local if mod.include?(Rumx::Bean)
+ embeds = embeds.merge(mod.bean_embeds_local) if mod.include?(Rumx::Bean)
end
return embeds
end
- def bean_embed_lists
- embed_lists = []
- self.ancestors.reverse_each do |mod|
- embed_lists += mod.bean_embed_lists_local if mod.include?(Rumx::Bean)
- end
- return embed_lists
- end
-
def bean_embeds_local
- @embeds ||= []
+ @embeds ||= {}
end
- def bean_embed_lists_local
- @embed_lists ||= []
- end
-
end
def self.included(base)
base.extend(ClassMethods)
end
@@ -176,49 +156,24 @@
def self.root
@root ||= Beans::Folder.new
end
def self.find(name_array)
- bean = root
- until name_array.empty?
- name = name_array.shift.to_sym
- child_bean = bean.bean_children[name]
- if !child_bean && bean.class.bean_embeds.include?(name)
- child_bean = bean.send(name)
- end
- if !child_bean && bean.class.bean_embed_lists.include?(name)
- list = bean.send(name)
- if list
- index = name_array.shift
- child_bean = list[index.to_i] if index && index.match(/\d+/)
- end
- end
- return nil unless child_bean
- bean = child_bean
- end
- return bean
+ root.bean_find(name_array)
end
# Return [bean, attribute, param_name, value] list or nil if not found
def self.find_attribute(name_array)
- name = name_array.pop
+ attribute_name = name_array.last
+ name_array = name_array[0..-2]
# If it's a list attribute
if name.match(/^\d+$/)
index = name.to_i
name = name_array.pop
bean = Bean.find(name_array)
return nil unless bean
name = name.to_sym
- bean.class.bean_list_attributes.each do |attribute|
- if name == attribute.name
- obj = bean.send(attribute.name)
- if obj
- param_name = "#{attribute.name}[#{index}]"
- return [bean, attribute, param_name, attribute.get_index_value(obj, index)]
- end
- end
- end
# else just a regular attribute
else
bean = Bean.find(name_array)
return nil unless bean
name = name.to_sym
@@ -241,113 +196,150 @@
return [bean, operation] if name == operation.name
end
return nil
end
- # Mutex for synchronization of attributes/operations
- def bean_mutex
- # TBD: How to initialize this in a module and avoid race condition?
- @mutex || Mutex.new
+ # Monitor for synchronization of attributes/operations
+ def bean_monitor
+ # TODO: How to initialize this in a module and avoid race condition?
+ @monitor ||= Monitor.new
end
# Synchronize access to attributes and operations
def bean_synchronize
- bean_mutex.synchronize do
+ bean_monitor.synchronize do
yield
end
end
def bean_children
+ # TODO: How to initialize this in a module and avoid race condition?
@bean_children ||= {}
end
def bean_add_child(name, child_bean)
- # TBD - Should I mutex protect this? All beans would normally be registered during the code initialization process
- raise "Error trying to add #{name} to embedded bean" if @bean_is_embedded
- bean_children[name.to_sym] = child_bean
+ bean_synchronize do
+ bean_children[name.to_sym] = child_bean
+ end
end
def bean_remove_child(name)
- bean_children.delete(name.to_sym)
+ bean_synchronize do
+ bean_children.delete(name.to_sym)
+ end
end
- def bean_has_attributes?
- return true unless self.class.bean_attributes.empty? && self.class.bean_list_attributes.empty?
- self.class.bean_embeds.each do |name|
- bean = send(name)
- return true if bean && bean.bean_has_attributes?
+ # Find the bean
+ def bean_find(name_array, index = 0)
+ return self if index == name_array.size
+ name = name_array[index].to_sym
+ child_bean = bean_children[name] || bean_embedded(name)
+ return nil unless child_bean
+ return child_bean.bean_find(name_array, index+1)
+ end
+
+ def bean_each(ancestry=[], &block)
+ yield self, ancestry
+ bean_each_child_recursive(ancestry) do |child_bean, child_ancestry|
+ yield child_bean, child_ancestry
end
- self.class.bean_embed_lists.each do |list_name|
- list = send(list_name)
- if list
- list.each do |bean|
- return true if bean.bean_has_attributes?
- end
+ end
+
+ def bean_each_child_recursive(ancestry, &block)
+ child_ancestry = ancestry.dup
+ # Save some object creation
+ child_index = child_ancestry.size
+ bean_each_child do |name, bean|
+ child_ancestry[child_index] = name
+ bean.bean_each(child_ancestry, &block)
+ end
+ end
+
+ # Call the block for each direct child of this bean (includes the bean_children and the embedded beans)
+ def bean_each_child(&block)
+ bean_children.each do |name, bean|
+ yield name, bean
+ end
+ bean_each_embedded_child do |name, bean|
+ yield name, bean
+ end
+ end
+
+ # Call the block for all the embedded beans
+ def bean_each_embedded_child(&block)
+ self.class.bean_embeds.each do |name, bean_klass|
+ bean = send(name)
+ if bean
+ # bean_klass is either ListBean or HashBean, otherwise we already have our bean
+ bean = bean_klass.new(bean) if bean_klass
+ yield name, bean
end
end
+ end
+
+ def bean_embedded(name)
+ return nil unless self.class.bean_embeds.key?(name)
+ bean = send(name)
+ if bean
+ bean_klass = self.class.bean_embeds[name]
+ bean = bean_klass.new(bean) if bean_klass
+ end
+ return bean
+ end
+
+ def bean_has_attributes?
+ return true unless self.class.bean_attributes.empty?
+ bean_each_embedded_child do |name, bean|
+ return true if bean.bean_has_attributes?
+ end
return false
end
- def bean_get_attributes(rel_path=nil, param_name=nil, &block)
+ def bean_get_attributes(ancestry=[], &block)
bean_synchronize do
- do_bean_get_attributes(rel_path, param_name, &block)
+ do_bean_get_attributes(ancestry, &block)
end
end
def bean_set_attributes(params)
bean_synchronize do
do_bean_set_attributes(params)
end
end
- def bean_get_and_set_attributes(params, rel_path=nil, param_name=nil, &block)
+ def bean_get_and_set_attributes(params, ancestry=[], &block)
bean_synchronize do
- val = do_bean_get_attributes(rel_path, param_name, &block)
+ val = do_bean_get_attributes(ancestry, &block)
do_bean_set_attributes(params)
val
end
end
- def bean_set_and_get_attributes(params, rel_path=nil, param_name=nil, &block)
+ def bean_set_and_get_attributes(params, ancestry=[], &block)
bean_synchronize do
do_bean_set_attributes(params)
- do_bean_get_attributes(rel_path, param_name, &block)
+ do_bean_get_attributes(ancestry, &block)
end
end
def bean_has_operations?
- return true unless self.class.bean_operations.empty?
- self.class.bean_embeds.each do |name|
- bean = send(name)
- return true if bean && bean.bean_has_operations?
- end
- self.class.bean_embed_lists.each do |list_name|
- list = send(list_name)
- if list
- list.each do |bean|
- return true if bean.bean_has_operations?
- end
- end
- end
- return false
+ !self.class.bean_operations.empty?
end
- def bean_each_operation(rel_path=nil, &block)
+ def bean_each_operation(&block)
self.class.bean_operations.each do |operation|
- yield operation, bean_join_rel_path(rel_path, operation.name.to_s)
+ yield operation
end
- self.class.bean_embeds.each do |name|
- bean = send(name)
- bean.bean_each_operation(bean_join_rel_path(rel_path, name), &block) if bean
- end
- self.class.bean_embed_lists.each do |name|
- list = send(name)
- if list
- list_rel_path = bean_join_rel_path(rel_path, name)
- list.each_with_index do |bean, i|
- bean.bean_each_operation(bean_join_rel_path(list_rel_path, i.to_s), &block)
- end
+ end
+
+ def bean_each_operation_recursive(&block)
+ bean_each do |bean, ancestry|
+ operation_ancestry = ancestry.dup
+ index = operation_ancestry.size
+ bean.class.bean_operations.each do |operation|
+ operation_ancestry[index] = operation.name
+ yield operation, operation_ancestry
end
end
end
#########
@@ -356,143 +348,49 @@
# Allow extenders to save changes, etc. if attribute values change
def bean_attributes_changed
end
- #######
- private
- #######
-
- # Separate call in case we're already mutex locked
- def do_bean_get_attributes(rel_path, param_name, &block)
+ # Separate call in case we're already monitor locked
+ def do_bean_get_attributes(ancestry, &block)
return do_bean_get_attributes_json unless block_given?
self.class.bean_attributes.each do |attribute|
- yield attribute, attribute.get_value(self), bean_join_rel_path(rel_path, attribute.name.to_s), bean_join_param_name(param_name, attribute.name.to_s)
+ attribute.each_attribute_info(self, ancestry) {|attribute_info| yield attribute_info}
end
- self.class.bean_list_attributes.each do |attribute|
- obj = send(attribute.name)
- if obj
- new_rel_path = bean_join_rel_path(rel_path, attribute.name.to_s)
- new_param_name = bean_join_param_name(param_name, attribute.name.to_s)
- obj.each_index do |i|
- yield attribute, attribute.get_index_value(obj, i), "#{new_rel_path}/#{i}", "#{new_param_name}[#{i}]"
- end
- end
+ child_ancestry = ancestry.dup
+ # Save some object creation
+ child_index = child_ancestry.size
+ bean_each_child do |name, bean|
+ child_ancestry[child_index] = name
+ bean.bean_get_attributes(child_ancestry, &block)
end
- self.class.bean_embeds.each do |name|
- bean = send(name)
- bean.bean_get_attributes(bean_join_rel_path(rel_path, name), bean_join_param_name(param_name, name), &block) if bean
- end
- self.class.bean_embed_lists.each do |name|
- list = send(name)
- if list
- list_rel_path = bean_join_rel_path(rel_path, name)
- list_param_name = bean_join_param_name(param_name, name)
- list.each_with_index do |bean, i|
- bean.bean_get_attributes(bean_join_rel_path(list_rel_path, i.to_s), bean_join_param_name(list_param_name, i.to_s), &block)
- end
- end
- end
end
def do_bean_get_attributes_json
hash = {}
self.class.bean_attributes.each do |attribute|
hash[attribute.name] = attribute.get_value(self)
end
- self.class.bean_list_attributes.each do |attribute|
- hash[attribute.name] = attribute.get_value(self)
+ bean_each_child do |name, bean|
+ hash[name] = bean.bean_get_attributes
end
- self.class.bean_embeds.each do |name|
- bean = send(name)
- hash[name] = bean.bean_get_attributes if bean
- end
- self.class.bean_embed_lists.each do |name|
- list = send(name)
- if list
- hash[name] = list.map {|bean| bean.bean_get_attributes}
- end
- end
return hash
end
- # Separate call in case we're already mutex locked
+ # Separate call in case we're already monitor locked
def do_bean_set_attributes(params)
return if !params || params.empty?
changed = false
self.class.bean_attributes.each do |attribute|
- if attribute.allow_write
- if params.has_key?(attribute.name)
- attribute.set_value(self, params[attribute.name])
- changed = true
- elsif params.has_key?(attribute.name.to_s)
- attribute.set_value(self, params[attribute.name.to_s])
- changed = true
- end
- end
+ changed = true if attribute.write?(self, params)
end
- self.class.bean_list_attributes.each do |attribute|
- if attribute.allow_write
- obj = send(attribute.name)
- sub_params = params[attribute.name] || params[attribute.name.to_s]
- raise "Can't assign value for nil list attribute" if !obj && sub_params
- if sub_params
- # TODO: Allow array?
- raise "Invalid param for #{attribute.name}" unless sub_params.kind_of?(Hash)
- max_size = attribute[:max_size]
- if max_size
- if max_size.kind_of?(Symbol)
- max_size = send(max_size)
- end
- else
- # Default to current size of the list if unset
- max_size = obj.size
- end
- sub_params.each do |index, value|
- if index.to_i < max_size
- attribute.set_index_value(obj, index.to_i, value)
- changed = true
- end
- end
- end
- end
- end
- self.class.bean_embeds.each do |name|
- bean = send(name)
- if bean
- embedded_params = params[name]
+ bean_each_child do |name, bean|
+ embedded_params = params[name] || params[name.to_s]
+ if embedded_params && !embedded_params.empty?
bean.bean_set_attributes(embedded_params)
changed = true
end
end
- self.class.bean_embed_lists.each do |name|
- list = send(name)
- if list
- list_params = params[name]
- if list_params
- list.each_with_index do |bean, i|
- bean.bean_set_attributes(list_params[i] || list_params[i.to_s])
- end
- changed = true
- end
- end
- end
bean_attributes_changed if changed
- end
-
- def bean_join_rel_path(parent_rel_path, name)
- if parent_rel_path
- "#{parent_rel_path}/#{name}"
- else
- name.to_s
- end
- end
-
- def bean_join_param_name(parent_param_name, name)
- if parent_param_name
- "#{parent_param_name}[#{name}]"
- else
- name.to_s
- end
end
end
end