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