activefacts-api 0.8.12 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.rspec +1 -0
- data/.travis.yml +9 -0
- data/Gemfile +14 -0
- data/Rakefile +21 -9
- data/VERSION +1 -1
- data/activefacts-api.gemspec +31 -12
- data/lib/activefacts/api.rb +1 -0
- data/lib/activefacts/api/constellation.rb +3 -1
- data/lib/activefacts/api/entity.rb +74 -29
- data/lib/activefacts/api/exceptions.rb +17 -0
- data/lib/activefacts/api/instance.rb +96 -1
- data/lib/activefacts/api/instance_index.rb +35 -37
- data/lib/activefacts/api/numeric.rb +62 -56
- data/lib/activefacts/api/object_type.rb +49 -23
- data/lib/activefacts/api/role.rb +8 -2
- data/lib/activefacts/api/role_values.rb +8 -26
- data/lib/activefacts/api/standard_types.rb +2 -17
- data/lib/activefacts/api/vocabulary.rb +1 -1
- data/lib/activefacts/tracer.rb +13 -1
- data/spec/{constellation_spec.rb → constellation/constellation_spec.rb} +127 -56
- data/spec/constellation/instance_index_spec.rb +90 -0
- data/spec/{instance_spec.rb → constellation/instance_spec.rb} +48 -42
- data/spec/{role_values_spec.rb → fact_type/role_values_spec.rb} +28 -19
- data/spec/{roles_spec.rb → fact_type/roles_spec.rb} +55 -21
- data/spec/fixtures/tax.rb +45 -0
- data/spec/{identification_spec.rb → identification_scheme/identification_spec.rb} +88 -74
- data/spec/identification_scheme/identity_change_spec.rb +118 -0
- data/spec/identification_scheme/identity_supertype_change_spec.rb +63 -0
- data/spec/{entity_type_spec.rb → object_type/entity_type/entity_type_spec.rb} +2 -4
- data/spec/object_type/entity_type/multipart_identification_spec.rb +77 -0
- data/spec/{autocounter_spec.rb → object_type/value_type/autocounter_spec.rb} +2 -4
- data/spec/object_type/value_type/numeric_spec.rb +63 -0
- data/spec/{value_type_spec.rb → object_type/value_type/value_type_spec.rb} +10 -14
- data/spec/simplecov_helper.rb +8 -0
- data/spec/spec_helper.rb +1 -1
- metadata +100 -19
    
        data/.rspec
    CHANGED
    
    
    
        data/.travis.yml
    ADDED
    
    
    
        data/Gemfile
    ADDED
    
    | @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            source 'https://rubygems.org'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            gem 'rake', :group => [:development, :test]
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            group :development do
         | 
| 6 | 
            +
              gem 'jeweler'
         | 
| 7 | 
            +
              gem 'rspec', '~>2.6.0'
         | 
| 8 | 
            +
            end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            group :test do
         | 
| 11 | 
            +
              # rcov 1.0.0 is broken for jruby, so 0.9.11 is the only one available.
         | 
| 12 | 
            +
              gem 'rcov', '~>0.9.11', :platforms => [:jruby, :mri_18], :require => false
         | 
| 13 | 
            +
              gem 'simplecov', '~>0.6.4', :platforms => :mri_19, :require => false
         | 
| 14 | 
            +
            end
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -29,19 +29,31 @@ Jeweler::RubygemsDotOrgTasks.new | |
| 29 29 |  | 
| 30 30 | 
             
            require 'rspec/core'
         | 
| 31 31 | 
             
            require 'rspec/core/rake_task'
         | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 32 | 
            +
            require 'rdoc/task'
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            task :default => :spec
         | 
| 35 35 |  | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 36 | 
            +
            desc "Run Rspec tests"
         | 
| 37 | 
            +
            RSpec::Core::RakeTask.new(:spec)
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            desc "Run RSpec tests and produce coverage files (results viewable in coverage/index.html)"
         | 
| 40 | 
            +
            RSpec::Core::RakeTask.new(:coverage) do |spec|
         | 
| 41 | 
            +
              if RUBY_VERSION < '1.9'
         | 
| 42 | 
            +
                spec.rcov_opts = [
         | 
| 43 | 
            +
                    '--exclude', 'spec',
         | 
| 44 | 
            +
                    '--exclude', 'lib/activefacts/tracer.rb',
         | 
| 45 | 
            +
                    '--exclude', 'gem/*'
         | 
| 46 | 
            +
                  ]
         | 
