50psi
50psi

Reputation: 23

Order then paginate with neo4j-will_paginate

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

Answers (1)

subvertallchris
subvertallchris

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

Related Questions