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,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