icss 0.1.3 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (98) hide show
  1. data/.watchr +35 -3
  2. data/CHANGELOG.md +38 -0
  3. data/Gemfile +19 -14
  4. data/README.md +296 -0
  5. data/Rakefile +2 -6
  6. data/TODO.md +13 -0
  7. data/VERSION +1 -1
  8. data/examples/avro_examples/complicated.icss.yaml +14 -13
  9. data/examples/bnc.icss.yaml +70 -0
  10. data/examples/chronic.icss.yaml +3 -3
  11. data/examples/license.icss.yaml +7 -0
  12. data/examples/source1.icss.yaml +4 -0
  13. data/examples/source2.icss.yaml +4 -0
  14. data/examples/test_icss.yaml +67 -0
  15. data/icss.gemspec +103 -43
  16. data/lib/icss.rb +37 -15
  17. data/lib/icss/core_types.rb +19 -0
  18. data/lib/icss/error.rb +4 -0
  19. data/{init.rb → lib/icss/init.rb} +0 -0
  20. data/lib/icss/message.rb +124 -66
  21. data/lib/icss/message/message_sample.rb +144 -0
  22. data/lib/icss/protocol.rb +184 -131
  23. data/lib/icss/protocol/code_asset.rb +18 -0
  24. data/lib/icss/protocol/data_asset.rb +23 -0
  25. data/lib/icss/protocol/license.rb +41 -0
  26. data/lib/icss/protocol/source.rb +37 -0
  27. data/lib/icss/protocol/target.rb +68 -0
  28. data/lib/icss/receiver_model.rb +24 -0
  29. data/lib/icss/receiver_model/active_model_shim.rb +36 -0
  30. data/lib/icss/receiver_model/acts_as_catalog.rb +170 -0
  31. data/lib/icss/receiver_model/acts_as_hash.rb +177 -0
  32. data/lib/icss/receiver_model/acts_as_loadable.rb +47 -0
  33. data/lib/icss/receiver_model/acts_as_tuple.rb +100 -0
  34. data/lib/icss/receiver_model/locale/en.yml +27 -0
  35. data/lib/icss/receiver_model/to_geo_json.rb +19 -0
  36. data/lib/icss/receiver_model/tree_merge.rb +34 -0
  37. data/lib/icss/receiver_model/validations.rb +31 -0
  38. data/lib/icss/serialization.rb +51 -0
  39. data/lib/icss/serialization/zaml.rb +443 -0
  40. data/lib/icss/type.rb +148 -501
  41. data/lib/icss/type/base_type.rb +0 -0
  42. data/lib/icss/type/named_type.rb +184 -0
  43. data/lib/icss/type/record_field.rb +77 -0
  44. data/lib/icss/type/record_model.rb +49 -0
  45. data/lib/icss/type/record_schema.rb +54 -0
  46. data/lib/icss/type/record_type.rb +325 -0
  47. data/lib/icss/type/simple_types.rb +72 -0
  48. data/lib/icss/type/structured_schema.rb +288 -0
  49. data/lib/icss/type/type_factory.rb +144 -0
  50. data/lib/icss/type/union_schema.rb +41 -0
  51. data/lib/icss/view_helper.rb +56 -19
  52. data/notes/named_array.md +32 -0
  53. data/notes/on_include_vs_extend_etc.rb +176 -0
  54. data/notes/technical_details.md +278 -0
  55. data/spec/core_types_spec.rb +119 -0
  56. data/spec/fixtures/zaml_complex_hash.yaml +35 -0
  57. data/spec/icss_spec.rb +86 -23
  58. data/spec/message/message_sample_spec.rb +4 -0
  59. data/spec/message_spec.rb +139 -0
  60. data/spec/protocol/license_spec.rb +67 -0
  61. data/spec/protocol/protocol_catalog_spec.rb +48 -0
  62. data/spec/protocol/protocol_validations_spec.rb +176 -0
  63. data/spec/protocol/source_spec.rb +65 -0
  64. data/spec/protocol_spec.rb +91 -37
  65. data/spec/receiver_model_spec.rb +111 -0
  66. data/spec/serialization/zaml_spec.rb +81 -0
  67. data/spec/serialization/zaml_test.rb +473 -0
  68. data/spec/serialization_spec.rb +63 -0
  69. data/spec/spec_helper.rb +24 -7
  70. data/spec/support/icss_test_helper.rb +67 -0
  71. data/spec/support/load_example_protocols.rb +17 -0
  72. data/spec/type/base_type_spec.rb +0 -0
  73. data/spec/type/named_type_spec.rb +75 -0
  74. data/spec/type/record_field_spec.rb +44 -0
  75. data/spec/type/record_model_spec.rb +206 -0
  76. data/spec/type/record_schema_spec.rb +161 -0
  77. data/spec/type/record_type_spec.rb +155 -0
  78. data/spec/type/simple_types_spec.rb +121 -0
  79. data/spec/type/structured_schema_spec.rb +300 -0
  80. data/spec/type/type_catalog_spec.rb +44 -0
  81. data/spec/type/type_factory_spec.rb +93 -0
  82. data/spec/type/union_schema_spec.rb +0 -0
  83. data/spec/type_spec.rb +63 -0
  84. metadata +205 -144
  85. data/CHANGELOG.textile +0 -9
  86. data/Gemfile.lock +0 -40
  87. data/README.textile +0 -29
  88. data/lib/icss/brevity.rb +0 -136
  89. data/lib/icss/code_asset.rb +0 -16
  90. data/lib/icss/core_ext.rb +0 -9
  91. data/lib/icss/data_asset.rb +0 -22
  92. data/lib/icss/old.rb +0 -96
  93. data/lib/icss/protocol_set.rb +0 -48
  94. data/lib/icss/sample_message_call.rb +0 -142
  95. data/lib/icss/target.rb +0 -72
  96. data/lib/icss/type/factory.rb +0 -196
  97. data/lib/icss/validations.rb +0 -16
  98. 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
@@ -1,28 +1,65 @@
1
1
  module Icss
2
- Message.class_eval do
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
- def api_url
12
- # all calls accept xml or json
13
- "http://api.infochimps.com/#{path}?#{query_string}"
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
- RecordField.class_eval do
18
- def title
19
- return "!!missing!!" if type.blank?
20
- # case type
21
- # when ArrayType then "array of #{type.title} #{type.to_hash.inspect}"
22
- # else type.title
23
- # end
24
- type.title
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
+