icss 0.1.3 → 0.3.2
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/.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
|
+
|