Permalink
Browse files

* new configuration syntax (backwards compatible)

* full text search index
  • Loading branch information...
1 parent 1480136 commit 1cbf15c6abac69b3a1d1c807a1fb30dd24617bc1 @elado committed Mar 1, 2012
View
@@ -0,0 +1,4 @@
+script: "bundle exec rake neo4j:install neo4j:start spec --trace"
+rvm:
+ - 1.9.2
+ - 1.9.3
View
@@ -1,3 +1,8 @@
+## v0.0.3
+
+* new configuration syntax (backwards compatible)
+* full text search index
+
## v0.0.2
* create node immediately after active record create
View
@@ -14,7 +14,7 @@ Neoid offers querying Neo4j for IDs of objects and then fetch them from your RDB
Add to your Gemfile and run the `bundle` command to install it.
- gem 'neoid'
+ gem 'neoid', git: 'git@github.com:elado/neoid.git'
**Requires Ruby 1.9.2 or later.**
@@ -64,25 +64,16 @@ For nodes, first include the `Neoid::Node` module in your model:
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.
-Then, you can customize what fields will be saved on the node in Neo4j, by implementing `to_neo` method:
+Then, you can customize what fields will be saved on the node in Neo4j, inside neoidable configuration:
class User < ActiveRecord::Base
include Neoid::Node
-
- def to_neo
- {
- slug: slug,
- display_name: display_name
- }
- end
- end
-
-You can use `neo_properties_to_hash`, a helper method to make things shorter:
-
-
- def to_neo
- neo_properties_to_hash(%w(slug display_name))
+
+ neoidable do |c|
+ c.field :slug
+ c.field :display_name
+ end
end
@@ -99,8 +90,9 @@ Let's assume that a `User` can `Like` `Movie`s:
has_many :likes
has_many :movies, through: :likes
- def to_neo
- neo_properties_to_hash(%w(slug display_name))
+ neoidable do |c|
+ c.field :slug
+ c.field :display_name
end
end
@@ -113,8 +105,9 @@ Let's assume that a `User` can `Like` `Movie`s:
has_many :likes
has_many :users, through: :likes
- def to_neo
- neo_properties_to_hash(%w(slug name))
+ neoidable do |c|
+ c.field :slug
+ c.field :name
end
end
@@ -128,15 +121,18 @@ Let's assume that a `User` can `Like` `Movie`s:
-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:
+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:
class Like < ActiveRecord::Base
belongs_to :user
belongs_to :movie
include Neoid::Relationship
- neoidable start_node: :user, end_node: :movie, type: :likes
+
+ neoidable do |c|
+ c.relationship start_node: :user, end_node: :movie, type: :likes
+ end
end
@@ -157,6 +153,28 @@ So you could do:
rel.rel_type # 'likes'
+## Index for Full-Text Search
+
+Using `search` block inside a `neoidable` block, you can store certain fields.
+
+ # movie.rb
+
+ class Movie < ActiveRecord::Base
+ include Neoid::Node
+
+ neoidable do |c|
+ c.field :slug
+ c.field :name
+
+ search do |s|
+ s.index :name
+ s.index :description
+ end
+ end
+ end
+
+Records will be automatically indexed when inserted or updated.
+
## Querying
You can query with all [Neography](https://github.com/maxdemarzi/neography)'s API: `traverse`, `execute_query` for Cypher, and `execute_script` for Gremlin.
@@ -165,7 +183,7 @@ You can query with all [Neography](https://github.com/maxdemarzi/neography)'s AP
These examples query Neo4j using Gremlin for IDs of objects, and then fetches them from ActiveRecord with an `in` query.
-Of course, you can store using the `to_neo` all the data you need in Neo4j and avoid querying ActiveRecord.
+Of course, you can store using the `neoidable do |c| c.field ... end` all the data you need in Neo4j and avoid querying ActiveRecord.
**Most popular categories**
@@ -194,7 +212,7 @@ Assuming we have another `Friendship` model which is a relationship with start/e
user = User.find(1)
gremlin_query = <<-GREMLIN
- u = g.idx('users_index')[[ar_id:'#{user.id}']][0].toList()[0]
+ u = g.idx('users_index')[[ar_id:'#{user.id}']].next()
movies = []
u
@@ -209,17 +227,22 @@ Assuming we have another `Friendship` model which is a relationship with start/e
Movie.where(id: movie_ids)
-`[0].toList()[0]` is in order to get a pipeline object which we can actually query on.
+`.next()` is in order to get a vertext object which we can actually query on.
+
+### Full Text Search
+
+TODO (see specs)
+
## Behind The Scenes
Whenever the `neo_node` on nodes or `neo_relationship` on relationships is called, Neoid checks if there's a corresponding node/relationship in Neo4j. If not, it does the following:
### For Nodes:
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)
-2. Creates a node based on the ActiveRecord, with the `id` attribute and all other attributes from `to_neo`
+2. Creates a node based on the ActiveRecord, with the `id` attribute and all other attributes from `neoidable`'s field list
3. Creates a relationship between the sub reference node and the newly created node
4. Adds the ActiveRecord `id` to a node index, pointing to the Neo4j node id, for fast lookup in the future
@@ -260,7 +283,7 @@ In `environments/test.rb`, add:
In your `spec_helper.rb`, add the following configurations:
config.before :all do
- RestClient.delete "#{ENV["NEO4J_URL"]}/cleandb/secret-key"
+ Neoid.clean_db(:yes_i_am_sure)
end
config.before :each do
@@ -280,10 +303,8 @@ Please create a [new issue](https://github.com/elado/neoid/issues) if you run in
## To Do
-* `after_update` to update a node/relationship.
-* Allow to disable sub reference nodes through options
-* 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)
-* Rake task to index all nodes and relatiohsips in Neo4j
+[To Do](TODO.md)
+
---
View
@@ -0,0 +1,8 @@
+# Neoid - To Do
+
+* `after_update` to update a relationship
+* Allow to disable sub reference nodes through options
+* 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)
+* Rake task to index all nodes and relatiohsips in Neo4j
+* Test update node
+* Full Text Search query documentation
View
@@ -10,6 +10,10 @@ class << self
attr_accessor :logger
attr_accessor :ref_node
+ def models
+ @models ||= []
+ end
+
def db
raise "Must set Neoid.db with a Neography::Rest instance" unless @db
@db
@@ -24,22 +28,15 @@ def ref_node
end
def reset_cached_variables
- [ User, Product, Expertise, Category ].each { |klass|
+ Neoid.models.each { |klass|
klass.instance_variable_set(:@_neo_subref_node, nil)
}
$neo_ref_node = nil
end
def clean_db(confirm)
puts "must call with confirm: Neoid.clean_db(:yes_i_am_sure)" and return unless confirm == :yes_i_am_sure
-
- if indexes = db.list_node_indexes
- indexes.values.each { |x| RestClient.delete x ['template'].gsub('/{key}/{value}', '') }
- end
-
- if relationships = db.get_node_relationships(0)
- relationships.each { |x| RestClient.delete x['self'] }
- end
+ RestClient.delete "#{Neoid.db.protocol}#{Neoid.db.server}:#{Neoid.db.port}/cleandb/secret-key"
end
def enabled
@@ -1,24 +1,37 @@
module Neoid
module ModelAdditions
module ClassMethods
- def neoidable(options)
- @config = Neoid::ModelConfig.new
- yield(@config) if block_given?
- @neoidable_options = options
+ attr_reader :neoid_config
+ attr_reader :neoid_options
+
+ def neoid_config
+ @neoid_config ||= Neoid::ModelConfig.new(self)
end
-
- def neoidable_options
- @neoidable_options
+
+ def neoidable(options = {})
+ yield(neoid_config) if block_given?
+ @neoid_options = options
end
def neo_index_name
@index_name ||= "#{self.name.tableize}_index"
end
+
+ def neo_search_index_name
+ @search_index_name ||= "#{self.name.tableize}_search_index"
+ end
end
module InstanceMethods
def to_neo
- {}
+ if self.class.neoid_config.stored_fields
+ self.class.neoid_config.stored_fields.inject({}) { |all, field|
+ all[field] = self.send(field) rescue (raise "No field #{field} for #{self.class.name}")
+ all
+ }
+ else
+ {}
+ end
end
protected
@@ -42,5 +55,11 @@ def _neo_representation
end
end
end
+
+ def self.included(receiver)
+ receiver.extend ClassMethods
+ receiver.send :include, InstanceMethods
+ Neoid.models << receiver
+ end
end
end
View
@@ -1,11 +1,47 @@
module Neoid
class ModelConfig
- @properties = []
-
- attr_accessor :properties
+ attr_reader :properties
+ attr_reader :search_options
+ attr_reader :relationship_options
+
+ def initialize(klass)
+ @klass = klass
+ end
+
+ def stored_fields
+ @stored_fields ||= []
+ end
+
+ def field(name)
+ self.stored_fields << name
+ end
+
+ def relationship(options)
+ @relationship_options = options
+ end
+
+ def search(&block)
+ raise "search needs a block" unless block_given?
+ @search_options = SearchConfig.new
+ block.(@search_options)
+ end
+
+ def inspect
+ "#<Neoid::ModelConfig @properties=#{properties.inspect} @search_options=#{@search_options.inspect}>"
+ end
+ end
- def property(name)
- @properties << name
+ class SearchConfig
+ def index_fields
+ @index_fields ||= {}
+ end
+
+ def index(field, options = {})
+ index_fields[field] = options
+ end
+
+ def inspect
+ "#<Neoid::SearchConfig @index_fields=#{index_fields.inspect}>"
end
end
end
Oops, something went wrong.

0 comments on commit 1cbf15c

Please sign in to comment.