activefacts 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. data/History.txt +4 -0
  2. data/Manifest.txt +83 -0
  3. data/README.rdoc +81 -0
  4. data/Rakefile +41 -0
  5. data/bin/afgen +46 -0
  6. data/bin/cql +52 -0
  7. data/examples/CQL/Address.cql +46 -0
  8. data/examples/CQL/Blog.cql +54 -0
  9. data/examples/CQL/CompanyDirectorEmployee.cql +51 -0
  10. data/examples/CQL/Death.cql +16 -0
  11. data/examples/CQL/Genealogy.cql +95 -0
  12. data/examples/CQL/Marriage.cql +18 -0
  13. data/examples/CQL/Metamodel.cql +238 -0
  14. data/examples/CQL/MultiInheritance.cql +19 -0
  15. data/examples/CQL/OilSupply.cql +47 -0
  16. data/examples/CQL/Orienteering.cql +108 -0
  17. data/examples/CQL/PersonPlaysGame.cql +17 -0
  18. data/examples/CQL/SchoolActivities.cql +31 -0
  19. data/examples/CQL/SimplestUnary.cql +12 -0
  20. data/examples/CQL/SubtypePI.cql +32 -0
  21. data/examples/CQL/Warehousing.cql +99 -0
  22. data/examples/CQL/WindowInRoomInBldg.cql +22 -0
  23. data/lib/activefacts.rb +10 -0
  24. data/lib/activefacts/api.rb +25 -0
  25. data/lib/activefacts/api/concept.rb +384 -0
  26. data/lib/activefacts/api/constellation.rb +106 -0
  27. data/lib/activefacts/api/entity.rb +239 -0
  28. data/lib/activefacts/api/instance.rb +54 -0
  29. data/lib/activefacts/api/numeric.rb +158 -0
  30. data/lib/activefacts/api/role.rb +94 -0
  31. data/lib/activefacts/api/standard_types.rb +67 -0
  32. data/lib/activefacts/api/support.rb +59 -0
  33. data/lib/activefacts/api/value.rb +122 -0
  34. data/lib/activefacts/api/vocabulary.rb +120 -0
  35. data/lib/activefacts/cql.rb +31 -0
  36. data/lib/activefacts/cql/CQLParser.treetop +104 -0
  37. data/lib/activefacts/cql/Concepts.treetop +112 -0
  38. data/lib/activefacts/cql/DataTypes.treetop +66 -0
  39. data/lib/activefacts/cql/Expressions.treetop +113 -0
  40. data/lib/activefacts/cql/FactTypes.treetop +185 -0
  41. data/lib/activefacts/cql/Language/English.treetop +92 -0
  42. data/lib/activefacts/cql/LexicalRules.treetop +169 -0
  43. data/lib/activefacts/cql/Rakefile +6 -0
  44. data/lib/activefacts/cql/parser.rb +88 -0
  45. data/lib/activefacts/generate/absorption.rb +87 -0
  46. data/lib/activefacts/generate/cql.rb +441 -0
  47. data/lib/activefacts/generate/cql/html.rb +397 -0
  48. data/lib/activefacts/generate/null.rb +19 -0
  49. data/lib/activefacts/generate/ordered.rb +557 -0
  50. data/lib/activefacts/generate/ruby.rb +326 -0
  51. data/lib/activefacts/generate/sql/server.rb +164 -0
  52. data/lib/activefacts/generate/text.rb +21 -0
  53. data/lib/activefacts/input/cql.rb +1268 -0
  54. data/lib/activefacts/input/orm.rb +926 -0
  55. data/lib/activefacts/persistence.rb +1 -0
  56. data/lib/activefacts/persistence/composition.rb +653 -0
  57. data/lib/activefacts/support.rb +51 -0
  58. data/lib/activefacts/version.rb +3 -0
  59. data/lib/activefacts/vocabulary.rb +6 -0
  60. data/lib/activefacts/vocabulary/extensions.rb +343 -0
  61. data/lib/activefacts/vocabulary/metamodel.rb +303 -0
  62. data/script/txt2html +71 -0
  63. data/spec/absorption_spec.rb +95 -0
  64. data/spec/api/autocounter.rb +82 -0
  65. data/spec/api/constellation.rb +130 -0
  66. data/spec/api/entity_type.rb +101 -0
  67. data/spec/api/instance.rb +428 -0
  68. data/spec/api/roles.rb +122 -0
  69. data/spec/api/value_type.rb +112 -0
  70. data/spec/api_spec.rb +14 -0
  71. data/spec/cql_cql_spec.rb +58 -0
  72. data/spec/cql_parse_spec.rb +31 -0
  73. data/spec/cql_ruby_spec.rb +60 -0
  74. data/spec/cql_sql_spec.rb +54 -0
  75. data/spec/cql_symbol_tables_spec.rb +259 -0
  76. data/spec/cql_unit_spec.rb +336 -0
  77. data/spec/cqldump_spec.rb +169 -0
  78. data/spec/norma_cql_spec.rb +48 -0
  79. data/spec/norma_ruby_spec.rb +50 -0
  80. data/spec/norma_sql_spec.rb +45 -0
  81. data/spec/norma_tables_spec.rb +94 -0
  82. data/spec/spec.opts +1 -0
  83. data/spec/spec_helper.rb +10 -0
  84. metadata +173 -0
