neo4j-core 3.0.0.alpha.1

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 (49) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +22 -0
  3. data/README.md +332 -0
  4. data/lib/neo4j-core.rb +27 -0
  5. data/lib/neo4j-core/cypher_translator.rb +34 -0
  6. data/lib/neo4j-core/hash_with_indifferent_access.rb +165 -0
  7. data/lib/neo4j-core/helpers.rb +25 -0
  8. data/lib/neo4j-core/label.rb +8 -0
  9. data/lib/neo4j-core/version.rb +5 -0
  10. data/lib/neo4j-embedded.rb +18 -0
  11. data/lib/neo4j-embedded/embedded_database.rb +29 -0
  12. data/lib/neo4j-embedded/embedded_label.rb +80 -0
  13. data/lib/neo4j-embedded/embedded_node.rb +163 -0
  14. data/lib/neo4j-embedded/embedded_relationship.rb +44 -0
  15. data/lib/neo4j-embedded/embedded_session.rb +151 -0
  16. data/lib/neo4j-embedded/property.rb +43 -0
  17. data/lib/neo4j-embedded/to_java.rb +49 -0
  18. data/lib/neo4j-server.rb +10 -0
  19. data/lib/neo4j-server/cypher_label.rb +28 -0
  20. data/lib/neo4j-server/cypher_node.rb +140 -0
  21. data/lib/neo4j-server/cypher_node_uncommited.rb +12 -0
  22. data/lib/neo4j-server/cypher_relationship.rb +82 -0
  23. data/lib/neo4j-server/cypher_response.rb +113 -0
  24. data/lib/neo4j-server/cypher_session.rb +156 -0
  25. data/lib/neo4j-server/cypher_transaction.rb +81 -0
  26. data/lib/neo4j-server/resource.rb +73 -0
  27. data/lib/neo4j/entity_equality.rb +9 -0
  28. data/lib/neo4j/jars/concurrentlinkedhashmap-lru-1.3.1.jar +0 -0
  29. data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
  30. data/lib/neo4j/jars/lucene-core-3.6.2.jar +0 -0
  31. data/lib/neo4j/jars/neo4j-cypher-2.0.0-M06.jar +0 -0
  32. data/lib/neo4j/jars/neo4j-kernel-2.0-SNAPSHOT-tests.jar +0 -0
  33. data/lib/neo4j/jars/neo4j-kernel-2.0.0-M06.jar +0 -0
  34. data/lib/neo4j/jars/neo4j-lucene-index-2.0.0-M06.jar +0 -0
  35. data/lib/neo4j/jars/neo4j-management-2.0.0-M06.jar +0 -0
  36. data/lib/neo4j/jars/org.apache.servicemix.bundles.jline-0.9.94_1.jar +0 -0
  37. data/lib/neo4j/jars/parboiled-core-1.1.6.jar +0 -0
  38. data/lib/neo4j/jars/parboiled-scala_2.10-1.1.6.jar +0 -0
  39. data/lib/neo4j/jars/scala-library-2.10.2.jar +0 -0
  40. data/lib/neo4j/label.rb +88 -0
  41. data/lib/neo4j/node.rb +185 -0
  42. data/lib/neo4j/property_container.rb +22 -0
  43. data/lib/neo4j/property_validator.rb +23 -0
  44. data/lib/neo4j/relationship.rb +84 -0
  45. data/lib/neo4j/session.rb +124 -0
  46. data/lib/neo4j/tasks/neo4j_server.rb +131 -0
  47. data/lib/neo4j/transaction.rb +52 -0
  48. data/neo4j-core.gemspec +35 -0
  49. metadata +144 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 52081ded1131dccd5959a5250721490e7e19edb2
