activefacts-api 0.9.9 → 1.0.0

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.
@@ -22,14 +22,15 @@ module ActiveFacts
22
22
  attr_reader :value_constraint # Counterpart Instances playing this role must meet this constraint
23
23
  attr_reader :is_identifying # Is this an identifying role for object_type?
24
24
 
25
- def initialize(fact_type, object_type, name, mandatory, unique)
25
+ def initialize(fact_type, object_type, role_name, mandatory, unique)
26
26
  @fact_type = fact_type
27
27
  @fact_type.all_role << self
28
28
  @object_type = object_type
29
- @name = name
29
+ @name = role_name
30
30
  @mandatory = mandatory
31
31
  @unique = unique
32
32
  @is_identifying = @object_type.is_entity_type && @object_type.identifying_role_names.include?(@name)
33
+ object_type.add_role(self)
33
34
  associate_role(@object_type)
34
35
  end
35
36
 
@@ -87,7 +88,6 @@ module ActiveFacts
87
88
  end
88
89
 
89
90
  # Every ObjectType has a Role collection
90
- # REVISIT: You can enumerate the object_type's own roles, or inherited roles as well.
91
91
  class RoleCollection < Hash #:nodoc:
92
92
  def verbalise
93
93
  keys.sort_by(&:to_s).inspect
@@ -8,11 +8,15 @@ module ActiveFacts
8
8
  module API
9
9
 
10
10
  class RoleValues #:nodoc:
11
+ attr_accessor :object_type
11
12
  attr_accessor :sort
13
+ attr_accessor :index_roles
12
14
 
13
- def initialize sort = false
14
- @sort = !!(sort || ENV[@@af_sort_name ||= "ACTIVEFACTS_SORT"])
15
+ def initialize object_type, index_roles = nil, sort = nil
16
+ @object_type = object_type
17
+ @sort = sort == nil ? API::sorted : !!sort
15
18
  @a = @sort ? RBTree.new : []
19
+ @index_roles = index_roles
16
20
  end
17
21
 
18
22
  def +(a)
@@ -23,6 +27,17 @@ module ActiveFacts
23
27
  end
24
28
  end
25
29
 
30
+ def [](*a)
31
+ if @sort
32
+ # REVISIT: Consider whether to return an array when a partial key is provided.
33
+ key = form_key(Array(a))
34
+ @a[key]
35
+ else
36
+ # Slow: Search the array for an element having the matching key:
37
+ @a.detect{|e| index_values(e) == a}
38
+ end
39
+ end
40
+
26
41
  def to_a
27
42
  @sort ? @a.values : @a
28
43
  end
@@ -36,12 +51,24 @@ module ActiveFacts
36
51
  end
37
52
 
38
53
  def form_key a
39
- KeyArray.new(Array(a))
54
+ a = Array(a)
55
+ if @index_roles && @index_roles.size != a.size
56
+ raise "Incorrectly-sized key #{a.inspect}. Index roles are #{@index_roles.map(&:name).inspect}"
57
+ end
58
+ KeyArray.new(a)
59
+ end
60
+
61
+ def index_values object
62
+ if @index_roles
63
+ @index_roles.map{|r| object.send(r.name).identifying_role_values}
64
+ else
65
+ object.identifying_role_values
66
+ end
40
67
  end
41
68
 
42
69
  def add_instance(value, key)
43
70
  if @sort
44
- @a[form_key(key)] = value
71
+ @a[form_key(index_values(value))] = value
45
72
  else
46
73
  @a << value
47
74
  end
@@ -51,17 +78,8 @@ module ActiveFacts
51
78
  if @sort
52
79
  deleted = @a.delete(form_key(key))
53
80
  else
54
- deleted = @a.delete(value)
81
+ deleted = @a.delete(value) # Slow: it has to search the array
55
82
  end
56
-
57
- # Test code:
58
- unless deleted
59
- p @a
60
- p value
61
- debugger
62
- true
63
- end
64
-
65
83
  end
66
84
 
67
85
  def verbalise
@@ -37,7 +37,6 @@ module ActiveFacts
37
37
  include Instance::ClassMethods
38
38
 
39
39
  def value_type *args, &block #:nodoc:
40
- # REVISIT: args could be a hash, with keys :length, :scale, :unit, :allow
41
40
  options = (args[-1].is_a?(Hash) ? args.pop : {})
