neo4j-core 3.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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