icss 0.1.3 → 0.3.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.watchr +35 -3
- data/CHANGELOG.md +38 -0
- data/Gemfile +19 -14
- data/README.md +296 -0
- data/Rakefile +2 -6
- data/TODO.md +13 -0
- data/VERSION +1 -1
- data/examples/avro_examples/complicated.icss.yaml +14 -13
- data/examples/bnc.icss.yaml +70 -0
- data/examples/chronic.icss.yaml +3 -3
- data/examples/license.icss.yaml +7 -0
- data/examples/source1.icss.yaml +4 -0
- data/examples/source2.icss.yaml +4 -0
- data/examples/test_icss.yaml +67 -0
- data/icss.gemspec +103 -43
- data/lib/icss.rb +37 -15
- data/lib/icss/core_types.rb +19 -0
- data/lib/icss/error.rb +4 -0
- data/{init.rb → lib/icss/init.rb} +0 -0
- data/lib/icss/message.rb +124 -66
- data/lib/icss/message/message_sample.rb +144 -0
- data/lib/icss/protocol.rb +184 -131
- data/lib/icss/protocol/code_asset.rb +18 -0
- data/lib/icss/protocol/data_asset.rb +23 -0
- data/lib/icss/protocol/license.rb +41 -0
- data/lib/icss/protocol/source.rb +37 -0
- data/lib/icss/protocol/target.rb +68 -0
- data/lib/icss/receiver_model.rb +24 -0
- data/lib/icss/receiver_model/active_model_shim.rb +36 -0
- data/lib/icss/receiver_model/acts_as_catalog.rb +170 -0
- data/lib/icss/receiver_model/acts_as_hash.rb +177 -0
- data/lib/icss/receiver_model/acts_as_loadable.rb +47 -0
- data/lib/icss/receiver_model/acts_as_tuple.rb +100 -0
- data/lib/icss/receiver_model/locale/en.yml +27 -0
- data/lib/icss/receiver_model/to_geo_json.rb +19 -0
- data/lib/icss/receiver_model/tree_merge.rb +34 -0
- data/lib/icss/receiver_model/validations.rb +31 -0
- data/lib/icss/serialization.rb +51 -0
- data/lib/icss/serialization/zaml.rb +443 -0
- data/lib/icss/type.rb +148 -501
- data/lib/icss/type/base_type.rb +0 -0
- data/lib/icss/type/named_type.rb +184 -0
- data/lib/icss/type/record_field.rb +77 -0
- data/lib/icss/type/record_model.rb +49 -0
- data/lib/icss/type/record_schema.rb +54 -0
- data/lib/icss/type/record_type.rb +325 -0
- data/lib/icss/type/simple_types.rb +72 -0
- data/lib/icss/type/structured_schema.rb +288 -0
- data/lib/icss/type/type_factory.rb +144 -0
- data/lib/icss/type/union_schema.rb +41 -0
- data/lib/icss/view_helper.rb +56 -19
- data/notes/named_array.md +32 -0
- data/notes/on_include_vs_extend_etc.rb +176 -0
- data/notes/technical_details.md +278 -0
- data/spec/core_types_spec.rb +119 -0
- data/spec/fixtures/zaml_complex_hash.yaml +35 -0
- data/spec/icss_spec.rb +86 -23
- data/spec/message/message_sample_spec.rb +4 -0
- data/spec/message_spec.rb +139 -0
- data/spec/protocol/license_spec.rb +67 -0
- data/spec/protocol/protocol_catalog_spec.rb +48 -0
- data/spec/protocol/protocol_validations_spec.rb +176 -0
- data/spec/protocol/source_spec.rb +65 -0
- data/spec/protocol_spec.rb +91 -37
- data/spec/receiver_model_spec.rb +111 -0
- data/spec/serialization/zaml_spec.rb +81 -0
- data/spec/serialization/zaml_test.rb +473 -0
- data/spec/serialization_spec.rb +63 -0
- data/spec/spec_helper.rb +24 -7
- data/spec/support/icss_test_helper.rb +67 -0
- data/spec/support/load_example_protocols.rb +17 -0
- data/spec/type/base_type_spec.rb +0 -0
- data/spec/type/named_type_spec.rb +75 -0
- data/spec/type/record_field_spec.rb +44 -0
- data/spec/type/record_model_spec.rb +206 -0
- data/spec/type/record_schema_spec.rb +161 -0
- data/spec/type/record_type_spec.rb +155 -0
- data/spec/type/simple_types_spec.rb +121 -0
- data/spec/type/structured_schema_spec.rb +300 -0
- data/spec/type/type_catalog_spec.rb +44 -0
- data/spec/type/type_factory_spec.rb +93 -0
- data/spec/type/union_schema_spec.rb +0 -0
- data/spec/type_spec.rb +63 -0
- metadata +205 -144
- data/CHANGELOG.textile +0 -9
- data/Gemfile.lock +0 -40
- data/README.textile +0 -29
- data/lib/icss/brevity.rb +0 -136
- data/lib/icss/code_asset.rb +0 -16
- data/lib/icss/core_ext.rb +0 -9
- data/lib/icss/data_asset.rb +0 -22
- data/lib/icss/old.rb +0 -96
- data/lib/icss/protocol_set.rb +0 -48
- data/lib/icss/sample_message_call.rb +0 -142
- data/lib/icss/target.rb +0 -72
- data/lib/icss/type/factory.rb +0 -196
- data/lib/icss/validations.rb +0 -16
- data/spec/validations_spec.rb +0 -171
@@ -0,0 +1,41 @@
|
|
1
|
+
module Icss
|
2
|
+
module Meta
|
3
|
+
|
4
|
+
#
|
5
|
+
# Describes an Avro Union type.
|
6
|
+
#
|
7
|
+
# Unions are represented using JSON arrays. For example, ["string", "null"]
|
8
|
+
# declares a schema which may be either a string or null.
|
9
|
+
#
|
10
|
+
# Unions may not contain more than one schema with the same type, except for
|
11
|
+
# the named types record, fixed and enum. For example, unions containing two
|
12
|
+
# array types or two map types are not permitted, but two types with different
|
13
|
+
# names are permitted. (Names permit efficient resolution when reading and
|
14
|
+
# writing unions.)
|
15
|
+
#
|
16
|
+
# Unions may not immediately contain other unions.
|
17
|
+
#
|
18
|
+
class UnionSchema
|
19
|
+
# extend Icss::Meta::ContainerType
|
20
|
+
# #
|
21
|
+
# attr_accessor :embedded_types
|
22
|
+
# attr_accessor :declaration_flavors
|
23
|
+
# #
|
24
|
+
# def receive! type_list
|
25
|
+
# self.declaration_flavors = []
|
26
|
+
# self.embedded_types = type_list.map do |schema|
|
27
|
+
# type = TypeFactory.receive(schema)
|
28
|
+
# declaration_flavors << TypeFactory.classify_schema_declaration(schema)
|
29
|
+
# type
|
30
|
+
# end
|
31
|
+
# end
|
32
|
+
# def to_schema
|
33
|
+
# embedded_types.zip(declaration_flavors).map do |t,fl|
|
34
|
+
# [:structured_schema].include?(fl) ? t.name : t.to_schema
|
35
|
+
# end
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/icss/view_helper.rb
CHANGED
@@ -1,28 +1,65 @@
|
|
1
1
|
module Icss
|
2
|
-
|
2
|
+
module Meta
|
3
|
+
Message.class_eval do
|
4
|
+
|
5
|
+
def query_string
|
6
|
+
req_fields = request.first.type.fields rescue nil ; return unless req_fields
|
7
|
+
req_fields.map do |field|
|
8
|
+
"#{field.name}=#{first_sample_request_param[field.name.to_s]}"
|
9
|
+
end.join("&")
|
10
|
+
end
|
11
|
+
|
12
|
+
def api_url
|
13
|
+
# all calls accept xml or json
|
14
|
+
"http://api.infochimps.com/#{path}?#{query_string}"
|
15
|
+
end
|
16
|
+
|
17
|
+
def sample_field_value(field)
|
18
|
+
field_name = ((field.respond_to?(:name) && field.name) || field).to_s
|
19
|
+
|
20
|
+
value = first_sample_request_param[field_name] # sample value for field
|
21
|
+
end
|
3
22
|
|
4
|
-
def query_string
|
5
|
-
fields = request.first.type.fields rescue nil ; return unless fields
|
6
|
-
fields.map do |field|
|
7
|
-
"#{field.name}=#{first_sample_request_param[field.name.to_s]}"
|
8
|
-
end.join("&")
|
9
23
|
end
|
10
24
|
|
11
|
-
|
12
|
-
|
13
|
-
|
25
|
+
RecordField.class_eval do
|
26
|
+
def title
|
27
|
+
return "!!missing!!" if type.blank?
|
28
|
+
# case type
|
29
|
+
# when ArrayType then "array of #{type.title} #{type.to_hash.inspect}"
|
30
|
+
# else type.title
|
31
|
+
# end
|
32
|
+
type.title
|
33
|
+
end
|
14
34
|
end
|
15
|
-
end
|
16
35
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
#
|
21
|
-
#
|
22
|
-
#
|
23
|
-
#
|
24
|
-
|
36
|
+
RecordType.class_eval do
|
37
|
+
|
38
|
+
# * when :display_fields are listed in the :_doc_hints, their field names
|
39
|
+
# are split on '.' and mapped to the right nested RecordField objects:
|
40
|
+
# content_location.geo.longitude will give the field for
|
41
|
+
# content_location in the current type, the geo field in that type, and
|
42
|
+
# the longitude field object in that type.
|
43
|
+
# * if no :display_fields is given, return the full (flat) set of fields.
|
44
|
+
#
|
45
|
+
def display_fields
|
46
|
+
df_names = self._schema._doc_hints[:display_fields]
|
47
|
+
return fields.map{|f| [f] } if df_names.blank?
|
48
|
+
df_names.map do |fn|
|
49
|
+
name_segs = fn.split('.')
|
50
|
+
type = self
|
51
|
+
sub_fields = []
|
52
|
+
name_segs.map do |name_seg|
|
53
|
+
sub_fields << (type.field_named(name_seg) || name_seg)
|
54
|
+
type = sub_fields.last.type if sub_fields.last.respond_to?(:type)
|
55
|
+
type = type.items if type.respond_to?(:items)
|
56
|
+
type = type.values if type.respond_to?(:values)
|
57
|
+
end
|
58
|
+
sub_fields
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
25
62
|
end
|
26
|
-
end
|
27
63
|
|
64
|
+
end
|
28
65
|
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
### NamedArray type
|
2
|
+
|
3
|
+
Defining
|
4
|
+
|
5
|
+
```ruby
|
6
|
+
field :slices, NamedArray, :of => AggregateQuantity, :pivoting_on => :name, :receives => :remaining
|
7
|
+
```
|
8
|
+
|
9
|
+
Lets it equivalently live as
|
10
|
+
|
11
|
+
```yaml
|
12
|
+
- name: foo
|
13
|
+
average_value: 3
|
14
|
+
slices:
|
15
|
+
- name: subcat_1
|
16
|
+
average_value: 3
|
17
|
+
- name: subcat_2
|
18
|
+
average_value: 3
|
19
|
+
```
|
20
|
+
|
21
|
+
or naturally pivot to be
|
22
|
+
|
23
|
+
```yaml
|
24
|
+
foo:
|
25
|
+
average_value: 3
|
26
|
+
subcat_1:
|
27
|
+
average_value: 3
|
28
|
+
subcat_2:
|
29
|
+
average_value: 3
|
30
|
+
```
|
31
|
+
|
32
|
+
All the rcvr_remaining (unclaimed) properties pivot on their :name field to look like the one or the other at your choice.
|
@@ -0,0 +1,176 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
|
4
|
+
|
5
|
+
# klass Foo [:module_self_foo, :module_meth_extif]
|
6
|
+
# sgtncl Foo []
|
7
|
+
# ---------------
|
8
|
+
# obj ClassIncludingFoo [:meth_in_class, :module_meth_foo, :module_meth_inclif]
|
9
|
+
# klass ClassIncludingFoo [:meth_in_sgtn_cl, :self_meth_in_class, -- -- ]
|
10
|
+
# sgtncl ClassIncludingFoo [:self_meth_sgtn_cl, -- -- -- ]
|
11
|
+
# ---------------
|
12
|
+
# obj ClassExtendingFoo [:meth_in_class, -- -- -- ]
|
13
|
+
# klass ClassExtendingFoo [:meth_in_sgtn_cl, :self_meth_in_class, :module_meth_foo, :module_meth_inclif]
|
14
|
+
# sgtncl ClassExtendingFoo [:self_meth_sgtn_cl, -- -- -- ]
|
15
|
+
# ---------------
|
16
|
+
# obj ClassInclFooInSgtnCl [:meth_in_class, -- -- -- ]
|
17
|
+
# klass ClassInclFooInSgtnCl [:meth_in_sgtn_cl, :self_meth_in_class, :module_meth_foo, :module_meth_inclif]
|
18
|
+
# sgtncl ClassInclFooInSgtnCl [:self_meth_sgtn_cl, -- -- -- ]
|
19
|
+
# ---------------
|
20
|
+
# obj ClassExtFooInSgtnCl [:meth_in_class, -- , -- -- ]
|
21
|
+
# klass ClassExtFooInSgtnCl [:meth_in_sgtn_cl, :self_meth_in_class, -- -- ]
|
22
|
+
# sgtncl ClassExtFooInSgtnCl [:self_meth_sgtn_cl, :module_meth_foo, :module_meth_inclif]
|
23
|
+
#
|
24
|
+
#
|
25
|
+
# Ancestor chain
|
26
|
+
#
|
27
|
+
# klass Foo IncludedInFoo
|
28
|
+
# sgtncl ExtendedInFoo Module Object Kernel BasicObject
|
29
|
+
# #
|
30
|
+
# klass ClassExtendingFoo SimpleClass Object Kernel BasicObject
|
31
|
+
# sgtncl Foo IncludedInFoo Class Module Object Kernel BasicObject
|
32
|
+
# #
|
33
|
+
# klass ClassIncludingFoo Foo IncludedInFoo SimpleClass Object Kernel BasicObject
|
34
|
+
# sgtncl Class Module Object Kernel BasicObject
|
35
|
+
# #
|
36
|
+
# klass ClassInclFooInSgtnCl SimpleClass Object Kernel BasicObject
|
37
|
+
# sgtncl Foo IncludedInFoo Class Module Object Kernel BasicObject
|
38
|
+
# #
|
39
|
+
# klass ClassExtFooInSgtnCl SimpleClass Object Kernel BasicObject
|
40
|
+
# sgtncl Class Module Object Kernel BasicObject
|
41
|
+
|
42
|
+
|
43
|
+
# --------------- Foo
|
44
|
+
# module_meth_extif class Foo Module
|
45
|
+
# module_self_foo class Foo Module
|
46
|
+
#
|
47
|
+
# --------------- ClassExtendingFoo
|
48
|
+
# meth_in_class obj #<ClassExtendingFoo:0x00000100e ClassExtendingFoo
|
49
|
+
# self_meth_in_class class ClassExtendingFoo Class #<Class:ClassExtendingFoo>
|
50
|
+
# meth_in_sgtn_cl class ClassExtendingFoo Class #<Class:ClassExtendingFoo>
|
51
|
+
# self_meth_sgtn_cl sgtn_cl #<Class:ClassExtendingFoo> Class
|
52
|
+
# module_meth_foo class ClassExtendingFoo Class #<Class:ClassExtendingFoo>
|
53
|
+
# module_meth_inclif class ClassExtendingFoo Class #<Class:ClassExtendingFoo>
|
54
|
+
#
|
55
|
+
# --------------- ClassIncludingFoo
|
56
|
+
# meth_in_class obj #<ClassIncludingFoo:0x000001020 ClassIncludingFoo
|
57
|
+
# self_meth_in_class class ClassIncludingFoo Class #<Class:ClassIncludingFoo>
|
58
|
+
# meth_in_sgtn_cl class ClassIncludingFoo Class #<Class:ClassIncludingFoo>
|
59
|
+
# self_meth_sgtn_cl sgtn_cl #<Class:ClassIncludingFoo> Class
|
60
|
+
# module_meth_foo obj #<ClassIncludingFoo:0x000001020 ClassIncludingFoo
|
61
|
+
# module_meth_inclif obj #<ClassIncludingFoo:0x000001020 ClassIncludingFoo
|
62
|
+
#
|
63
|
+
# --------------- ClassInclFooInSgtnCl
|
64
|
+
# meth_in_class obj #<ClassInclFooInSgtnCl:0x000001 ClassInclFooInSgtnCl
|
65
|
+
# self_meth_in_class class ClassInclFooInSgtnCl Class #<Class:ClassInclFooInSgtnCl>
|
66
|
+
# meth_in_sgtn_cl class ClassInclFooInSgtnCl Class #<Class:ClassInclFooInSgtnCl>
|
67
|
+
# self_meth_sgtn_cl sgtn_cl #<Class:ClassInclFooInSgtnCl> Class
|
68
|
+
# module_meth_foo class ClassInclFooInSgtnCl Class #<Class:ClassInclFooInSgtnCl>
|
69
|
+
# module_meth_inclif class ClassInclFooInSgtnCl Class #<Class:ClassInclFooInSgtnCl>
|
70
|
+
#
|
71
|
+
# --------------- ClassExtFooInSgtnCl
|
72
|
+
# meth_in_class obj #<ClassExtFooInSgtnCl:0x0000010 ClassExtFooInSgtnCl
|
73
|
+
# self_meth_in_class class ClassExtFooInSgtnCl Class #<Class:ClassExtFooInSgtnCl>
|
74
|
+
# meth_in_sgtn_cl class ClassExtFooInSgtnCl Class #<Class:ClassExtFooInSgtnCl>
|
75
|
+
# self_meth_sgtn_cl sgtn_cl #<Class:ClassExtFooInSgtnCl> Class
|
76
|
+
# module_meth_foo sgtn_cl #<Class:ClassExtFooInSgtnCl> Class
|
77
|
+
# module_meth_inclif sgtn_cl #<Class:ClassExtFooInSgtnCl> Class
|
78
|
+
|
79
|
+
def compare_methods(klass_a, klass_b)
|
80
|
+
puts( "%-31s\tobj \t%s" % [klass_a.to_s[0..30], (klass_a.new.public_methods - Object.new.public_methods).inspect]) if klass_a.respond_to?(:new)
|
81
|
+
puts( "%-31s\tklass \t%s" % [klass_a.to_s[0..30], (klass_a.public_methods - klass_b.public_methods).inspect])
|
82
|
+
puts( "%-31s\tsgtncl\t%s" % [klass_a.to_s[0..30], (klass_a.singleton_class.public_methods - klass_b.singleton_class.public_methods).inspect])
|
83
|
+
puts '---------------'
|
84
|
+
end
|
85
|
+
|
86
|
+
def show_anc_chain(klass_a)
|
87
|
+
puts ['klass ', klass_a.ancestors .reject{|kl| kl.name =~ /^(RSpec|PP::ObjectMixin)/ }].join("\t")
|
88
|
+
puts ['sgtncl', klass_a.singleton_class.ancestors.reject{|kl| kl.name =~ /^(RSpec|PP::ObjectMixin)/ }].join("\t")
|
89
|
+
end
|
90
|
+
|
91
|
+
def show_method_selfness(klass_a)
|
92
|
+
puts "\n", klass_a
|
93
|
+
on = (klass_a.respond_to?(:new) ? { 'obj' => klass_a.new } : {})
|
94
|
+
on['class'] = klass_a
|
95
|
+
on['sgtn_cl'] = klass_a.singleton_class
|
96
|
+
[
|
97
|
+
:meth_in_class,
|
98
|
+
:self_meth_in_class,
|
99
|
+
:meth_in_sgtn_cl,
|
100
|
+
:self_meth_sgtn_cl,
|
101
|
+
|
102
|
+
:explicit_method_extif,
|
103
|
+
:explicit_method_foo,
|
104
|
+
:module_meth_extif,
|
105
|
+
:module_meth_foo,
|
106
|
+
:explicit_method_inclif,
|
107
|
+
:module_meth_inclif,
|
108
|
+
:module_self_extif,
|
109
|
+
:module_self_foo,
|
110
|
+
:module_self_inclif,
|
111
|
+
].each do |meth|
|
112
|
+
on.each do |kind, obj|
|
113
|
+
next unless obj.respond_to?(meth)
|
114
|
+
puts( "%-31s\t%-7s\t%-31s\t%-31s\t%s" % [meth, kind, obj.send(meth)].flatten.map{|x| x.to_s[0..30] } )
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
class SimpleClass
|
121
|
+
class << self
|
122
|
+
def self.self_meth_sgtn_cl() ; [self, self.class, ((self.is_a?(Class) && self.ancestors.first == self) ? self.singleton_class : nil)] ; end
|
123
|
+
def meth_in_sgtn_cl() ; [self, self.class, ((self.is_a?(Class) && self.ancestors.first == self) ? self.singleton_class : nil)] ; end
|
124
|
+
end
|
125
|
+
def self.self_meth_in_class() ; [self, self.class, ((self.is_a?(Class) && self.ancestors.first == self) ? self.singleton_class : nil)] ; end
|
126
|
+
def meth_in_class() ; [self, self.class, ((self.is_a?(Class) && self.ancestors.first == self) ? self.singleton_class : nil)] ; end
|
127
|
+
end
|
128
|
+
|
129
|
+
module IncludedInFoo
|
130
|
+
def self.module_self_inclif() ; [self, self.class, ((self.is_a?(Class) && self.ancestors.first == self) ? self.singleton_class : nil)] ; end
|
131
|
+
def module_meth_inclif() ; [self, self.class, ((self.is_a?(Class) && self.ancestors.first == self) ? self.singleton_class : nil)] ; end
|
132
|
+
end
|
133
|
+
|
134
|
+
module ExtendedInFoo
|
135
|
+
def self.module_self_extif() ; [self, self.class, ((self.is_a?(Class) && self.ancestors.first == self) ? self.singleton_class : nil)] ; end
|
136
|
+
def module_meth_extif() ; [self, self.class, ((self.is_a?(Class) && self.ancestors.first == self) ? self.singleton_class : nil)] ; end
|
137
|
+
end
|
138
|
+
|
139
|
+
module Foo
|
140
|
+
include IncludedInFoo
|
141
|
+
extend ExtendedInFoo
|
142
|
+
def self.module_self_foo() ; [self, self.class, ((self.is_a?(Class) && self.ancestors.first == self) ? self.singleton_class : nil)] ; end
|
143
|
+
def module_meth_foo() ; [self, self.class, ((self.is_a?(Class) && self.ancestors.first == self) ? self.singleton_class : nil)] ; end
|
144
|
+
end
|
145
|
+
|
146
|
+
class ClassExtendingFoo < SimpleClass
|
147
|
+
extend Foo
|
148
|
+
class << self
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class ClassIncludingFoo < SimpleClass
|
153
|
+
include Foo
|
154
|
+
class << self
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
class ClassInclFooInSgtnCl < SimpleClass
|
159
|
+
class << self
|
160
|
+
include Foo
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
class ClassExtFooInSgtnCl < SimpleClass
|
165
|
+
class << self
|
166
|
+
extend Foo
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
[Foo,
|
171
|
+
ClassExtendingFoo, ClassIncludingFoo, ClassInclFooInSgtnCl,
|
172
|
+
ClassExtFooInSgtnCl,
|
173
|
+
].each do |kl|
|
174
|
+
show_method_selfness(kl)
|
175
|
+
compare_methods(kl , Class)
|
176
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
|
2
|
+
#### Types, Properties and Individuals
|
3
|
+
|
4
|
+
There are things in the world that we know as "restaurants" -- for example, in
|
5
|
+
Austin you can't to better than "Torchy's Tacos". It, like all restaurants, have
|
6
|
+
some notion of a "name" and a "menu".
|
7
|
+
|
8
|
+
* "Restaurant" is a _*type*_: a group sharing common properties.
|
9
|
+
* "Torchy's Tacos" is an _*individual*_.
|
10
|
+
* "name" and "menu" are _*properties*_ of a type.
|
11
|
+
* An individual has _*values*_ for those properties: the restaurant "Torchy's
|
12
|
+
Taco's" has a menu which includes the "Dirty Sanchez" (no, really.)
|
13
|
+
|
14
|
+
In ruby, we might represent this as follows (greatly simplified):
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
class Restaurant
|
18
|
+
field :name, String
|
19
|
+
field :menu, Array, :items => String
|
20
|
+
field :geo, GeoCoordinates
|
21
|
+
end
|
22
|
+
yum = Restaurant.receive({
|
23
|
+
:name => "Torchy's Taco's",
|
24
|
+
:menu => ["Dirty Sanchez", "Fried Avocado"] ,
|
25
|
+
:geo => { :longitude => 30.295, :latitude => -97.745 },
|
26
|
+
})
|
27
|
+
yum.name # "Torchy's Taco's"
|
28
|
+
yum.geo.latitude # -97.745
|
29
|
+
```
|
30
|
+
|
31
|
+
* The _type_ is implemented with the *class* `Restaurant`.
|
32
|
+
* Its _properties_ are described as `field`s, each with its own type.
|
33
|
+
* An _individual_ is represented by an *instance* of that class.
|
34
|
+
|
35
|
+
Probably most of this was boring and/or familiar. To clear the rest of the
|
36
|
+
straightforward stuff out of the way,
|
37
|
+
|
38
|
+
* A "Restaurant" is a specialization of a "Place": it has a "geo" property, just
|
39
|
+
as "Bowling Alley" and "Mountain" do. We'll represent that as straightforward
|
40
|
+
inheritance: `Restaurant` is a subclass of `Place`.
|
41
|
+
* A "Place" is itself a specialization of "Tangible Things", for which we will
|
42
|
+
use the ruby class `Thing`.
|
43
|
+
* A "Restaurant" is actually also a "Business", itself also a specialization of
|
44
|
+
"Thing". Using a trick you'll hear more about below, we can actually handle
|
45
|
+
this easily using module inclusion (`include`).
|
46
|
+
|
47
|
+
So this gives us (still simplified, but less so):
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class Thing
|
51
|
+
field :name, String
|
52
|
+
end
|
53
|
+
|
54
|
+
class Place < Thing
|
55
|
+
field :geo, GeoCoordinates
|
56
|
+
end
|
57
|
+
|
58
|
+
class Business < Thing
|
59
|
+
field :opening_hours, Duration
|
60
|
+
end
|
61
|
+
|
62
|
+
class Restaurant < Place
|
63
|
+
include Meta::BusinessType
|
64
|
+
field :menu, Array, :items => String
|
65
|
+
end
|
66
|
+
|
67
|
+
class Mountain < Place
|
68
|
+
field :height_of_peak, Integer, :doc => "In meters"
|
69
|
+
end
|
70
|
+
|
71
|
+
yum = Restaurant.receive({
|
72
|
+
:name => "Torchy's Taco's",
|
73
|
+
:menu => ["Dirty Sanchez", "Fried Avocado"] ,
|
74
|
+
:geo => { :longitude => 30.295, :latitude => -97.745 },
|
75
|
+
})
|
76
|
+
yum.name # "Torchy's Taco's"
|
77
|
+
yum.geo.latitude # -97.745
|
78
|
+
```
|
79
|
+
|
80
|
+
### RecordModel
|
81
|
+
|
82
|
+
Including `Icss::Meta::RecordModel` gives you these superpowers:
|
83
|
+
|
84
|
+
* `.field` -- create accessors and receivers for a property
|
85
|
+
* `.fields` and `.field_names` -- describe the fields. (field names is guaranteed in order.)
|
86
|
+
* `.receive` -- create an instance given a (nested) hash of values; calls
|
87
|
+
in turn `.receive` on each field's type.
|
88
|
+
|
89
|
+
The class and instance behavior of `Place` (a direct child class of `Thing`) is
|
90
|
+
effectively (apart from one important detail) as follows:
|
91
|
+
|
92
|
+
Behavior Model Instance Methods Type Class Methods
|
93
|
+
-------------- ----------------- ----------------- --------------------- ------------------------
|
94
|
+
(superclasses) Object ...many... ...many...
|
95
|
+
structure type: Meta::BaseType .metamodel,.to_schema
|
96
|
+
Meta::RecordModel #receive! Meta::RecordType .receive, .field, .fields
|
97
|
+
parent models: Thing #name,#receive_name,&c
|
98
|
+
model klass: Place #geo, #receive_geo,&c
|
99
|
+
|
100
|
+
Note carefully the following relationships:
|
101
|
+
|
102
|
+
torchys = Place.new({...})
|
103
|
+
|
104
|
+
# true:
|
105
|
+
torchys.is_a? Thing
|
106
|
+
torchys.is_a? Meta::RecordModel
|
107
|
+
torchys.is_a? Object
|
108
|
+
|
109
|
+
# not true:
|
110
|
+
torchys.is_a? Meta::RecordType
|
111
|
+
|
112
|
+
# true:
|
113
|
+
Place < Thing
|
114
|
+
Place < Meta::RecordModel
|
115
|
+
Place.is_a? Meta::RecordType
|
116
|
+
|
117
|
+
### metamodel
|
118
|
+
|
119
|
+
Here's the important detail promised earlier about inheritance. We actually slip
|
120
|
+
in a little overlay module, the `metamodel`:
|
121
|
+
|
122
|
+
Behavior Model Instance Methods Type Class Methods
|
123
|
+
-------------- ----------------- ----------------- --------------------- ------------------------
|
124
|
+
(superclasses) Object ...many... ...many...
|
125
|
+
structure type: Meta::BaseType .metamodel,.to_schema
|
126
|
+
Meta::RecordModel #receive! Meta::RecordType .receive, .field, .fields
|
127
|
+
parent models: Meta::ThingModel #name,#receive_name,&c
|
128
|
+
Thing
|
129
|
+
metamodel: Meta::PlaceModel #geo, #receive_geo,&c
|
130
|
+
model klass: Place <--- call super, override things, go crazy --->
|
131
|
+
|
132
|
+
This lets us (a) enable multiple inheritance, and (b) let a class declare a
|
133
|
+
field but only override *some* of its behavior (i.e. still letting you use
|
134
|
+
`super`).
|
135
|
+
|
136
|
+
We scribble all the instance methods for a place onto the `Meta::PlaceModel`
|
137
|
+
*module*. When the `Place` class calls `include Meta::PlaceModel`, it acquires
|
138
|
+
the prescribed behavior, but can override as required. Furthermore, any other
|
139
|
+
class can accomplish "Placehood" by *also* `include`ing the `Meta::PlaceModel`
|
140
|
+
metamodel.
|
141
|
+
|
142
|
+
The infochimps API uses this to good effect, where data sets often introduce
|
143
|
+
behavior across an entire universe of otherwise distinct types. Objects from
|
144
|
+
Twitter would generically be represented as `Social::Person` (an account),
|
145
|
+
`Event::UserTweets` (messages), and subclasses of `Geo::Place` (place checkins).
|
146
|
+
All have implied geolocations, and could naturally coexist on a map. Other data
|
147
|
+
sources imply other attributes: not just foursquare and yelp but also news
|
148
|
+
articles and stock filings. We can inject Twitter geolocation information
|
149
|
+
without subclassing everything in sight
|
150
|
+
(`RestaurantOnTwitterThatIsAlsoABranchOfAPubliclyTradedCompany`) or requiring
|
151
|
+
the prolix madness of an RDF graph
|
152
|
+
(`<http://ding.us/not/actually/a/url/torchys_tacos>
|
153
|
+
<http://some.rand.om/ontology#hasPropertyYouCantRememberSpellingOf>
|
154
|
+
34^^<http://with.com/value/that/is/twelve/miles/long/even/though/its/a/goddamn/integer>`)
|
155
|
+
|
156
|
+
### Model vs Type vs Schema
|
157
|
+
|
158
|
+
Now we want a way to programmatically describe and generate types. If we're not
|
159
|
+
careful, we'll end up going mad discussing how to type up the typical type of
|
160
|
+
type `Type` properly having property `schema` of type `RecordSchema`, or (even
|
161
|
+
worse) wind up in the omphaloskeptic realm of Semantic Web Ontologies. Eff that
|
162
|
+
-- this is called the Infochimps Stupid Schema, precisely to remind you it's
|
163
|
+
just a data model, we should really just relax.
|
164
|
+
|
165
|
+
However, if you must spend some time to understand how a small amount of clever
|
166
|
+
enables that greater and more productive recklessness, a certain measure of
|
167
|
+
careful terminology is required.
|
168
|
+
|
169
|
+
The regular kind of _type_ we've been discussing is a `Model` -- its individuals
|
170
|
+
correspond to things in the real world. A `Thing` is a model, and most things
|
171
|
+
are `Thing`s. `Integer` and `IsFriendOf` (a kind of `Relation`) are both models
|
172
|
+
but not `Thing`s. Recapping what you know,
|
173
|
+
|
174
|
+
- **instances** of a model represent individuals;
|
175
|
+
- **instance methods** of a model characterize a type's behavior, specifically
|
176
|
+
- **fields**, which represent properties and accept values describing the individual.
|
177
|
+
|
178
|
+
We also want to be able to discuss properties and impart shared behavior among
|
179
|
+
types themselves. For example,
|
180
|
+
|
181
|
+
- a `Restaurant` place, a `WeatherObservation` statistical summary, and a
|
182
|
+
`IsFriendOf` relation are quite different at the model level -- they
|
183
|
+
represent respectively a tangible Thing, an intangible Thing and a
|
184
|
+
non-Thing. But all of them are structured records, with fields an so on.
|
185
|
+
|
186
|
+
- A `FilePath`, a `RawJsonBlob` and a `Url` are all represented as simple
|
187
|
+
strings, but each has a distinct semantic meaning, and each implies rules
|
188
|
+
that would allow a computer to validate an instance's contents look right.
|
189
|
+
|
190
|
+
- We of course use `Array` to hold collections of items, and `Hash` to hold
|
191
|
+
labelled collections of items. Wherever possible, we'd like to specify
|
192
|
+
"Array of MusicAlbum" and so forth.
|
193
|
+
|
194
|
+
Let us now introduce the `Schema`, which characterizes commonalities among `Model` types.
|
195
|
+
|
196
|
+
|
197
|
+
<"Torchy's" instance> manufactured by Restaurant
|
198
|
+
<"Torchy's">.menu() instance methods from Restaurant
|
199
|
+
|
200
|
+
<Restaurant class> described by RecordSchema
|
201
|
+
<Restaurant>.field_names class methods from RecordSchema
|
202
|
+
|
203
|
+
A person who find this sort of thing elegant might point out that the
|
204
|
+
properties of a Schema describe specifics of a model; given values
|
205
|
+
characterizing those specifics, the Schema manufactures an individual
|
206
|
+
Model. That is: a Schema is a type for individual Types.
|
207
|
+
|
208
|
+
But if you're like me you're better off just remembering
|
209
|
+
|
210
|
+
> Schema == classes and class methods for a type;
|
211
|
+
> Model == instances and instance methods for a type.
|
212
|
+
|
213
|
+
In implementation land, a Schema is a class, with a distinct existence from its
|
214
|
+
type. If you're working with types on their own, you want schema objects;
|
215
|
+
|
216
|
+
(In fact, you may discover to your horror that a Schema is itself a RecordType:
|
217
|
+
it's turtles all the way down.)
|
218
|
+
|
219
|
+
### Structured Schema (Array, Map, Enum, Fixed)
|
220
|
+
|
221
|
+
#### Meta::ArraySchema
|
222
|
+
|
223
|
+
The schema prescribes the *class* of items the `Array` should hold: for example,
|
224
|
+
an album's `tracks` property is an array of `MusicRecordings`.
|
225
|
+
|
226
|
+
Behavior Model Instance Methods Type Class Methods
|
227
|
+
-------------- ----------------- ----------------- --------------------- ------------------------
|
228
|
+
(superclasses) Object,Array ...many... ...many...
|
229
|
+
structure type: Meta::BaseType .metamodel,.to_schema
|
230
|
+
Meta::ArrayType .receive
|
231
|
+
metamodel: --
|
232
|
+
model klass: ArrayOfInt (directly on class) .items
|
233
|
+
|
234
|
+
Schema: class Meta::ArraySchema
|
235
|
+
|
236
|
+
For a record model, an individual schema typically doesn't have much to say
|
237
|
+
about the model class itself. Since models are usually real-world objects we'd
|
238
|
+
like to handle generically, we focus our interest on fields and instance
|
239
|
+
methods. An `Array` is something whose *individuals* should be completely
|
240
|
+
generic: we care about all the interesting things in a list, not the bag that
|
241
|
+
holds them. The schema for an array (as well as for Hash, Fixed and Enum)
|
242
|
+
prescribes information about the *type* not the *model*
|
243
|
+
|
244
|
+
Now after I just sold you on the virtues of a metamodel in the case of a record,
|
245
|
+
you might expect an analogous module to extend the class. There's no practical
|
246
|
+
benefit or need for this, though, so we currently just scribble the .items class
|
247
|
+
method directly onto the class.
|
248
|
+
|
249
|
+
#### Meta::HashSchema
|
250
|
+
|
251
|
+
Behavior Model Instance Methods Type Class Methods
|
252
|
+
-------------- ----------------- ----------------- --------------------- ------------------------
|
253
|
+
(superclasses) Object,Hash ...many... ...many...
|
254
|
+
structure type: Meta::BaseType .metamodel,.to_schema
|
255
|
+
Meta::HashType .receive
|
256
|
+
metamodel: --
|
257
|
+
model klass: HashOfYourMom (directly on class) .values
|
258
|
+
|
259
|
+
#### Meta::FixedSchema
|
260
|
+
|
261
|
+
Behavior Model Instance Methods Type Class Methods
|
262
|
+
-------------- ----------------- ----------------- --------------------- ------------------------
|
263
|
+
(superclasses) Object,String ...many... ...many...
|
264
|
+
structure type: Meta::BaseType .metamodel,.to_schema
|
265
|
+
Meta::FixedType .receive
|
266
|
+
metamodel: --
|
267
|
+
model klass: Fixed16 (directly on class) .bytesize
|
268
|
+
|
269
|
+
#### Meta::EnumSchema
|
270
|
+
|
271
|
+
Behavior Model Instance Methods Type Class Methods
|
272
|
+
-------------- ----------------- ----------------- --------------------- ------------------------
|
273
|
+
(superclasses) Object,String ...many... ...many...
|
274
|
+
structure type: Meta::BaseType .metamodel,.to_schema
|
275
|
+
Meta::EnumType .receive
|
276
|
+
metamodel: --
|
277
|
+
model klass: CountryCode (directly on class) .symbols
|
278
|
+
|