42
41
  options.each do |key, value|
43
42
  raise UnrecognisedOptionsException.new('ValueType', basename, key) unless respond_to?(key)
@@ -67,9 +66,7 @@ module ActiveFacts
67
66
 
68
67
  # verbalise this ValueType
69
68
  def verbalise
70
- # REVISIT: Add length and scale here, if set
71
- # REVISIT: Set vocabulary name of superclass if not same as this
72
- "#{basename} = #{superclass.basename}();"
69
+ "#{basename} < #{superclass.basename}();"
73
70
  end
74
71
 
75
72
  def identifying_role_values(constellation, args) #:nodoc:
@@ -129,6 +126,7 @@ module ActiveFacts
129
126
  def inherited(other) #:nodoc:
130
127
  # Copy the type parameters here, etc?
131
128
  other.send :realise_supertypes, self
129
+ TypeInheritanceFactType.new(self, other)
132
130
  vocabulary.__add_object_type(other)
133
131
  super
134
132
  end
@@ -58,7 +58,7 @@ module ActiveFacts
58
58
  @object_type.keys.sort.map do |object_type|
59
59
  c = @object_type[object_type]
60
60
  __bind(c.basename)
61
- c.verbalise + "\n\t\t// Roles played: " + c.roles.verbalise
61
+ c.verbalise + "\n\t\t// Roles played: " + c.all_role.verbalise
62
62
  end.
63
63
  join("\n\t")
64
64
  end
@@ -465,7 +465,7 @@ describe "A Constellation instance" do
465
465
  end
466
466
  end
467
467
  c = @constellation.ListedCompany("foo", :auto_counter_val => 23)
468
- }.should_not raise_error(NameError)
468
+ }.should_not raise_error
469
469
  end
470
470
 
471
471
  it "should be able to attach a new supertype on an entity type to make it a (sub-)subtype" do
@@ -21,11 +21,12 @@ describe ActiveFacts::API::InstanceIndex do
21
21
  end
22
22
 
23
23
  class EntityB < EntityA
24
- identified_by :value_b
25
- one_to_one :value_b
24
+ identified_by :value_c
25
+ one_to_one :value_c, :class => ValueB
26
26
  end
27
27
 
28
28
  class EntityD < EntityA
29
+ has_one :value_d, :class => ValueB
29
30
  end
30
31
 
31
32
  class EntityC < EntityB
@@ -35,8 +36,8 @@ describe ActiveFacts::API::InstanceIndex do
35
36
 
36
37
  @constellation = ActiveFacts::API::Constellation.new(Mod)
37
38
  @a = @constellation.EntityA(:value_a => 1, :value_b => 'a')
38
- @b = @constellation.EntityB(:value_a => 12, :value_b => 'ab')
39
- @c = @constellation.EntityC(:value_a => 123, :value_b => 'abc')
39
+ @b = @constellation.EntityB(:value_a => 12, :value_b => 'ab', :value_c => 'abc')
40
+ @c = @constellation.EntityC(:value_a => 123, :value_b => 'abc1', :value_c => 'abc2', :value_d => 'abcd')
40
41
  end
41
42
 
42
43
  it "should index an instance under its own class" do
@@ -55,12 +56,12 @@ describe ActiveFacts::API::InstanceIndex do
55
56
  end
56
57
 
57
58
  it "should recursively try to use identifying role values within an array" do
58
- value_b = @constellation.ValueB('abc')
59
+ value_b = @constellation.ValueB('abc2')
59
60
  @constellation.EntityC[[value_b]].should == @c
60
61
  end
61
62
 
62
63
  it "should use the value as-is if it doesn't have identifying role values" do
63
- @constellation.EntityC[%w{abc}].should == @c
64
+ @constellation.EntityC[%w{abc2}].should == @c
64
65
  end
65
66
  end
66
67
 
@@ -79,7 +80,7 @@ describe ActiveFacts::API::InstanceIndex do
79
80
  b_index = @constellation.EntityB
80
81
  b_index.size.should == 2
81
82
  b_index.send(api) do |k, v, *a|
82
- [['ab'], ['abc']].should include(k)
83
+ [['ab'], ['abc'], ['abc2']].should include(k)
83
84
  [@b, @c].should include v
