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
File without changes
|
@@ -0,0 +1,184 @@
|
|
1
|
+
module Icss
|
2
|
+
module Meta
|
3
|
+
|
4
|
+
#
|
5
|
+
# Record, Error, Enum and Fixed are named types. Each has a fullname that is
|
6
|
+
# composed of two parts; a name and a namespace. Equality of names is
|
7
|
+
# defined on the fullname.
|
8
|
+
#
|
9
|
+
# The name portion of a fullname, and record field names, must:
|
10
|
+
#
|
11
|
+
# * start with [A-Za-z_]
|
12
|
+
# * subsequently contain only [A-Za-z0-9_]
|
13
|
+
# * A namespace is a dot-separated sequence of such names.
|
14
|
+
#
|
15
|
+
# References to previously defined names are as in the latter two cases above:
|
16
|
+
# if they contain a dot they are a fullname, if they do not contain a dot, the
|
17
|
+
# namespace is the namespace of the enclosing definition.
|
18
|
+
#
|
19
|
+
# Simple type names have no namespace and their names may not be defined in
|
20
|
+
# any namespace. A schema may only contain multiple definitions of a
|
21
|
+
# fullname if the definitions are equivalent.
|
22
|
+
#
|
23
|
+
module NamedType
|
24
|
+
def doc() "" end
|
25
|
+
def doc=(str)
|
26
|
+
singleton_class.class_eval do
|
27
|
+
remove_possible_method(:doc)
|
28
|
+
define_method(:doc){ str }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
#
|
32
|
+
def fullname
|
33
|
+
::Icss::Meta::Type.fullname_for(self.name)
|
34
|
+
end
|
35
|
+
def basename
|
36
|
+
@basename ||= fullname.to_s.gsub(/.*[\.]/, "")
|
37
|
+
end
|
38
|
+
def namespace
|
39
|
+
@namespace ||= fullname.to_s.include?('.') ? fullname.to_s.gsub(/\.[^\.]+\z/so, '') : ''
|
40
|
+
end
|
41
|
+
def pathname
|
42
|
+
fullname.to_s.gsub(/\./, '/')
|
43
|
+
end
|
44
|
+
#
|
45
|
+
def to_schema
|
46
|
+
if respond_to?(:_schema) then return _schema.to_hash ; end
|
47
|
+
{
|
48
|
+
:name => fullname,
|
49
|
+
# :namespace => namespace,
|
50
|
+
:doc => doc,
|
51
|
+
}.compact_blank
|
52
|
+
end
|
53
|
+
|
54
|
+
def is_core?
|
55
|
+
respond_to?(:_schema) && _schema.is_core?
|
56
|
+
end
|
57
|
+
|
58
|
+
# ---------------------------------------------------------------------------
|
59
|
+
#
|
60
|
+
# Type Factory methods
|
61
|
+
#
|
62
|
+
|
63
|
+
#
|
64
|
+
# Returns the metamodel -- a module extending the type, on which all the
|
65
|
+
# accessors and receive methods are inscribed. (This allows you to call
|
66
|
+
# +super()+ from within receive_foo)
|
67
|
+
#
|
68
|
+
def metamodel
|
69
|
+
return @metamodel if @metamodel
|
70
|
+
@metamodel = Icss::Meta::NamedType.get_meta_module(self.to_s)
|
71
|
+
self.class_eval{ include(@metamodel) }
|
72
|
+
@metamodel
|
73
|
+
end
|
74
|
+
|
75
|
+
#
|
76
|
+
# Manufactures klass and metamodel
|
77
|
+
#
|
78
|
+
# for type science.astronomy.ufo_sighting, we synthesize
|
79
|
+
# * a module, ::Icss::Meta::Science::Astronomy::UfoSightingType
|
80
|
+
# * a class, ::Icss::Science::Astronomy::UfoSighting
|
81
|
+
#
|
82
|
+
# If no superklass is given, Icss::Entity is used.
|
83
|
+
def self.make(fullname, superklass)
|
84
|
+
klass = get_model_klass(fullname, superklass)
|
85
|
+
metamodel = get_meta_module(klass.to_s)
|
86
|
+
klass.class_eval{ extend(::Icss::Meta::NamedType) }
|
87
|
+
klass.class_eval{ include(metamodel) }
|
88
|
+
[klass, metamodel]
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def define_metamodel_method(meth_name, visibility=:public, &blk)
|
94
|
+
metamodel.class_eval do
|
95
|
+
define_method(meth_name, &blk) unless method_defined?(meth_name)
|
96
|
+
case visibility
|
97
|
+
when :protected then protected meth_name
|
98
|
+
when :private then private meth_name
|
99
|
+
when :public then public meth_name
|
100
|
+
else raise ArgumentError, "visibility must be :public, :private or :protected"
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns the klass for the given scope and name, starting with '::Icss'
|
106
|
+
# and creating all necessary parents along the way. Note that if the given
|
107
|
+
# class or its parent scopes already exist, they're trusted to be correct
|
108
|
+
# -- we don't do any error checking as to their type or superclass.
|
109
|
+
#
|
110
|
+
# @example
|
111
|
+
# Icss::Meta::Type.get_model_klass('this.that.the_other')
|
112
|
+
# # Icss::This::That::TheOther
|
113
|
+
#
|
114
|
+
# @param scope_names [Array of String]
|
115
|
+
# @param superklass [Class] - the superclass to use if the class doesn't exist.
|
116
|
+
def self.get_model_klass(fn, superklass)
|
117
|
+
fullname = Icss::Meta::Type.fullname_for(fn)
|
118
|
+
return Class.new(superklass) if fullname.nil?
|
119
|
+
#
|
120
|
+
scope_names = scope_names_for(fullname)
|
121
|
+
klass_name = scope_names.pop
|
122
|
+
parent_module = get_nested_module(%w[Icss] + scope_names)
|
123
|
+
|
124
|
+
# const_defined?(klass, inherit), const_get(klass, inherit)
|
125
|
+
# inherit = false makes these methods be scoped to parent_module instead of universally
|
126
|
+
if parent_module.const_defined?(klass_name, false)
|
127
|
+
klass = parent_module.const_get(klass_name, false)
|
128
|
+
# #{superklass.object_id}: #{klass.ancestors.map{|o| [o, o.object_id] }}
|
129
|
+
unless klass.ancestors.include?(superklass)
|
130
|
+
warn "+++++++++++++++++++++++++++++++ Superclass and is_a? mismatch for #{klass.inspect} (doesn't inherit from #{superklass.inspect})"
|
131
|
+
# p [klass_name, klass, Icss::Thing.object_id, ::Icss::Thing.object_id, Icss.const_get(:Thing).object_id,]
|
132
|
+
# p klass .ancestors.flatten.map{|t| [t, t.object_id]}
|
133
|
+
# p Icss::Thing.ancestors.flatten.map{|t| [t, t.object_id]}
|
134
|
+
end
|
135
|
+
klass
|
136
|
+
else
|
137
|
+
parent_module.const_set(klass_name, Class.new(superklass))
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns the meta-module for the given scope and name, starting with
|
142
|
+
# '::Icss::Meta' and creating all necessary parents along the way.
|
143
|
+
# @example
|
144
|
+
# Icss::Meta::TypeFactory.get_meta_module(["This", "That"], "TheOther")
|
145
|
+
# # Icss::Meta::This::That::TheOtherModel
|
146
|
+
def self.get_meta_module(fullname)
|
147
|
+
fullname = Icss::Meta::Type.fullname_for(fullname)
|
148
|
+
return Module.new if fullname.nil?
|
149
|
+
#
|
150
|
+
scope_names = scope_names_for(fullname)
|
151
|
+
scope_names[-1] += "Model"
|
152
|
+
get_nested_module(%w[Icss Meta] + scope_names)
|
153
|
+
end
|
154
|
+
|
155
|
+
# Turns a dotted namespace.name into camelized rubylike names for a class
|
156
|
+
# @example
|
157
|
+
# scope_names_for('this.that.the_other')
|
158
|
+
# # ["This", "That", "TheOther"]
|
159
|
+
def self.scope_names_for(fullname)
|
160
|
+
fullname.split('.').map(&:camelize)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns a module for the given scope names, rooted always at Object (so
|
164
|
+
# implicity with '::').
|
165
|
+
# @example
|
166
|
+
# get_nested_module(["This", "That", "TheOther"])
|
167
|
+
# # This::That::TheOther
|
168
|
+
def self.get_nested_module(scope_names)
|
169
|
+
scope_names.inject(Object) do |parent_module, module_name|
|
170
|
+
|
171
|
+
# const_defined?(klass, inherit), const_get(klass, inherit)
|
172
|
+
# inherit = false makes these methods be scoped to parent_module instead of universally
|
173
|
+
if parent_module.const_defined?(module_name, false)
|
174
|
+
parent_module.const_get(module_name, false)
|
175
|
+
else
|
176
|
+
parent_module.const_set(module_name.to_sym, Module.new)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
module Icss
|
2
|
+
module Meta
|
3
|
+
|
4
|
+
class RecordField
|
5
|
+
include Icss::Meta::RecordModel
|
6
|
+
include Icss::ReceiverModel::ActsAsHash
|
7
|
+
include Gorillib::Hashlike
|
8
|
+
include Gorillib::Hashlike::Keys
|
9
|
+
include Icss::ReceiverModel
|
10
|
+
include Icss::ReceiverModel::ActiveModelShim
|
11
|
+
remove_possible_method(:type)
|
12
|
+
|
13
|
+
ALLOWED_ORDERS = %w[ascending descending ignore].freeze unless defined?(ALLOWED_ORDERS)
|
14
|
+
|
15
|
+
field :name, Symbol, :required => true
|
16
|
+
field :type, Icss::Meta::TypeFactory, :required => true
|
17
|
+
field :doc, String
|
18
|
+
field :default, Icss::Meta::IdenticalFactory
|
19
|
+
field :replace, Hash
|
20
|
+
field :required, Boolean
|
21
|
+
field :aliases, Array, :items => Symbol
|
22
|
+
field :order, String, :validates => { :inclusion => { :in => ALLOWED_ORDERS } }
|
23
|
+
field :accessor, Symbol
|
24
|
+
field :receiver, Symbol
|
25
|
+
field :validates, Hash
|
26
|
+
rcvr_remaining :_extra_params
|
27
|
+
attr_accessor :parent
|
28
|
+
|
29
|
+
# FIXME: cruft
|
30
|
+
field :indexed_on, Symbol
|
31
|
+
field :identifier, Boolean, :doc => 'indicates the field is suitable for use'
|
32
|
+
|
33
|
+
after_receive(:warnings) do |hsh|
|
34
|
+
warn "Extra params given to field #{self}: #{_extra_params.inspect}" if _extra_params.present?
|
35
|
+
warn "Validation failed for field #{self}: #{errors.inspect}" if respond_to?(:errors) && (not valid?)
|
36
|
+
end
|
37
|
+
|
38
|
+
# track recursion of type references
|
39
|
+
after_receive(:am_i_a_reference) do |hsh|
|
40
|
+
hsh = hsh.symbolize_keys
|
41
|
+
nonreference_klasses = [::Icss::Meta::ArrayType, ::Icss::Meta::HashType, ::Icss::Meta::EnumType, ::Icss::Meta::FixedType]
|
42
|
+
|
43
|
+
@is_reference = (hsh[:type].is_a?(String) || hsh[:type].is_a?(Symbol) || (hsh[:type].is_a?(Class) && nonreference_klasses.none?{|klass| hsh[:type].is_a?(klass) }))
|
44
|
+
end
|
45
|
+
|
46
|
+
# is the field a reference to a named type (true), or an inline schema (false)?
|
47
|
+
def is_reference?() @is_reference ; end
|
48
|
+
|
49
|
+
def to_hash()
|
50
|
+
hsh = super
|
51
|
+
hsh = hsh.merge({ :type => (is_reference? ? type.fullname : Type.schema_for(type)) })
|
52
|
+
hsh.delete(:_extra_params)
|
53
|
+
hsh
|
54
|
+
end
|
55
|
+
def to_schema
|
56
|
+
to_hash
|
57
|
+
end
|
58
|
+
|
59
|
+
# Hack hack -- makes it so fields go thru the receiver door when merged in RecordType
|
60
|
+
def merge(hsh)
|
61
|
+
dup.receive!(hsh)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Order is defined by the avro spec
|
65
|
+
def order() @order || 'ascending' ; end
|
66
|
+
def order_direction() case order when 'ascending' then 1 when 'descending' then -1 else 0 ; end ; end
|
67
|
+
end
|
68
|
+
|
69
|
+
module RecordType
|
70
|
+
protected
|
71
|
+
# create new field_schema as a RecordField object, not Hash
|
72
|
+
def make_field_schema
|
73
|
+
RecordField.new
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Icss
|
2
|
+
module Meta
|
3
|
+
|
4
|
+
module RecordModel
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(Icss::Meta::RecordType)
|
7
|
+
base.metamodel
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# modify object in place with new typecast values.
|
12
|
+
#
|
13
|
+
def receive!(hsh={})
|
14
|
+
raise ArgumentError, "Can't receive (it isn't hashlike): {#{hsh.inspect}}" unless hsh.respond_to?(:[]) && hsh.respond_to?(:has_key?)
|
15
|
+
self.class.send(:_rcvr_methods).each do |attr, meth|
|
16
|
+
if hsh.has_key?(attr) then val = hsh[attr]
|
17
|
+
elsif hsh.has_key?(attr.to_s) then val = hsh[attr.to_s]
|
18
|
+
else next ; end
|
19
|
+
self.send(meth, val)
|
20
|
+
end
|
21
|
+
run_after_receivers(hsh)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
# true if the attr is a receiver variable and it has been set
|
26
|
+
def attr_set?(attr)
|
27
|
+
self.class.has_field?(attr) && self.instance_variable_defined?("@#{attr}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def unset!(attr)
|
31
|
+
self.send(:remove_instance_variable, "@#{attr}") if self.instance_variable_defined?("@#{attr}")
|
32
|
+
end
|
33
|
+
protected :unset!
|
34
|
+
|
35
|
+
protected
|
36
|
+
|
37
|
+
def run_after_receivers(hsh)
|
38
|
+
self.class.after_receivers.each do |after_hook_name, after_hook|
|
39
|
+
self.instance_exec(hsh, &after_hook)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def _set_field_val(field_name, val)
|
44
|
+
self.instance_variable_set("@#{field_name}", val)
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Icss
|
2
|
+
module Meta
|
3
|
+
|
4
|
+
class RecordSchema < SimpleSchema
|
5
|
+
include Icss::Meta::RecordModel
|
6
|
+
include Icss::ReceiverModel::ActsAsHash
|
7
|
+
include Gorillib::Hashlike
|
8
|
+
include Gorillib::Hashlike::Keys
|
9
|
+
#
|
10
|
+
field :_domain_id_field, String, :default => 'name'
|
11
|
+
field :_doc_hints, Hash, :default => {}
|
12
|
+
field :fields, Array, :default => []
|
13
|
+
#
|
14
|
+
|
15
|
+
def type() :record ; end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
{
|
19
|
+
:name => basename,
|
20
|
+
:namespace => namespace,
|
21
|
+
:type => type,
|
22
|
+
:is_a => (respond_to?(:is_a) ? is_a : []),
|
23
|
+
:doc => doc,
|
24
|
+
:fields => fields.map(&:to_schema),
|
25
|
+
}.compact_blank
|
26
|
+
end
|
27
|
+
|
28
|
+
def model_klass
|
29
|
+
return @model_klass if @model_klass
|
30
|
+
super
|
31
|
+
@model_klass.class_eval{ include(::Icss::Meta::RecordModel)}
|
32
|
+
self.fields.each do |field_schema|
|
33
|
+
@model_klass.field(field_schema[:name], field_schema[:type], field_schema)
|
34
|
+
end
|
35
|
+
@model_klass
|
36
|
+
end
|
37
|
+
|
38
|
+
def receive_fields(flds)
|
39
|
+
super(flds.map(&:symbolize_keys!))
|
40
|
+
end
|
41
|
+
|
42
|
+
def attrs_to_inscribe
|
43
|
+
[ :doc, :fullname, :is_a, :_domain_id_field, :_doc_hints ]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
class ErrorSchema
|
48
|
+
include Icss::Meta::RecordType
|
49
|
+
end
|
50
|
+
module ErrorModel
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,325 @@
|
|
1
|
+
require 'icss/type/type_factory'
|
2
|
+
|
3
|
+
module Icss
|
4
|
+
module Meta
|
5
|
+
|
6
|
+
#
|
7
|
+
# RecordType -- class methods for a RecordModel
|
8
|
+
#
|
9
|
+
# Endows the model class with
|
10
|
+
# * klass.field -- adds a field
|
11
|
+
# * klass.fields -- list of record_fields
|
12
|
+
#
|
13
|
+
# * klass.after_receive(&blk) -- blocks to execute after .receive is called
|
14
|
+
# * klass.after_receivers -- list of after_receivers
|
15
|
+
# * klass.metamodel -- overlay module that actually carries the model's instance methods
|
16
|
+
# * klass.to_schema --
|
17
|
+
module RecordType
|
18
|
+
include Icss::Meta::NamedType
|
19
|
+
|
20
|
+
#
|
21
|
+
# Returns a new instance with the given hash used to set all rcvrs.
|
22
|
+
#
|
23
|
+
# All args up to the last one are passed to the initializer.
|
24
|
+
# The last arg must be a hash -- its attributes are set on the newly-created object
|
25
|
+
#
|
26
|
+
# @param hsh [Hash] attr-value pairs to set on the newly created object.
|
27
|
+
# @param *args [Array] arguments to pass to the constructor
|
28
|
+
# @return [Object] a new instance
|
29
|
+
def receive *args
|
30
|
+
hsh = args.pop
|
31
|
+
raise ArgumentError, "#{self} can't receive '#{hsh.inspect}' (it isn't hashlike)" unless hsh.respond_to?(:[]) && hsh.respond_to?(:has_key?)
|
32
|
+
obj = self.new(*args)
|
33
|
+
obj.receive!(hsh)
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Describes a field in a Record object.
|
38
|
+
#
|
39
|
+
# Each field has the following attributes:
|
40
|
+
#
|
41
|
+
# @param [Symbol] name -- a string providing the name of the field
|
42
|
+
# (required)
|
43
|
+
#
|
44
|
+
# @param [Class, Icss::Meta::Type] type a schema, or a string or symbol
|
45
|
+
# naming a record definition (required)
|
46
|
+
#
|
47
|
+
# avro type json type ruby type kind example
|
48
|
+
# --------- --------- ---------- -------- ---------
|
49
|
+
# null null NilClass simple nil
|
50
|
+
# boolean boolean Boolean simple true
|
51
|
+
# int,long integer Integer simple 1
|
52
|
+
# float,double number Float simple 1.1
|
53
|
+
# bytes string String simple "\u00FF"
|
54
|
+
# string string String simple "foo"
|
55
|
+
# record object RecordModel named {"a": 1}
|
56
|
+
# enum string Enum named "FOO"
|
57
|
+
# array array Array container [1]
|
58
|
+
# map object Hash container { "a": 1 }
|
59
|
+
# fixed string String container "\u00ff"
|
60
|
+
# union object XxxFactory union
|
61
|
+
# time string Time simple "2011-01-02T03:04:05Z"
|
62
|
+
#
|
63
|
+
# @option schema [String] :doc -- description of field for users (optional)
|
64
|
+
#
|
65
|
+
# @option schema [Object] :default -- a default value for this field, used
|
66
|
+
# when reading instances that lack this field (optional).
|
67
|
+
# Permitted values depend on the field's schema type, according to the
|
68
|
+
# table below. Default values for union fields correspond to the first
|
69
|
+
# schema in the union. Default values for bytes and fixed fields are
|
70
|
+
# JSON strings, where Unicode code points 0-255 are mapped to unsigned
|
71
|
+
# 8-bit byte values 0-255.
|
72
|
+
#
|
73
|
+
# @option schema [String] :order -- specifies how this field impacts sort
|
74
|
+
# ordering of this record (optional).
|
75
|
+
# Valid values are "ascending" (the default), "descending", or
|
76
|
+
# "ignore". For more details on how this is used, see the the sort
|
77
|
+
# order section below.
|
78
|
+
#
|
79
|
+
# @option schema [Boolean] :required -- same as :validates => :presence
|
80
|
+
#
|
81
|
+
# @option schema [Hash] :validates -- sends the validation on to
|
82
|
+
# Icss::Type::Validations. Uses syntax parallel to ActiveModel's:
|
83
|
+
#
|
84
|
+
# @option schema [Symbol] :accessor -- with +:none+, no accessor is
|
85
|
+
# created. With +:protected+, +:private+, or +:public+, applies
|
86
|
+
# corresponding access rule.
|
87
|
+
#
|
88
|
+
# :presence => true
|
89
|
+
# :uniqueness => true
|
90
|
+
# :numericality => true
|
91
|
+
# :length => { :minimum => 0, maximum => 2000 }
|
92
|
+
# :format => { :with => /.*/ }
|
93
|
+
# :inclusion => { :in => [1,2,3] }
|
94
|
+
# :exclusion => { :in => [1,2,3] }
|
95
|
+
#
|
96
|
+
def field(field_name, type, schema={})
|
97
|
+
field_name = field_name.to_sym
|
98
|
+
#
|
99
|
+
schema = add_field_schema(field_name, type, schema)
|
100
|
+
add_field_accessor(field_name, schema)
|
101
|
+
rcvr(field_name, schema)
|
102
|
+
add_validator(field_name) if respond_to?(:add_validator)
|
103
|
+
end
|
104
|
+
|
105
|
+
def field_names
|
106
|
+
all_f = []
|
107
|
+
call_ancestor_chain(:field_names){|anc_f| all_f = all_f | anc_f }
|
108
|
+
all_f
|
109
|
+
end
|
110
|
+
|
111
|
+
def fields
|
112
|
+
field_schemas.values # _at(*field_names)
|
113
|
+
end
|
114
|
+
|
115
|
+
def field_named(fn)
|
116
|
+
fn = fn.to_sym
|
117
|
+
ancestors.each{|anc| hsh = anc.instance_variable_get('@field_schemas') or next ; return(hsh[fn]) if hsh[fn] }
|
118
|
+
nil
|
119
|
+
end
|
120
|
+
|
121
|
+
def has_field?(fn)
|
122
|
+
!! field_schemas.has_key?(fn.to_sym)
|
123
|
+
end
|
124
|
+
|
125
|
+
#
|
126
|
+
# define a receiver attribute.
|
127
|
+
#
|
128
|
+
# @param [Symbol] field_name - name of the receiver property
|
129
|
+
# @param [Class] type - a
|
130
|
+
#
|
131
|
+
# @option [Object] :default - After any receive! operation, attribute is set to this value unless attr_set? is true
|
132
|
+
# @option [Class] :items - For collections (Array, Hash, etc), the type of the collection's items
|
133
|
+
#
|
134
|
+
def rcvr(field_name, schema={})
|
135
|
+
return if schema[:receiver] == :none
|
136
|
+
klass = schema[:type]
|
137
|
+
define_metamodel_method("receive_#{field_name}") do |val|
|
138
|
+
_set_field_val(field_name, klass.receive(val))
|
139
|
+
end
|
140
|
+
_register_rcvr_for(field_name, "receive_#{field_name}")
|
141
|
+
add_after_receivers(field_name)
|
142
|
+
end
|
143
|
+
|
144
|
+
def rcvr_alias(fake_attr, field_name)
|
145
|
+
_register_rcvr_for(fake_attr, "receive_#{field_name}")
|
146
|
+
end
|
147
|
+
|
148
|
+
#
|
149
|
+
def to_schema
|
150
|
+
{
|
151
|
+
:name => fullname,
|
152
|
+
:namespace => namespace,
|
153
|
+
:type => :record,
|
154
|
+
:is_a => (respond_to?(:is_a) ? is_a : []),
|
155
|
+
:doc => doc,
|
156
|
+
:fields => fields.map(&:to_hash),
|
157
|
+
}.compact_blank
|
158
|
+
end
|
159
|
+
|
160
|
+
#
|
161
|
+
# Defines a receiver for attributes sent to receive! that are
|
162
|
+
# * not defined as receivers
|
163
|
+
# * field's name does not start with '_'
|
164
|
+
#
|
165
|
+
# @example
|
166
|
+
# class Foo ; include RecordModel
|
167
|
+
# field :bob, String
|
168
|
+
# rcvr_remaining :other_params
|
169
|
+
# end
|
170
|
+
# foo_obj = Foo.receive(:bob => 'hi, bob", :joe => 'hi, joe')
|
171
|
+
# # => <Foo @bob='hi, bob' @other_params={ :joe => 'hi, joe' }>
|
172
|
+
def rcvr_remaining(field_name, schema={})
|
173
|
+
schema[:values] ||= Object
|
174
|
+
field(field_name, Hash, schema)
|
175
|
+
after_receive(:rcvr_remaining) do |hsh|
|
176
|
+
remaining_vals_hsh = hsh.reject{|k,v| (self.class.has_field?(k)) || (k.to_s =~ /^_/) }
|
177
|
+
self.send("receive_#{field_name}", remaining_vals_hsh)
|
178
|
+
end
|
179
|
+
add_after_receivers(field_name)
|
180
|
+
end
|
181
|
+
|
182
|
+
# make a block to run after each time .receive! is invoked
|
183
|
+
def after_receive(after_hook_name, &after_hook)
|
184
|
+
@after_receivers ||= {}
|
185
|
+
@after_receivers[after_hook_name] = after_hook
|
186
|
+
end
|
187
|
+
|
188
|
+
# after_receive blocks for self and all ancestors
|
189
|
+
def after_receivers
|
190
|
+
all_f = {} # @after_receivers || {}
|
191
|
+
call_ancestor_chain(:after_receivers){|anc_f| all_f.merge!(anc_f) }
|
192
|
+
all_f
|
193
|
+
end
|
194
|
+
|
195
|
+
protected
|
196
|
+
|
197
|
+
#
|
198
|
+
# yield, in turn, the given instance variable from each ancestor having
|
199
|
+
# that variable. Ancestors are called from parent to great-grandparent
|
200
|
+
#
|
201
|
+
# So you're asking yourself "Self, why not just call .super?
|
202
|
+
#
|
203
|
+
# Consider:
|
204
|
+
#
|
205
|
+
# class Base
|
206
|
+
# extend(Icss::Meta::RecordType)
|
207
|
+
# field :smurfiness, Integer
|
208
|
+
# end
|
209
|
+
# class Poppa < Base
|
210
|
+
# field :height, Integer
|
211
|
+
# end
|
212
|
+
#
|
213
|
+
# Poppa.field_names calls Icss::Meta::RecordType --
|
214
|
+
# it's the first member of its inheritance chain to define the method.
|
215
|
+
# We want it to do so for each ancestor that has added fields.
|
216
|
+
def call_ancestor_chain(attr)
|
217
|
+
# if caller.length > 200
|
218
|
+
# puts "\n\n********\n#{self}\n#{attr}\n#{ancestors.inspect}"
|
219
|
+
# puts caller.grep(%r{lib/icss})
|
220
|
+
# end
|
221
|
+
ivar = "@#{attr}"
|
222
|
+
self.ancestors.reverse.each do |ancestor|
|
223
|
+
yield(ancestor.instance_variable_get(ivar)) if ancestor.instance_variable_defined?(ivar)
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
def make_field_schema
|
228
|
+
Hash.new
|
229
|
+
end
|
230
|
+
|
231
|
+
def field_schemas
|
232
|
+
all_f = {}
|
233
|
+
call_ancestor_chain(:field_schemas){|anc_f| all_f.merge!(anc_f) }
|
234
|
+
all_f
|
235
|
+
end
|
236
|
+
|
237
|
+
# register the field schema internally.
|
238
|
+
# To preserve field order for 1.8.7, we track field names as an array
|
239
|
+
def add_field_schema(name, type, schema)
|
240
|
+
@field_names ||= [] ; @field_schemas ||= {}
|
241
|
+
@field_names = @field_names | [name]
|
242
|
+
schema = schema.symbolize_keys.merge({ :name => name })
|
243
|
+
#
|
244
|
+
# FIXME: this is terrible, especially given what's in TypeFactory anyway.
|
245
|
+
#
|
246
|
+
schema[:type] =
|
247
|
+
case
|
248
|
+
when type == Hash && schema.has_key?(:values) then Icss::Meta::TypeFactory.receive({ :type => :hash, :values => schema.delete(:values)})
|
249
|
+
when type == Array && schema.has_key?(:items) then Icss::Meta::TypeFactory.receive({ :type => :array, :items => schema.delete(:items) })
|
250
|
+
when type == Hash then IdenticalHashFactory
|
251
|
+
when type == Array then IdenticalArrayFactory
|
252
|
+
else
|
253
|
+
Icss::Meta::TypeFactory.receive(type)
|
254
|
+
end
|
255
|
+
fld = (field_schemas[name] || make_field_schema).merge(schema)
|
256
|
+
fld[:parent] = self
|
257
|
+
@field_schemas[name] = fld
|
258
|
+
end
|
259
|
+
|
260
|
+
def add_field_accessor(field_name, schema)
|
261
|
+
visibility = schema[:accessor] || :public
|
262
|
+
reader_meth = field_name ; writer_meth = "#{field_name}=" ; attr_name = "@#{field_name}"
|
263
|
+
unless (visibility == :none)
|
264
|
+
define_metamodel_method(reader_meth, visibility){ instance_variable_get(attr_name) }
|
265
|
+
define_metamodel_method(writer_meth, visibility){|v| instance_variable_set(attr_name, v) }
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def _rcvr_methods
|
270
|
+
all_f = {}
|
271
|
+
call_ancestor_chain(:_rcvr_methods){|anc_f| all_f.merge!(anc_f) }
|
272
|
+
all_f
|
273
|
+
end
|
274
|
+
|
275
|
+
def _register_rcvr_for(attr_name, rcvr_meth)
|
276
|
+
@_rcvr_methods ||= {}
|
277
|
+
@_rcvr_methods[attr_name.to_sym] = rcvr_meth.to_sym
|
278
|
+
end
|
279
|
+
|
280
|
+
# Adds after_receivers to implement some of the options to .field
|
281
|
+
#
|
282
|
+
# @option schema [Object] :default -- if field is unset by time of
|
283
|
+
# after_receive, the field will be set to a copy of this value
|
284
|
+
#
|
285
|
+
# @option schema [Hash] :replace -- if value is in the hash
|
286
|
+
# class Foo < Icss::Thing
|
287
|
+
# field :temperature, Integer, :replace => { 9999 => nil }
|
288
|
+
# end
|
289
|
+
# f = Foo.receive({:temperature => 9999})
|
290
|
+
# # #<Foo:0x10156c820 @temperature=nil>
|
291
|
+
#
|
292
|
+
def add_after_receivers(field_name)
|
293
|
+
schema = field_named(field_name)
|
294
|
+
set_field_default(field_name, schema[:default]) if schema.has_key?(:default)
|
295
|
+
if schema.has_key?(:replace)
|
296
|
+
repl = schema[:replace]
|
297
|
+
after_receive(:"replace_#{field_name}") do
|
298
|
+
val = self.send(field_name)
|
299
|
+
if repl.has_key?(val)
|
300
|
+
self._set_field_val(field_name, repl[val])
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
# super(field_name) if defined?(super)
|
305
|
+
end
|
306
|
+
|
307
|
+
|
308
|
+
def set_field_default(field_name, default_val=nil, &blk)
|
309
|
+
blk = default_val if default_val.is_a?(Proc)
|
310
|
+
if blk
|
311
|
+
after_receive(:"default_#{field_name}") do
|
312
|
+
val = instance_exec(&blk)
|
313
|
+
self._set_field_val(field_name, val) unless attr_set?(field_name)
|
314
|
+
end
|
315
|
+
else
|
316
|
+
after_receive(:"default_#{field_name}") do
|
317
|
+
val = default_val.try_dup
|
318
|
+
self._set_field_val(field_name, val) unless attr_set?(field_name)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
end # RecordType
|
324
|
+
end
|
325
|
+
end
|