activefacts 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +4 -0
- data/Manifest.txt +83 -0
- data/README.rdoc +81 -0
- data/Rakefile +41 -0
- data/bin/afgen +46 -0
- data/bin/cql +52 -0
- data/examples/CQL/Address.cql +46 -0
- data/examples/CQL/Blog.cql +54 -0
- data/examples/CQL/CompanyDirectorEmployee.cql +51 -0
- data/examples/CQL/Death.cql +16 -0
- data/examples/CQL/Genealogy.cql +95 -0
- data/examples/CQL/Marriage.cql +18 -0
- data/examples/CQL/Metamodel.cql +238 -0
- data/examples/CQL/MultiInheritance.cql +19 -0
- data/examples/CQL/OilSupply.cql +47 -0
- data/examples/CQL/Orienteering.cql +108 -0
- data/examples/CQL/PersonPlaysGame.cql +17 -0
- data/examples/CQL/SchoolActivities.cql +31 -0
- data/examples/CQL/SimplestUnary.cql +12 -0
- data/examples/CQL/SubtypePI.cql +32 -0
- data/examples/CQL/Warehousing.cql +99 -0
- data/examples/CQL/WindowInRoomInBldg.cql +22 -0
- data/lib/activefacts.rb +10 -0
- data/lib/activefacts/api.rb +25 -0
- data/lib/activefacts/api/concept.rb +384 -0
- data/lib/activefacts/api/constellation.rb +106 -0
- data/lib/activefacts/api/entity.rb +239 -0
- data/lib/activefacts/api/instance.rb +54 -0
- data/lib/activefacts/api/numeric.rb +158 -0
- data/lib/activefacts/api/role.rb +94 -0
- data/lib/activefacts/api/standard_types.rb +67 -0
- data/lib/activefacts/api/support.rb +59 -0
- data/lib/activefacts/api/value.rb +122 -0
- data/lib/activefacts/api/vocabulary.rb +120 -0
- data/lib/activefacts/cql.rb +31 -0
- data/lib/activefacts/cql/CQLParser.treetop +104 -0
- data/lib/activefacts/cql/Concepts.treetop +112 -0
- data/lib/activefacts/cql/DataTypes.treetop +66 -0
- data/lib/activefacts/cql/Expressions.treetop +113 -0
- data/lib/activefacts/cql/FactTypes.treetop +185 -0
- data/lib/activefacts/cql/Language/English.treetop +92 -0
- data/lib/activefacts/cql/LexicalRules.treetop +169 -0
- data/lib/activefacts/cql/Rakefile +6 -0
- data/lib/activefacts/cql/parser.rb +88 -0
- data/lib/activefacts/generate/absorption.rb +87 -0
- data/lib/activefacts/generate/cql.rb +441 -0
- data/lib/activefacts/generate/cql/html.rb +397 -0
- data/lib/activefacts/generate/null.rb +19 -0
- data/lib/activefacts/generate/ordered.rb +557 -0
- data/lib/activefacts/generate/ruby.rb +326 -0
- data/lib/activefacts/generate/sql/server.rb +164 -0
- data/lib/activefacts/generate/text.rb +21 -0
- data/lib/activefacts/input/cql.rb +1268 -0
- data/lib/activefacts/input/orm.rb +926 -0
- data/lib/activefacts/persistence.rb +1 -0
- data/lib/activefacts/persistence/composition.rb +653 -0
- data/lib/activefacts/support.rb +51 -0
- data/lib/activefacts/version.rb +3 -0
- data/lib/activefacts/vocabulary.rb +6 -0
- data/lib/activefacts/vocabulary/extensions.rb +343 -0
- data/lib/activefacts/vocabulary/metamodel.rb +303 -0
- data/script/txt2html +71 -0
- data/spec/absorption_spec.rb +95 -0
- data/spec/api/autocounter.rb +82 -0
- data/spec/api/constellation.rb +130 -0
- data/spec/api/entity_type.rb +101 -0
- data/spec/api/instance.rb +428 -0
- data/spec/api/roles.rb +122 -0
- data/spec/api/value_type.rb +112 -0
- data/spec/api_spec.rb +14 -0
- data/spec/cql_cql_spec.rb +58 -0
- data/spec/cql_parse_spec.rb +31 -0
- data/spec/cql_ruby_spec.rb +60 -0
- data/spec/cql_sql_spec.rb +54 -0
- data/spec/cql_symbol_tables_spec.rb +259 -0
- data/spec/cql_unit_spec.rb +336 -0
- data/spec/cqldump_spec.rb +169 -0
- data/spec/norma_cql_spec.rb +48 -0
- data/spec/norma_ruby_spec.rb +50 -0
- data/spec/norma_sql_spec.rb +45 -0
- data/spec/norma_tables_spec.rb +94 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +10 -0
- 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
|