neoon 0.0.3 → 0.0.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 08ac0ff644d4961a328938854c086db4a7d35689
4
- data.tar.gz: 54aa3709453cecc5d1533036418812868547affe
3
+ metadata.gz: b1ff7ac30436f19194698ede55cfe68529918122
4
+ data.tar.gz: 3b62de2caf2e48745e2f67d9705490b5cfe43e48
5
5
  SHA512:
6
- metadata.gz: 84a8aaca5d0e2a78c120517f29aba0ad0a63d9b8c4e818a4e5a9fbb5239f02d0611ce2d5d92a544e1fc7b99e41fbc8d2f8c6e55378394b191f818f81a8621494
7
- data.tar.gz: 50662379431fe64b80fbd9d64930060d1a582adb737a52bddb92f9135b2c9880d53d3f0cff493f80492fafdfe29e0d60bb479799f25193f7389c9e3362c6070e
6
+ metadata.gz: a5baa0e81c289446fee6744879834a45a878b976d265c10c871b0b666bdcc9c69f581108a665c1959533e0f73ae9241a2247da2d822043ac5ff8d084e708b0ed
7
+ data.tar.gz: dcb1ea83f6b94370962a93f9a829833f2a1e1f6f882e1ea4458b530f56f8776b078644433bce4090b99b56b683743fae9e444ad645db6234a0be490497ee1026
data/.gitignore CHANGED
@@ -16,3 +16,4 @@ test/tmp
16
16
  test/version_tmp
17
17
  tmp
18
18
  .ruby-version
19
+ neo4j
data/.travis.yml CHANGED
@@ -4,6 +4,9 @@ rvm:
4
4
  - 2.0.0
5
5
  - jruby
6
6
  jdk:
7
+ - oraclejdk7
7
8
  - openjdk7
8
- services:
9
- - neo4j
9
+ before_script:
10
+ - bundle exec rake neo4j:server:install neo4j:server:start
11
+ after_script:
12
+ - bundle exec rake neo4j:server:stop
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  A simple Ruby wrapper for Neo4j with focus on Cypher and the features of Neo4j 2.0
6
6
 
7
- #### Inspired by [Neoid](https://github.com/elado/neoid)
7
+ ---
8
8
 
9
9
  ## Installation
10
10
 
@@ -22,6 +22,22 @@ Or install it yourself as:
22
22
 
23
23
  ## Usage
24
24
 
25
+ You can easily install Neo4j in your application path:
26
+
27
+ ```ruby
28
+ bundle exec rake neo4j:server:install
29
+ ```
30
+
31
+ Then start(stop/restart) the Neo4j server by:
32
+
33
+ ```ruby
34
+ bundle exec rake neo4j:server:start
35
+
36
+ bundle exec rake neo4j:server:stop
37
+
38
+ bundle exec rake neo4j:server:restart
39
+ ```
40
+
25
41
  First you have to initialize a client:
26
42
 
27
43
  ```ruby
@@ -34,17 +50,17 @@ Set configuration:
34
50
 
35
51
  ```ruby
36
52
  Neoon.configure do |config|
37
- config.preload_models = true # (default: false) This will load your models — so to update the indexes at the (Rails) boot
53
+ config.preload_models = true # This will load your models — helps updating the indexed nodes at the (Rails) boot (default: false)
38
54
  end
39
55
  ```
40
56
 
41
57
  To query using Cypher:
42
58
 
43
59
  ```ruby
44
- $neo.q("START node=node(*) RETURN node")
60
+ $neo.q('START node=node(*) RETURN node')
45
61
  ```
46
62
 
47
- To your ActiveRecord model, initialize Neoon like so (with example of using properties/index):
63
+ With ActiveRecord models, initialize Neoon like so (with example of using properties/index):
48
64
 
49
65
  ```ruby
50
66
  class Topic < ActiveRecord::Base
@@ -52,45 +68,88 @@ class Topic < ActiveRecord::Base
52
68
  include Neoon::Node
53
69
 
54
70
  neoon do |c|
55
- c.property :name
56
- c.property :slug, :index => true do
71
+ c.property :name, :index => true
72
+ c.property :slug, :index => :unique do
57
73
  "#{self.id}-#{self.name.underscore}"
58
74
  end
75
+ c.property :created_at
59
76
  end
60
77
 
61
78
  end
62
79
  ```
63
80
 
64
- ### Indexing
81
+ #### Indexing
65
82
 
66
- This will be used internally to auto indexing the model depends on what described in the neoon model config.
83
+ This will be used internally to auto index models nodes.
67
84
 
68
85
  ```ruby
69
- Neoon.db.list 'Topic' #=> ["slug"]
86
+ Topic.neo_index_list #=> [:name, :slug]
87
+
88
+ #
89
+ # Sync the indexed nodes as described in each model config. It returns the indexed fields.
90
+ # Remember, this will be called on each model on the boot if preload_models set to true.
91
+ Topic.neo_schema_update #=> [:name, :slug]
92
+ ```
70
93
 
71
- Neoon.db.create 'Topic', [:name, ...]
94
+ ---
72
95
 
73
- Neoon.db.drop 'Topic' [:name, ...]
96
+ ### Neoon::Cypher::Query
74
97
 
75
- # Alternativly
98
+ `Neoon::Cypher::InstanceQuery` should be initialized with an Class name or `label`. You can use `Neoon::Cypher::Query` to manually create indexes, constraints, etc.
76
99
 
77
- Topic.neo_index_list #=> ["slug"]
100
+ ```ruby
101
+ l = Neoon::Cypher::Query.new('Person') #=> #<Neoon::Cypher::Query:0x007fe8926d2068 @label="Person">
78
102
 