@@ -0,0 +1,106 @@
1
+ #
2
+ # The ActiveFacts Runtime API Constellation class
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+
6
+ module ActiveFacts
7
+ module API
8
+ # A Constellation is a population of instances of the Concept classes of a Vocabulary.
9
+ # Every concept class is either a Value type or an Entity type.
10
+ #
11
+ # Value types are uniquely identified by their value, and a constellation will only
12
+ # ever have a single instance of a given value of that class.
13
+ #
14
+ # Entity instances are uniquely identified by their identifying roles, and again, a
15
+ # constellation will only ever have a single entity instance for the values of those
16
+ # identifying roles.
17
+ #
18
+ # As a result, you cannot "create" an object in a constellation - you merely _assert_
19
+ # its existence. This is done using method_missing; @constellation.Thing(3) creates
20
+ # an instance (or returns the existing instance) of Thing identified by the value 3.
21
+ #
22
+ # You can ##delete any instance, and that removes it from the constellation (will delete
23
+ # it from the database when the constellation is saved), and nullifies any references
24
+ # to it.
25
+ #
26
+ # A Constellation may or not be valid according to the vocabulary's constraints,
27
+ # but it may also represent a portion of a larger population (a database) with
28
+ # which it may be merged to form a valid population. In other words, an invalid
29
+ # Constellation may be invalid only because it lacks some of the facts.
30
+ #
31
+ class Constellation
32
+ attr_reader :vocabulary
33
+ # 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!
34
+ attr_reader :instances # Can say c.instances[MyClass].each{|k, v| ... }
35
+ # Can also say c.MyClass.each{|k, v| ... }
36
+
37
+ # Create a new empty Constellation over the given Vocabulary
38
+ def initialize(vocabulary)
39
+ @vocabulary = vocabulary
40
+ @instances = Hash.new{|h,k| h[k] = {} }
41
+ end
42
+
43
+ def inspect #:nodoc:
44
+ "Constellation:#{object_id}"
45
+ end
46
+
47
+ # This method removes the given instance from this constellation's indexes
48
+ def delete(instance) #:nodoc:
49
+ # REVISIT: Need to search, as key values are gone already. Is there a faster way?
50
+ ([instance.class]+instance.class.supertypes_transitive).each do |klass|
51
+ @instances[klass].delete_if{|k,v| v == instance }
52
+ end
53
+ end
54
+
55
+ # With parameters, assert an instance of the concept whose name is the missing method.
56
+ # With no parameters, return the collection of all instances of that concept.
57
+ def method_missing(m, *args)
58
+ if klass = @vocabulary.const_get(m)
59
+ if args.size == 0
60
+ # Return the collection of all instances of this class in the constellation:
61
+ @instances[klass]
62
+ else
63
+ # Assert a new ground fact (concept instance) of the specified class, identified by args:
64
+ # REVISIT: create a constructor method here instead?
65
+ instance, key = klass.assert_instance(self, args)
66
+ instance
67
+ end
68
+ end
69
+ end
70
+
71
+ # Constellations verbalise all members of all classes in alphabetical order, showing
72
+ # non-identifying role values as well
73
+ def verbalise
74
+ "Constellation over #{vocabulary.name}:\n" +
75
+ vocabulary.concept.keys.sort.map{|concept|
76
+ klass = vocabulary.const_get(concept)
77
+
78
+ # REVISIT: It would be better not to rely on the role name pattern here:
79
+ single_roles, multiple_roles = klass.roles.keys.sort_by(&:to_s).partition{|r| r.to_s !~ /\Aall_/ }
80
+ single_roles -= klass.identifying_roles if (klass.respond_to?(:identifying_roles))
81
+ # REVISIT: Need to include superclass roles also.
82
+
83
+ instances = send(concept.to_sym)
84
+ next nil unless instances.size > 0
85
+ "\tEvery #{concept}:\n" +
86
+ instances.map{|key, instance|
87
+ s = "\t\t" + instance.verbalise
88
+ if (single_roles.size > 0)
89
+ role_values =
90
+ single_roles.map{|role|
91
+ [ role_name = role.to_s.camelcase(true),
92
+ value = instance.send(role)]
93
+ }.select{|role_name, value|
94
+ value
95
+ }.map{|role_name, value|
96
+ "#{role_name} = #{value ? value.verbalise : "nil"}"
97
+ }
98
+ s += " where " + role_values*", " if role_values.size > 0
99
+ end
100
+ s
101
+ } * "\n"
102
+ }.compact*"\n"
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,239 @@
1
+ #
2
+ # The ActiveFacts Runtime API Entity class
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ # An Entity type is any Concept that isn't a value type.
6
+ # All Entity types must have an identifier made up of one or more roles.
7
+ #
8
+ module ActiveFacts
9
+ module API
10
+ module Entity
11
+ include Instance
12
+
13
+ # Assign the identifying roles to initialise a new Entity instance.
14
+ # The role values are asserted in the constellation first, so you
15
+ # can pass bare values (array, string, integer, etc) for any role
16
+ # whose instances can be constructed using those values.
17
+ #
18
+ # A value must be provided for every identifying role, but if the
19
+ # last argument is a hash, they may come from there.
20
+ #
21
+ # Any additional (non-identifying) roles may also be passed in the final hash.
22
+ def initialize(*args)
23
+ super(args)
24
+ klass = self.class
25
+ hash = {}
26
+ hash = args.pop.clone if Hash === args[-1]
27
+
28
+ # Pick any missing identifying_roles out of the hash if possible:
29
+ while args.size < (ir = klass.identifying_roles).size
30
+ value = hash[role = ir[args.size]]
31
+ hash.delete(role)
32
+ args.push value
33
+ end
34
+
35
+ # If one arg is expected but more are passed, they might be the args for the object that plays the identifying role:
36
+ args = [args] if klass.identifying_roles.size == 1 && args.size > 1
37
+
38
+ # This should now only occur when there are too many args passed:
39
+ raise "Wrong number of parameters to #{klass}.new, " +
40
+ "expect (#{klass.identifying_roles*","}) " +
41
+ "got (#{args.map{|a| a.to_s.inspect}*", "})" if args.size != klass.identifying_roles.size
42
+
43
+ # Assign the identifying roles in order, then the other roles passed as a hash:
44
+ (klass.identifying_roles.zip(args) + hash.entries).each do |role_name, value|
45
+ role = klass.roles(role_name)
46
+ send("#{role_name}=", value)
47
+ end
48
+ end
49
+
50
+ def inspect #:nodoc:
51
+ "\#<#{
52
+ self.class.basename
53
+ }:#{
54
+ object_id
55
+ }#{
56
+ constellation ? " in #{constellation.inspect}" : ""
57
+ } #{
58
+ # REVISIT: Where there are one-to-one roles, this cycles
59
+ self.class.identifying_roles.map{|role| "@#{role}="+send(role).inspect }*" "
60
+ }>"
61
+ end
62
+
63
+ # When used as a hash key, the hash key of this entity instance is calculated
64
+ # by hashing the values of its identifying roles
65
+ def hash
66
+ self.class.identifying_roles.map{|role|
67
+ send role
68
+ }.inject(0) { |h,v|
69
+ h ^= v.hash
70
+ h
71
+ }
72
+ end
73
+
74
+ # When used as a hash key, this entity instance is compared with another by
75
+ # comparing the values of its identifying roles
76
+ def eql?(other)
77
+ return false unless self.class == other.class
78
+ self.class.identifying_roles.each{|role|
79
+ return false unless send(role).eql?(other.send(role))
80
+ }
81
+ return true
82
+ end
83
+
84
+ # Verbalise this entity instance
85
+ def verbalise(role_name = nil)
86
+ "#{role_name || self.class.basename}(#{
87
+ self.class.identifying_roles.map{|role_sym|
88
+ value = send(role_sym)
89
+ role_name = self.class.roles(role_sym).name.to_s.camelcase(true)
90
+ value ? value.verbalise(role_name) : "nil"
91
+ }*", "
92
+ })"
93
+ end
94
+
95
+ # Return the array of the values of this entity instance's identifying roles
96
+ def identifying_role_values
97
+ self.class.identifying_roles.map{|role|
98
+ send(role)
99
+ }
100
+ end
101
+
102
+ # All classes that become Entity types receive the methods of this class as class methods:
103
+ module ClassMethods
104
+ include Instance::ClassMethods
105
+
106
+ # Return the array of Role objects that define the identifying relationships of this Entity type:
107
+ def identifying_roles
108
+ @identifying_roles ||= []
109
+ end
110
+
111
+ # Return an array of Instance objects that can identify an instance of this Entity type:
112
+ def identifying_role_values(*args)
113
+ #puts "Getting identifying role values #{identifying_roles.inspect} of #{basename} using #{args.inspect}"
114
+
115
+ # If the single arg is an instance of the correct class or a subclass,
116
+ # use the instance's identifying_role_values
117
+ if (args.size == 1 and
118
+ self === args[0]) # REVISIT: or a secondary supertype
119
+ return args[0].identifying_role_values
120
+ end
121
+
122
+ ir = identifying_roles
123
+ args, arg_hash = ActiveFacts::extract_hash_args(ir, args)
124
+ if args.size < ir.size
125
+ raise "#{basename} requires all identifying values, you're missing #{ir[args.size..-1].map(&:to_sym)*', '}"
126
+ elsif args.size > ir.size
127
+ raise "#{basename} requires all identifying values, you have #{args.size-ir.size} extras #{args[ir.size..-1].map(&:inspect)*', '}"
128
+ end
129
+
130
+ role_args = ir.map{|role_sym| roles(role_sym)}.zip(args)
131
+ role_args.map do |role, arg|
132
+ #puts "Getting identifying_role_value for #{role.player.basename} using #{arg.inspect}"
133
+ next nil unless arg
134
+ next !!arg unless role.counterpart # Unary
135
+ if role.player === arg # REVISIT: or a secondary supertype
136
+ # Note that with a secondary supertype, it must still return the values of these identifying_roles
137
+ next arg.identifying_role_values
138
+ end
139
+ role.player.identifying_role_values(*arg)
140
+ end
141
+ end
142
+
143
+ def assert_instance(constellation, args) #:nodoc:
144
+ # Build the key for this instance from the args
145
+ # The key of an instance is the value or array of keys of the identifying values.
146
+ # The key values aren't necessarily present in the constellation, even after this.
147
+ key = identifying_role_values(*args)
148
+
149
+ # Find and return an existing instance matching this key
150
+ instances = constellation.instances[self] # All instances of this class in this constellation
151
+ instance = instances[key]
152
+ # DEBUG: puts "assert #{self.basename} #{key.inspect} #{instance ? "exists" : "new"}"
153
+ return instance, key if instance # A matching instance of this class
154
+
155
+ # Now construct each of this object's identifying roles
156
+ ir = identifying_roles
157
+ args, arg_hash = ActiveFacts::extract_hash_args(ir, args)
158
+ role_values = ir.map{|role_sym| roles(role_sym)}.zip(args)
159
+ key = [] # Gather the actual key (AutoCounters are special)
160
+ values = role_values.map do |role, arg|
161
+ if !arg
162
+ value = role_key = nil # No value
163
+ elsif !role.counterpart
164
+ value = role_key = !!arg # Unary
165
+ elsif role.player === arg # REVISIT: or a secondary supertype
166
+ raise "Connecting values across constellations" unless arg.constellation == constellation
167
+ value, role_key = arg, arg.identifying_role_values
168
+ else
169
+ value, role_key = role.player.assert_instance(constellation, Array(arg))
170
+ end
171
+ key << role_key
172
+ value
173
+ end
174
+ values << arg_hash if arg_hash and !arg_hash.empty?
175
+
176
+ #puts "Creating new #{basename} using #{values.inspect}"
177
+ instance = new(*values)
178
+
179
+ # Make the new entity instance a member of this constellation:
180
+ instance.constellation = constellation
181
+ return *index_instance(instance, key, ir)
182
+ end
183
+
184
+ def index_instance(instance, key = nil, key_roles = nil) #:nodoc:
185
+ # Derive a new key if we didn't receive one or if the roles are different:
186
+ unless key && key_roles && key_roles == identifying_roles
187
+ key = (key_roles = identifying_roles).map do |role_name|
188
+ instance.send role_name
189
+ end
190
+ end
191
+
192
+ # Index the instance for this class in the constellation
193
+ instances = instance.constellation.instances[self]
194
+ instances[key] = instance
195
+ # DEBUG: puts "indexing entity #{basename} using #{key.inspect} in #{constellation.object_id}"
196
+
197
+ # Index the instance for each supertype:
198
+ supertypes.each do |supertype|
199
+ supertype.index_instance(instance, key, key_roles)
200
+ end
201
+
202
+ return instance, key
203
+ end
204
+
205
+ # A concept that isn't a ValueType must have an identification scheme,
206
+ # which is a list of roles it plays. The identification scheme may be
207
+ # inherited from a superclass.
208
+ def initialise_entity_type(*args) #:nodoc:
209
+ #puts "Initialising entity type #{self} using #{args.inspect}"
210
+ @identifying_roles = superclass.identifying_roles if superclass.respond_to?(:identifying_roles)
211
+ # REVISIT: @identifying_roles here are the symbols passed in, not the Role objects we should use.
212
+ # We'd need late binding to use Role objects...
213
+ @identifying_roles = args if args.size > 0 || !@identifying_roles
214
+ end
215
+
216
+ def inherited(other) #:nodoc:
217
+ other.identified_by *identifying_roles
218
+ vocabulary.add_concept(other)
219
+ end
220
+
221
+ # verbalise this concept
222
+ def verbalise
223
+ "#{basename} = entity type known by #{identifying_roles.map{|role_sym| role_sym.to_s.camelcase(true)}*" and "};"
224
+ end
225
+ end
226
+
227
+ def Entity.included other #:nodoc:
228
+ other.send :extend, ClassMethods
229
+
230
+ # Register ourselves with the parent module, which has become a Vocabulary:
231
+ vocabulary = other.modspace
232
+ unless vocabulary.respond_to? :concept # Extend module with Vocabulary if necessary
233
+ vocabulary.send :extend, Vocabulary
234
+ end
235
+ vocabulary.add_concept(other)
236
+ end
237
+ end
238
+ end
239
+ end
@@ -0,0 +1,54 @@
1
+ #
2
+ # The ActiveFacts Runtime API Instance extension module.
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ # Instance methods are extended into all instances, whether of value or entity types.
6
+ #
7
+ module ActiveFacts
8
+ module API
9
+ # Every Instance of a Concept (A Value type or an Entity type) includes the methods of this module:
10
+ module Instance
11
+ # What constellation does this Instance belong to (if any):
12
+ attr_accessor :constellation
13
+
14
+ def initialize(args = []) #:nodoc:
15
+ unless (self.class.respond_to?(:identifying_roles))
16
+ #if (self.class.superclass != Object)
17
+ # puts "constructing #{self.class.superclass} with #{args.inspect}"
18
+ super(*args)
19
+ end
20
+ end
21
+
22
+ # Verbalise this instance
23
+ def verbalise
24
+ # This method should always be overridden in subclasses
25
+ raise "#{self.class} Instance verbalisation needed"
26
+ end
27
+
28
+ # De-assign all functional roles and remove from constellation, if any.
29
+ def delete
30
+ # Delete from the constellation first, so it can remember our identifying role values
31
+ @constellation.delete(self) if @constellation
32
+
33
+ # Now, for all roles (from this class and all supertypes), assign nil to all functional roles
34
+ # The counterpart roles get cleared automatically.
35
+ ([self.class]+self.class.supertypes_transitive).each do |klass|
36
+ klass.roles.each do |role_name, role|
37
+ next if role.unary?
38
+ next if !role.unique
39
+ send "#{role.name}=", nil
40
+ end
41
+ end
42
+ end
43
+
44
+ module ClassMethods #:nodoc:
45
+ include Concept
46
+ # Add Instance class methods here
47
+ end
48
+
49
+ def Instance.included other #:nodoc:
50
+ other.send :extend, ClassMethods
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,158 @@
1
+ #
2
+ # The ActiveFacts Runtime API Numeric hacks to handle immediate types.
3
+ # Copyright (c) 2008 Clifford Heath. Read the LICENSE file.
4
+ #
5
+ # This hack is required because Integer & Float don't support new,
6
+ # and can't be sensibly subclassed. Just delegate to an instance var.
7
+ #
8
+ require 'delegate'
9
+ require 'date'
10
+
11
+ # It's not possible to subclass Integer, so instead we delegate to it.
12
+ class Int < SimpleDelegator
13
+ def initialize(i = nil)
14
+ __setobj__(Integer(i))
15
+ end
16
+
17
+ def to_s
18
+ __getobj__.to_s
19
+ end
20
+
21
+ def hash
22
+ __getobj__.hash ^ self.class.hash
23
+ end
24
+
25
+ def eql?(o)
26
+ self.class == o.class and __getobj__.eql?(Integer(o))
27
+ end
28
+
29
+ def ==(o)
30
+ __getobj__.==(o)
31
+ end
32
+
33
+ def inspect
34
+ "#{self.class.basename}:#{__getobj__.inspect}"
35
+ end
36
+ end
37
+
38
+ # It's not possible to subclass Float, so instead we delegate to it.
39
+ class Real < SimpleDelegator
40
+ def initialize(r = nil)
41
+ __setobj__(Float(r))
42
+ end
43
+
44
+ def hash
45
+ __getobj__.hash ^ self.class.hash
46
+ end
47
+
48
+ def to_s
49
+ __getobj__.to_s
50
+ end
51
+
52
+ def eql?(o)
53
+ self.class == o.class and __getobj__.eql?(Float(o))
54
+ end
55
+
56
+ def ==(o)
57
+ __getobj__.==(o)
58
+ end
59
+
60
+ def inspect
61
+ "#{self.class.basename}:#{__getobj__.inspect}"
62
+ end
63
+ end
64
+
65
+ # A Date can be constructed from any Date subclass, not just using the normal date constructors.
66
+ class ::Date #:nodoc:
67
+ class << self; alias_method :old_new, :new end
68
+ def self.new(*a, &b)
69
+ #puts "Constructing date with #{a.inspect} from #{caller*"\n\t"}"
70
+ if (a.size == 1 && Date === a[0])
71
+ a = a[0]
72
+ civil(a.year, a.month, a.day, a.start)
73
+ else
74
+ civil(*a, &b)
75
+ end
76
+ end
77
+ end
78
+
79
+ # A DateTime can be constructed from any Date or DateTime subclass
80
+ class ::DateTime #:nodoc:
81
+ class << self; alias_method :old_new, :new end
82
+ def self.new(*a, &b)
83
+ #puts "Constructing DateTime with #{a.inspect} from #{caller*"\n\t"}"
84
+ if (a.size == 1)
85
+ a = a[0]
86
+ if (DateTime === a)
87
+ civil(a.year, a.month, a.day, a.hour, a.min, a.sec, a.start)
88
+ elsif (Date === a)
89
+ civil(a.year, a.month, a.day, a.start)
90
+ else
91
+ civil(*a, &b)
92
+ end
93
+ else
94
+ civil(*a, &b)
95
+ end
96
+ end
97
+ end
98
+
99
+ # The AutoCounter class is an integer, but only after the value
100
+ # has been established in the database.
101
+ # Construct it with the value :new to get an uncommitted value.
102
+ # You can use this new instance as a role value to identify an entity instance,
103
+ # or anywhere else for that matter.
104
+ # The assigned value will be filled out everywhere it needs to be, upon save.
105
+ class AutoCounter
106
+ def initialize(i = :new)
107
+ raise "AutoCounter #{self.class} may not be #{i.inspect}" unless i == :new or Integer === i
108
+ # puts "new AutoCounter #{self.class} from\n\t#{caller.select{|s| s !~ %r{rspec}}*"\n\t"}"
109
+ @value = i == :new ? nil : i
110
+ end
111
+
112
+ def assign(i)
113
+ raise ArgumentError if @value
114
+ @value = i.to_i
115
+ end
116
+
117
+ def defined?
118
+ !@value.nil?
119
+ end
120
+
121
+ def to_s
122
+ if self.defined?
123
+ @value.to_s
124
+ else
125
+ "new_#{object_id}"
126
+ end
127
+ end
128
+
129
+ def self.coerce(i)
130
+ raise ArgumentError unless @value
131
+ [ i.to_i, @value ]
132
+ end
133
+
134
+ def inspect
135
+ "\#<AutoCounter "+to_s+">"
136
+ end
137
+
138
+ def hash
139
+ to_s.hash ^ self.class.hash
140
+ end
141
+
142
+ def eql?(o)
143
+ self.class == o.class and to_s.eql?(o.to_s)
144
+ end
145
+
146
+ def self.inherited(other)
147
+ def other.identifying_role_values(*args)
148
+ return nil if args == [:new] # A new object has no identifying_role_values
149
+ return new(*args)
150
+ end
151
+ super
152
+ end
153
+
154
+ private
155
+ def clone
156
+ raise "Not allowed to clone AutoCounters"
157
+ end
158
+ end