Reputation: 23
I am trying to implement pagination with the neo4j-will_paginate gem on my rails app, but I'm having problems ordering the records then paginating them.
If I try:
@things = Thing.all.order(title: :desc)
or
@things = Thing.all.paginate(:page => params[:page], :per_page => 20)
These work fine individually, but if I try
@things = Thing.all.order(title: :desc).paginate(:page => params[:page], :per_page => 20)
it results in an "Unknown identifier `n`." error
From the rails server, the Cypher query that was generated was:
CYPHER 13ms MATCH (n:`Thing`) RETURN count(n) AS count ORDER BY n.title DESC
Completed 500 Internal Server Error in 14ms
Neo4j::Session::CypherError (Unknown identifier `n`.):
I've also tried
@things = Thing.all.paginate(:page => params[:page], :per_page => 2, :order => 'title DESC')
Which doesn't give an error, but the results are not ordered.
I'd really appreciate any help i can get! Thanks in advance!
Upvotes: 2
Views: 332
Reputation: 5472
I am the person to, well... this is awkward... blame for this issue. The neo4j-will_paginate
gem was adapted for Neo4jrb 3.0 rather quickly and, as a result, I've been patching it as needed since putting it out there.
Anyway, there are two reasons this was failing and neither of them were your fault. I just patched both the Neo4j and neo4j-will_paginate gems to fix this. I added an order
option as you demonstrated in your last attempt, but you'll need to use a symbol, hash, or string with identifier. One of these:
@things = Thing.all.paginate(:page => params[:page], :per_page => 2, :order => :title)
@things = Thing.all.paginate(:page => params[:page], :per_page => 2, :order => { title: :desc })
@things = Thing.as(:t).paginate(:page => params[:page], :per_page => 2, :order => 't.title DESC')
I can't do a new release of neo4j-will_paginate
until we do a new release for neo4j
, so in the meantime, point your gemfile at either the master branch of each repo or ref the noted commits.
gem 'neo4j', github: 'neo4jrb/neo4j', branch: 'master',
gem 'neo4j-will_paginate', github: 'neo4jrb/neo4j-will_paginate', branch: 'master'
#or
gem 'neo4j', github: 'neo4jrb/neo4j', ref: 'b4ee152becb827d87a7659d1beebfb043d0560f6'
gem 'neo4j-will_paginate', github: 'neo4jrb/neo4j-will_paginate', ref: 'b85b622087f3929c37231570f4d24021dcff4ee0'
We keep a fully passing master branch for Neo4j.
Before fixing it directly in the gem, I figured out the cause of the problem and an awful workaround and typed it up. I'm going to include it here because it reveals some information about why this was happening and how you can workaround limitations of the Cypher DSL if and when they pop up. Ultimately, your suggestion of an order
option in paginate
gave me the idea of how it could be handled properly.
The unnecessary workaround:
Thing.as(:t).where('true = true WITH t ORDER BY t.name desc').paginate(:page => params[:page], :per_page => 2)
In Cypher, that results in two queries:
MATCH (t:`Thing`) WHERE true = true WITH t ORDER BY t.name desc RETURN count(DISTINCT t) AS t
MATCH (t:`Thing`) WHERE true = true WITH t ORDER BY t.name desc RETURN t SKIP 0 LIMIT 10
Make sure to take note of the fact that we're setting an identifier with as
right at the beginning. You can set whatever you want as long as you use the same thing in the where
method.
The reason all that is necessary is because of how will_paginate
figures out the total number of pages to display. When you call paginate
, your query has count
called on it, as you see in that first Cypher statement. Now that the count
bug has been fixed (EDIT this was included in an intro portion, where I mentioned fixing the bug that caused your specific error but not the problem as a whole), that looks like this:
MATCH (result:`Thing`) RETURN count(result) AS result ORDER BY result.name, result.desc
# results in error:
# Neo4j::Session::CypherError: Type mismatch: expected Map, Node or Relationship but was Integer (line 1, column 66)
The query and the order make it clear what the problem is: you can't order an integer. WITH
to the rescue! WITH
essentially separates our single query into two separate queries. Clauses that typically have to be called as part of a RETURN
can be called there. We perform our order there instead of at the end, then we let the rest of it play out as it would typically.
This is a fine idea but the problem problem here is that QueryProxy
is an abstraction of the Neo4j::Core::Query
class and does not have a with
method. We get around THAT with that where('true = true...
nonsense. We are essentially using a Cypher Injection attack against the server. Ouch.
I think I can fix this by patching the gem to use with
...
At this point, I realized exactly how it would work, stopped typing, and patched the gem. The end. I created an issue at https://github.com/neo4jrb/neo4j/issues/540, so feel free to comment there if you have any trouble.
Upvotes: 1