79
- Topic.neo_index_create [:name, ...]
103
+ l.create_index(:name).run
104
+ # l.drop_index(:name).run
80
105
 
81
- Topic.neo_index_drop [:name, ...]
106
+ l.create_constraint(:username).run
107
+ # l.drop_constraint(:username).run
82
108
 
83
- # Sync the indexes you described in model config. It returns the indexed fields.
84
- # Remember, this will be called on each model on the boot if preload_models set to true.
85
- Topic.neo_index_update #=> ["slug"]
109
+ l.list_indexes #=> { :name => true, :username => "UNIQUENESS" }
110
+ ```
111
+
112
+ ---
113
+
114
+ ### Neoon::Cypher::InstanceQuery
115
+
116
+ `Neoon::Cypher::InstanceQuery` should be initialized with an object that respond to `id`, `class.name` as it will represent the `label` and `neo_node_properties` as it will represent the `args`.
117
+
118
+ You can use `Neoon::Cypher::InstanceQuery` to manually create operations on nodes related to an object, etc.
119
+
120
+ Use it with Struct:
121
+
122
+ ```ruby
123
+ Customer = Struct.new(:id, :neo_node_properties)
124
+ cus = Customer.new(50, {:name => 'Julie', :address => 'PS'}) #=> #<Neoon::Cypher::InstanceQuery:0x007feb35953d00 @id=50, @label="Customer", @args={:name=>"Julie", :address=>"PS"}>
125
+
126
+ c = Neoon::Cypher::InstanceQuery.new(cus)
127
+
128
+ c.find_node.run #=> Return node in Neo4j if already saved
129
+ c.create_node.run #=> Create object node / or update it
130
+ c.delete_node.run #=> Remove object node
131
+ ```
132
+
133
+ Note that the key of finding nodes in Neo4j is `id` as saved in Neo4j with key `db_id`.
134
+
135
+ Another example on the model we defined above:
136
+
137
+ ```ruby
138
+ t = Neoon::Cypher::InstanceQuery.new(Topic.first) #=> #<Neoon::Cypher::InstanceQuery:0x007fe894410b98 @id=1, @label="Topic", @args={...}>
139
+
140
+ t.find_node.run #=> Returns node in Neo4j if already saved
141
+ t.create_node.run #=> Create object node / or update it
142
+ t.delete_node.run #=> Remove object node
86
143
  ```
87
144
 
145
+ ---
146
+
88
147
  **The gem is still at heavy development. More to come!**
89
148
 
90
149
  ## TODO
91
150
 
92
- 1. ADD TESTS
93
- 2. ..
151
+ 1. Add inline docs
152
+ 2. ADD TESTS!!!
94
153
 
95
154
  ## Contributing
96
155
 
data/Rakefile CHANGED
@@ -2,6 +2,8 @@ require 'rubygems'
2
2
  require 'bundler/gem_tasks'
3
3
  require 'rspec/core/rake_task'
4
4
 
5
+ load 'neoon/tasks/server.rake'
6
+
5
7
  RSpec::Core::RakeTask.new(:spec)
6
8
 
7
9
  task :default => :spec
@@ -0,0 +1,26 @@
1
+ module Faraday
2
+ module Neoon
3
+ class RaiseError < Faraday::Response::Middleware
4
+
5
+ def call(env)
6
+ @app.call(env).on_complete do |env|
7
+ case env[:status]
8
+ when (400..499)
9
+ body = JSON.parse(env[:body])
10
+ raise "Neoon::Error::#{body["cause"]["exception"]}".constantize, "#{{
11
+ :message => body["message"],
12
+ :exception => body["exception"],
13
+ :cause => {
14
+ :message => body["cause"]["message"],
15
+ :exception => body["cause"]["exception"]
16
+ }
17
+ } if env[:body]}"
18
+ when (500..599)
19
+ raise 'Something went error with Neo4j server.'
20
+ end
21
+ end
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -12,7 +12,6 @@ module Neoon
12
12
  end
13
13
 
14
14
  include Request
15
- include Indexing
16
15
 
17
16
  private
18
17
 
@@ -36,6 +35,7 @@ module Neoon
36
35
  builder.use FaradayMiddleware::EncodeJson
37
36
  builder.use FaradayMiddleware::Mashify
38
37
  builder.use FaradayMiddleware::ParseJson, :content_type => /\bjson$/
38
+ builder.use Faraday::Neoon::RaiseError
39
39
 
40
40
  builder.adapter Faraday.default_adapter
41
41
  end
@@ -1,5 +1,3 @@
1
- require 'faraday_middleware'
2
-
3
1
  module Neoon
4
2
  module Client
5
3
  module Request
@@ -0,0 +1,36 @@
1
+ module Neoon
2
+ module Cypher
3
+ class Handler
4
+
5
+ attr_reader :query, :args
6
+
7
+ def initialize(hash)
8
+ @query = hash[:query]
9
+ @args = hash[:args]
10
+ end
11
+
12
+ def to_cypher
13
+ if query.is_a?(Array)
14
+ query.map { |q| q.gsub(/\s+/, ' ').strip }
15
+ else
16
+ query.gsub(/\s+/, ' ').strip
17
+ end
18
+ end
19
+
20
+ def run
21
+ if query.is_a?(Array)
22
+ query.each { |q| make_cypher_request(q, args) }
23
+ else
24
+ make_cypher_request(query, args)
25
+ end
26
+ end
27
+
28
+ protected
29
+
30
+ def make_cypher_request(query, args = nil)
31
+ Neoon.db.cypher(query, args)
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,50 @@
1
+ module Neoon
2
+ module Cypher
3
+ class InstanceQuery
4
+
5
+ attr_reader :id, :label, :args
6
+
7
+ def initialize(object)
8
+ @id = object.id
9
+ @label = object.class.name
10
+ @args = object.neo_node_properties
11
+ end
12
+
13
+ def find_node
14
+ cypher_query = <<-CYPHER
15
+ MATCH node:#{label} WHERE node._id = #{id}
16
+ RETURN node
17
+ CYPHER
18
+ cypherable(:query => cypher_query)
19
+ end
20
+
21
+ def create_node
22
+ cypher_query = <<-CYPHER
23
+ MERGE (node:#{label} { _id: #{id} })
24
+ ON CREATE node SET node = {props}
25
+ ON MATCH node SET node = {props}
26
+ RETURN node
27
+ CYPHER
28
+ cypherable(:query => cypher_query, :args => {:props => args})
29
+ end
30
+ alias_method :update_node, :create_node
31
+ alias_method :save_node, :create_node
32
+
33
+ def delete_node
34
+ cypher_query = <<-CYPHER
35
+ MATCH node:#{label}
36
+ WHERE node._id = #{id}
37
+ DELETE node
38
+ CYPHER
39
+ cypherable(:query => cypher_query)
40
+ end
41
+
42
+ protected
43
+
44
+ def cypherable(query)
45
+ Cypher::Handler.new(query)
46
+ end
47
+
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,22 @@
1
+ module Neoon
2
+ module Cypher
3
+ class Query
4
+
5
+ attr_reader :label
6
+
7
+ def initialize(klass)
8
+ @label = klass.is_a?(String) ? klass : klass.name
9
+ end
10
+
11
+ include Neoon::Cypher::Schema::Indexes
12
+ include Neoon::Cypher::Schema::Constraints
13
+
14
+ protected
15
+
16
+ def cypherable(query)
17
+ Cypher::Handler.new(query)
18
+ end
19
+
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,19 @@
1
+ module Neoon
2
+ module Cypher
3
+ module Schema
4
+ module Constraints
5
+
6
+ def create_constraint(key)
7
+ cypher_query = "CREATE CONSTRAINT ON (node:#{label}) ASSERT node.#{key.to_s.downcase} IS UNIQUE"
8
+ cypherable(:query => cypher_query)
9
+ end
10
+
11
+ def drop_constraint(key)
12
+ cypher_query = "DROP CONSTRAINT ON (node:#{label}) ASSERT node.#{key.to_s.downcase} IS UNIQUE"
13
+ cypherable(:query => cypher_query)
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ module Neoon
2
+ module Cypher
3
+ module Schema
4
+ module Indexes
5
+
6
+ def list_indexes
7
+ # The only none Cypher query
8
+ idx_keys = Neoon.db.get("/schema/index/#{label}") + Neoon.db.get("/schema/constraint/#{label}")
9
+ idx_keys.reduce({}) { |k, v| k[v.send('property-keys').first.to_sym] = v.type || true; k }
10
+ end
11
+
12
+ def create_index(key)
13
+ cypher_query = "CREATE INDEX ON :#{label}(#{key.to_s.downcase})"
14
+ cypherable(:query => cypher_query)
15
+ end
16
+
17
+ def drop_index(key)
18
+ cypher_query = "DROP INDEX ON :#{label}(#{key.to_s.downcase})"
19
+ cypherable(:query => cypher_query)
20
+ end
21
+
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,71 @@
1
+ module Neoon
2
+ module Error
3
+
4
+ class Exception < StandardError; end
5
+ class IndexException < Exception; end
6
+ class SchemaException < Exception; end
7
+ class CypherException < Exception; end
8
+
9
+ # org.neo4j.kernel.api.exceptions
10
+ class BeginTransactionFailureException < Exception; end
11
+ class ConstraintCreationException < Exception; end
12
+ class EntityNotFoundException < Exception; end
13
+ class KernelException < Exception; end
14
+ class LabelNotFoundKernelException < Exception; end
15
+ class PropertyKeyIdNotFoundKernelException < Exception; end
16
+ class PropertyKeyNotFoundException < Exception; end
17
+ class PropertyNotFoundException < Exception; end
18
+ class RelationshipTypeIdNotFoundKernelException < Exception; end
19
+ class TransactionalException < Exception; end
20
+ class TransactionFailureException < Exception; end
21
+
22
+ # org.neo4j.kernel.api.exceptions.index
23
+ class ExceptionDuringFlipKernelException < IndexException; end
24
+ class FlipFailedKernelException < IndexException; end
25
+ class IndexNotFoundKernelException < IndexException; end
26
+ class IndexPopulationFailedKernelException < IndexException; end
27
+ class IndexProxyAlreadyClosedKernelException < IndexException; end
28
+
29
+ # org.neo4j.kernel.api.exceptions.schema
30
+ class AddIndexFailureException < SchemaException; end
31
+ class AlreadyConstrainedException < SchemaException; end
32
+ class AlreadyIndexedException < SchemaException; end
33
+ class CreateConstraintFailureException < SchemaException; end
34
+ class DropConstraintFailureException < SchemaException; end
35
+ class DropIndexFailureException < SchemaException; end
36
+ class IllegalTokenNameException < SchemaException; end
37
+ class IndexBelongsToConstraintException < SchemaException; end
38
+ class IndexBrokenKernelException < SchemaException; end
39
+ class MalformedSchemaRuleException < SchemaException; end
40
+ class NoSuchConstraintException < SchemaException; end
41
+ class NoSuchIndexException < SchemaException; end
42
+ class SchemaAndDataModificationInSameTransactionException < SchemaException; end
43
+ class SchemaKernelException < SchemaException; end
44
+ class SchemaRuleNotFoundException < SchemaException; end
45
+ class TooManyLabelsException < SchemaException; end
46
+
47
+ # org.neo4j.cypher.CypherException
48
+ class CypherExecutionException < CypherException; end
49
+ class UniquePathNotUniqueException < CypherException; end
50
+ # class EntityNotFoundException < CypherException; end
51
+ class CypherTypeException < CypherException; end
52
+ class IterableRequiredException < CypherException; end
53
+ class ParameterNotFoundException < CypherException; end
54
+ class ParameterWrongTypeException < CypherException; end
55
+ class PatternException < CypherException; end
56
+ class InternalException < CypherException; end
57
+ class MissingIndexException < CypherException; end
58
+ class MissingConstraintException < CypherException; end
59
+ class InvalidAggregateException < CypherException; end
60
+ class NodeStillHasRelationshipsException < CypherException; end
61
+ class ProfilerStatisticsNotReadyException < CypherException; end
62
+ class UnknownLabelException < CypherException; end
63
+ class IndexHintException < CypherException; end
64
+ class LabelScanHintException < CypherException; end
65
+ class UnableToPickStartPointException < CypherException; end
66
+ class InvalidSemanticsException < CypherException; end
67
+ class OutOfBoundsException < CypherException; end
68
+ class MergeConstraintConflictException < CypherException; end
69
+
70
+ end
71
+ end
@@ -0,0 +1,44 @@
1
+ module Neoon
2
+ module Model
3
+ module Node
4
+
5
+ def neo_node
6
+ _cypher_query.find_node.run.data.first.first.data
7
+ end
8
+
9
+ def neo_create
10
+ _cypher_query.create_node.run.data
11
+ end
12
+ alias_method :neo_save, :neo_create
13
+ alias_method :neo_update, :neo_create
14
+
15
+ def neo_destroy
16
+ _cypher_query.delete_node.run.data
17
+ end
18
+
19
+ def neo_node_properties
20
+ _neo_node.merge({ :_id => self.id })
21
+ end
22
+
23
+ protected
24
+
25
+ def _cypher_query
26
+ Neoon::Cypher::InstanceQuery.new(self)
27
+ end
28
+
29
+ def _neo_node
30
+ return {} unless self.class.neo_model_config.properties
31
+ hash = self.class.neo_model_config.properties.inject({}) do |all, (field, block)|
32
+ all[field] = if block[:block]
33
+ instance_eval(&block[:block])
34
+ else
35
+ self.send(field) rescue (raise "No field #{field} for #{self.class.name}")
36
+ end
37
+ all
38
+ end
39
+ hash.reject { |k, v| v.nil? }
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -3,37 +3,59 @@ module Neoon
3
3
  module Schema
4
4
 
5
5
  def neo_index_list
6
- Neoon.db.list self.name
6
+ _cypher_query.list_indexes
7
7
  end
8
8
 
9
- def neo_index_create keys = []
10
- Neoon.db.create self.name, keys
9
+ def neo_index_create key
10
+ if neo_schema_index_keys.select { |k,v| v == 'UNIQUENESS' }.include? key
11
+ _cypher_query.create_constraint(key).run
12
+ else
13
+ _cypher_query.create_index(key).run
14
+ end
15
+ true
11
16
  end
12
17
 
13
- def neo_index_drop keys = []
14
- Neoon.db.drop self.name, keys
18
+ def neo_index_drop key
19
+ if neo_index_list[key] == 'UNIQUENESS'
20
+ _cypher_query.drop_constraint(key).run
21
+ else
22
+ _cypher_query.drop_index(key).run
23
+ end
24
+ true
25
+ end
26
+
27
+ def neo_index_drop_all
28
+ neo_index_list.each { |k, _| neo_index_drop(k) }
15
29
  end
16
30
 
17
31
  def neo_index_update
18
- cl = neo_index_list
19
- ck = neo_node_keys_to_index
20
- return cl if (cl) == (ck)
32
+ cl, ck = neo_index_list.to_a, neo_schema_index_keys.to_a
33
+ return cl if cl == ck
34
+ return neo_index_drop_all if ck.empty?
21
35
 
22
- neo_index_create(ck - cl) unless (ck - cl).empty?
23
- neo_index_drop(cl - ck) unless (cl - ck).empty?
24
- neo_index_list
36
+ (cl - ck).each{ |k| neo_index_drop(k.first) } unless (cl - ck).empty?
37
+ (ck - cl).each{ |k| neo_index_create(k.first) } unless (ck - cl).empty?
25
38
  end
26
39
 
27
- private
40
+ def neo_schema_update
41
+ neo_index_update
42
+ neo_index_list
43
+ end
28
44
 
29
- def neo_model_props
30
- self.neo_model_config.properties
45
+ def neo_schema_index_keys
46
+ neo_model_config.properties.inject({}) do |all, (k, v)|
47
+ all[k] = true if v[:index]
48
+ all[k] = 'UNIQUENESS' if v[:index] == :unique
49
+ all
50
+ end
31
51
  end
32
52
 
33
- def neo_node_keys_to_index
34
- neo_model_props.select{ |k, v| v[:index]==true }.keys.map(&:to_s).sort
53
+ protected
54
+
55
+ def _cypher_query
56
+ Neoon::Cypher::Query.new(self)
35
57
  end
36
58
 
37
59
  end
38
60
  end
39
- end
61
+ end
data/lib/neoon/node.rb CHANGED
@@ -2,15 +2,30 @@ module Neoon
2
2
  module Node
3
3
 
4
4
  module ClassMethods
5
- end
5
+ attr_reader :neo_model_config
6
+
7
+ def neo_model_config
8
+ @neo_model_config ||= Model::Config.new(self)
9
+ end
10
+
11
+ def neoon(opts = {})
12
+ yield(neo_model_config) if block_given?
6
13
 
7
- module InstanceMethods
14
+ opts.each do |key, value|
15
+ raise "No such option #{key} for #{self.name} model" unless neo_model_config.respond_to?("#{key}=")
16
+ neo_model_config.send("#{key}=", value)
17
+ end
18
+ end
8
19
  end
9
20
 
10
21
  def self.included(receiver)
11
- receiver.send :include, Model::Service
22
+ receiver.send :include, Model::Node
23
+ receiver.extend Model::Schema
12
24
  receiver.extend ClassMethods
13
- receiver.send :include, InstanceMethods
25
+
26
+ receiver.after_save :neo_save
27
+ receiver.after_destroy :neo_destroy
28
+
14
29
  Neoon.config.models << receiver
15
30
  end
16
31
 
data/lib/neoon/railtie.rb CHANGED
@@ -6,16 +6,17 @@ module Rails
6
6
  class Railtie < ::Rails::Railtie
7
7
 
8
8
  rake_tasks do
9
- load "neoon/railties/database.rake"
9
+ load 'neoon/tasks/server.rake'
10
+ load 'neoon/tasks/database.rake'
10
11
  end
11
12
 
12
- initializer "neoon.neo_index_update" do
13
+ initializer 'neoon.neo_schema_update' do
13
14
  config.after_initialize do
14
- ::Neoon.config.models.each(&:neo_index_update)
15
+ ::Neoon.config.models.each(&:neo_schema_update)
15
16
  end
16
17
  end
17
18
 
18
- initializer "neoon.preload_models" do |app|
19
+ initializer 'neoon.preload_models' do |app|
19
20
  config.to_prepare do
20
21
  Rails::Neoon.preload_models(app)
21
22
  end
@@ -0,0 +1,15 @@
1
+ namespace :neo4j do
2
+ namespace :db do
3
+
4
+ namespace :index do
5
+ desc 'Index/reindex the nodes'
6
+ task :build do
7
+ end
8
+
9
+ desc 'Remove nodes indexing'
10
+ task :destroy do
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,35 @@
1
+ namespace :neo4j do
2
+ namespace :server do
3
+
4
+ # Taken from https://github.com/maxdemarzi/neography/blob/master/lib/neography/tasks.rb (Unix)
5
+ desc 'Install Neo4j server'
6
+ task :install, :edition, :version do |t, args|
7
+ args.with_defaults(:edition => 'community', :version => '2.0.0-M05')
8
+ puts "Installing Neo4j-#{args[:edition]}-#{args[:version]}..."
9
+ %x[curl -O http://dist.neo4j.org/neo4j-#{args[:edition]}-#{args[:version]}-unix.tar.gz]
10
+ %x[tar -xvzf neo4j-#{args[:edition]}-#{args[:version]}-unix.tar.gz]
11
+ %x[mv neo4j-#{args[:edition]}-#{args[:version]} neo4j]
12
+ %x[rm neo4j-#{args[:edition]}-#{args[:version]}-unix.tar.gz]
13
+ puts 'Neo4j Installed in to neo4j directory.'
14
+ end
15
+
16
+ desc 'Start Neo4j server'
17
+ task :start do
18
+ puts 'Starting Neo4j...'
19
+ %x[neo4j/bin/neo4j start]
20
+ end
21
+
22
+ desc 'Stop Neo4j server'
23
+ task :stop do
24
+ puts 'Stopping Neo4j...'
25
+ %x[neo4j/bin/neo4j stop]
26
+ end
27
+
28
+ desc 'Restart Neo4j server'
29
+ task :restart do
30
+ puts 'Restarting Neo4j...'
31
+ %x[neo4j/bin/neo4j restart]
32
+ end
33
+
34
+ end
35
+ end
data/lib/neoon/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Neoon
2
- VERSION = '0.0.3'
2
+ VERSION = '0.0.4'
3
3
  end
data/lib/neoon.rb CHANGED
@@ -1,16 +1,28 @@
1
1
  require 'multi_json'
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+ require 'faraday/neoon/raise_error'
2
5
 
3
6
  require 'neoon/version'
4
7
  require 'neoon/config'
8
+ require 'neoon/error'
9
+
5
10
  require 'neoon/client/request'
6
- require 'neoon/client/indexing'
7
11
  require 'neoon/client/connection'
8
12
 
13
+ require 'neoon/cypher/handler'
14
+ require 'neoon/cypher/schema/indexes'
15
+ require 'neoon/cypher/schema/constraints'
16
+ require 'neoon/cypher/query'
17
+ require 'neoon/cypher/instance_query'
18
+
9
19
  require 'neoon/model/config'
10
20
  require 'neoon/model/schema'
11
- require 'neoon/model/service'
21
+ require 'neoon/model/node'
22
+
12
23
  require 'neoon/node'
13
24
 
25
+
14
26
  if defined?(Rails)
15
27
  require 'neoon/railtie'
16
28
  end
data/lib/rails/neoon.rb CHANGED
@@ -6,13 +6,13 @@ module Rails
6
6
  models.each do |path|
7
7
  files = Dir.glob("#{path}/**/*.rb")
8
8
  files.sort.each do |file|
9
- load_model(file.gsub("#{path}/" , "").gsub(".rb", ""))
9
+ load_model(file.gsub("#{path}/" , '').gsub('.rb', ''))
10
10
  end
11
11
  end
12
12
  end
13
13
 
14
14
  def preload_models(app)
15
- models = app.config.paths["app/models"]
15
+ models = app.config.paths['app/models']
16
16
  load_models(models) if ::Neoon.config.preload_models
17
17
  end
18
18
 
data/neoon.gemspec CHANGED
@@ -19,10 +19,11 @@ Gem::Specification.new do |spec|
19
19
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
20
  spec.require_paths = ['lib']
21
21
 
22
- spec.required_ruby_version = ">= 1.9"
22
+ spec.required_ruby_version = '>= 1.9'
23
23
 
24
24
  spec.add_runtime_dependency 'faraday'
25
25
  spec.add_runtime_dependency 'faraday_middleware'
26
+ spec.add_runtime_dependency 'activesupport'
26
27
  spec.add_runtime_dependency 'multi_json'
27
28
  spec.add_runtime_dependency 'hashie'
28
29
 
@@ -2,10 +2,11 @@ class Topic < ActiveRecord::Base
2
2
  include Neoon::Node
3
3
 
4
4
  neoon do |c|
5
- c.property :name
6
- c.property :slug, :index => true do
5
+ c.property :name, :index => true
6
+ c.property :slug, :index => :unique do
7
7
  "#{self.id}-#{self.name.underscore}"
8
8
  end
9
+ c.property :created_at
9
10
  end
10
11
 
11
12
  end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neoon::Config do
4
+
5
+ context 'no block given' do
6
+ it 'returns the config singleton' do
7
+ expect(Neoon.config.class).to eq Neoon::Config
8
+ end
9
+
10
+ it 'returns config.preload_models false' do
11
+ expect(Neoon.config.preload_models).to be_false
12
+ end
13
+
14
+ it 'should have no model' do
15
+ expect(Neoon.config.models).to be_empty
16
+ end
17
+ end
18
+
19
+ context 'with block given' do
20
+ before do
21
+ Neoon.configure do |c|
22
+ c.preload_models = true
23
+ end
24
+ require 'app/models/topic' # fake loading a model
25
+ end
26
+
27
+ after do
28
+ Neoon.configure do |c|
29
+ c.preload_models = false
30
+ end
31
+ end
32
+
33
+ it 'returns config.preload_models true' do
34
+ expect(Neoon.config.preload_models).to be_true
35
+ end
36
+
37
+ it 'should have models' do
38
+ expect(Neoon.config.models).not_to be_empty
39
+ end
40
+ end
41
+
42
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neoon::Model::Config do
4
+
5
+ before do
6
+ require 'app/models/topic'
7
+ end
8
+
9
+ it 'returns the model config singleton' do
10
+ expect(Topic.neo_model_config.class).to eq Neoon::Model::Config
11
+ end
12
+
13
+ context 'properties' do
14
+ it 'stores them' do
15
+ expect(Topic.neo_model_config.properties).not_to be_nil
16
+ expect(Topic.neo_model_config.properties.keys).to eql [ :name, :slug, :created_at ]
17
+ end
18
+
19
+ it 'has block' do
20
+ expect(Topic.neo_model_config.properties[:slug][:block]).to be_a(Proc)
21
+ end
22
+
23
+ it 'has index' do
24
+ expect(Topic.neo_model_config.properties[:name][:index]).to be_true
25
+ end
26
+
27
+ it 'has unique index' do
28
+ expect(Topic.neo_model_config.properties[:slug][:index]).to eql :unique
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,82 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neoon::Model::Node do
4
+
5
+ before :each do
6
+ require 'app/models/topic'
7
+ Topic.destroy_all
8
+ end
9
+
10
+ context '' do
11
+ it 'responds to neoon' do
12
+ expect(Topic).to respond_to(:neoon)
13
+ end
14
+
15
+ it 'responds to neo_model_config' do
16
+ expect(Topic).to respond_to(:neo_model_config)
17
+ end
18
+ end
19
+
20
+ context 'save' do
21
+ before do
22
+ @t = Topic.where(:name => 'Apple').first_or_create
23
+ end
24
+
25
+ after :all do
26
+ Topic.destroy_all
27
+ end
28
+
29
+ it 'responds to neo_save' do
30
+ @t.respond_to?(:neo_save).should be_true
31
+ end
32
+
33
+ it 'sets neo_node_properties' do
34
+ @t.neo_node_properties[:_id].should == @t.id
35
+ @t.neo_node_properties[:name].should == @t.name
36
+ @t.neo_node_properties[:slug].should == "#{@t.id}-#{@t.name.underscore}"
37
+ end
38
+
39
+ it 'saves to neo' do
40
+ @t.neo_node._id.should == @t.id
41
+ end
42
+
43
+ it 'updates to neo after assigning attr and save OR update_attribute()' do
44
+ @t.name = 'MacBook'
45
+ @t.save
46
+ @t.neo_node.name.should == 'MacBook'
47
+ @t.neo_node._id.should == @t.id
48
+
49
+ @t.update_attribute(:name, 'Technology')
50
+ @t.neo_node.name.should == 'Technology'
51
+ @t.neo_node._id.should == @t.id
52
+ end
53
+
54
+ it 'has 1 record' do
55
+ Topic.count.should == Neoon.db.q('MATCH n:Topic return n').data.count
56
+ Topic.count.should == 1
57
+ end
58
+ end
59
+
60
+ context 'destroy' do
61
+ before do
62
+ @t1 = Topic.where(:name => 'Amr').first_or_create
63
+ end
64
+
65
+ after :all do
66
+ Topic.destroy_all
67
+ end
68
+
69
+ it 'responds to neo_destroy' do
70
+ @t1.respond_to?(:neo_destroy).should be_true
71
+ end
72
+
73
+ it 'has 2 records' do
74
+ topic = Topic.new name: 'Jo'
75
+ topic.save
76
+
77
+ Topic.count.should == Neoon.db.q('MATCH n:Topic return n').data.count
78
+ Topic.count.should == 2
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,105 @@
1
+ require 'spec_helper'
2
+
3
+ describe Neoon::Model::Schema do
4
+
5
+ before do
6
+ require 'app/models/topic'
7
+ Topic.neo_schema_update
8
+ end
9
+
10
+ context 'list index' do
11
+ context 'from model config' do
12
+ it 'responds to neo_schema_index_keys' do
13
+ expect(Topic).to respond_to(:neo_schema_index_keys)
14
+ end
15
+ it 'returns all indexed properties' do
16
+ expect(Topic.neo_schema_index_keys.keys).to eql [:name, :slug]
17
+ end
18
+ it 'returns indexed properties' do
19
+ expect(Topic.neo_schema_index_keys.select{|_, v| v == true}.keys).to eql [:name]
20
+ end
21
+ it 'returns unique indexed properties' do
22
+ expect(Topic.neo_schema_index_keys.select{|_, v| v == 'UNIQUENESS'}.keys).to eql [:slug]
23
+ end
24
+
25
+ end
26
+
27
+ context 'from Neo4j' do
28
+ it 'returns all indexed properties' do
29
+ expect(Topic.neo_index_list.keys).to eql [:name, :slug]
30
+ end
31
+ it 'returns indexed properties' do
32
+ expect(Topic.neo_index_list.select{|_, v| v == true}.keys).to eql [:name]
33
+ end
34
+ it 'returns unique indexed properties' do
35
+ expect(Topic.neo_index_list.select{|_, v| v == 'UNIQUENESS'}.keys).to eql [:slug]
36
+ end
37
+ end
38
+ end
39
+
40
+ context 'index operation' do
41
+ before :each do
42
+ begin
43
+ Topic.neo_index_drop(:name)
44
+ Topic.neo_index_drop(:slug)
45
+ rescue
46
+
47
+ end
48
+ end
49
+
50
+ it 'no indexes in Neo4j' do
51
+ expect(Topic.neo_index_list.keys).to eql []
52
+ end
53
+
54
+ context 'create index' do
55
+ it 'responds to neo_index_create' do
56
+ expect(Topic).to respond_to(:neo_index_create)
57
+ end
58
+
59
+ it 'creates indexes for properties' do
60
+ expect(Topic.neo_index_create(:name)).to be_true
61
+ expect(Topic.neo_index_list.keys).to eql [:name]
62
+
63
+ expect(Topic.neo_index_create(:slug)).to be_true
64
+ expect(Topic.neo_index_list.keys).to eql [:name, :slug]
65
+
66
+ expect { Topic.neo_index_create(:name) }.to raise_error Neoon::Error::AlreadyIndexedException
67
+ expect { Topic.neo_index_create(:slug) }.to raise_error Neoon::Error::AlreadyConstrainedException
68
+ end
69
+
70
+ end
71
+
72
+ context 'drop index' do
73
+ before do
74
+ Topic.neo_index_create(:name)
75
+ Topic.neo_index_create(:slug)
76
+ end
77
+
78
+ it 'responds to neo_index_drop' do
79
+ expect(Topic).to respond_to(:neo_index_drop)
80
+ end
81
+
82
+ it 'drops indexes for properties' do
83
+ expect(Topic.neo_index_drop(:name)).to be_true
84
+ expect(Topic.neo_index_list.keys).to eql [:slug]
85
+
86
+ expect(Topic.neo_index_drop(:slug)).to be_true
87
+ expect(Topic.neo_index_list.keys).to eql []
88
+
89
+ expect { Topic.neo_index_drop(:name) }.to raise_error Neoon::Error::DropIndexFailureException
90
+ expect { Topic.neo_index_drop(:slug) }.to raise_error Neoon::Error::DropIndexFailureException
91
+ end
92
+ end
93
+
94
+ context 'update index' do
95
+ before do
96
+ Topic.neo_schema_update
97
+ end
98
+
99
+ it 'updates index as in model config' do
100
+ Topic.neo_index_list.keys.should == [:name, :slug]
101
+ end
102
+ end
103
+ end
104
+
105
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: neoon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Amr Tamimi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-23 00:00:00.000000000 Z
11
+ date: 2013-09-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - '>='
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: multi_json
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -136,22 +150,32 @@ files:
136
150
  - LICENSE.txt
137
151
  - README.md
138
152
  - Rakefile
153
+ - lib/faraday/neoon/raise_error.rb
139
154
  - lib/neoon.rb
140
155
  - lib/neoon/client/connection.rb
141
- - lib/neoon/client/indexing.rb
142
156
  - lib/neoon/client/request.rb
143
157
  - lib/neoon/config.rb
158
+ - lib/neoon/cypher/handler.rb
159
+ - lib/neoon/cypher/instance_query.rb
160
+ - lib/neoon/cypher/query.rb
161
+ - lib/neoon/cypher/schema/constraints.rb
162
+ - lib/neoon/cypher/schema/indexes.rb
163
+ - lib/neoon/error.rb
144
164
  - lib/neoon/model/config.rb
165
+ - lib/neoon/model/node.rb
145
166
  - lib/neoon/model/schema.rb
146
- - lib/neoon/model/service.rb
147
167
  - lib/neoon/node.rb
148
168
  - lib/neoon/railtie.rb
149
- - lib/neoon/railties/database.rake
169
+ - lib/neoon/tasks/database.rake
170
+ - lib/neoon/tasks/server.rake
150
171
  - lib/neoon/version.rb
151
172
  - lib/rails/neoon.rb
152
173
  - neoon.gemspec
153
174
  - spec/app/models/topic.rb
154
- - spec/neoon_spec.rb
175
+ - spec/neoon/config_spec.rb
176
+ - spec/neoon/model/config_spec.rb
177
+ - spec/neoon/model/node_spec.rb
178
+ - spec/neoon/model/schema_spec.rb
155
179
  - spec/spec_helper.rb
156
180
  - spec/support/database.yml
157
181
  - spec/support/schema.rb
@@ -181,7 +205,10 @@ specification_version: 4
181
205
  summary: A simple Ruby wrapper for Neo4j with focus on Cypher
182
206
  test_files:
183
207
  - spec/app/models/topic.rb
184
- - spec/neoon_spec.rb
208
+ - spec/neoon/config_spec.rb
209
+ - spec/neoon/model/config_spec.rb
210
+ - spec/neoon/model/node_spec.rb
211
+ - spec/neoon/model/schema_spec.rb
185
212
  - spec/spec_helper.rb
186
213
  - spec/support/database.yml
187
214
  - spec/support/schema.rb
@@ -1,25 +0,0 @@
1
- module Neoon
2
- module Client
3
- module Indexing
4
-
5
- def list(label)
6
- Neoon.db.get("/schema/index/#{label}")
7
- .map{|f| f.send("property-keys")}.flatten.map(&:to_s).sort
8
- end
9
-
10
- def create(label, keys = [])
11
- keys.each do |key|
12
- Neoon.db.cypher("CREATE INDEX ON :#{label}(#{key.to_s.downcase})")
13
- end
14
- end
15
-
16
- def drop(label, keys = [])
17
- keys.each do |key|
18
- Neoon.db.cypher("DROP INDEX ON :#{label}(#{key.to_s.downcase})")
19
- end
20
- neo_index_list
21
- end
22
-
23
- end
24
- end
25
- end
@@ -1,69 +0,0 @@
1
- module Neoon
2
- module Model
3
- module Service
4
-
5
- module ClassMethods
6
- attr_reader :neo_model_config
7
-
8
- def neo_model_config
9
- @neo_model_config ||= Neoon::Model::Config.new(self)
10
- end
11
-
12
- def neoon(opts = {})
13
- yield(neo_model_config) if block_given?
14
-
15
- opts.each do |key, value|
16
- raise "No such option #{key} for #{self.name} model" unless neo_model_config.respond_to?("#{key}=")
17
- neo_model_config.send("#{key}=", value)
18
- end
19
- end
20
- end
21
-
22
- module InstanceMethods
23
- def neo_node_props
24
- neo_node.merge({ :db_id => self.id })
25
- end
26
-
27
- def neo_save
28
- cypher_query = <<-CYPHER
29
- MERGE (node:#{self.class.name} { db_id: #{self.id} })
30
- ON CREATE node SET node = {props}
31
- ON MATCH node SET node = {props}
32
- RETURN node
33
- CYPHER
34
- Neoon.db.q(cypher_query, { :props => neo_node_props })
35
- end
36
-
37
- def neo_destroy
38
- cypher_query = <<-CYPHER
39
- CYPHER
40
- Neoon.db.q(cypher_query)
41
- end
42
-
43
- protected
44
-
45
- def neo_node
46
- return {} unless self.class.neo_model_props
47
- hash = self.class.neo_model_props.inject({}) do |all, (field, block)|
48
- all[field] = if block[:block]
49
- instance_eval(&block[:block])
50
- else
51
- self.send(field) rescue (raise "No field #{field} for #{self.class.name}")
52
- end
53
- all
54
- end
55
- hash.reject { |k, v| v.nil? }
56
- end
57
- end
58
-
59
- def self.included(receiver)
60
- receiver.extend ClassMethods
61
- receiver.extend Schema
62
- receiver.send :include, InstanceMethods
63
-
64
- receiver.after_save :neo_save
65
- end
66
-
67
- end
68
- end
69
- end
@@ -1,2 +0,0 @@
1
- namespace :db do
2
- end
data/spec/neoon_spec.rb DELETED
@@ -1,45 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe Neoon do
4
-
5
- describe ".config" do
6
-
7
- context "no block given" do
8
- it "returns the config singleton" do
9
- Neoon.config.should eq Neoon::config
10
- end
11
-
12
- it "returns config.preload_models false" do
13
- Neoon.config.preload_models.should be_false
14
- end
15
-
16
- it "should have no model" do
17
- Neoon.config.models.should be_empty
18
- end
19
- end
20
-
21
- context "with block given" do
22
- before do
23
- Neoon.configure do |c|
24
- c.preload_models = true
25
- end
26
- require 'app/models/topic' # fake loading a model
27
- end
28
-
29
- after do
30
- Neoon.configure do |c|
31
- c.preload_models = false
32
- end
33
- end
34
-
35
- it "returns config.preload_models true" do
36
- Neoon.config.preload_models.should be_true
37
- end
38
-
39
- it "should have models" do
40
- Neoon.config.models.should_not be_empty
41
- end
42
- end
43
-
44
- end
45
- end