activefacts-api 0.9.9 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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