84
85
  a.size.should == 0
85
86
  false
@@ -3,12 +3,6 @@
3
3
  # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
4
  #
5
5
 
6
- #require 'ruby-debug'; Debugger.start
7
- #trap "INT" do
8
- # puts caller*"\n\t"
9
- # debugger
10
- #end
11
-
12
6
  describe "An instance of every type of ObjectType" do
13
7
  before :all do
14
8
  Object.send :remove_const, :Mod if Object.const_defined?("Mod")
@@ -302,7 +296,7 @@ describe "An instance of every type of ObjectType" do
302
296
  # All identifying roles should be in the verbalisation.
303
297
  # Strictly this should be the role name, but we don't set names here.
304
298
  entity_type.identifying_role_names.each do |ir|
305
- role = entity_type.roles(ir)
299
+ role = entity_type.all_role(ir)
306
300
  role.should_not be_nil
307
301
  counterpart_object_type = role.counterpart.object_type
308
302
  verbalisation.should =~ %r{\b#{counterpart_object_type.basename}\b}
@@ -336,7 +330,7 @@ describe "An instance of every type of ObjectType" do
336
330
  verbalisation = entity.verbalise
337
331
  verbalisation.should =~ %r{\b#{entity.class.basename}\b}
338
332
  entity.class.identifying_role_names.each do |ir|
339
- role = entity.class.roles(ir)
333
+ role = entity.class.all_role(ir)
340
334
  role.should_not be_nil
341
335
  counterpart_object_type = role.counterpart.object_type
342
336
  verbalisation.should =~ %r{\b#{counterpart_object_type.basename}\b}
@@ -82,7 +82,7 @@ end
82
82
  describe "Roles of an Object Type" do
83
83
 
84
84
  it "should return a roles collection" do
85
- roles = TestValueTypesModule::Octopus.roles
85
+ roles = TestValueTypesModule::Octopus.all_role
86
86
  roles.should_not be_nil
87
87
  roles.size.should == 2+VALUE_TYPES.size*5*2
88
88
 
@@ -215,7 +215,7 @@ describe "Object type role values" do
215
215
  octopus = @constellation.Octopus(0)
216
216
  octopus_role_name = :"octopus_as_one_#{object_type_name.snakecase}"
217
217
  object.send(:"#{octopus_role_name}=", octopus)
218
- counterpart_name = object.class.roles[octopus_role_name].counterpart.name
218
+ counterpart_name = object.class.all_role[octopus_role_name].counterpart.name
219
219
 
220
220
  # Create a reference by assigning the object from a RoleProxy:
221
221
  proxy = octopus.send(counterpart_name)
@@ -239,7 +239,7 @@ describe "Object type role values" do
239
239
  before :each do
240
240
  @constellation = ActiveFacts::API::Constellation.new(TestValueTypesModule)
241
241
  @object = @constellation.Octopus(0)
242
- @roles = @object.class.roles
242
+ @roles = @object.class.all_role
243
243
  end
244
244
 
245
245
  it "should return its constellation and vocabulary" do
@@ -249,7 +249,7 @@ describe "Object type role values" do
249
249
  @object.class.vocabulary.should == TestValueTypesModule
250
250
  end
251
251
 
252
- TestValueTypesModule::Octopus.roles.each do |role_name, role|
252
+ TestValueTypesModule::Octopus.all_role.each do |role_name, role|
253
253
  next if role_name == :zero
254
254
 
255
255
  it "should respond to getting its #{role_name} role" do
@@ -48,7 +48,7 @@ describe "Roles" do
48
48
  has_one :name
49
49
  end
50
50
  end
51
- role = Mod::Existing1.roles(:name)
51
+ role = Mod::Existing1.all_role(:name)
52
52
  role.should_not be_nil
53
53
  role.inspect.class.should == String
54
54
  role.counterpart.object_type.should == Mod::Name
@@ -138,10 +138,10 @@ describe "Roles" do
138
138
  end
139
139
  end
140
140
  # REVISIT: need to make more tests for the class's role accessor methods:
141
- Mod::Name.roles(:all_existing1).should == Mod::Name.all_existing1_role
141
+ Mod::Name.all_role(:all_existing1).should == Mod::Name.all_existing1_role
142
142
 
143
- Mod::Name.roles(:all_existing1).should_not be_nil
144
- Mod::LegalEntity.roles(:all_contract_as_first).should_not be_nil
143
+ Mod::Name.all_role(:all_existing1).should_not be_nil
144
+ Mod::LegalEntity.all_role(:all_contract_as_first).should_not be_nil
145
145
  end
146
146
 
147
147
  it "should associate a role name with a matching object_type after it's created" do
@@ -151,8 +151,8 @@ describe "Roles" do
151
151
  has_one :given_name
152
152
  end
153
153
  end
154
- # print "Mod::Existing2.roles = "; p Mod::Existing2.roles
155
- r = Mod::Existing2.roles(:given_name)
154
+ # print "Mod::Existing2.all_role = "; p Mod::Existing2.all_role
155
+ r = Mod::Existing2.all_role(:given_name)
156
156
  r.should_not be_nil
157
157
  r.counterpart.should be_nil
158
158
  module Mod
@@ -161,7 +161,7 @@ describe "Roles" do
161
161
  end
162
162
  end
163
163
  # puts "Should resolve now:"
164
- r = Mod::Existing2.roles(:given_name)
164
+ r = Mod::Existing2.all_role(:given_name)
165
165
  r.should_not be_nil
166
166
  r.counterpart.object_type.should == Mod::GivenName
167
167
  end
@@ -173,10 +173,10 @@ describe "Roles" do
173
173
  one_to_one :patriarch, :class => Person
174
174
  end
175
175
  end
176
- r = Mod::FamilyName.roles(:patriarch)
176
+ r = Mod::FamilyName.all_role(:patriarch)
177
177
  r.should_not be_nil
178
178
  r.counterpart.object_type.should == Mod::Person
179
- r.counterpart.object_type.roles(:family_name_as_patriarch).counterpart.object_type.should == Mod::FamilyName
179
+ r.counterpart.object_type.all_role(:family_name_as_patriarch).counterpart.object_type.should == Mod::FamilyName
180
180
  end
181
181
 
182
182
  it "should instantiate the matching object_type on assignment" do
@@ -241,4 +241,43 @@ describe "Roles" do
241
241
  identifier.employee.name.should == "PuppetMaster"
242
242
  end
243
243
 
244
+ it "should create TypeInheritance fact type and roles" do
245
+ module Mod
246
+ class GivenName < Name
247
+ end
248
+ class Document
249
+ identified_by :name
250
+ one_to_one :name
251
+ end
252
+ class Contract
253
+ supertypes Document
254
+ end
255
+ end
256
+ [Mod::GivenName, Mod::Person, Mod::Contract].each do |subtype|
257
+ subtype.supertypes.each do |supertype|
258
+ # Get the role names:
259
+ supertype_role_name = supertype.name.gsub(/.*::/,'').to_sym
260
+ subtype_role_name = subtype.name.gsub(/.*::/,'').to_sym
261
+
262
+ # Check that the roles are indexed:
263
+ subtype.all_role.should include(supertype_role_name)
264
+ supertype.all_role.should include(subtype_role_name)
265
+
266
+ # Get the role objects:
267
+ supertype_role = subtype.all_role[supertype_role_name]
268
+ subtype_role = supertype.all_role[subtype_role_name]
269
+
270
+ # Check uniqueness and mandatory:
271
+ supertype_role.unique.should be_true
272
+ subtype_role.unique.should be_true
273
+ supertype_role.mandatory.should be_true
274
+ subtype_role.mandatory.should be_false
275
+
276
+ # Check they belong to the same TypeInheritanceFactType:
277
+ subtype_role.fact_type.class.should be(ActiveFacts::API::TypeInheritanceFactType)
278
+ subtype_role.fact_type.should == supertype_role.fact_type
279
+ end
280
+ end
281
+ end
282
+
244
283
  end
@@ -5,7 +5,7 @@
5
5
 
6
6
  describe "An Entity Type" do
7
7
  before :all do
8
- module Mod
8
+ module ModIS
9
9
  class Name < String
10
10
  value_type
11
11
  end
@@ -16,12 +16,12 @@ describe "An Entity Type" do
16
16
  end
17
17
  end
18
18
  before :each do
19
- @c = ActiveFacts::API::Constellation.new(Mod)
19
+ @c = ActiveFacts::API::Constellation.new(ModIS)
20
20
  end
21
21
 
22
22
  describe "whose instances are identified by a single value role" do
23
23
  before :all do
24
- module Mod
24
+ module ModIS
25
25
  class Business
26
26
  identified_by :name
27
27
  one_to_one :name
@@ -31,7 +31,7 @@ describe "An Entity Type" do
31
31
 
32
32
  it "should fail if the role isn't one-to-one" do
33
33
  proc do
34
- module Mod
34
+ module ModIS
35
35
  class Cat
36
36
  identified_by :name
37
37
  has_one :name
@@ -48,11 +48,11 @@ describe "An Entity Type" do
48
48
  end
49
49
 
50
50
  it "should return a new instance if not previously present" do
51
- @bus.should be_a(Mod::Business)
51
+ @bus.should be_a(ModIS::Business)
52
52
  end
53
53
 
54
54
  it "should assert the identifying value" do
55
- @acme.should be_a(Mod::Name)
55
+ @acme.should be_a(ModIS::Name)
56
56
  end
57
57
 
58
58
  it "should be found in the constellation using the value" do
@@ -97,7 +97,7 @@ describe "An Entity Type" do
97
97
  end
98
98
 
99
99
  it "should assert the new identifier" do
100
- @c.Name['Bloggs'].should be_a(Mod::Name)
100
+ @c.Name['Bloggs'].should be_a(ModIS::Name)
101
101
  end
102
102
 
103
103
  it "should allow the change" do
@@ -168,7 +168,7 @@ describe "An Entity Type" do
168
168
 
169
169
  describe "and the identifying value plays other roles" do
170
170
  before :all do
171
- module Mod
171
+ module ModIS
172
172
  class Dog
173
173
  identified_by :name
174
174
  one_to_one :name
@@ -191,7 +191,7 @@ describe "An Entity Type" do
191
191
 
192
192
  describe "identified by two values" do
193
193
  before :all do
194
- module Mod
194
+ module ModIS
195
195
  class Building
196
196
  identified_by :name
197
197
  one_to_one :name
@@ -222,12 +222,12 @@ describe "An Entity Type" do
222
222
  end
223
223
 
224
224
  before :each do
225
- @c = ActiveFacts::API::Constellation.new(Mod)
225
+ @c = ActiveFacts::API::Constellation.new(ModIS)
226
226
  end
227
227
 
228
228
  it "should fail if any role is one-to-one" do
229
229
  proc do
230
- module Mod
230
+ module ModIS
231
231
  class Floor
232
232
  identified_by :building, :number
233
233
  has_one :building
@@ -249,11 +249,11 @@ describe "An Entity Type" do
249
249
  end
250
250
 
251
251
  it "should return a new instance if not previously present" do
252
- @r.should be_a(Mod::Room)
252
+ @r.should be_a(ModIS::Room)
253
253
  end
254
254
 
255
255
  it "should assert the identifying values" do
256
- @rn.should be_a(Mod::Number)
256
+ @rn.should be_a(ModIS::Number)
257
257
  @c.Number[@rn.identifying_role_values].should == @rn # Yes
258
258
  @c.Number[101].should == @rn # No
259
259
  @c.Number[101].should be_eql 101 # No
@@ -318,6 +318,9 @@ describe "An Entity Type" do
318
318
  end
319
319
 
320
320
  it "should be found under the new identifier even on deep associations" do
321
+ # p @c.OwnerRoom.keys[0]
322
+ # p @new_number
323
+ # p [@o.identifying_role_values, @r.identifying_role_values]
321
324
  @c.OwnerRoom[[@o.identifying_role_values, @r.identifying_role_values]].should == @or
322
325
  @c.OwnerRoom[[[1_001, ['Mackay']], [['Mackay'], 103]]].should == @or
323
326
  @c.OwnerRoom[[[1_001, ['Mackay']], [['Mackay'], 101]]].should be_nil
@@ -378,7 +381,7 @@ describe "An Entity Type" do
378
381
 
379
382
  describe "which has a supertype that has separate identification" do
380
383
  before :each do
381
- module Mod
384
+ module ModIS
382
385
  class Animal
383
386
  identified_by :number
384
387
  one_to_one :neumber