| 47 | 
            +
                spec.rcov = true
         | 
| 48 | 
            +
              else
         | 
| 49 | 
            +
                spec.rspec_opts = ['--require', 'simplecov_helper']
         | 
| 50 | 
            +
              end
         | 
| 40 51 | 
             
            end
         | 
| 41 52 |  | 
| 42 | 
            -
            task : | 
| 53 | 
            +
            task :cov => :coverage
         | 
| 54 | 
            +
            task :rcov => :coverage
         | 
| 55 | 
            +
            task :simplecov => :coverage
         | 
| 43 56 |  | 
| 44 | 
            -
            require 'rdoc/task'
         | 
| 45 57 | 
             
            Rake::RDocTask.new do |rdoc|
         | 
| 46 58 | 
             
              version = File.exist?('VERSION') ? File.read('VERSION') : ""
         | 
| 47 59 |  | 
    
        data/VERSION
    CHANGED
    
    | @@ -1 +1 @@ | |
| 1 | 
            -
            0. | 
| 1 | 
            +
            0.9.1
         | 
    
        data/activefacts-api.gemspec
    CHANGED
    
    | @@ -5,11 +5,11 @@ | |
| 5 5 |  | 
| 6 6 | 
             
            Gem::Specification.new do |s|
         | 
| 7 7 | 
             
              s.name = "activefacts-api"
         | 
| 8 | 
            -
              s.version = "0. | 
| 8 | 
            +
              s.version = "0.9.1"
         | 
| 9 9 |  | 
| 10 10 | 
             
              s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
         | 
| 11 11 | 
             
              s.authors = ["Clifford Heath"]
         | 
| 12 | 
            -
              s.date = "2012- | 
| 12 | 
            +
              s.date = "2012-10-17"
         | 
| 13 13 | 
             
              s.description = "\nThe ActiveFacts API is a Ruby DSL for managing constellations of elementary facts.\nEach fact is either existential (a value or an entity), characteristic (boolean) or\nbinary relational (A rel B). Relational facts are consistently co-referenced, so you\ncan traverse them efficiently in any direction. Each constellation maintains constraints\nover the fact population.\n"
         | 
| 14 14 | 
             
              s.email = "clifford.heath@gmail.com"
         | 
