activefacts 0.6.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.
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