Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Rbtree #6

Closed
wants to merge 19 commits into
from
View
@@ -4,6 +4,4 @@ rvm:
- 1.9.2
- 1.9.3
- jruby-18mode
- - jruby-19mode
- - rbx-18mode
- - rbx-19mode
+ - jruby-19mode
View
@@ -1,6 +1,7 @@
source 'https://rubygems.org'
gem 'rake', :group => [:development, :test]
+gem 'rbtree-pure'
group :development do
gem 'jeweler'
View
@@ -36,17 +36,32 @@ task :default => :spec
desc "Run Rspec tests"
RSpec::Core::RakeTask.new(:spec)
+namespace :spec do
+ namespace :rubies do
+ SUPPORTED_RUBIES = %w{ 1.8.7 1.9.2 1.9.3 jruby-1.6.7.2 rbx }
+
+ desc "Run Rspec tests on all supported rubies"
+ task :all_tasks => [:install_gems, :exec]
+
+ desc "Run `bundle install` on all rubies"
+ task :install_gems do
+ sh %{ rvm #{SUPPORTED_RUBIES.join(',')} exec bundle install }
+ end
+
+ desc "Run `bundle exec rake` on all rubies"
+ task :exec do
+ sh %{ rvm #{SUPPORTED_RUBIES.join(',')} exec bundle exec rake spec }
+ end
+ end
+end
+
desc "Run RSpec tests and produce coverage files (results viewable in coverage/index.html)"
RSpec::Core::RakeTask.new(:coverage) do |spec|
if RUBY_VERSION < '1.9'
- spec.rcov_opts = [
- '--exclude', 'spec',
- '--exclude', 'lib/activefacts/tracer.rb',
- '--exclude', 'gem/*'
- ]
+ spec.rcov_opts = %{ --exclude spec --exclude lib/activefacts/tracer.rb --exclude gem/* }
spec.rcov = true
else
- spec.rspec_opts = ['--require', 'simplecov_helper']
+ spec.rspec_opts = %w{ --require simplecov_helper }
end
end
View
@@ -32,7 +32,9 @@
require 'activefacts/api/support' # General support code and core patches
require 'activefacts/api/vocabulary' # A Ruby module may become a Vocabulary
+require 'activefacts/api/flat_hash' # Common behavior shared by RoleValues and InstanceIndex
require 'activefacts/api/instance_index' # The index used by a constellation to record every instance
+require 'activefacts/api/comparable_hash_key' # The keys used by RoleValues and InstanceIndex
require 'activefacts/api/constellation' # A Constellation is a query result or fact population
require 'activefacts/api/object_type' # A Ruby class may become a ObjectType in a Vocabulary
require 'activefacts/api/role' # A ObjectType has a collection of Roles
@@ -0,0 +1,83 @@
+#
+# ActiveFacts Runtime API
+# InstanceIndex class
+#
+# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
+#
+
+module ActiveFacts
+ module API
+
+ # ComparableHashKey provides a way to compare hashes with nil values.
+ class ComparableHashKey
+
+ # Key value
+ attr_reader :value
+
+ # Initialize an instance index key base on a hash.
+ def initialize(hash)
+ @value = flatten_key(hash)
+ end
+
+ # Compare an instance index key with another.
+ #
+ # Keys containing nil values will be compared using their string
+ # representation. (see inspect)
+ def <=>(other)
+ if !contains_nil?(@value) && !contains_nil?(other.value)
+ result = @value <=> other.value rescue nil
+ if result.nil?
+ result = @value.inspect <=> other.value.inspect
+ end
+ result
+ else
+ @value.inspect <=> other.value.inspect
+ end
+ end
+
+ # Checks if arr contains a nil value.
+ def contains_nil?(arr)
+ if arr.class.ancestors.include?(Array)
+ arr.any? do |el|
+ if el.nil?
+ true
+ else
+ contains_nil?(el)
+ end
+ end
+ else
+ arr.nil?
+ end
+ end
+
+ def ==(other)
+ @value == other.value
+ end
+
+ def eql?(other)
+ if self.class == other.class
+ self == other
+ else
+ false
+ end
+ end
+
+ def hash
+ @value.hash
+ end
+
+ private
+ # Any entity contained in `key` will be changed into its identifying role
+ # values.
+ def flatten_key(key)
+ if key.is_a?(Array)
+ key.map { |identifier| flatten_key(identifier) }
+ elsif key.respond_to?(:identifying_role_values)
+ key.identifying_role_values
+ else
+ key
+ end
+ end
+ end
+ end
+end
@@ -66,6 +66,16 @@ def retract(*instances)
self
end
+ # Get instance from InstanceIndex.
+ def get_instance(entity_class, args)
+ instances[entity_class][args]
+ end
+
+ # Copies object type across constellations.
+ def copy(value)
+ send(value.class.basename, value.clone_identity)
+ end
+
=begin
def assert *args
case
@@ -105,7 +115,7 @@ def verbalise
instances.map do |key, instance|
s = "\t\t" + instance.verbalise
if (single_roles.size > 0)
- role_values =
+ role_values =
single_roles.map{|role|
[ role_name = role.to_s.camelcase,
value = instance.send(role)]
@@ -114,6 +114,16 @@ def identity_as_hash
identity_by(self.class)
end
+ # Clones identity.
+ #
+ # Cloning an entity identity means copying its class identifying values and also its supertypes identifying
+ # values.
+ def clone_identity
+ self.class.supertypes_transitive.inject(identity_as_hash) do |roles_hash, supertype|
+ roles_hash.merge!(identity_by(supertype))
+ end
+ end
+
# Identifying role values in a hash form by class (entity).
#
# Subtypes may have different identifying roles compared to their supertype, and therefore, a subtype entity
@@ -153,12 +163,14 @@ def identifying_roles
end
def find_inherited_role(role_name)
- if !superclass.is_entity_type
- false
- elsif superclass.roles.has_key?(role_name)
- superclass.roles[role_name]
+ if superclass.is_entity_type
+ if superclass.roles.has_key?(role_name)
+ superclass.roles[role_name]
+ else
+ superclass.find_inherited_role(role_name)
+ end
else
- superclass.find_inherited_role(role_name)
+ false
end
end
@@ -204,23 +216,26 @@ def identifying_role_values(*args)
end
end
+ def has_unused_params?(assert_args)
+ args, arg_hash = ActiveFacts.extract_hash_args(identifying_role_names, assert_args)
+ !arg_hash.empty?
+ end
+
# REVISIT: This method should verify that all identifying roles (including
# those required to identify any superclass) are present (if mandatory)
# and are unique... BEFORE it creates any new object(s)
# This is a hard problem because it's recursive.
def assert_instance(constellation, args) #:nodoc:
- # Build the key for this instance from the args
- # The key of an instance is the value or array of keys of the identifying values.
- # The key values aren't necessarily present in the constellation, even after this.
+ # Hijack assert_instance if an instance is already present.
key = identifying_role_values(*args)
- # Find and return an existing instance matching this key
- instances = constellation.instances[self] # All instances of this class in this constellation
- instance = instances[key]
- # REVISIT: This ignores any additional attribute assignments
+ instance = constellation.get_instance(self, key)
if instance
- raise "Additional role values are ignored when asserting an existing instance" if args[-1].is_a? Hash and !args[-1].empty?
- return instance, key # A matching instance of this class
+ if has_unused_params?(args)
+ raise "Additional role values are ignored when asserting an existing instance"
+ else
+ return instance, key
+ end
end
# Now construct each of this object's identifying roles
@@ -232,7 +247,7 @@ def assert_instance(constellation, args) #:nodoc:
# We received a single argument of a compatible type
# With a secondary supertype or a type having separate identification,
# we would get the wrong identifier from arg.identifying_role_values:
- key =
+ key =
values = identifying_role_values(args[0])
values = values + [arg_hash = args.pop] if has_hash
else
@@ -279,9 +294,6 @@ def assert_instance(constellation, args) #:nodoc:
rescue DuplicateIdentifyingValueException
@created_instances.each do |role, v|
- if !v.respond_to?(:retract)
- v = constellation.send(role.object_type.basename.to_sym)[[v]]
- end
v.retract if v
end
@created_instances = []
@@ -8,9 +8,12 @@ module ActiveFacts
module API
class DuplicateIdentifyingValueException < StandardError
def initialize(desc)
+ verbalised_entities = desc[:value].related_entities.map do |entity, role_obj, role_value|
+ entity.verbalise
+ end
super("Illegal attempt to assert #{desc[:class].basename} having identifying value" +
" (#{desc[:role].name} is #{desc[:value].verbalise})," +
- " when #{desc[:value].related_entities.map(&:verbalise).join(", ")} already exists")
+ " when #{verbalised_entities} already exists")
end
end
end
@@ -0,0 +1,52 @@
+#
+# ActiveFacts Runtime API
+# InstanceIndex class
+#
+# Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
+#
+
+module ActiveFacts
+ module API
+ # FlatHash implements two different behaviors.
+ #
+ # It uses comparable hash keys which allows comparison of nil values and
+ # it makes traversing methods of the hash work like an array.
+ #
+ # Example:
+ # flat_hash.each { |v| p v }
+ # # instead of
+ # hash.each { |k, v| p v }
+ # # while keeping behaviors such as
+ # flat_hash['hello'] = 'world'
+ module FlatHash
+ def serialize_key(key)
+ ComparableHashKey.new(key)
+ end
+
+ def []=(key, value)
+ @hash[serialize_key(key)] = value
+ end
+
+ def [](key)
+ @hash[serialize_key(key)]
+ end
+
+ def keys
+ @hash.keys.map { |key| key.value }
+ end
+
+ def each(&block)
+ if block.arity < 2
+ @hash.each { |_,v| block.call(v) }
+ else
+ @hash.each(&block)
+ end
+ end
+
+ def refresh_key(key)
+ value = self.delete(key)
+ self.[]=(value, value) if value
+ end
+ end
+ end
+end
Oops, something went wrong.