activefacts-api 0.8.9

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2008 Clifford Heath.
2
+
3
+ This software is provided 'as-is', without any express or implied warranty.
4
+ In no event will the authors be held liable for any damages arising from the
5
+ use of this software.
6
+
7
+ Permission is granted to anyone to use this software for any purpose,
8
+ including commercial applications, and to alter it and redistribute it
9
+ freely, subject to the following restrictions:
10
+
11
+ 1. The origin of this software must not be misrepresented; you must not
12
+ claim that you wrote the original software. If you use this software
13
+ in a product, an acknowledgment in the product documentation would be
14
+ appreciated but is not required.
15
+
16
+ 2. Altered source versions must be plainly marked as such, and must not be
17
+ misrepresented as being the original software.
18
+
19
+ 3. This notice may not be removed or altered from any source distribution.
@@ -0,0 +1,41 @@
1
+ = activefacts-api
2
+
3
+ The ActiveFacts API provides the fact-oriented information management API
4
+ for the ActiveFacts project. It is a Ruby DSL for managing constellations
5
+ of elementary facts. Each fact is either existential (a value or an entity),
6
+ characteristic (boolean) or binary relational (A rel B). Relational facts are
7
+ consistently co-referenced, so you can traverse them efficiently in any
8
+ direction. Each constellation maintains constraints over the fact population.
9
+
10
+ Contrary to object-oriented and relational modeling, fact oriented models
11
+ do not use the concept of attributes. Fact types which express one-to-one or
12
+ many-to-one relationships are fully mutual relationships between independent
13
+ objects, which play the respective roles in the fact relationship. In addition,
14
+ all objects are intrinsically identified, not by an external object-id. A
15
+ constellation can not contain more than one instance of an object having the
16
+ same identification. Accordingly there is no 'new' or 'delete' operations,
17
+ just 'assert' and 'retract'. This prevents problems caused by having duplicate
18
+ representations of the same object.
19
+
20
+ The constellation is a universal and liberating data structure.
21
+
22
+ * http://dataconstellation.com/ActiveFacts/
23
+
24
+ == INSTALL:
25
+
26
+ * sudo gem install activefacts-api
27
+
28
+ == Contributing to activefacts-api
29
+
30
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
31
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
32
+ * Fork the project
33
+ * Start a feature/bugfix branch
34
+ * Commit and push until you are happy with your contribution
35
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
36
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
37
+
38
+ == Copyright
39
+
40
+ Copyright (c) 2008-2011 Clifford Heath. See LICENSE.txt for further details.
41
+
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gem|
6
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
7
+ gem.name = "activefacts-api"
8
+ gem.homepage = "http://github.com/cjheath/activefacts-api"
9
+ gem.license = "MIT"
10
+ gem.summary = "A semantic modeling and query language (CQL) and application runtime (the Constellation API)"
11
+ gem.description = %q{
12
+ The ActiveFacts API is a Ruby DSL for managing constellations of elementary facts.
13
+ Each fact is either existential (a value or an entity), characteristic (boolean) or
14
+ binary relational (A rel B). Relational facts are consistently co-referenced, so you
15
+ can traverse them efficiently in any direction. Each constellation maintains constraints
16
+ over the fact population.
17
+ }
18
+ gem.email = "clifford.heath@gmail.com"
19
+ gem.authors = ["Clifford Heath"]
20
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
21
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
22
+ gem.add_development_dependency "rspec", "~> 2.3.0"
23
+ gem.add_development_dependency "bundler", "~> 1.0.0"
24
+ gem.add_development_dependency "jeweler", "~> 1.5.2"
25
+ # gem.add_development_dependency "rcov", ">= 0"
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "activefacts-api #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.8.9
@@ -0,0 +1,44 @@
1
+ #
2
+ # ActiveFacts Runtime API.
3
+ #
4
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
5
+ #
6
+ # The ActiveFacts API is heavily metaprogrammed, so difficult to document.
7
+ #
8
+ # It operates on the principle that a Ruby module is used to encapsulate
9
+ # a Vocabulary (the methods of the class Vocabulary are extend()ed into
10
+ # the module). A Vocabulary contains classes that either derive from a
11
+ # builtin Value type class (see standard_types.rb), or that use the method
12
+ # Class#_identified_by_ to become an Entity (their classes are extend()ed
13
+ # by the class Entity::ClassMethods). Each Value and Entity class also
14
+ # contains the methods of the class ObjectType.
15
+ #
16
+ # A module becomes a Vocabulary when the first ObjectType class is defined within it.
17
+ # A Constellation is a unique collection of ObjectType instances; no two instances may
18
+ # exist of the same value of a ValueType, or having the same identifying roles for
19
+ # an Entity type.
20
+ #
21
+ # Both kinds of ObjectTypes play Roles, which are either binary or unary. Each Role
22
+ # corresponds to an accessor method on Instances which are used to access the
23
+ # counterpart. Roles are created by the class methods *has_one*, *one_to_one*,
24
+ # and *maybe*. The former two create *two* roles, since the role has a counterpart
25
+ # object_type that also plays a role. In the case of a has_one role, the counterpart
26
+ # role is a set, implemented by the RoleValues class, and the accessor method is
27
+ # named beginning with *all_*.
28
+ #
29
+ # The roles of any Instance of any ObjectType may only be played by another Instance
30
+ # of the counterpart ObjectType. There are no raw values, only instances of ValueType
31
+ # classes.
32
+
33
+ require 'activefacts/api/support' # General support code and core patches
34
+ require 'activefacts/api/vocabulary' # A Ruby module may become a Vocabulary
35
+ require 'activefacts/api/role_proxy' # Experimental proxy for has_one/one_to_one role accessors
36
+ require 'activefacts/api/instance_index' # The index used by a constellation to record every instance
37
+ require 'activefacts/api/constellation' # A Constellation is a query result or fact population
38
+ require 'activefacts/api/object_type' # A Ruby class may become a ObjectType in a Vocabulary
39
+ require 'activefacts/api/role' # A ObjectType has a collection of Roles
40
+ require 'activefacts/api/role_values' # The container used for sets of role players in many_one's
41
+ require 'activefacts/api/instance' # An Instance is an instance of a ObjectType class
42
+ require 'activefacts/api/value' # A Value is an Instance of a value class (String, Numeric, etc)
43
+ require 'activefacts/api/entity' # An Entity class is an Instance not of a value class
44
+ require 'activefacts/api/standard_types' # Value classes are augmented so their subclasses may become Value Types
@@ -0,0 +1,128 @@
1
+ #
2
+ # ActiveFacts Runtime API
3
+ # Constellation class
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+
8
+ module ActiveFacts
9
+ module API #:nodoc:
10
+ # A Constellation is a population of instances of the ObjectType classes of a Vocabulary.
11
+ # Every object_type class is either a Value type or an Entity type.
12
+ #
13
+ # Value types are uniquely identified by their value, and a constellation will only
14
+ # ever have a single instance of a given value of that class.
15
+ #
16
+ # Entity instances are uniquely identified by their identifying roles, and again, a
17
+ # constellation will only ever have a single entity instance for the values of those
18
+ # identifying roles.
19
+ #
20
+ # As a result, you cannot "create" an object in a constellation - you merely _assert_
21
+ # its existence. This is done using method_missing; @constellation.Thing(3) creates
22
+ # an instance (or returns the existing instance) of Thing identified by the value 3.
23
+ # You can also use the populate() method to apply a block of assertions.
24
+ #
25
+ # You can instance##retract any instance, and that removes it from the constellation (will
26
+ # delete it from the database when the constellation is saved), and nullifies any
27
+ # references to it.
28
+ #
29
+ # A Constellation may or not be valid according to the vocabulary's constraints,
30
+ # but it may also represent a portion of a larger population (a database) with
31
+ # which it may be merged to form a valid population. In other words, an invalid
32
+ # Constellation may be invalid only because it lacks some of the facts.
33
+ #
34
+ class Constellation
35
+ attr_reader :vocabulary
36
+ # All instances are indexed in this hash, keyed by the class object. Each instance is indexed for every supertype it has (including multiply-inherited ones). It's a bad idea to try to modify these indexes!
37
+ attr_reader :instances # Can say c.instances[MyClass].each{|k, v| ... }
38
+ # Can also say c.MyClass.each{|k, v| ... }
39
+
40
+ # Create a new empty Constellation over the given Vocabulary
41
+ def initialize(vocabulary)
42
+ @vocabulary = vocabulary
43
+ @instances = Hash.new do |h,k|
44
+ raise "A constellation over #{@vocabulary.name} can only index instances of object_types in that vocabulary, not #{k.inspect}" unless k.is_a?(Class) and k.modspace == vocabulary
45
+ h[k] = InstanceIndex.new
46
+ end
47
+ end
48
+
49
+ def inspect #:nodoc:
50
+ "Constellation:#{object_id}"
51
+ end
52
+
53
+ # Evaluate assertions against the population of this Constellation
54
+ def populate *args, &block
55
+ # REVISIT: Use args for something? Like options to enable/disable validation?
56
+ instance_eval(&block)
57
+ end
58
+
59
+ # Delete instances from the constellation, nullifying (or cascading) the roles each plays
60
+ def retract(*instances)
61
+ Array(instances).each do |i|
62
+ i.retract
63
+ end
64
+ end
65
+
66
+ # Constellations verbalise all members of all classes in alphabetical order, showing
67
+ # non-identifying role values as well
68
+ def verbalise
69
+ "Constellation over #{vocabulary.name}:\n" +
70
+ vocabulary.object_type.keys.sort.map{|object_type|
71
+ klass = vocabulary.const_get(object_type)
72
+
73
+ # REVISIT: It would be better not to rely on the role name pattern here:
74
+ single_roles, multiple_roles = klass.roles.keys.sort_by(&:to_s).partition{|r| r.to_s !~ /\Aall_/ }
75
+ single_roles -= klass.identifying_role_names if (klass.is_entity_type)
76
+ # REVISIT: Need to include superclass roles also.
77
+
78
+ instances = send(object_type.to_sym)
79
+ next nil unless instances.size > 0
80
+ "\tEvery #{object_type}:\n" +
81
+ instances.map{|key, instance|
82
+ s = "\t\t" + instance.verbalise
83
+ if (single_roles.size > 0)
84
+ role_values =
85
+ single_roles.map{|role|
86
+ [ role_name = role.to_s.camelcase,
87
+ value = instance.send(role)]
88
+ }.select{|role_name, value|
89
+ value
90
+ }.map{|role_name, value|
91
+ "#{role_name} = #{value ? value.verbalise : "nil"}"
92
+ }
93
+ s += " where " + role_values*", " if role_values.size > 0
94
+ end
95
+ s
96
+ } * "\n"
97
+ }.compact*"\n"
98
+ end
99
+
100
+ # This method removes the given instance from this constellation's indexes
101
+ # It must be called before the identifying roles get deleted or nullified.
102
+ def __retract(instance) #:nodoc:
103
+ # REVISIT: Need to search, as key values are gone already. Is there a faster way?
104
+ ([instance.class]+instance.class.supertypes_transitive).each do |klass|
105
+ @instances[klass].delete_if{|k,v| v == instance }
106
+ end
107
+ # REVISIT: Need to nullify all the roles this object plays.
108
+ # If mandatory on the counterpart side, this may/must propagate the delete (without mutual recursion!)
109
+ end
110
+
111
+ # With parameters, assert an instance of the object_type whose name is the missing method, identified by the values passed as *args*.
112
+ # With no parameters, return the collection of all instances of that object_type.
113
+ def method_missing(m, *args)
114
+ if klass = @vocabulary.const_get(m)
115
+ if args.size == 0
116
+ # Return the collection of all instances of this class in the constellation:
117
+ @instances[klass]
118
+ else
119
+ # Assert a new ground fact (object_type instance) of the specified class, identified by args:
120
+ # REVISIT: create a constructor method here instead?
121
+ instance, key = klass.assert_instance(self, args)
122
+ instance
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,260 @@
1
+ #
2
+ # ActiveFacts Runtime API
3
+ # Entity class (a mixin module for the class Class)
4
+ #
5
+ # Copyright (c) 2009 Clifford Heath. Read the LICENSE file.
6
+ #
7
+ module ActiveFacts
8
+ module API
9
+ # An Entity type is any ObjectType that isn't a value type.
10
+ # All Entity types must have an identifier made up of one or more roles.
11
+ module Entity
12
+ include Instance
13
+
14
+ # Assign the identifying roles to initialise a new Entity instance.
15
+ # The role values are asserted in the constellation first, so you
16
+ # can pass bare values (array, string, integer, etc) for any role
17
+ # whose instances can be constructed using those values.
18
+ #
19
+ # A value must be provided for every identifying role, but if the
20
+ # last argument is a hash, they may come from there.
21
+ #
22
+ # Any additional (non-identifying) roles may also be passed in the final hash.
23
+ def initialize(*args)
24
+ super(args)
25
+ klass = self.class
26
+ hash = {}
27
+ hash = args.pop.clone if Hash === args[-1]
28
+
29
+ # Pick any missing identifying roles out of the hash if possible:
30
+ while args.size < (ir = klass.identifying_role_names).size
31
+ value = hash[role = ir[args.size]]
32
+ hash.delete(role)
33
+ args.push value
34
+ end
35
+
36
+ # If one arg is expected but more are passed, they might be the args for the object that plays the identifying role:
37
+ args = [args] if klass.identifying_role_names.size == 1 && args.size > 1
38
+
39
+ # This should now only occur when there are too many args passed:
40
+ raise "Wrong number of parameters to #{klass}.new, " +
41
+ "expect (#{klass.identifying_role_names*","}) " +
42
+ "got (#{args.map{|a| a.to_s.inspect}*", "})" if args.size != klass.identifying_role_names.size
43
+
44
+ # Assign the identifying roles in order, then the other roles passed as a hash:
45
+ (klass.identifying_role_names.zip(args) + hash.entries).each do |role_name, value|
46
+ role = klass.roles(role_name)
47
+ send("#{role_name}=", value)
48
+ end
49
+ end
50
+
51
+ def inspect #:nodoc:
52
+ "\#<#{
53
+ self.class.basename
54
+ }:#{
55
+ object_id
56
+ }#{
57
+ constellation ? " in #{constellation.inspect}" : ""
58
+ } #{
59
+ # REVISIT: Where there are one-to-one roles, this cycles
60
+ self.class.identifying_role_names.map{|role| "@#{role}="+send(role).inspect }*" "
61
+ }>"
62
+ end
63
+
64
+ # When used as a hash key, the hash key of this entity instance is calculated
65
+ # by hashing the values of its identifying roles
66
+ def hash
67
+ self.class.identifying_role_names.map{|role|
68
+ instance_variable_get("@#{role}")
69
+ }.inject(0) { |h,v|
70
+ h ^= v.hash
71
+ h
72
+ }
73
+ end
74
+
75
+ # When used as a hash key, this entity instance is compared with another by
76
+ # comparing the values of its identifying roles
77
+ def eql?(other)
78
+ return false unless self.class == other.class
79
+ self.class.identifying_role_names.each{|role|
80
+ return false unless send(role).eql?(other.send(role))
81
+ }
82
+ return true
83
+ end
84
+
85
+ # Verbalise this entity instance
86
+ def verbalise(role_name = nil)
87
+ "#{role_name || self.class.basename}(#{
88
+ self.class.identifying_role_names.map{|role_sym|
89
+ value = send(role_sym)
90
+ role_name = self.class.roles(role_sym).name.to_s.camelcase
91
+ value ? value.verbalise(role_name) : "nil"
92
+ }*", "
93
+ })"
94
+ end
95
+
96
+ # Return the array of the values of this entity instance's identifying roles
97
+ def identifying_role_values
98
+ self.class.identifying_role_names.map{|role|
99
+ send(role)
100
+ }
101
+ end
102
+
103
+ # All classes that become Entity types receive the methods of this class as class methods:
104
+ module ClassMethods
105
+ include Instance::ClassMethods
106
+
107
+ # Return the array of Role objects that define the identifying relationships of this Entity type:
108
+ def identifying_role_names
109
+ @identifying_role_names ||= []
110
+ end
111
+
112
+ def identifying_roles
113
+ debug :persistence, "Identifying roles for #{basename}" do
114
+ @identifying_role_names.map{|name|
115
+ role = roles[name] || (!superclass.is_entity_type || superclass.roles[name])
116
+ debug :persistence, "#{name} -> #{role ? "found" : "NOT FOUND"}"
117
+ role
118
+ }
119
+ end
120
+ end
121
+
122
+ # Convert the passed arguments into an array of Instance objects that can identify an instance of this Entity type:
123
+ def identifying_role_values(*args)
124
+ #puts "Getting identifying role values #{identifying_role_names.inspect} of #{basename} using #{args.inspect}"
125
+
126
+ # If the single arg is an instance of the correct class or a subclass,
127
+ # use the instance's identifying_role_values
128
+ if (args.size == 1 and
129
+ (arg = args[0]).is_a?(self)) # REVISIT: or a secondary supertype
130
+ arg = arg.__getobj__ if RoleProxy === arg
131
+ return arg.identifying_role_values
132
+ end
133
+
134
+ ir = identifying_role_names
135
+ args, arg_hash = ActiveFacts::extract_hash_args(ir, args)
136
+
137
+ if args.size > ir.size
138
+ raise "You've provided too many values for the identifier of #{basename}, which expects (#{ir*', '})"
139
+ end
140
+
141
+ role_args = ir.map{|role_sym| roles(role_sym)}.zip(args)
142
+ role_args.map do |role, arg|
143
+ #puts "Getting identifying_role_value for #{role.counterpart_object_type.basename} using #{arg.inspect}"
144
+ next !!arg unless role.counterpart # Unary
145
+ arg = arg.__getobj__ if RoleProxy === arg
146
+ if arg.is_a?(role.counterpart_object_type) # REVISIT: or a secondary supertype
147
+ # Note that with a secondary supertype, it must still return the values of these identifying_role_names
148
+ next arg.identifying_role_values
149
+ end
150
+ if arg == nil # But not false
151
+ if role.mandatory
152
+ raise "You must provide a #{role.counterpart_object_type.name} to identify a #{basename}"
153
+ end
154
+ else
155
+ role.counterpart_object_type.identifying_role_values(*arg)
156
+ end
157
+ end
158
+ end
159
+
160
+ def assert_instance(constellation, args) #:nodoc:
161
+ # Build the key for this instance from the args
162
+ # The key of an instance is the value or array of keys of the identifying values.
163
+ # The key values aren't necessarily present in the constellation, even after this.
164
+ key = identifying_role_values(*args)
165
+
166
+ # Find and return an existing instance matching this key
167
+ instances = constellation.instances[self] # All instances of this class in this constellation
168
+ instance = instances[key]
169
+ # DEBUG: puts "assert #{self.basename} #{key.inspect} #{instance ? "exists" : "new"}"
170
+ return instance, key if instance # A matching instance of this class
171
+
172
+ # Now construct each of this object's identifying roles
173
+ ir = identifying_role_names
174
+ args, arg_hash = ActiveFacts::extract_hash_args(ir, args)
175
+ role_values = ir.map{|role_sym| roles(role_sym)}.zip(args)
176
+ key = [] # Gather the actual key (AutoCounters are special)
177
+ values = role_values.map do |role, arg|
178
+ if !arg
179
+ value = role_key = nil # No value
180
+ elsif !role.counterpart
181
+ value = role_key = !!arg # Unary
182
+ elsif arg.is_a?(role.counterpart_object_type) # REVISIT: or a secondary supertype
183
+ arg = arg.__getobj__ if RoleProxy === arg
184
+ raise "Connecting values across constellations" unless arg.constellation == constellation
185
+ value, role_key = arg, arg.identifying_role_values
186
+ else
187
+ value, role_key = role.counterpart_object_type.assert_instance(constellation, Array(arg))
188
+ end
189
+ key << role_key
190
+ value
191
+ end
192
+ values << arg_hash if arg_hash and !arg_hash.empty?
193
+
194
+ #puts "Creating new #{basename} using #{values.inspect}"
195
+ instance = new(*values)
196
+
197
+ # Make the new entity instance a member of this constellation:
198
+ instance.constellation = constellation
199
+ return *index_instance(instance, key, ir)
200
+ end
201
+
202
+ def index_instance(instance, key = nil, key_roles = nil) #:nodoc:
203
+ # Derive a new key if we didn't receive one or if the roles are different:
204
+ unless key && key_roles && key_roles == identifying_role_names
205
+ key = (key_roles = identifying_role_names).map do |role_name|
206
+ instance.send role_name
207
+ end
208
+ end
209
+
210
+ # Index the instance for this class in the constellation
211
+ instances = instance.constellation.instances[self]
212
+ instances[key] = instance
213
+ # DEBUG: puts "indexing entity #{basename} using #{key.inspect} in #{constellation.object_id}"
214
+
215
+ # Index the instance for each supertype:
216
+ supertypes.each do |supertype|
217
+ supertype.index_instance(instance, key, key_roles)
218
+ end
219
+
220
+ return instance, key
221
+ end
222
+
223
+ # A object_type that isn't a ValueType must have an identification scheme,
224
+ # which is a list of roles it plays. The identification scheme may be
225
+ # inherited from a superclass.
226
+ def initialise_entity_type(*args) #:nodoc:
227
+ #puts "Initialising entity type #{self} using #{args.inspect}"
228
+ @identifying_role_names = superclass.identifying_role_names if superclass.is_entity_type
229
+ # REVISIT: @identifying_role_names here are the symbols passed in, not the Role objects we should use.
230
+ # We'd need late binding to use Role objects...
231
+ @identifying_role_names = args if args.size > 0 || !@identifying_role_names
232
+ end
233
+
234
+ def inherited(other) #:nodoc:
235
+ other.identified_by *identifying_role_names
236
+ subtypes << other unless subtypes.include? other
237
+ #puts "#{self.name} inherited by #{other.name}"
238
+ vocabulary.__add_object_type(other)
239
+ end
240
+
241
+ # verbalise this object_type
242
+ def verbalise
243
+ "#{basename} is identified by #{identifying_role_names.map{|role_sym| role_sym.to_s.camelcase}*" and "};"
244
+ end
245
+ end
246
+
247
+ def Entity.included other #:nodoc:
248
+ other.send :extend, ClassMethods
249
+
250
+ # Register ourselves with the parent module, which has become a Vocabulary:
251
+ vocabulary = other.modspace
252
+ # puts "Entity.included(#{other.inspect})"
253
+ unless vocabulary.respond_to? :object_type # Extend module with Vocabulary if necessary
254
+ vocabulary.send :extend, Vocabulary
255
+ end
256
+ vocabulary.__add_object_type(other)
257
+ end
258
+ end
259
+ end
260
+ end