4
+ data.tar.gz: 8376070a82544ac1db4ebb5070adbf7bde0c69e9
5
+ SHA512:
6
+ metadata.gz: 4b3e4f7d925395e6a021421db53ab56498dde23b0d2415b5adb366fbcbc0fe3fe936ace5b4fbaf95cbfc22576de6a32ed0cf2547a67059220a62822ae476cc16
7
+ data.tar.gz: ed4927902a386ca3b0c6ca03a3ab9587432f95a3f5019252362611182701894a2f1f8dacb61e11a394b6a558eec0afef279aa631d42f895383da6c9249835a9f
data/Gemfile ADDED
@@ -0,0 +1,22 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'neo4j-cypher', '~> 1.0.1'
6
+
7
+
8
+ #gem 'neo4j-advanced', '>= 1.8.1', '< 2.0', :require => false
9
+ #gem 'neo4j-enterprise', '>= 1.8.1', '< 2.0', :require => false
10
+
11
+ group 'development' do
12
+ gem 'os'
13
+ gem 'yard'
14
+ # gem 'pry'
15
+ end
16
+
17
+ group 'test' do
18
+ gem "rake", ">= 0.8.7"
19
+ gem "rspec", "~> 2.8"
20
+ # gem "its" # its(:with, :arguments) { should be_possible }
21
+ end
22
+
data/README.md ADDED
@@ -0,0 +1,332 @@
1
+ # Neo4j-core v3.0 [![Code Climate](https://codeclimate.com/github/andreasronge/neo4j-core.png)](https://codeclimate.com/github/andreasronge/neo4j-core) [![Build Status](https://travis-ci.org/andreasronge/neo4j-core.png)](https://travis-ci.org/andreasronge/neo4j-core)
2
+
3
+ A simple Ruby wrapper around the Neo4j graph database that works with the server and embedded Neo4j API.
4
+ This gem can be used both from JRuby and normal MRI. You may get better performance using it from JRuby and the embedded
5
+ Neo4j, but it will probably be easier to develop (e.g. faster to run tests) on MRI and neo4j server.
6
+ This gem is designed to work well together with the neo4j active model compliant gem (see the 3.0 branch).
7
+
8
+ For the stable v2.0 version, see the v2.0 branch https://github.com/andreasronge/neo4j-core/tree/v2.x
9
+ Do not use this gem in production.
10
+
11
+
12
+ ## Installation
13
+
14
+ ### Usage from Neo4j Server
15
+
16
+ You need to install the Neo4j server. This can be done by included Rake file.
17
+
18
+ Example
19
+
20
+ ```
21
+ rake neo4j:install[community-2.0.0,M06]
22
+ rake neo4j:start
23
+ ```
24
+
25
+ ### Usage from Neo4j Embedded
26
+
27
+ The Gemfile contains references to Neo4j Java libraries. Nothing is needed to be installed.
28
+ The embedded database is only accessible from JRuby (unlike the Neo4j Server).
29
+
30
+ ## Neo4j-core API, v3.0
31
+
32
+ ### Creating a database session
33
+
34
+ There are currently two available types of session, one for connecting to a neo4j server
35
+ and one for connecting to the embedded Neo4j database (which requires JRuby).
36
+
37
+ Using the Neo4j Server: `:server_db`
38
+
39
+ ```ruby
40
+ # Using Neo4j Server Cypher Database
41
+ session = Neo4j::Session.open(:server_db, "http://localhost:7474")
42
+ ```
43
+
44
+ Using the Neo4j Embedded Database, `:embedded_db`
45
+
46
+ ```ruby
47
+ # Using Neo4j Embedded Database
48
+ session = Neo4j::Session.open(:embedded_db, '/folder/db', auto_commit: true)
49
+ session.start
50
+ ```
51
+
52
+ When a session has been created it will be stored in the `Neo4j::Session` object.
53
+ Example, get the default session
54
+
55
+ ```ruby
56
+ session = Neo4j::Session.current
57
+ ```
58
+
59
+ The default session is used by all operation unless specified as the last argument.
60
+ For example create a node with a different session:
61
+
62
+ ```ruby
63
+ my_session = Neo4j::Session.open(:server_db, "http://localhost:7474")
64
+ Neo4j::Node.create(name: 'kalle', my_session)
65
+ ```
66
+
67
+
68
+ ### Label and Index Support
69
+
70
+ Create a node with an label `person` and one property
71
+ ```ruby
72
+ Neo4j::Node.create({name: 'kalle'}, :person)
73
+ ```
74
+
75
+ Add index on a label
76
+
77
+ ```ruby
78
+ person = Label.create(:person)
79
+ person.create_index(:name) # compound keys will be supported in Neo4j 2.1
80
+
81
+ # drop index
82
+ person.drop_index(:name)
83
+ ```
84
+
85
+ ```ruby
86
+ # which indexes do we have and on which properties,
87
+ red.indexes.each {|i| puts "Index #{i.label} properties: #{i.properties}"}
88
+
89
+ # drop index, we assume it's the first one we want
90
+ red.indexes.first.drop(:name)
91
+
92
+ # which indices exist ?
93
+ # (compound keys will be supported in Neo4j 2.1 (?))
94
+ red.indexes # => {:property_keys => [[:age]]}
95
+ ```
96
+
97
+ ### Creating Nodes
98
+
99
+ ```ruby
100
+ # notice, label argument can be both Label objects or string/symbols.
101
+ node = Node.create({name: 'andreas'}, red, :green)
102
+ puts "Created node #{node[:name]} with labels #{node.labels.map(&:name).join(', ')}"
103
+ ```
104
+
105
+ Notice, nodes will be indexed based on which labels they have.
106
+
107
+ Setting properties
108
+
109
+ ```ruby
110
+ node = Node.create({name: 'andreas'}, red, :green)
111
+ node[:name] = 'changed name' # changes immediately one property
112
+ node[:name] # => 'changed name'
113
+ node.props # => {name: 'changed name'}
114
+ node.props={ foo: 42} # replace all properties
115
+ ```
116
+
117
+ Notice properties are never stored in ruby objects, instead they are always fetched from the database.
118
+
119
+ ### Finding Nodes
120
+
121
+ Each node and relationship has a id, `neo_id`
122
+
123
+ ```ruby
124
+ node = Neo4j::Node.create
125
+ # load the node again from the database
126
+ node2 = Neo4j::Node.load(node.neo_id)
127
+ ```
128
+
129
+ Finding nodes by label:
130
+
131
+ ```ruby
132
+ # Find nodes using an index, returns an Enumerable
133
+ Neo4j::Label.find_nodes(:red, :name, "andreas")
134
+
135
+ # Find all nodes for this label, returns an Enumerable
136
+ Neo4j::Label.find_all_nodes(:red)
137
+
138
+ # which labels does a node have ?
139
+ node.labels # [:red]
140
+ ```
141
+
142
+ Example, Finding with order by on label :person
143
+
144
+ ```ruby
145
+ Neo4j::Label.query(:person, order: [:name, {age: :asc}])
146
+ ```
147
+
148
+
149
+ ### Transactions
150
+
151
+ By default each Neo4j operation is wrapped in an transaction.
152
+ If you want to execute several operation in one operation you can use the `Neo4j::Transaction` class, example:
153
+
154
+ ```ruby
155
+ Neo4j::Transaction.run do
156
+ n = Neo4j::Node.create(name: 'kalle')
157
+ n[:age] = 42
158
+ end
159
+ ```
160
+
161
+ ### Relationship
162
+
163
+ How to create a relationship between node n1 and node n2 with one property
164
+
165
+ ```ruby
166
+ n1 = Neo4j::Node.create
167
+ n2 = Neo4j::Node.create
168
+ rel = n1.create_rel(:knows, n2, since: 1994)
169
+ ```
170
+
171
+ Finding relationships
172
+
173
+ ```ruby
174
+ # any type any direction any label
175
+ n1.rels
176
+
177
+ # Outgoing of one type:
178
+ n1.rels(dir: :outgoing, type: :know).to_a
179
+
180
+ # same but expects only one relationship
181
+ n1.rel(dir: :outgoing, type: :best_friend)
182
+
183
+ # several types
184
+ n1.rels(types: [:knows, :friend])
185
+
186
+ # label
187
+ n1.rels(label: :rich)
188
+
189
+ # matching several labels
190
+ n1.rels(labels: [:rich, :poor])
191
+
192
+ # outgoing between two nodes
193
+ n1.rels(dir: :outgoing, between: n2)
194
+ ```
195
+
196
+ Returns nodes instead of relationships
197
+
198
+ ```ruby
199
+ # same parameters as rels method
200
+ n1.nodes(dir: outgoing)
201
+ n1.node(dir: outgoing)
202
+ ```
203
+
204
+
205
+ Delete relationship
206
+
207
+ ```ruby
208
+ rel = n1.rel(:outgoing, :know) # expects only one relationship
209
+ rel.del
210
+ ```
211
+ ### Identity
212
+
213
+ NOT WORKING YET, TODO.
214
+ By default the identity for a node is the same as the native Neo4j id.
215
+ You can specify your own identity of nodes.
216
+
217
+ ```ruby
218
+ session = Neo4j::CypherDatabase.connect('URL')
219
+ session.config.node_identity = '_my_id'
220
+ ```
221
+
222
+ ## Implementation:
223
+
224
+ All method prefixed with `_` gives direct access to the java layer/rest layer.
225
+ Notice, the database starts with auto commit by default.
226
+
227
+ No state is cached in the neo4j-core (e.g. neo4j properties).
228
+
229
+ The public `Neo4j::Node` classes is abstract and provides a common API/docs for both the embedded and
230
+ neo4j server.
231
+
232
+ The Neo4j::Embedded and Neo4j::Server modules contains drivers for classes like the `Neo4j::Node`.
233
+ This is implemented something like this:
234
+
235
+ ```ruby
236
+ class Neo4j::Node
237
+ # YARD docs
238
+ def [](key)
239
+ # abstract method - impl using either HTTP or Java API
240
+ get_property(key,session=Neo4j::Session.current)
241
+ end
242
+
243
+
244
+ def self.create(props, session=Neo4j::Session.current)
245
+ session.create_node(props)
246
+ end
247
+ end
248
+ ```
249
+
250
+ Both implementation use the same E2E specs.
251
+
252
+
253
+ ## Testing
254
+
255
+ The testing will be using much more mocking.
256
+
257
+ * The `unit` rspec folder only contains testing for one Ruby module. All other modules should be mocked.
258
+ * The `integration` rspec folder contains testing for two or more modules but mocks the neo4j database access.
259
+ * The `e2e` rspec folder for use the real database (or Neo4j's ImpermanentDatabase (todo))
260
+ * The `shared_examples` common specs for different types of databases
261
+
262
+
263
+ ## The public API
264
+
265
+ * {Neo4j::Node} The Neo4j Node
266
+
267
+ * {Neo4j::Relationship} The Relationship
268
+
269
+ * {Neo4j::Session} The session to the embedded or server database.
270
+
271
+ * `Neo4j::Cypher` Cypher Query DSL, see {Neo4j Wiki}[https://github.com/andreasronge/neo4j/wiki/Neo4j%3A%3ACore-Cypher]
272
+
273
+
274
+ See also the cypher DSL gem, [Neo4j Wiki](https://github.com/andreasronge/neo4j/wiki/Neo4j%3A%3ACore-Cypher)
275
+
276
+ ## Version 3.0
277
+
278
+ The neo4j-core version 3.0 uses the java Jar and/or the Neo4j Server version 2.0.0-M6+ . This mean that it should work on
279
+ Ruby implementation and not just JRuby !
280
+
281
+ It uses the new label feature in order to do mappings between `Neo4j::Node` (java objects) and your own ruby classes.
282
+
283
+ The code base for the 3.0 should be smaller and simpler to maintain because there is less work to be done in the
284
+ Ruby layer but also by removing features that are too complex or not that useful.
285
+
286
+ The neo4j-wrapper source code is included in this git repo until the refactoring has stabilized.
287
+ The old source code for neo4j-core is also included (lib.old). The old source code might later on be copied into the
288
+ 3.0 source code (the lib folder).
289
+
290
+ The neo4j-core gem will work for both the embedded Neo4j API and the server api.
291
+ That means that neo4j.rb will work on any Ruby implementation and not just JRuby. This is under investigation !
292
+ It's possible that some features for the Neo4j.rb 2.0 will not be available in the 3.0 version since it has to work
293
+ with both the Neo4j server and Neo4j embedded APIs.
294
+
295
+ Since neo4j-core provides one unified API to both the server end embedded neo4j database the neo4j-wrapper and neo4j
296
+ gems will also work with server and embedded neo4j databases.
297
+
298
+ New features:
299
+
300
+ * neo4j-core provides the same API to both the Embedded database and the Neo4j Server
301
+ * auto commit is each operation is now default (neo4j-core)
302
+
303
+ Removed features:
304
+
305
+ * auto start of the database (neo4j-core)
306
+ * wrapping of Neo4j::Relationship java objects but there will be a work around (neo4j-wrapper)
307
+ * traversals (the outgoing/incoming/both methods) moves to a new gem, neo4j-traversal.
308
+ * rules will not be supported
309
+ * versioning will not be supported, will Neo4j support it ?
310
+ * multitenancy will not be supported, will Neo4j support it ?
311
+
312
+ Changes:
313
+
314
+ * `Neo4j::Node.create` now creates a node instead of `Neo4j::Node.new`
315
+ * `Neo4j::Node#rels` different arguments, see below
316
+ * Many Neo4j Java methods requires you to close an ResourceIterable as well as be in an transaction (even for read operations)
317
+ In neo4j-core there are two version of these methods, one that create transaction and close the iterable for you and one raw
318
+ where you have to do it yourself (which may give you be better performance).
319
+ * The neo4j-core includes the neo4j-wrapper implementation.
320
+
321
+ Future (when Neo4j 2.1 is released)
322
+ * Support for fulltext search
323
+ * Compound keys in index
324
+
325
+
326
+ ## License
327
+ * Neo4j.rb - MIT, see the LICENSE file http://github.com/andreasronge/neo4j-core/tree/master/LICENSE.
328
+ * Lucene - Apache, see http://lucene.apache.org/java/docs/features.html
329
+ * \Neo4j - Dual free software/commercial license, see http://neo4j.org/
330
+
331
+ Notice there are different license for the neo4j-community, neo4j-advanced and neo4j-enterprise jar gems.
332
+ Only the neo4j-community gem is by default required.
data/lib/neo4j-core.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'forwardable'
2
+ require 'fileutils'
3
+
4
+ require 'neo4j-cypher'
5
+
6
+ require 'neo4j/property_validator'
7
+ require 'neo4j/property_container'
8
+ require 'neo4j-core/helpers'
9
+ require 'neo4j-core/cypher_translator'
10
+
11
+ require 'neo4j/entity_equality'
12
+ require 'neo4j/node'
13
+ require 'neo4j/label'
14
+ require 'neo4j/session'
15
+
16
+ require 'neo4j/relationship'
17
+ require 'neo4j/transaction'
18
+
19
+ require 'neo4j-server'
20
+
21
+ if RUBY_PLATFORM == 'java'
22
+ require 'neo4j-embedded'
23
+ else
24
+ # just for the tests
25
+ module Neo4j::Embedded
26
+ end
27
+ end
@@ -0,0 +1,34 @@
1
+ module Neo4j::Core
2
+ module CypherTranslator
3
+ # Cypher Helper
4
+ def escape_value(value)
5
+ case value
6
+ when String
7
+ "'#{value}'" # TODO escape ' and "
8
+ else
9
+ value
10
+ end
11
+ end
12
+
13
+ # Cypher Helper
14
+ def cypher_prop_list(props)
15
+ return "" unless props
16
+ list = props.keys.map{|k| "#{k} : #{escape_value(props[k])}"}.join(',')
17
+ "{#{list}}"
18
+ end
19
+
20
+ # Stolen from keymaker
21
+ # https://github.com/therubymug/keymaker/blob/master/lib/keymaker/parsers/cypher_response_parser.rb
22
+ def self.translate_response(response_body, result)
23
+ Hashie::Mash.new(Hash[sanitized_column_names(response_body).zip(result)])
24
+ end
25
+
26
+ def self.sanitized_column_names(response_body)
27
+ response_body.columns.map do |column|
28
+ column[/[^\.]+$/]
29
+ end
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,165 @@
1
+ module Neo4j
2
+ module Core
3
+ # Stolen from http://as.rubyonrails.org/classes/HashWithIndifferentAccess.html
4
+ # We don't want to depend on active support
5
+ class HashWithIndifferentAccess < Hash
6
+
7
+ # Always returns true, so that <tt>Array#extract_options!</tt> finds members of this class.
8
+ def extractable_options?
9
+ true
10
+ end
11
+
12
+ def with_indifferent_access
13
+ dup
14
+ end
15
+
16
+ def nested_under_indifferent_access
17
+ self
18
+ end
19
+
20
+ def initialize(constructor = {})
21
+ if constructor.is_a?(Hash)
22
+ super()
23
+ update(constructor)
24
+ else
25
+ super(constructor)
26
+ end
27
+ end
28
+
29
+ def default(key = nil)
30
+ if key.is_a?(Symbol) && include?(key = key.to_s)
31
+ self[key]
32
+ else
33
+ super
34
+ end
35
+ end
36
+
37
+ def self.new_from_hash_copying_default(hash)
38
+ new(hash).tap do |new_hash|
39
+ new_hash.default = hash.default
40
+ end
41
+ end
42
+
43
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
44
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
45
+
46
+ # Assigns a new value to the hash:
47
+ #
48
+ # hash = HashWithIndifferentAccess.new
49
+ # hash[:key] = "value"
50
+ #
51
+ def []=(key, value)
52
+ regular_writer(convert_key(key), convert_value(value))
53
+ end
54
+
55
+ alias_method :store, :[]=
56
+
57
+ # Updates the instantized hash with values from the second:
58
+ #
59
+ # hash_1 = HashWithIndifferentAccess.new
60
+ # hash_1[:key] = "value"
61
+ #
62
+ # hash_2 = HashWithIndifferentAccess.new
63
+ # hash_2[:key] = "New Value!"
64
+ #
65
+ # hash_1.update(hash_2) # => {"key"=>"New Value!"}
66
+ #
67
+ def update(other_hash)
68
+ if other_hash.is_a? HashWithIndifferentAccess
69
+ super(other_hash)
70
+ else
71
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
72
+ self
73
+ end
74
+ end
75
+
76
+ alias_method :merge!, :update
77
+
78
+ # Checks the hash for a key matching the argument passed in:
79
+ #
80
+ # hash = HashWithIndifferentAccess.new
81
+ # hash["key"] = "value"
82
+ # hash.key? :key # => true
83
+ # hash.key? "key" # => true
84
+ #
85
+ def key?(key)
86
+ super(convert_key(key))
87
+ end
88
+
89
+ alias_method :include?, :key?
90
+ alias_method :has_key?, :key?
91
+ alias_method :member?, :key?
92
+
93
+ # Fetches the value for the specified key, same as doing hash[key]
94
+ def fetch(key, *extras)
95
+ super(convert_key(key), *extras)
96
+ end
97
+
98
+ # Returns an array of the values at the specified indices:
99
+ #
100
+ # hash = HashWithIndifferentAccess.new
101
+ # hash[:a] = "x"
102
+ # hash[:b] = "y"
103
+ # hash.values_at("a", "b") # => ["x", "y"]
104
+ #
105
+ def values_at(*indices)
106
+ indices.collect {|key| self[convert_key(key)]}
107
+ end
108
+
109
+ # Returns an exact copy of the hash.
110
+ def dup
111
+ self.class.new(self).tap do |new_hash|
112
+ new_hash.default = default
113
+ end
114
+ end
115
+
116
+ # Merges the instantized and the specified hashes together, giving precedence to the values from the second hash.
117
+ # Does not overwrite the existing hash.
118
+ def merge(hash)
119
+ self.dup.update(hash)
120
+ end
121
+
122
+ # Performs the opposite of merge, with the keys and values from the first hash taking precedence over the second.
123
+ # This overloaded definition prevents returning a regular hash, if reverse_merge is called on a <tt>HashWithDifferentAccess</tt>.
124
+ def reverse_merge(other_hash)
125
+ super self.class.new_from_hash_copying_default(other_hash)
126
+ end
127
+
128
+ def reverse_merge!(other_hash)
129
+ replace(reverse_merge( other_hash ))
130
+ end
131
+
132
+ # Removes a specified key from the hash.
133
+ def delete(key)
134
+ super(convert_key(key))
135
+ end
136
+
137
+ def stringify_keys!; self end
138
+ def stringify_keys; dup end
139
+ # undef :symbolize_keys!
140
+ def symbolize_keys; to_hash.symbolize_keys end
141
+ def to_options!; self end
142
+
143
+ # Convert to a Hash with String keys.
144
+ def to_hash
145
+ Hash.new(default).merge!(self)
146
+ end
147
+
148
+ protected
149
+ def convert_key(key)
150
+ key.kind_of?(Symbol) ? key.to_s : key
151
+ end
152
+
153
+ def convert_value(value)
154
+ if value.is_a? Hash
155
+ value #.nested_under_indifferent_access
156
+ elsif value.is_a?(Array)
157
+ value.dup.replace(value.map { |e| convert_value(e) })
158
+ else
159
+ value
160
+ end
161
+ end
162
+ end
163
+
164
+ end
165
+ end