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.
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,18 @@
1
+ module Icss
2
+ module Meta
3
+ class CodeAsset
4
+ include Icss::ReceiverModel
5
+
6
+ field :name, String
7
+ field :location, String
8
+
9
+ def to_hash()
10
+ { :name => name, :location => location}
11
+ end
12
+
13
+ def to_json() to_hash.to_json ; end
14
+
15
+ end
16
+ end
17
+
18
+ end
@@ -0,0 +1,23 @@
1
+ module Icss
2
+ module Meta
3
+ class DataAsset
4
+ include Icss::ReceiverModel
5
+
6
+ field :name, String
7
+ field :location, String
8
+ # overriding ruby's deprecated but still present type attr on objects
9
+ attr_accessor :type
10
+ field :type, String
11
+ field :doc, String
12
+
13
+ def named? nm
14
+ name == nm
15
+ end
16
+
17
+ def to_hash()
18
+ { :name => name, :location => location, :type => type, :doc => doc }
19
+ end
20
+ def to_json() to_hash.to_json ; end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,41 @@
1
+ module Icss
2
+ module Meta
3
+ class License
4
+ include Icss::ReceiverModel
5
+ include Icss::ReceiverModel::ActsAsCatalog
6
+
7
+ field :license_id, String
8
+ field :title, String
9
+ field :url, String
10
+ field :description, String
11
+ field :summary, String
12
+ field :article_body, String
13
+
14
+ def fullname
15
+ license_id
16
+ end
17
+
18
+ def name
19
+ license_id.split('.').last
20
+ end
21
+ alias_method :basename, :name
22
+
23
+ def self.catalog_sections
24
+ ['licenses']
25
+ end
26
+
27
+ def to_hash()
28
+ { :license_id => license_id,
29
+ :title => title,
30
+ :url => url,
31
+ :description => description,
32
+ :summary => summary,
33
+ :article_body => article_body }
34
+ end
35
+
36
+ def to_json() to_hash.to_json ; end
37
+
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,37 @@
1
+ module Icss
2
+ module Meta
3
+ class Source
4
+ include Icss::ReceiverModel
5
+ include Icss::ReceiverModel::ActsAsCatalog
6
+
7
+ field :source_id, String
8
+ field :title, String
9
+ field :description, String
10
+ field :url, String
11
+
12
+ def fullname
13
+ source_id
14
+ end
15
+
16
+ def name
17
+ source_id.split('.').last
18
+ end
19
+ alias_method :basename, :name
20
+
21
+ def self.catalog_sections
22
+ ['sources']
23
+ end
24
+
25
+ def to_hash()
26
+ { :source_id => source_id,
27
+ :title => title,
28
+ :description => description,
29
+ :url => url }
30
+ end
31
+
32
+ def to_json() to_hash.to_json ; end
33
+
34
+ end
35
+ end
36
+
37
+ end
@@ -0,0 +1,68 @@
1
+ module Icss
2
+
3
+ #
4
+ # Instantiates an array of target objects
5
+ #
6
+ class TargetListFactory
7
+ def self.receive target_info_list, target_name
8
+ klass = "Icss::#{target_name.to_s.camelize}Target".constantize
9
+ target_info_list.map{|target_info| klass.receive(target_info)}
10
+ end
11
+ end
12
+
13
+ class Target
14
+ include Icss::ReceiverModel
15
+ field :name, String
16
+ alias_method :basename, :name
17
+ end
18
+
19
+ class MysqlTarget < Target
20
+ field :data_assets, Array, :items => String
21
+ field :database, String
22
+ field :table_name, String
23
+ end
24
+
25
+ class ApeyeyeTarget < Target
26
+ field :code_assets, Array, :items => String
27
+ end
28
+
29
+ class HbaseTarget < Target
30
+ field :data_assets, Array, :items => String
31
+ field :table_name, String
32
+ field :column_families, Array, :items => String
33
+ field :column_family, String
34
+ field :loader, String
35
+ field :id_field, String
36
+ end
37
+
38
+ class ElasticSearchTarget < Target
39
+ field :data_assets, Array, :items => String
40
+ field :index_name, String
41
+ field :id_field, String
42
+ field :object_type, String
43
+ field :loader, String
44
+ end
45
+
46
+ class GeoIndexTarget < Target
47
+ field :data_assets, Array, :items => String
48
+ field :table_name, String
49
+ field :min_zoom, Integer
50
+ field :max_zoom, Integer
51
+ field :chars_per_page, Integer
52
+ field :sort_field, String
53
+ end
54
+
55
+ class CatalogTarget < Target
56
+ field :name, String
57
+ field :license, String
58
+ field :title, String
59
+ field :link, String
60
+ field :description, String
61
+ field :owner, String
62
+ field :price, Float
63
+ field :tags, Array, :items => String
64
+ field :messages, Array, :items => String
65
+ field :packages, Array, :items => { :type => Hash, :values => Object }
66
+ end
67
+
68
+ end
@@ -0,0 +1,24 @@
1
+ module Icss
2
+ module ReceiverModel
3
+ include Gorillib::Hashlike
4
+ include Icss::ReceiverModel::ActsAsHash
5
+ include Gorillib::Hashlike::TreeMerge
6
+ include Icss::ReceiverModel::ActsAsLoadable
7
+ include Icss::Meta::RecordModel
8
+ include Icss::ReceiverModel::ActsAsTuple
9
+
10
+ module ClassMethods
11
+ include Icss::Meta::RecordType
12
+ include Icss::ReceiverModel::ActsAsTuple::ClassMethods
13
+ end
14
+
15
+ # put all the things in ClassMethods at class level
16
+ def self.included base
17
+ base.class_eval do
18
+ include Icss::ReceiverModel::ActiveModelShim
19
+ extend Icss::ReceiverModel::ClassMethods
20
+ end
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,36 @@
1
+ require 'active_model'
2
+ # require 'active_model/deprecated_error_methods'
3
+ # require 'active_model/errors'
4
+ # require 'active_model/naming'
5
+ # require 'active_model/validator'
6
+ # require 'active_model/translation'
7
+ # require 'active_model/validations'
8
+ # require 'active_support/i18n'
9
+ # I18n.load_path << File.join(File.expand_path(File.dirname(__FILE__)), 'locale/en.yml')
10
+
11
+ module Icss
12
+ module ReceiverModel
13
+
14
+ module ActiveModelShim
15
+
16
+ def to_model
17
+ self
18
+ end
19
+
20
+ def new_record?() true end
21
+
22
+ def destroyed?() false end
23
+
24
+ def errors
25
+ @_errors ||= ActiveModel::Errors.new(self)
26
+ end
27
+
28
+ def self.included(base)
29
+ base.class_eval do
30
+ extend ActiveModel::Naming
31
+ include ActiveModel::Validations
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,170 @@
1
+ require 'active_support/core_ext/module'
2
+
3
+ # including class is required to implement the following methods:
4
+ # * catalog_sections
5
+ # * fullname
6
+
7
+ # including module is required to additionally implement:
8
+ # * receive (if not implemented)
9
+ # * after_receiver (to register objects)
10
+
11
+
12
+ module Icss
13
+ module ReceiverModel
14
+ module ActsAsCatalog
15
+ def self.included(base)
16
+ base.extend(ClassMethods)
17
+ end
18
+
19
+ #
20
+ # Register object in class's registry
21
+ #
22
+ def register(obj)
23
+ self.class.register(obj)
24
+ end
25
+
26
+ module ClassMethods
27
+ include Icss::ReceiverModel::ActsAsLoadable::ClassMethods
28
+
29
+
30
+ #
31
+ # Include ActAsLoadable for file receivers
32
+ # Declare and initialize registry class/module variable to hash
33
+ # Set after_receiver(:register) for classes to register their objects
34
+ #
35
+ def self.extended(base)
36
+ base.class_eval do
37
+ if is_a?(Class)
38
+ include Icss::ReceiverModel::ActsAsLoadable::ClassMethods
39
+ class_attribute :registry
40
+ class_attribute :_catalog_loaded
41
+ after_receive(:register) do |hsh|
42
+ register(self)
43
+ end
44
+ elsif is_a?(Module)
45
+ mattr_accessor :registry
46
+ mattr_accessor :_catalog_loaded
47
+ end
48
+ self.registry = Hash.new
49
+ end
50
+ end
51
+
52
+ #
53
+ # Add object to registry using fullname method as the identifier
54
+ #
55
+
56
+ def register(obj)
57
+ registry[obj.fullname] = obj
58
+ end
59
+
60
+ #
61
+ # Basic ActiveRecord inspired methods
62
+ # Name can include wildcards(*) for simple searching
63
+ # - example: geo.*.location.*
64
+ # matches anything with a namespace starting with 'geo'
65
+ # and containing 'location'
66
+ #
67
+
68
+ def all(name='*')
69
+ find(:all, name)
70
+ end
71
+
72
+ def first(name='*')
73
+ find(:first, name)
74
+ end
75
+
76
+ def last(name='*')
77
+ find(:last, name)
78
+ end
79
+
80
+ #
81
+ # ActiveRecord inspired find method
82
+ # Params:
83
+ # 1. :all, :first, :last, or registry identifier(ignores wildcards to force exact match)
84
+ # 2. registry name with wildcards(*)
85
+ # - example: geo.*.location.*
86
+ #
87
+
88
+ def find(name_or_find_type, name='*')
89
+ if !self._catalog_loaded
90
+ self._catalog_loaded = true
91
+ load_catalog(true)
92
+ end
93
+
94
+ method_name = case name_or_find_type
95
+ when :all then :to_a
96
+ when :first then :first
97
+ when :last then :last
98
+ else
99
+ # If exact match not in registry, try looking in file catalog
100
+ result = (find_in_registry(name_or_find_type, :exact_match => true) || load_files_from_catalog(name_or_find_type, :exact_match => true))
101
+ raise Icss::NotFoundError, "Cannot find #{name_or_find_type}" if result.nil?
102
+ return result
103
+ end
104
+
105
+ # If not in registry, try looking in file catalog
106
+ (find_in_registry(name) || load_files_from_catalog(name)).send method_name
107
+ end
108
+
109
+ def load_from_catalog(fullname)
110
+ filepath = fullname.to_s.gsub(/(\.icss\.yaml)?$/,'').gsub(/\./, '/')
111
+ filenames = catalog_filenames(filepath, [''])
112
+ filenames.each{|filename| receive_from_file(filename) }.compact
113
+ end
114
+
115
+ private
116
+ #
117
+ # Search registry for matching objects
118
+ # Return single object for exact_match=true
119
+ #
120
+ def find_in_registry(name, args={})
121
+ return registry[name] if args.symbolize_keys[:exact_match]
122
+
123
+ name = name.to_s.gsub('*', '[^\./]*').gsub(/\.\//, '\.').gsub(/\*$/, '.+') if name.include?('*')
124
+ name_regexp = Regexp.new("^#{name}$", true)
125
+ registry.select{|k, v| k.match(name_regexp) }.values
126
+ end
127
+
128
+ #
129
+ # Load files from catalog files
130
+ # Namespaces used to correspond to directories
131
+ # Load single file for exact_match=true
132
+ #
133
+ def load_files_from_catalog(name, args={})
134
+ # don't do anything if name is invalid format
135
+ if args.symbolize_keys[:exact_match]
136
+ filename = catalog_filenames(name.to_s.gsub(/\./, '/'))[0]
137
+ receive_from_file(filename) if filename
138
+ elsif /\A([A-Za-z_\*]\w*\.?)+\Z/ === name
139
+ filename = name.to_s.gsub(/(\.icss\.yaml)?$/,'').gsub(/\./, '/').gsub(/\*$/, '**/*')
140
+ filenames = catalog_filenames(filename)
141
+ filenames.collect{|filename| receive_from_file(filename) } unless filenames.empty?
142
+ end
143
+ # Return object/s from registry after loading files
144
+ # Useful for unexpected file contents / protocols containing many types
145
+ find_in_registry(name, args)
146
+ end
147
+
148
+ #
149
+ # Expand filenames to full paths using catalog_root, catalog_sections, and provided filename
150
+ #
151
+ def catalog_filenames(filename='*',catalog_sections=catalog_sections)
152
+ catalog_sections.collect{ |section|
153
+ Dir[File.join(Settings[:catalog_root], section, filename + '.icss.yaml')] }.flatten
154
+ end
155
+
156
+ #
157
+ # Conditional empty registry and load all found files
158
+ #
159
+ def load_catalog(flush=false)
160
+ flush_registry if flush
161
+ load_files_from_catalog('*')
162
+ end
163
+
164
+ def flush_registry
165
+ registry.clear
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,177 @@
1
+ require 'gorillib/hashlike'
2
+
3
+ module Icss
4
+ module ReceiverModel
5
+
6
+ #
7
+ # Makes an Icss::RecordModel behave mostly like a hash.
8
+ #
9
+ # By default, the hashlike methods iterate over the fields: instance #keys
10
+ # delegates to self.class.field_names. All methods are defined naturally on
11
+ # [], []= and has_key? so the interface is well-guarded.
12
+ #
13
+ # in addition to the below, by including Enumerable, this also adds
14
+ #
15
+ # :each_cons, :each_entry, :each_slice, :each_with_index, :each_with_object,
16
+ # :map, :collect, :collect_concat, :entries, :to_a, :flat_map, :inject, :reduce,
17
+ # :group_by, :chunk, :cycle, :partition, :reverse_each, :slice_before, :drop,
18
+ # :drop_while, :take, :take_while, :detect, :find, :find_all, :find_index, :grep,
19
+ # :all?, :any?, :none?, :one?, :first, :count, :zip :max, :max_by, :min, :min_by,
20
+ # :minmax, :minmax_by, :sort, :sort_by
21
+ #
22
+ # As opposed to hash, does *not* define
23
+ #
24
+ # default, default=, default_proc, default_proc=, shift, flatten, compare_by_identity
25
+ # compare_by_identity? rehash
26
+ #
27
+ module ActsAsHash
28
+
29
+ # Hashlike#[]
30
+ #
31
+ # Element Reference -- Retrieves the value stored for +key+.
32
+ #
33
+ # In a normal hash, a default value can be set; none is provided here.
34
+ #
35
+ # Delegates to self.send(key)
36
+ #
37
+ # @example
38
+ # hsh = { :a => 100, :b => 200 }
39
+ # hsh[:a] # => 100
40
+ # hsh[:c] # => nil
41
+ #
42
+ # @param key [Object] key to retrieve
43
+ # @return [Object] the value stored for key, nil if missing
44
+ #
45
+ def [](key)
46
+ key = convert_key(key)
47
+ self.send(key)
48
+ end
49
+
50
+ # Hashlike#[]=
51
+ # Hashlike#store
52
+ #
53
+ # Element Assignment -- Associates the value given by +val+ with the key
54
+ # given by +key+.
55
+ #
56
+ # key should not have its value changed while it is in use as a key. In a
57
+ # normal hash, a String passed as a key will be duplicated and frozen. No such
58
+ # guarantee is provided here
59
+ #
60
+ # Delegates to self.send("key=", val)
61
+ #
62
+ # @example
63
+ # hsh = { :a => 100, :b => 200 }
64
+ # hsh[:a] = 9
65
+ # hsh[:c] = 4
66
+ # hsh # => { :a => 9, :b => 200, :c => 4 }
67
+ #
68
+ # hsh[key] = val -> val
69
+ # hsh.store(key, val) -> val
70
+ #
71
+ # @param key [Object] key to associate
72
+ # @param val [Object] value to associate it with
73
+ # @return [Object]
74
+ #
75
+ def []=(key, val)
76
+ key = convert_key(key)
77
+ self.send("#{key}=", val) if respond_to?("#{key}=")
78
+ val
79
+ end
80
+
81
+ # Hashlike#delete
82
+ #
83
+ # Deletes and returns the value from +hsh+ whose key is equal to +key+. If the
84
+ # optional code block is given and the key is not found, pass in the key and
85
+ # return the result of +block+.
86
+ #
87
+ # In a normal hash, a default value can be set; none is provided here.
88
+ #
89
+ # @example
90
+ # hsh = { :a => 100, :b => 200 }
91
+ # hsh.delete(:a) # => 100
92
+ # hsh.delete(:z) # => nil
93
+ # hsh.delete(:z){|el| "#{el} not found" } # => "z not found"
94
+ #
95
+ # @overload hsh.delete(key) -> val
96
+ # @param key [Object] key to remove
97
+ # @return [Object, Nil] the removed object, nil if missing
98
+ #
99
+ # @overload hsh.delete(key){|key| block } -> val
100
+ # @param key [Object] key to remove
101
+ # @yield [Object] called (with key) if key is missing
102
+ # @yieldparam key
103
+ # @return [Object, Nil] the removed object, or if missing, the return value
104
+ # of the block
105
+ #
106
+ def delete(key, &block)
107
+ key = convert_key(key)
108
+ if has_key?(key)
109
+ val = self[key]
110
+ self.send(:remove_instance_variable, "@#{key}")
111
+ val
112
+ elsif block_given?
113
+ block.call(key)
114
+ else
115
+ nil
116
+ end
117
+ end
118
+
119
+ # Hashlike#keys
120
+ #
121
+ # Returns a new array populated with the keys from this hashlike.
122
+ #
123
+ # @see Hashlike#values.
124
+ #
125
+ # @example
126
+ # hsh = { :a => 100, :b => 200, :c => 300, :d => 400 }
127
+ # hsh.keys # => [:a, :b, :c, :d]
128
+ #
129
+ # @return [Array] list of keys
130
+ #
131
+ def keys
132
+ self.class.field_names & instance_variables.map{|s| convert_key(s[1..-1]) }
133
+ end
134
+
135
+ #
136
+ # Returns a hash with each key set to its associated value.
137
+ #
138
+ # @example
139
+ # my_hshlike = MyHashlike.new
140
+ # my_hshlike[:a] = 100; my_hshlike[:b] = 200
141
+ # my_hshlike.to_hash # => { :a => 100, :b => 200 }
142
+ #
143
+ # @return [Hash] a new Hash instance, with each key set to its associated value.
144
+ #
145
+ def attributes
146
+ {}.tap do |hsh|
147
+ each_pair do |key, val|
148
+ hsh[key] = val.respond_to?(:to_hash) ? val.to_hash : val
149
+ end
150
+ end
151
+ end
152
+
153
+ #
154
+ # Returns a hash with each key set to its associated value.
155
+ #
156
+ # Delegates to #attributes
157
+ #
158
+ def to_hash
159
+ attributes
160
+ end
161
+
162
+ def self.included(base)
163
+ base.class_eval do
164
+ include Gorillib::Hashlike
165
+ end
166
+ end
167
+
168
+ protected
169
+
170
+ def convert_key(key)
171
+ raise ArgumentError, "Keys for #{self.class} must be symbols, strings or respond to #to_sym" unless key.respond_to?(:to_sym)
172
+ key.to_sym
173
+ end
174
+ end
175
+
176
+ end
177
+ end