neoid 0.0.2 → 0.0.5.alpha
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +4 -0
- data/CHANGELOG.md +14 -0
- data/README.md +216 -121
- data/Rakefile +1 -0
- data/TODO.md +6 -0
- data/lib/neoid/database_cleaner.rb +12 -0
- data/lib/neoid/middleware.rb +14 -0
- data/lib/neoid/model_additions.rb +43 -13
- data/lib/neoid/model_config.rb +49 -5
- data/lib/neoid/node.rb +101 -25
- data/lib/neoid/railtie.rb +15 -0
- data/lib/neoid/relationship.rb +81 -6
- data/lib/neoid/search_session.rb +28 -0
- data/lib/neoid/version.rb +1 -1
- data/lib/neoid.rb +139 -6
- data/neoid.gemspec +7 -5
- data/spec/neoid/model_additions_spec.rb +61 -79
- data/spec/neoid/model_config_spec.rb +24 -0
- data/spec/neoid/search_spec.rb +92 -0
- data/spec/spec_helper.rb +20 -4
- data/spec/support/database.yml +6 -0
- data/spec/support/models.rb +97 -0
- data/spec/support/schema.rb +40 -0
- metadata +75 -23
data/.travis.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
## v0.0.41
|
2
|
+
|
3
|
+
* fixed really annoying bug caused by Rails design -- Rails doesn't call `after_destroy` when assigning many to many relationships to a model, like `user.movies = [m1, m2, m3]` or `user.update_attributes(params[:user])` where it contains `params[:user][:movie_ids]` list (say from checkboxes), but it DOES CALL after_create for the new relationships. the fix adds after_remove callback to the has_many relationships, ensuring neo4j is up to date with all changes, no matter how they were committed
|
4
|
+
|
5
|
+
## v0.0.4
|
6
|
+
|
7
|
+
* rewrote seacrch. one index for all types instead of one for type. please run neo_search_index on all of your models.
|
8
|
+
search in multiple types at once with `Neoid.search(types_array, term)
|
9
|
+
|
10
|
+
## v0.0.3
|
11
|
+
|
12
|
+
* new configuration syntax (backwards compatible)
|
13
|
+
* full text search index
|
14
|
+
|
1
15
|
## v0.0.2
|
2
16
|
|
3
17
|
* create node immediately after active record create
|
data/README.md
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# Neoid
|
2
2
|
|
3
|
+
[![Build Status](https://secure.travis-ci.org/elado/neoid.png)](http://travis-ci.org/elado/neoid)
|
4
|
+
|
5
|
+
|
6
|
+
|
3
7
|
Make your ActiveRecords stored and searchable on Neo4j graph database, in order to make fast graph queries that MySQL would crawl while doing them.
|
4
8
|
|
5
9
|
Neoid to Neo4j is like Sunspot to Solr. You get the benefits of Neo4j speed while keeping your schema on your plain old RDBMS.
|
@@ -14,38 +18,40 @@ Neoid offers querying Neo4j for IDs of objects and then fetch them from your RDB
|
|
14
18
|
|
15
19
|
Add to your Gemfile and run the `bundle` command to install it.
|
16
20
|
|
17
|
-
|
18
|
-
|
21
|
+
```ruby
|
22
|
+
gem 'neoid', git: 'git://github.com/elado/neoid.git'
|
23
|
+
```
|
19
24
|
|
20
25
|
**Requires Ruby 1.9.2 or later.**
|
21
26
|
|
22
27
|
## Usage
|
23
28
|
|
24
|
-
###
|
29
|
+
### Rails app configuration:
|
25
30
|
|
26
31
|
In an initializer, such as `config/initializers/01_neo4j.rb`:
|
27
32
|
|
28
|
-
|
29
|
-
|
30
|
-
uri = URI.parse(ENV["NEO4J_URL"])
|
33
|
+
```ruby
|
34
|
+
ENV["NEO4J_URL"] ||= "http://localhost:7474"
|
31
35
|
|
32
|
-
|
36
|
+
uri = URI.parse(ENV["NEO4J_URL"])
|
33
37
|
|
34
|
-
|
35
|
-
c.server = uri.host
|
36
|
-
c.port = uri.port
|
38
|
+
$neo = Neography::Rest.new(uri.to_s)
|
37
39
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
c.password = uri.password
|
42
|
-
end
|
43
|
-
end
|
40
|
+
Neography.configure do |c|
|
41
|
+
c.server = uri.host
|
42
|
+
c.port = uri.port
|
44
43
|
|
45
|
-
|
44
|
+
if uri.user && uri.password
|
45
|
+
c.authentication = 'basic'
|
46
|
+
c.username = uri.user
|
47
|
+
c.password = uri.password
|
48
|
+
end
|
49
|
+
end
|
46
50
|
|
51
|
+
Neoid.db = $neo
|
52
|
+
```
|
47
53
|
|
48
|
-
`01_` in the file name is in order to get this file loaded first, before the models (
|
54
|
+
`01_` in the file name is in order to get this file loaded first, before the models (initializers are loaded alphabetically).
|
49
55
|
|
50
56
|
If you have a better idea (I bet you do!) please let me know.
|
51
57
|
|
@@ -57,105 +63,137 @@ If you have a better idea (I bet you do!) please let me know.
|
|
57
63
|
For nodes, first include the `Neoid::Node` module in your model:
|
58
64
|
|
59
65
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
66
|
+
```ruby
|
67
|
+
class User < ActiveRecord::Base
|
68
|
+
include Neoid::Node
|
69
|
+
end
|
70
|
+
```
|
64
71
|
|
65
72
|
This will help to create a corresponding node on Neo4j when a user is created, delete it when a user is destroyed, and update it if needed.
|
66
73
|
|
67
|
-
Then, you can customize what fields will be saved on the node in Neo4j,
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
74
|
+
Then, you can customize what fields will be saved on the node in Neo4j, inside neoidable configuration:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
class User < ActiveRecord::Base
|
78
|
+
include Neoid::Node
|
79
|
+
|
80
|
+
neoidable do |c|
|
81
|
+
c.field :slug
|
82
|
+
c.field :display_name
|
83
|
+
c.field :display_name_length do
|
84
|
+
self.display_name.length
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
```
|
80
89
|
|
81
|
-
You can use `neo_properties_to_hash`, a helper method to make things shorter:
|
82
90
|
|
91
|
+
#### Relationships
|
83
92
|
|
84
|
-
|
85
|
-
neo_properties_to_hash(%w(slug display_name))
|
86
|
-
end
|
93
|
+
Let's assume that a `User` can `Like` `Movie`s:
|
87
94
|
|
88
95
|
|
89
|
-
|
96
|
+
```ruby
|
97
|
+
# user.rb
|
90
98
|
|
91
|
-
|
99
|
+
class User < ActiveRecord::Base
|
100
|
+
include Neoid::Node
|
92
101
|
|
102
|
+
has_many :likes
|
103
|
+
has_many :movies, through: :likes
|
93
104
|
|
94
|
-
|
105
|
+
neoidable do |c|
|
106
|
+
c.field :slug
|
107
|
+
c.field :display_name
|
108
|
+
end
|
109
|
+
end
|
95
110
|
|
96
|
-
class User < ActiveRecord::Base
|
97
|
-
include Neoid::Node
|
98
|
-
|
99
|
-
has_many :likes
|
100
|
-
has_many :movies, through: :likes
|
101
|
-
|
102
|
-
def to_neo
|
103
|
-
neo_properties_to_hash(%w(slug display_name))
|
104
|
-
end
|
105
|
-
end
|
106
111
|
|
112
|
+
# movie.rb
|
107
113
|
|
108
|
-
|
114
|
+
class Movie < ActiveRecord::Base
|
115
|
+
include Neoid::Node
|
109
116
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
has_many :likes
|
114
|
-
has_many :users, through: :likes
|
115
|
-
|
116
|
-
def to_neo
|
117
|
-
neo_properties_to_hash(%w(slug name))
|
118
|
-
end
|
119
|
-
end
|
117
|
+
has_many :likes
|
118
|
+
has_many :users, through: :likes
|
120
119
|
|
120
|
+
neoidable do |c|
|
121
|
+
c.field :slug
|
122
|
+
c.field :name
|
123
|
+
end
|
124
|
+
end
|
121
125
|
|
122
|
-
# like.rb
|
123
126
|
|
124
|
-
|
125
|
-
belongs_to :user
|
126
|
-
belongs_to :movie
|
127
|
-
end
|
127
|
+
# like.rb
|
128
128
|
|
129
|
+
class Like < ActiveRecord::Base
|
130
|
+
belongs_to :user
|
131
|
+
belongs_to :movie
|
132
|
+
end
|
133
|
+
```
|
129
134
|
|
130
135
|
|
131
|
-
Now let's make the `Like` model a Neoid, by including the `Neoid::Relationship` module, and define the relationship (start & end nodes and relationship type) options with `neoidable` method:
|
136
|
+
Now let's make the `Like` model a Neoid, by including the `Neoid::Relationship` module, and define the relationship (start & end nodes and relationship type) options with `neoidable` config and `relationship` method:
|
132
137
|
|
133
138
|
|
134
|
-
|
135
|
-
|
136
|
-
|
139
|
+
```ruby
|
140
|
+
class Like < ActiveRecord::Base
|
141
|
+
belongs_to :user
|
142
|
+
belongs_to :movie
|
137
143
|
|
138
|
-
|
139
|
-
neoidable start_node: :user, end_node: :movie, type: :likes
|
140
|
-
end
|
144
|
+
include Neoid::Relationship
|
141
145
|
|
146
|
+
neoidable do |c|
|
147
|
+
c.relationship start_node: :user, end_node: :movie, type: :likes
|
148
|
+
end
|
149
|
+
end
|
150
|
+
```
|
142
151
|
|
143
152
|
Neoid adds `neo_node` and `neo_relationships` to nodes and relationships, respectively.
|
144
153
|
|
145
154
|
So you could do:
|
146
155
|
|
147
|
-
|
148
|
-
|
149
|
-
|
156
|
+
```ruby
|
157
|
+
user = User.create!(display_name: "elado")
|
158
|
+
user.movies << Movie.create("Memento")
|
159
|
+
user.movies << Movie.create("Inception")
|
160
|
+
|
161
|
+
user.neo_node # => #<Neography::Node…>
|
162
|
+
user.neo_node.display_name # => "elado"
|
163
|
+
|
164
|
+
rel = user.likes.first.neo_relationship
|
165
|
+
rel.start_node # user.neo_node
|
166
|
+
rel.end_node # user.movies.first.neo_node
|
167
|
+
rel.rel_type # 'likes'
|
168
|
+
```
|
169
|
+
|
170
|
+
## Index for Full-Text Search
|
171
|
+
|
172
|
+
Using `search` block inside a `neoidable` block, you can store certain fields.
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
# movie.rb
|
176
|
+
|
177
|
+
class Movie < ActiveRecord::Base
|
178
|
+
include Neoid::Node
|
179
|
+
|
180
|
+
neoidable do |c|
|
181
|
+
c.field :slug
|
182
|
+
c.field :name
|
150
183
|
|
151
|
-
|
152
|
-
|
184
|
+
c.search do |s|
|
185
|
+
# full-text index fields
|
186
|
+
s.fulltext :name
|
187
|
+
s.fulltext :description
|
153
188
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
189
|
+
# just index for exact matches
|
190
|
+
s.index :year
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
```
|
158
195
|
|
196
|
+
Records will be automatically indexed when inserted or updated.
|
159
197
|
|
160
198
|
## Querying
|
161
199
|
|
@@ -165,51 +203,86 @@ You can query with all [Neography](https://github.com/maxdemarzi/neography)'s AP
|
|
165
203
|
|
166
204
|
These examples query Neo4j using Gremlin for IDs of objects, and then fetches them from ActiveRecord with an `in` query.
|
167
205
|
|
168
|
-
Of course, you can store using the `
|
206
|
+
Of course, you can store using the `neoidable do |c| c.field ... end` all the data you need in Neo4j and avoid querying ActiveRecord.
|
169
207
|
|
170
208
|
|
171
209
|
**Most popular categories**
|
172
210
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
g.v(0)
|
177
|
-
.out('movies_subref').out
|
178
|
-
.inE('likes')
|
179
|
-
.inV
|
180
|
-
.groupCount(m).iterate()
|
211
|
+
```ruby
|
212
|
+
gremlin_query = <<-GREMLIN
|
213
|
+
m = [:]
|
181
214
|
|
182
|
-
|
183
|
-
|
215
|
+
g.v(0)
|
216
|
+
.out('movies_subref').out
|
217
|
+
.inE('likes')
|
218
|
+
.inV
|
219
|
+
.groupCount(m).iterate()
|
184
220
|
|
185
|
-
|
221
|
+
m.sort{-it.value}.collect{it.key.ar_id}
|
222
|
+
GREMLIN
|
186
223
|
|
187
|
-
|
224
|
+
movie_ids = Neoid.db.execute_script(gremlin_query)
|
188
225
|
|
226
|
+
Movie.where(id: movie_ids)
|
227
|
+
```
|
189
228
|
|
190
229
|
Assuming we have another `Friendship` model which is a relationship with start/end nodes of `user` and type of `friends`,
|
191
230
|
|
192
231
|
**Movies of user friends that the user doesn't have**
|
193
232
|
|
194
|
-
|
233
|
+
```ruby
|
234
|
+
user = User.find(1)
|
235
|
+
|
236
|
+
gremlin_query = <<-GREMLIN
|
237
|
+
u = g.idx('users_index')[[ar_id:user_id]].next()
|
238
|
+
movies = []
|
239
|
+
|
240
|
+
u
|
241
|
+
.out('likes').aggregate(movies).back(2)
|
242
|
+
.out('friends').out('likes')
|
243
|
+
.dedup
|
244
|
+
.except(movies).collect{it.ar_id}
|
245
|
+
GREMLIN
|
246
|
+
|
247
|
+
movie_ids = Neoid.db.execute_script(gremlin_query, user_id: user.id)
|
248
|
+
|
249
|
+
Movie.where(id: movie_ids)
|
250
|
+
```
|
251
|
+
|
252
|
+
`.next()` is in order to get a vertex object which we can actually query on.
|
253
|
+
|
254
|
+
|
255
|
+
### Full Text Search
|
256
|
+
|
257
|
+
```ruby
|
258
|
+
# will match all movies with full-text match for name/description. returns ActiveRecord instanced
|
259
|
+
Movie.neo_search("*hello*").results
|
260
|
+
|
261
|
+
# same as above but returns hashes with the values that were indexed on Neo4j
|
262
|
+
Movie.search("*hello*").hits
|
195
263
|
|
196
|
-
|
197
|
-
|
198
|
-
movies = []
|
264
|
+
# search in multiple types
|
265
|
+
Neoid.neo_search([Movie, User], "hello")
|
199
266
|
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
.dedup
|
204
|
-
.except(movies).collect{it.ar_id}
|
205
|
-
GREMLIN
|
267
|
+
# search with exact matches (pass a hash of field/value)
|
268
|
+
Movie.neo_search(year: 2013).results
|
269
|
+
```
|
206
270
|
|
207
|
-
|
271
|
+
## Inserting records of existing app
|
208
272
|
|
209
|
-
|
273
|
+
If you have an existing database and just want to integrate Neoid, configure the `neoidable`s and run in a rake task or console
|
210
274
|
|
275
|
+
```ruby
|
276
|
+
[ Like.includes(:user).includes(:movie), OtherRelationshipModel ].each { |model| model.all.each(&:neo_update) }
|
211
277
|
|
212
|
-
|
278
|
+
NodeModel.all.each(&:neo_update)
|
279
|
+
```
|
280
|
+
|
281
|
+
This will loop through all of your relationship records and generate the two edge nodes along with a relationship (eager loading for better performance).
|
282
|
+
The second line is for nodes without relationships.
|
283
|
+
|
284
|
+
For large data sets use pagination.
|
285
|
+
Better interface for that in the future.
|
213
286
|
|
214
287
|
|
215
288
|
## Behind The Scenes
|
@@ -219,7 +292,7 @@ Whenever the `neo_node` on nodes or `neo_relationship` on relationships is calle
|
|
219
292
|
### For Nodes:
|
220
293
|
|
221
294
|
1. Ensures there's a sub reference node (read [here](http://docs.neo4j.org/chunked/stable/tutorials-java-embedded-index.html) about sub reference nodes)
|
222
|
-
2. Creates a node based on the ActiveRecord, with the `id` attribute and all other attributes from `
|
295
|
+
2. Creates a node based on the ActiveRecord, with the `id` attribute and all other attributes from `neoidable`'s field list
|
223
296
|
3. Creates a relationship between the sub reference node and the newly created node
|
224
297
|
4. Adds the ActiveRecord `id` to a node index, pointing to the Neo4j node id, for fast lookup in the future
|
225
298
|
|
@@ -235,35 +308,57 @@ Like Nodes, it uses an index (relationship index) to look up a relationship by A
|
|
235
308
|
|
236
309
|
## Testing
|
237
310
|
|
238
|
-
|
311
|
+
In order to test your app or this gem, you need a running Neo4j database, dedicated to tests.
|
239
312
|
|
240
|
-
|
313
|
+
I use port 7574 for this. To run another database locally:
|
241
314
|
|
242
|
-
Copy the Neo4j folder to a different location,
|
315
|
+
Copy the entire Neo4j database folder to a different location,
|
243
316
|
|
244
317
|
**or**
|
245
318
|
|
246
|
-
symlink `bin`, `lib`, `plugins`, `system`, copy `conf` and create an empty `data` folder.
|
319
|
+
symlink `bin`, `lib`, `plugins`, `system`, copy `conf` to a single folder, and create an empty `data` folder.
|
247
320
|
|
248
321
|
Then, edit `conf/neo4j-server.properties` and set the port (`org.neo4j.server.webserver.port`) from 7474 to 7574 and run the server with `bin/neo4j start`
|
249
322
|
|
323
|
+
## Testing Your App with Neoid (RSpec)
|
324
|
+
|
325
|
+
In `environments/test.rb`, add:
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
ENV["NEO4J_URL"] = 'http://localhost:7574'
|
329
|
+
```
|
330
|
+
|
331
|
+
In your `spec_helper.rb`, add the following configurations:
|
250
332
|
|
251
|
-
|
333
|
+
```ruby
|
334
|
+
config.before :all do
|
335
|
+
Neoid.clean_db(:yes_i_am_sure)
|
336
|
+
end
|
252
337
|
|
338
|
+
config.before :each do
|
339
|
+
Neoid.reset_cached_variables
|
340
|
+
end
|
341
|
+
```
|
342
|
+
|
343
|
+
## Testing This Gem
|
344
|
+
|
345
|
+
Just run `rake` from the gem folder.
|
253
346
|
|
254
347
|
## Contributing
|
255
348
|
|
256
349
|
Please create a [new issue](https://github.com/elado/neoid/issues) if you run into any bugs. Contribute patches via pull requests. Write tests and make sure all tests pass.
|
257
350
|
|
258
351
|
|
352
|
+
## Heroku Support
|
353
|
+
|
354
|
+
Unfortunately, as for now, Neo4j add-on on Heroku doesn't support Gremlin. Therefore, this gem won't work on Heroku's add on. You should self-host a Neo4j instance on an EC2 or any other server.
|
355
|
+
|
259
356
|
|
260
357
|
## To Do
|
261
358
|
|
262
|
-
|
263
|
-
|
264
|
-
* Execute queries/scripts from model and not Neography (e.g. `Movie.neo_gremlin(gremlin_query)` with query that outputs IDs, returns a list of `Movie`s)
|
265
|
-
* Rake task to index all nodes and relatiohsips in Neo4j
|
359
|
+
[To Do](https://github.com/elado/neoid/blob/master/TODO.md)
|
360
|
+
|
266
361
|
|
267
362
|
---
|
268
363
|
|
269
|
-
Developed by [@elado](http://twitter.com/elado)
|
364
|
+
Developed by [@elado](http://twitter.com/elado)
|
data/Rakefile
CHANGED
data/TODO.md
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
# Neoid - To Do
|
2
|
+
|
3
|
+
* Allow to disable sub reference nodes through options
|
4
|
+
* Execute queries/scripts from model and not Neography (e.g. `Movie.neo_gremlin(gremlin_query)` with query that outputs IDs, returns a list of `Movie`s)
|
5
|
+
* Rake task to index all nodes and relatiohsips in Neo4j
|
6
|
+
* Test update node
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module Neoid
|
2
|
+
class NeoDatabaseCleaner
|
3
|
+
def self.clean_db(start_node = Neoid.db.get_root)
|
4
|
+
Neoid.db.execute_script <<-GREMLIN
|
5
|
+
g.V.toList().each { if (it.id != 0) g.removeVertex(it) }
|
6
|
+
g.indices.each { g.dropIndex(it.indexName); }
|
7
|
+
GREMLIN
|
8
|
+
|
9
|
+
true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Ndoid
|
2
|
+
class Middleware
|
3
|
+
def initialize(app)
|
4
|
+
@app = app
|
5
|
+
end
|
6
|
+
|
7
|
+
def call(env)
|
8
|
+
old, Thread.current[:neoid_enabled] = Thread.current[:neoid_enabled], true
|
9
|
+
@app.call(env)
|
10
|
+
ensure
|
11
|
+
Thread.current[:neoid_enabled] = old
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -1,14 +1,16 @@
|
|
1
1
|
module Neoid
|
2
2
|
module ModelAdditions
|
3
3
|
module ClassMethods
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
attr_reader :neoid_config
|
5
|
+
attr_reader :neoid_options
|
6
|
+
|
7
|
+
def neoid_config
|
8
|
+
@neoid_config ||= Neoid::ModelConfig.new(self)
|
8
9
|
end
|
9
|
-
|
10
|
-
def
|
11
|
-
|
10
|
+
|
11
|
+
def neoidable(options = {})
|
12
|
+
yield(neoid_config) if block_given?
|
13
|
+
@neoid_options = options
|
12
14
|
end
|
13
15
|
|
14
16
|
def neo_index_name
|
@@ -18,13 +20,32 @@ module Neoid
|
|
18
20
|
|
19
21
|
module InstanceMethods
|
20
22
|
def to_neo
|
21
|
-
|
23
|
+
if self.class.neoid_config.stored_fields
|
24
|
+
hash = self.class.neoid_config.stored_fields.inject({}) do |all, (field, block)|
|
25
|
+
all[field] = if block
|
26
|
+
instance_eval(&block)
|
27
|
+
else
|
28
|
+
self.send(field) rescue (raise "No field #{field} for #{self.class.name}")
|
29
|
+
end
|
30
|
+
|
31
|
+
all
|
32
|
+
end
|
33
|
+
|
34
|
+
hash.reject { |k, v| v.nil? }
|
35
|
+
else
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def neo_resave
|
41
|
+
_reset_neo_representation
|
42
|
+
neo_update
|
22
43
|
end
|
23
44
|
|
24
45
|
protected
|
25
|
-
def neo_properties_to_hash(*
|
26
|
-
|
27
|
-
all[property] = self.
|
46
|
+
def neo_properties_to_hash(*attribute_list)
|
47
|
+
attribute_list.flatten.inject({}) { |all, property|
|
48
|
+
all[property] = self.send(property)
|
28
49
|
all
|
29
50
|
}
|
30
51
|
end
|
@@ -36,11 +57,20 @@ module Neoid
|
|
36
57
|
if results
|
37
58
|
neo_load(results.first['self'])
|
38
59
|
else
|
39
|
-
|
40
|
-
node
|
60
|
+
neo_create
|
41
61
|
end
|
42
62
|
end
|
43
63
|
end
|
64
|
+
|
65
|
+
def _reset_neo_representation
|
66
|
+
@_neo_representation = nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.included(receiver)
|
71
|
+
receiver.extend ClassMethods
|
72
|
+
receiver.send :include, InstanceMethods
|
73
|
+
Neoid.models << receiver
|
44
74
|
end
|
45
75
|
end
|
46
76
|
end
|
data/lib/neoid/model_config.rb
CHANGED
@@ -1,11 +1,55 @@
|
|
1
1
|
module Neoid
|
2
2
|
class ModelConfig
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
attr_reader :properties
|
4
|
+
attr_reader :search_options
|
5
|
+
attr_reader :relationship_options
|
6
|
+
|
7
|
+
def initialize(klass)
|
8
|
+
@klass = klass
|
9
|
+
end
|
10
|
+
|
11
|
+
def stored_fields
|
12
|
+
@stored_fields ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def field(name, &block)
|
16
|
+
self.stored_fields[name] = block
|
17
|
+
end
|
18
|
+
|
19
|
+
def relationship(options)
|
20
|
+
@relationship_options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
def search(&block)
|
24
|
+
raise "search needs a block" unless block_given?
|
25
|
+
@search_options = SearchConfig.new
|
26
|
+
block.(@search_options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def inspect
|
30
|
+
"#<Neoid::ModelConfig @properties=#{properties.inspect} @search_options=#{@search_options.inspect}>"
|
31
|
+
end
|
32
|
+
end
|
6
33
|
|
7
|
-
|
8
|
-
|
34
|
+
class SearchConfig
|
35
|
+
def index_fields
|
36
|
+
@index_fields ||= {}
|
37
|
+
end
|
38
|
+
|
39
|
+
def fulltext_fields
|
40
|
+
@fulltext_fields ||= {}
|
41
|
+
end
|
42
|
+
|
43
|
+
def index(field, options = {}, &block)
|
44
|
+
index_fields[field] = options.merge(block: block)
|
45
|
+
end
|
46
|
+
|
47
|
+
def fulltext(field, options = {}, &block)
|
48
|
+
fulltext_fields[field] = options.merge(block: block)
|
49
|
+
end
|
50
|
+
|
51
|
+
def inspect
|
52
|
+
"#<Neoid::SearchConfig @index_fields=#{index_fields.inspect} @fulltext_fields=#{fulltext_fields.inspect}>"
|
9
53
|
end
|
10
54
|
end
|
11
55
|
end
|