| 15 15 | 
             
              s.extra_rdoc_files = [
         | 
| @@ -20,6 +20,8 @@ Gem::Specification.new do |s| | |
| 20 20 | 
             
              s.files = [
         | 
| 21 21 | 
             
                ".document",
         | 
| 22 22 | 
             
                ".rspec",
         | 
| 23 | 
            +
                ".travis.yml",
         | 
| 24 | 
            +
                "Gemfile",
         | 
| 23 25 | 
             
                "LICENSE.txt",
         | 
| 24 26 | 
             
                "README.rdoc",
         | 
| 25 27 | 
             
                "Rakefile",
         | 
| @@ -28,6 +30,7 @@ Gem::Specification.new do |s| | |
| 28 30 | 
             
                "lib/activefacts/api.rb",
         | 
| 29 31 | 
             
                "lib/activefacts/api/constellation.rb",
         | 
| 30 32 | 
             
                "lib/activefacts/api/entity.rb",
         | 
| 33 | 
            +
                "lib/activefacts/api/exceptions.rb",
         | 
| 31 34 | 
             
                "lib/activefacts/api/instance.rb",
         | 
| 32 35 | 
             
                "lib/activefacts/api/instance_index.rb",
         | 
| 33 36 | 
             
                "lib/activefacts/api/numeric.rb",
         | 
| @@ -39,37 +42,53 @@ Gem::Specification.new do |s| | |
| 39 42 | 
             
                "lib/activefacts/api/value.rb",
         | 
| 40 43 | 
             
                "lib/activefacts/api/vocabulary.rb",
         | 
| 41 44 | 
             
                "lib/activefacts/tracer.rb",
         | 
| 42 | 
            -
                "spec/ | 
| 43 | 
            -
                "spec/ | 
| 44 | 
            -
                "spec/ | 
| 45 | 
            -
                "spec/ | 
| 46 | 
            -
                "spec/ | 
| 47 | 
            -
                "spec/ | 
| 48 | 
            -
                "spec/ | 
| 49 | 
            -
                "spec/ | 
| 50 | 
            -
                "spec/ | 
| 45 | 
            +
                "spec/constellation/constellation_spec.rb",
         | 
| 46 | 
            +
                "spec/constellation/instance_index_spec.rb",
         | 
| 47 | 
            +
                "spec/constellation/instance_spec.rb",
         | 
| 48 | 
            +
                "spec/fact_type/role_values_spec.rb",
         | 
| 49 | 
            +
                "spec/fact_type/roles_spec.rb",
         | 
| 50 | 
            +
                "spec/fixtures/tax.rb",
         | 
| 51 | 
            +
                "spec/identification_scheme/identification_spec.rb",
         | 
| 52 | 
            +
                "spec/identification_scheme/identity_change_spec.rb",
         | 
| 53 | 
            +
                "spec/identification_scheme/identity_supertype_change_spec.rb",
         | 
| 54 | 
            +
                "spec/object_type/entity_type/entity_type_spec.rb",
         | 
| 55 | 
            +
                "spec/object_type/entity_type/multipart_identification_spec.rb",
         | 
| 56 | 
            +
                "spec/object_type/value_type/autocounter_spec.rb",
         | 
| 57 | 
            +
                "spec/object_type/value_type/numeric_spec.rb",
         | 
| 58 | 
            +
                "spec/object_type/value_type/value_type_spec.rb",
         | 
| 59 | 
            +
                "spec/simplecov_helper.rb",
         | 
| 60 | 
            +
                "spec/spec_helper.rb"
         | 
| 51 61 | 
             
              ]
         | 
| 52 62 | 
             
              s.homepage = "http://github.com/cjheath/activefacts-api"
         | 
| 53 63 | 
             
              s.licenses = ["MIT"]
         | 
| 54 64 | 
             
              s.require_paths = ["lib"]
         | 
| 55 | 
            -
              s.rubygems_version = "1.8. | 
| 65 | 
            +
              s.rubygems_version = "1.8.24"
         | 
| 56 66 | 
             
              s.summary = "A fact-based data model DSL and API"
         | 
| 57 67 |  | 
| 58 68 | 
             
              if s.respond_to? :specification_version then
         | 
| 59 69 | 
             
                s.specification_version = 3
         | 
| 60 70 |  | 
| 61 71 | 
             
                if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
         | 
| 72 | 
            +
                  s.add_development_dependency(%q<rake>, [">= 0"])
         | 
| 73 | 
            +
                  s.add_development_dependency(%q<jeweler>, [">= 0"])
         | 
| 74 | 
            +
                  s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
         | 
| 62 75 | 
             
                  s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
         | 
| 63 76 | 
             
                  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
         | 
| 64 77 | 
             
                  s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
         | 
| 65 78 | 
             
                  s.add_development_dependency(%q<rdoc>, [">= 2.4.2"])
         | 
| 66 79 | 
             
                else
         | 
| 80 | 
            +
                  s.add_dependency(%q<rake>, [">= 0"])
         | 
| 81 | 
            +
                  s.add_dependency(%q<jeweler>, [">= 0"])
         | 
| 82 | 
            +
                  s.add_dependency(%q<rspec>, ["~> 2.6.0"])
         | 
| 67 83 | 
             
                  s.add_dependency(%q<rspec>, ["~> 2.3.0"])
         | 
| 68 84 | 
             
                  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
         | 
| 69 85 | 
             
                  s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
         | 
| 70 86 | 
             
                  s.add_dependency(%q<rdoc>, [">= 2.4.2"])
         | 
| 71 87 | 
             
                end
         | 
| 72 88 | 
             
              else
         | 
| 89 | 
            +
                s.add_dependency(%q<rake>, [">= 0"])
         | 
| 90 | 
            +
                s.add_dependency(%q<jeweler>, [">= 0"])
         | 
| 91 | 
            +
                s.add_dependency(%q<rspec>, ["~> 2.6.0"])
         | 
| 73 92 | 
             
                s.add_dependency(%q<rspec>, ["~> 2.3.0"])
         | 
| 74 93 | 
             
                s.add_dependency(%q<bundler>, ["~> 1.0.0"])
         | 
| 75 94 | 
             
                s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
         | 
    
        data/lib/activefacts/api.rb
    CHANGED
    
    | @@ -41,4 +41,5 @@ require 'activefacts/api/instance'              # An Instance is an instance of | |
| 41 41 | 
             
            require 'activefacts/api/value'                 # A Value is an Instance of a value class (String, Numeric, etc)
         | 
| 42 42 | 
             
            require 'activefacts/api/entity'                # An Entity class is an Instance not of a value class
         | 
| 43 43 | 
             
            require 'activefacts/api/standard_types'        # Value classes are augmented so their subclasses may become Value Types
         | 
| 44 | 
            +
            require 'activefacts/api/exceptions'            # Relevant exceptions
         | 
| 44 45 | 
             
            require 'activefacts/tracer'
         | 
| @@ -33,7 +33,9 @@ module ActiveFacts | |
| 33 33 | 
             
                #
         | 
| 34 34 | 
             
                class Constellation
         | 
| 35 35 | 
             
                  attr_reader :vocabulary
         | 
| 36 | 
            -
                  # All instances are indexed in this hash, keyed by the class object. | 
| 36 | 
            +
                  # All instances are indexed in this hash, keyed by the class object.
         | 
| 37 | 
            +
                  # Each instance is indexed for every supertype it has (including multiply-inherited ones).
         | 
| 38 | 
            +
                  # It's a bad idea to try to modify these indexes!
         | 
| 37 39 | 
             
                  attr_reader :instances      # Can say c.instances[MyClass].each{|k, v| ... }
         | 
| 38 40 | 
             
                                              # Can also say c.MyClass.each{|k, v| ... }
         | 
| 39 41 |  | 
| @@ -29,17 +29,9 @@ module ActiveFacts | |
| 29 29 | 
             
                      klass = klass.superclass
         | 
| 30 30 | 
             
                    end
         | 
| 31 31 |  | 
| 32 | 
            -
             | 
| 33 | 
            -
                       | 
| 34 | 
            -
             | 
| 35 | 
            -
                      # in the constellation this object will exist in, since they won't get
         | 
| 36 | 
            -
                      # attached to/cloned into that constellation merely by being assigned here.
         | 
| 37 | 
            -
                      # REVISIT: Nothing takes care of that, currently.
         | 
| 38 | 
            -
                      #
         | 
| 39 | 
            -
                      # The solution to this is to have an empty initialize, add the new instance
         | 
| 40 | 
            -
                      # to the Constellation, then initialise_roles using normal assignment.
         | 
| 41 | 
            -
            #        end
         | 
| 42 | 
            -
             | 
| 32 | 
            +
                    if args[-1].respond_to?(:has_key?) && args[-1].has_key?(:constellation)
         | 
| 33 | 
            +
                      @constellation = args.pop[:constellation]
         | 
| 34 | 
            +
                    end
         | 
| 43 35 | 
             
                    hash = args[-1].is_a?(Hash) ? args.pop.clone : nil
         | 
| 44 36 |  | 
| 45 37 | 
             
                    # Pass just the hash, if there is one, else no arguments:
         | 
| @@ -93,11 +85,11 @@ module ActiveFacts | |
| 93 85 | 
             
                  # When used as a hash key, this entity instance is compared with another by
         | 
| 94 86 | 
             
                  # comparing the values of its identifying roles
         | 
| 95 87 | 
             
                  def eql?(other)
         | 
| 96 | 
            -
                     | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
                       | 
| 100 | 
            -
                     | 
| 88 | 
            +
                    if self.class == other.class
         | 
| 89 | 
            +
                      identity_as_hash == other.identity_as_hash
         | 
| 90 | 
            +
                    else
         | 
| 91 | 
            +
                      false
         | 
| 92 | 
            +
                    end
         | 
| 101 93 | 
             
                  end
         | 
| 102 94 |  | 
| 103 95 | 
             
                  # Verbalise this entity instance
         | 
| @@ -117,12 +109,30 @@ module ActiveFacts | |
| 117 109 | 
             
                    end
         | 
| 118 110 | 
             
                  end
         | 
| 119 111 |  | 
| 112 | 
            +
                  # Identifying role values in a hash form.
         | 
| 113 | 
            +
                  def identity_as_hash
         | 
| 114 | 
            +
                    identity_by(self.class)
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  # Identifying role values in a hash form by class (entity).
         | 
| 118 | 
            +
                  #
         | 
| 119 | 
            +
                  # Subtypes may have different identifying roles compared to their supertype, and therefore, a subtype entity
         | 
| 120 | 
            +
                  # may be identified differently if compared to one of its supertype.
         | 
| 121 | 
            +
                  def identity_by(klass)
         | 
| 122 | 
            +
                    roles_hash = {}
         | 
| 123 | 
            +
                    klass.identifying_roles.each do |role|
         | 
| 124 | 
            +
                      roles_hash[role.getter] = send(role.getter)
         | 
| 125 | 
            +
                    end
         | 
| 126 | 
            +
                    roles_hash
         | 
| 127 | 
            +
                  end
         | 
| 128 | 
            +
             | 
| 120 129 | 
             
                  # All classes that become Entity types receive the methods of this class as class methods:
         | 
| 121 130 | 
             
                  module ClassMethods
         | 
| 122 131 | 
             
                    include Instance::ClassMethods
         | 
| 123 132 |  | 
| 124 133 | 
             
                    attr_accessor :identification_inherited_from
         | 
| 125 134 | 
             
                    attr_accessor :overrides_identification_of
         | 
| 135 | 
            +
                    attr_accessor :created_instances
         | 
| 126 136 |  | 
| 127 137 | 
             
                    # Return the array of Role objects that define the identifying relationships of this Entity type:
         | 
| 128 138 | 
             
                    def identifying_role_names
         | 
| @@ -137,11 +147,21 @@ module ActiveFacts | |
| 137 147 | 
             
                      # REVISIT: Should this return nil if identification_inherited_from?
         | 
| 138 148 | 
             
                      @identifying_roles ||=
         | 
| 139 149 | 
             
                        identifying_role_names.map do |role_name|
         | 
| 140 | 
            -
                          role = roles[role_name] || ( | 
| 150 | 
            +
                          role = roles[role_name] || find_inherited_role(role_name)
         | 
| 141 151 | 
             
                          role
         | 
| 142 152 | 
             
                        end
         | 
| 143 153 | 
             
                    end
         | 
| 144 154 |  | 
| 155 | 
            +
                    def find_inherited_role(role_name)
         | 
| 156 | 
            +
                      if !superclass.is_entity_type
         | 
| 157 | 
            +
                        false
         | 
| 158 | 
            +
                      elsif superclass.roles.has_key?(role_name)
         | 
| 159 | 
            +
                        superclass.roles[role_name]
         | 
| 160 | 
            +
                      else
         | 
| 161 | 
            +
                        superclass.find_inherited_role(role_name)
         | 
| 162 | 
            +
                      end
         | 
| 163 | 
            +
                    end
         | 
| 164 | 
            +
             | 
| 145 165 | 
             
                    # Convert the passed arguments into an array of raw values (or arrays of values, transitively)
         | 
| 146 166 | 
             
                    # that identify an instance of this Entity type:
         | 
| 147 167 | 
             
                    def identifying_role_values(*args)
         | 
| @@ -150,7 +170,7 @@ module ActiveFacts | |
| 150 170 | 
             
                      # If the single arg is an instance of the correct class or a subclass,
         | 
| 151 171 | 
             
                      # use the instance's identifying_role_values
         | 
| 152 172 | 
             
                      has_hash = args[-1].is_a?(Hash)
         | 
| 153 | 
            -
                      if (args.size == 1+(has_hash ?1:0) and (arg = args[0]).is_a?(self))
         | 
| 173 | 
            +
                      if (args.size == 1+(has_hash ? 1 : 0) and (arg = args[0]).is_a?(self))
         | 
| 154 174 | 
             
                        # With a secondary supertype or a subtype having separate identification,
         | 
| 155 175 | 
             
                        # we would get the wrong identifier from arg.identifying_role_values:
         | 
| 156 176 | 
             
                        return irns.map do |role_name|
         | 
| @@ -163,7 +183,7 @@ module ActiveFacts | |
| 163 183 | 
             
                      args, arg_hash = ActiveFacts::extract_hash_args(irns, args)
         | 
| 164 184 |  | 
| 165 185 | 
             
                      if args.size > irns.size
         | 
| 166 | 
            -
                        raise " | 
| 186 | 
            +
                        raise "#{basename} expects only (#{irns*', '}) for its identifier, but you provided the extra values #{args[irns.size..-1].inspect}"
         | 
| 167 187 | 
             
                      end
         | 
| 168 188 |  | 
| 169 189 | 
             
                      role_args = irns.map{|role_sym| roles(role_sym)}.zip(args)
         | 
| @@ -198,13 +218,18 @@ module ActiveFacts | |
| 198 218 | 
             
                      instances = constellation.instances[self]   # All instances of this class in this constellation
         | 
| 199 219 | 
             
                      instance = instances[key]
         | 
| 200 220 | 
             
                      # REVISIT: This ignores any additional attribute assignments
         | 
| 201 | 
            -
                       | 
| 221 | 
            +
                      if instance
         | 
| 222 | 
            +
                        # raise "Additional role values are ignored when asserting an existing instance" if args[-1].is_a? Hash and !args[-1].empty?
         | 
| 223 | 
            +
                        assign_additional_roles(instance, args[-1]) if args[-1].is_a? Hash and !args[-1].empty?
         | 
| 224 | 
            +
                        return instance, key      # A matching instance of this class
         | 
| 225 | 
            +
                      end
         | 
| 202 226 |  | 
| 203 227 | 
             
                      # Now construct each of this object's identifying roles
         | 
| 204 228 | 
             
                      irns = identifying_role_names
         | 
| 229 | 
            +
                      @created_instances ||= []
         | 
| 205 230 |  | 
| 206 231 | 
             
                      has_hash = args[-1].is_a?(Hash)
         | 
| 207 | 
            -
                      if args.size == 1+(has_hash ?1:0) and args[0].is_a?(self)
         | 
| 232 | 
            +
                      if args.size == 1+(has_hash ? 1 : 0) and args[0].is_a?(self)
         | 
| 208 233 | 
             
                        # We received a single argument of a compatible type
         | 
| 209 234 | 
             
                        # With a secondary supertype or a type having separate identification,
         | 
| 210 235 | 
             
                        # we would get the wrong identifier from arg.identifying_role_values:
         | 
| @@ -222,9 +247,13 @@ module ActiveFacts | |
| 222 247 | 
             
                            elsif !arg
         | 
| 223 248 | 
             
                              value = role_key = nil
         | 
| 224 249 | 
             
                            else
         | 
| 225 | 
            -
                               | 
| 226 | 
            -
                                 | 
| 227 | 
            -
                               | 
| 250 | 
            +
                              if role.counterpart.object_type.is_entity_type
         | 
| 251 | 
            +
                                add = !constellation.send(role.counterpart.object_type.basename.to_sym).include?([arg])
         | 
| 252 | 
            +
                              else
         | 
| 253 | 
            +
                                add = !constellation.send(role.counterpart.object_type.basename.to_sym).include?(arg)
         | 
| 254 | 
            +
                              end
         | 
| 255 | 
            +
                              value, role_key = role.counterpart.object_type.assert_instance(constellation, Array(arg))
         | 
| 256 | 
            +
                              @created_instances << [role.counterpart, value] if add
         | 
| 228 257 | 
             
                            end
         | 
| 229 258 | 
             
                            key << role_key
         | 
| 230 259 | 
             
                            value
         | 
| @@ -233,19 +262,35 @@ module ActiveFacts | |
| 233 262 | 
             
                      end
         | 
| 234 263 |  | 
| 235 264 | 
             
                      #trace :assert, "Constructing new #{self} with #{values.inspect}" do
         | 
| 236 | 
            -
             | 
| 265 | 
            +
                      values << { :constellation => constellation }
         | 
| 266 | 
            +
                      instance = new(*values)
         | 
| 237 267 | 
             
                      #end
         | 
| 238 268 |  | 
| 239 | 
            -
                       | 
| 240 | 
            -
             | 
| 269 | 
            +
                      assign_additional_roles(instance, arg_hash)
         | 
| 270 | 
            +
             | 
| 271 | 
            +
                      return *index_instance(instance, key, irns)
         | 
| 272 | 
            +
             | 
| 273 | 
            +
                    rescue DuplicateIdentifyingValueException
         | 
| 274 | 
            +
                      @created_instances.each do |role, v|
         | 
| 275 | 
            +
                        if !v.respond_to?(:retract)
         | 
| 276 | 
            +
                          v = constellation.send(role.object_type.basename.to_sym)[[v]]
         | 
| 277 | 
            +
                        end
         | 
| 278 | 
            +
                        v.retract if v
         | 
| 279 | 
            +
                      end
         | 
| 280 | 
            +
                      @created_instances = []
         | 
| 281 | 
            +
                      raise
         | 
| 282 | 
            +
                    end
         | 
| 241 283 |  | 
| 284 | 
            +
                    def assign_additional_roles(instance, arg_hash)
         | 
| 242 285 | 
             
                      # Now assign any extra args in the hash which weren't identifiers (extra identifiers will be assigned again)
         | 
| 243 286 | 
             
                      (arg_hash ? arg_hash.entries : []).each do |role_name, value|
         | 
| 244 287 | 
             
                        role = roles(role_name)
         | 
| 288 | 
            +
             | 
| 289 | 
            +
                        if !instance.instance_index_counterpart(role).include?(value)
         | 
| 290 | 
            +
                          @created_instances << [role, value]
         | 
| 291 | 
            +
                        end
         | 
| 245 292 | 
             
                        instance.send(role.setter, value)
         | 
| 246 293 | 
             
                      end
         | 
| 247 | 
            -
             | 
| 248 | 
            -
                      return *index_instance(instance, key, irns)
         | 
| 249 294 | 
             
                    end
         | 
| 250 295 |  | 
| 251 296 | 
             
                    def index_instance(instance, key = nil, key_roles = nil) #:nodoc:
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            #
         | 
| 2 | 
            +
            #       ActiveFacts Runtime API
         | 
| 3 | 
            +
            #       Custom exception classes
         | 
| 4 | 
            +
            #
         | 
| 5 | 
            +
            # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module ActiveFacts
         | 
| 8 | 
            +
              module API
         | 
| 9 | 
            +
                class DuplicateIdentifyingValueException < StandardError
         | 
| 10 | 
            +
                  def initialize(desc)
         | 
| 11 | 
            +
                    super("Illegal attempt to assert #{desc[:class].basename} having identifying value" +
         | 
| 12 | 
            +
                          " (#{desc[:role].name} is #{desc[:value].verbalise})," +
         | 
| 13 | 
            +
                          " when #{desc[:value].related_entities.map(&:verbalise).join(", ")} already exists")
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -24,8 +24,99 @@ module ActiveFacts | |
| 24 24 | 
             
                    end
         | 
| 25 25 | 
             
                  end
         | 
| 26 26 |  | 
| 27 | 
            +
                  # Detect inconsistencies within constellation if this entity was updated
         | 
| 28 | 
            +
                  # with the specified role/value pair.
         | 
| 29 | 
            +
                  def detect_inconsistencies(role, value)
         | 
| 30 | 
            +
                    if duplicate_identifying_values?(role, value)
         | 
| 31 | 
            +
                      exception_data = {
         | 
| 32 | 
            +
                        :value => value,
         | 
| 33 | 
            +
                        :role  => role,
         | 
| 34 | 
            +
                        :class => self.class
         | 
| 35 | 
            +
                      }
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      raise DuplicateIdentifyingValueException.new(exception_data)
         | 
| 38 | 
            +
                    end
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  # Checks if instance have duplicate values within its constellation.
         | 
| 42 | 
            +
                  #
         | 
| 43 | 
            +
                  # Only works on identifying roles.
         | 
| 44 | 
            +
                  def duplicate_identifying_values?(role, value)
         | 
| 45 | 
            +
                    @constellation && role.is_identifying && !is_unique?(:role => role, :value => value)
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  # Checks if instance would still be unique if it was updated with
         | 
| 49 | 
            +
                  # args.
         | 
| 50 | 
            +
                  #
         | 
| 51 | 
            +
                  # args should be a hash containing the role and value to update
         | 
| 52 | 
            +
                  # and the name of the identifying value as the key.
         | 
| 53 | 
            +
                  #
         | 
| 54 | 
            +
                  # For example, if a Person is identified by name and family_name:
         | 
| 55 | 
            +
                  # updated_values = { :name => "John" }
         | 
| 56 | 
            +
                  # Would merge this hash with the one defining the current instance
         | 
| 57 | 
            +
                  # and verify in our constellation if it exists.
         | 
| 58 | 
            +
                  #
         | 
| 59 | 
            +
                  # The uniqueness of the entity will also be checked within its supertypes.
         | 
| 60 | 
            +
                  #
         | 
| 61 | 
            +
                  # An Employee -subtype of a Person- identified by its employee_id would
         | 
| 62 | 
            +
                  # collide with a Person if it has the same name. But `name` may not be
         | 
| 63 | 
            +
                  # an identifying value for the Employee identification scheme.
         | 
| 64 | 
            +
                  def is_unique?(args)
         | 
| 65 | 
            +
                    duplicate = ([self.class] + self.class.supertypes_transitive).detect do |klass|
         | 
| 66 | 
            +
                      old_identity = identity_by(klass)
         | 
| 67 | 
            +
                      if klass.identifying_roles.include?(args[:role])
         | 
| 68 | 
            +
                        new_identity = old_identity.merge(args[:role].getter => args[:value])
         | 
| 69 | 
            +
                        @constellation.instances[klass].include?(new_identity)
         | 
| 70 | 
            +
                      else
         | 
| 71 | 
            +
                        false
         | 
| 72 | 
            +
                      end
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    !duplicate
         | 
| 76 | 
            +
                  end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                  # List entities which reference the current one.
         | 
| 79 | 
            +
                  #
         | 
| 80 | 
            +
                  # Once an entity is found, it will also search for
         | 
| 81 | 
            +
                  # related entities of this instance.
         | 
| 82 | 
            +
                  def related_entities(instances = [])
         | 
| 83 | 
            +
                    self.class.roles.each do |role_name, role|
         | 
| 84 | 
            +
                      instance_index_counterpart(role).each do |irv, instance|
         | 
| 85 | 
            +
                        if instance.class.is_entity_type && instance.is_identified_by?(self)
         | 
| 86 | 
            +
                          if !instances.include?(instance)
         | 
| 87 | 
            +
                            instances << instance
         | 
| 88 | 
            +
                            instance.related_entities(instances)
         | 
| 89 | 
            +
                          end
         | 
| 90 | 
            +
                        end
         | 
| 91 | 
            +
                      end
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                    instances
         | 
| 94 | 
            +
                  end
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  # Determine if entity is an identifying value
         | 
| 97 | 
            +
                  # of the current instance.
         | 
| 98 | 
            +
                  def is_identified_by?(entity)
         | 
| 99 | 
            +
                    self.class.identifying_roles.detect do |role|
         | 
| 100 | 
            +
                      send(role.getter) == entity
         | 
| 101 | 
            +
                    end
         | 
| 102 | 
            +
                  end
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  def instance_index
         | 
| 105 | 
            +
                    @constellation.send(self.class.basename.to_sym)
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                  def instance_index_counterpart(role)
         | 
| 109 | 
            +
                    if @constellation && role.counterpart
         | 
| 110 | 
            +
                      @constellation.send(role.counterpart.object_type.basename.to_sym)
         | 
| 111 | 
            +
                    else
         | 
| 112 | 
            +
                      []
         | 
| 113 | 
            +
                    end
         | 
| 114 | 
            +
                  end
         | 
| 115 | 
            +
             | 
| 27 116 | 
             
                  # Verbalise this instance
         | 
| 117 | 
            +
                  # REVISIT: Should it raise an error if it was not redefined ?
         | 
| 28 118 | 
             
                  def verbalise
         | 
| 119 | 
            +
                    # REVISIT: Should it raise an error if it was not redefined ?
         | 
| 29 120 | 
             
                    # This method should always be overridden in subclasses
         | 
| 30 121 | 
             
                  end
         | 
| 31 122 |  | 
| @@ -48,7 +139,11 @@ module ActiveFacts | |
| 48 139 | 
             
                          # puts "Not removing role #{role_name} from counterpart RoleValues #{counterpart.name}"
         | 
| 49 140 | 
             
                          # Duplicate the array using to_a, as the RoleValues here will be modified as we traverse it:
         | 
| 50 141 | 
             
                          send(role.name).to_a.each do |v|
         | 
| 51 | 
            -
                             | 
| 142 | 
            +
                            if counterpart.is_identifying
         | 
| 143 | 
            +
                              v.retract
         | 
| 144 | 
            +
                            else
         | 
| 145 | 
            +
                              v.send(counterpart.setter, nil)
         | 
| 146 | 
            +
                            end
         | 
| 52 147 | 
             
                          end
         | 
| 53 148 | 
             
                        end
         | 
| 54 149 | 
             
                      end
         |