{"issue":{"milestone":null,"user":{"id":6504,"login":"andreasronge","url":"https://api.github.com/users/andreasronge","avatar_url":"https://secure.gravatar.com/avatar/f3e2505cbe4fb083ed8d836a87fbd0fd?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"f3e2505cbe4fb083ed8d836a87fbd0fd"},"title":"Support for Cypher DSL","created_at":"2011-12-15T21:59:51Z","updated_at":"2011-12-17T09:36:37Z","labels":[],"state":"open","id":2573163,"closed_at":null,"pull_request":{"patch_url":null,"html_url":null,"diff_url":null},"html_url":"https://github.com/andreasronge/neo4j/issues/110","body":"## Design Goals\r\n\r\n * Should be easy to understand how the DSL maps to the Cypher language\r\n * Should make it easy to use prefixed Neo4j.rb relationships and lucene indices\r\n * Should have a Rubyish syntax\r\n\r\n### Using Strings\r\n\r\nThe DSL should support using strings that are sent directly to the Cypher Java Api.\r\n\r\n```ruby\r\nNeo4j.query do\r\n  START \"foo=node(1)\"\r\n  MATCH \"foo-[foo_bar]->bar\"\r\n  RETURN \"bar, foo_bar\"\r\nend\r\n```\r\n\r\nIt might be a bit controversial to have method names with capital letters like `START`, but I think it's easier to read.\r\n\r\n\r\n### Using Hashes and Bindings\r\n\r\nIt does also support using hashes instead of strings. It's also possible to mix.\r\nExample:\r\n\r\n```ruby\r\nNeo4j.query do\r\n  START :foo => node(1)\r\n  # following is same as MATCH \"foo-[foo_bar?]->bar\"\r\n  MATCH foo.outgoing.as(:foo_bar).to(:bar)\r\n  RETURN bar, foo_bar\r\nend\r\n```\r\n\r\nThe symbols above will define instance variables which can be used later used.\r\nExample, `START :foo => node(1)` will create instance variable `foo` which can be used later.\r\nThis means that we can actually check the syntax of the DSL and make sure that all variables are defined.\r\nThe second line `MATCH foo.outgoing.as(:foo_bar).to(:bar)` will use the instance variable `foo` and\r\ndefine two new instance variables `foo_bar` and `bar` which is used in the RETURN method.\r\n\r\n### Using Method Chaining\r\n\r\nFor simpler queries method chaining is supported.\r\nExample of using method chaining instead of the block above:\r\n\r\n```ruby\r\nNeo4j.query.start{:foo => node(1)}.match{foo.outgoing.as(:foo_bar)}.to(:bar)}.returns{bar, foo_bar}\r\n```\r\n\r\n### Starting from any node\r\n\r\nMethod chaining will probably be more used when you already have the start node since the query will be simpler.\r\n\r\n```ruby\r\nnode = Person.find(:name => 'andreas')\r\nnode.query.match{friends}.to(:bar)}.returns{friends.since, bar}\r\n```\r\nWe know the declared relationships on the node so we don't have to specify `outgoing` but instead can `friends` as shown in the example above.\r\n\r\n### Optional Relationships\r\n\r\n```ruby\r\nNeo4j.query do\r\n  START \"a=node(2)\"\r\n  MATCH \"a-[?]->x\"\r\n  RETURN \"a,x\"\r\nend\r\n```\r\n\r\nSame as\r\n\r\n```ruby\r\nNeo4j.query do\r\n  START :a=>node(2)\r\n  MATCH a.outgoing?.to(:x)\r\n  RETURN a, x\r\n```\r\n\r\nWith types\r\n\r\n```ruby\r\nNeo4j.query do\r\n  START :a=>node(2)\r\n  MATCH a.outgoing?.as(:foo)\r\n  RETURN a, foo\r\n```\r\n\r\n### Using Lucene index\r\n\r\n```ruby\r\nNeo4j.query do\r\n  START :user => index(Person, :name => \"Anakin\"),\r\n               \"foo=>node(42)\"\r\n  # etc ...\r\nend\r\n```\r\n\r\nWe will then know which type of index should be used (node/relationship and exact or fulltext).\r\n\r\n### Using Relationship\r\n\r\nNeo4j.rb can have prefix relationships, example:\r\n\r\n```ruby\r\nclass Actor\r\n  include Neo4j::NodeMixin\r\n  has_n(:acted_in).to(Movie)\r\n```\r\n\r\nThis example will prefix acted_in relationship as `Actor#acted_in`\r\n\r\n```ruby\r\nNeo4j.query do\r\n  START :actor => :Actor(:name => \"Anakin\")\r\n  MATCH actor.outgoing(Actor.acted_in).as(:foo_bar).to(:bar)  \r\n  # or maybe this since we know it is an Actor\r\n  MATCH actor.acted_in.as(:foo_bar).to(:bar)  \r\nend\r\n```\r\n\r\n### Multiple Relationships\r\n\r\nThe following query:\r\n\r\n```ruby\r\nNeo4j.query do\r\n  START \"john=node:Person_exact(name = 'John')\"\r\n  MATCH \"john-[:friend]->()-[:friend]->fof\"\r\n  RETURN \"john, fof\"\r\nend\r\n```\r\n\r\ncan also be written as this:\r\n\r\n```ruby\r\nNeo4j.query do\r\n  START :john => index(Person, :name => 'John')\r\n  MATCH john.outgoing(:friends).to().outgoing(:friends).as(:fof)\r\n  RETURN john, fof\r\nend\r\n```\r\n\r\n### Where \r\n\r\nThe following query\r\n\r\n```ruby\r\nNeo4j.query do \r\n  START \"user=node(5,4,1,2,3)\"\r\n  MATCH \"user-[:friend]->follower\"\r\n  WHERE \"follower.name =~ /S.*/\"\r\n  RETURN \"user, follower.name\"\r\n```\r\n\r\ncan also be written like this\r\n\r\n```ruby\r\nNeo4j.query do \r\n  START :user=>node(5,4,1,2,3)\r\n  MATCH user.outgoing(:friend).to(:follower)\r\n  WHERE follower.name == /S.*/\r\n  RETURN \"user, follower.name\"\r\n```\r\nYes, this is actually possible but maybe a bit crazy. \r\nThe `follower` object implements the `method_missing` method and returns an object which overloads \r\nthe `==` operator on the object.\r\n\r\nThis also means that we get validation on the RegExp (but it migh have a different syntax in Java, so we should also allow a String as a regexp somehow)\r\n\r\n\r\n### Setting depth\r\n\r\n```ruby\r\nNeo4j.query do\r\n  START :actor => :Actor(:name => \"Anakin\")\r\n  MATCH actor.outgoing(Actor.acted_in).as(:foo_bar).to(:bar).depth(:any)\r\n  # or \r\n  #  MATCH actor.acted_in.as(:foo_bar).to(:bar).depth(1..3)\r\nend\r\n```\r\n\r\n*This is just an early draft. Feedback is very welcome !*\r\n","number":110,"url":"https://api.github.com/repos/andreasronge/neo4j/issues/110","comments":7,"assignee":null},"comment":{"user":{"id":6504,"login":"andreasronge","url":"https://api.github.com/users/andreasronge","avatar_url":"https://secure.gravatar.com/avatar/f3e2505cbe4fb083ed8d836a87fbd0fd?d=https://a248.e.akamai.net/assets.github.com%2Fimages%2Fgravatars%2Fgravatar-140.png","gravatar_id":"f3e2505cbe4fb083ed8d836a87fbd0fd"},"created_at":"2011-12-17T09:36:37Z","updated_at":"2011-12-17T09:36:37Z","id":3188552,"body":"Yes, that would be nice.\r\nBut it requires a lot of operator overloading which is a bit limited (not all Ruby operators are allowed to overload).\r\n`{ created_at >= since }` should be translated into a string `\"something.created_at >= since\"`. \r\n\r\nHave to do some more thinking and study the squeel syntax.\r\n\r\nRegarding `.match{foo.outgoing.as(:foo_bar)}` that means traverse any outgoing relationship, not just the `foo_bar` relationship types.\r\nBut we should support `foo.foo_bar` as well.\r\n\r\nYes, I know having method names in upper case is controversial. But we sort of build our own language and I just thought it was easier to read. But I'm willing to change to lower case anyway, since I know it might upset people :-)\r\n","url":"https://api.github.com/repos/andreasronge/neo4j/issues/comments/3188552"},"action":"created"}