neo4j-core 3.0.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +22 -0
- data/README.md +332 -0
- data/lib/neo4j-core.rb +27 -0
- data/lib/neo4j-core/cypher_translator.rb +34 -0
- data/lib/neo4j-core/hash_with_indifferent_access.rb +165 -0
- data/lib/neo4j-core/helpers.rb +25 -0
- data/lib/neo4j-core/label.rb +8 -0
- data/lib/neo4j-core/version.rb +5 -0
- data/lib/neo4j-embedded.rb +18 -0
- data/lib/neo4j-embedded/embedded_database.rb +29 -0
- data/lib/neo4j-embedded/embedded_label.rb +80 -0
- data/lib/neo4j-embedded/embedded_node.rb +163 -0
- data/lib/neo4j-embedded/embedded_relationship.rb +44 -0
- data/lib/neo4j-embedded/embedded_session.rb +151 -0
- data/lib/neo4j-embedded/property.rb +43 -0
- data/lib/neo4j-embedded/to_java.rb +49 -0
- data/lib/neo4j-server.rb +10 -0
- data/lib/neo4j-server/cypher_label.rb +28 -0
- data/lib/neo4j-server/cypher_node.rb +140 -0
- data/lib/neo4j-server/cypher_node_uncommited.rb +12 -0
- data/lib/neo4j-server/cypher_relationship.rb +82 -0
- data/lib/neo4j-server/cypher_response.rb +113 -0
- data/lib/neo4j-server/cypher_session.rb +156 -0
- data/lib/neo4j-server/cypher_transaction.rb +81 -0
- data/lib/neo4j-server/resource.rb +73 -0
- data/lib/neo4j/entity_equality.rb +9 -0
- data/lib/neo4j/jars/concurrentlinkedhashmap-lru-1.3.1.jar +0 -0
- data/lib/neo4j/jars/geronimo-jta_1.1_spec-1.1.1.jar +0 -0
- data/lib/neo4j/jars/lucene-core-3.6.2.jar +0 -0
- data/lib/neo4j/jars/neo4j-cypher-2.0.0-M06.jar +0 -0
- data/lib/neo4j/jars/neo4j-kernel-2.0-SNAPSHOT-tests.jar +0 -0
- data/lib/neo4j/jars/neo4j-kernel-2.0.0-M06.jar +0 -0
- data/lib/neo4j/jars/neo4j-lucene-index-2.0.0-M06.jar +0 -0
- data/lib/neo4j/jars/neo4j-management-2.0.0-M06.jar +0 -0
- data/lib/neo4j/jars/org.apache.servicemix.bundles.jline-0.9.94_1.jar +0 -0
- data/lib/neo4j/jars/parboiled-core-1.1.6.jar +0 -0
- data/lib/neo4j/jars/parboiled-scala_2.10-1.1.6.jar +0 -0
- data/lib/neo4j/jars/scala-library-2.10.2.jar +0 -0
- data/lib/neo4j/label.rb +88 -0
- data/lib/neo4j/node.rb +185 -0
- data/lib/neo4j/property_container.rb +22 -0
- data/lib/neo4j/property_validator.rb +23 -0
- data/lib/neo4j/relationship.rb +84 -0
- data/lib/neo4j/session.rb +124 -0
- data/lib/neo4j/tasks/neo4j_server.rb +131 -0
- data/lib/neo4j/transaction.rb +52 -0
- data/neo4j-core.gemspec +35 -0
- 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
|