Retrieve list of mongo documents by ids preserving order - ruby

Which is the best way to retrieve a list of mongodb documents using mongoid in the order specified in the list.
My current solution is:
docs = Doc.where(:_id.in => ids).sort { |x, y| ids.index(x.id) <=> ids.index(y.id) }
It seems there should be a better solution for this using mongoid query interface. Any ideas?

If the number of ids is small you might get away with this (no need to sort it though):
docs = ids.map { |id| Doc.find(id) }
The drawback is of course that it will still go to the database for every document.
The closest method I could find is Doc.criteria.for_ids(ids) but it will not honor the order of the ids and fetch every document only once. See this question.

Related

Get neo4j result

I'm using neo4j jdbc driver to access a local service and make the following query to find what I need:
"MATCH (u:User)-->(d:Deck)-[p:Played]->(g:Game)" +
"WHERE id(g) = ?" +
"RETURN {r {user :u, deck :d, played:p, game:g}}"
I cast it as a map and can find the parts and, right there in my face, I can see the stuff I need. How do I get it? (the {comment= ...} etc)
I do know how to get it by
RETURN p.comment, p.place ... etc
But I fear this will soon get out of hand. If I can just get at least that json string I would be happy.
There is a built-in Cypher function PROPERTIES(), outlined here, that will convert anything that has properties (nodes, relationships, even pre-existing Maps) to a Map of the properties with no other data. The language drivers have built-in tools to hydrate Nodes and Relationships so that property access is simple, but if you require a Map and only a Map to be returned, just use RETURN PROPERTIES(p) and you'll get it.
Reading between the lines, it looks like you are trying to get all the properties of specific nodes and properties, without any extraneous metadata.
After installing the APOC plugin, you can use the apoc.map.fromPairs procedure to generate a map with all the properties of a node or relationship.
For example, to get all the properties of every User node:
MATCH (u:User)
CALL apoc.map.fromPairs([k IN KEYS(u) | [k, u[k]]]) YIELD value AS props
RETURN props;
[k IN KEYS(u) | [k, u[k]]] generates a collection of key/value collections.
The apoc.map.fromPairs() procedure takes a collection of key/value collections, and converts it into a map.

If I eager load associated child records, then that means future WHERE retrievals won't dig through database again?

Just trying to understand... if at the start of some method I eager load a record and its associated children like this:
#object = Object.include(:children).where(email:"test#example.com").first
Then does that mean that if later I have to look through that object's children this will not generate more database queries?
I.e.,
#found_child = #object.children.where(type_of_child:"this type").first
Unfortunately not - using ActiveRecord::Relation methods such as where will query the database again.
You could however filter the data without any further queries, using the standard Array / Enumerable methods:
#object.children.detect {|child| child.type_of_child == "this type"}
It will generate another database query in your case.
Eager loading is used to avoid N+1 queries. This is done by loading all associated objects. But this doesn't work when you want to filter that list with where later on, Rails will than build a new query and run that one.
That said: In your example the include makes your code actually slower, because it loads associated object, but cannot use them.
I would change your example to:
#object = Object.find_by(email: "test#example.com")
#found_child = #object.children.find_by(type_of_child: "this type")

How to get the collection based upon the wildchar redis key using redis-rb gem?

The redis objects created using the redis-rb gem.
$redis = Redis.new
$redis.sadd("work:the-first-task", 1)
$redis.sadd("work:another-task", 2)
$redis.sadd("work:yet-another-task", 3)
Is there any method to get the collection that has "work:*" keys?
Actually, if you just want to build a collection on Redis, you only need one key.
The example you provided builds 3 distinct collections, each of them with a single item. This is probably not that you wanted to do. The example could be rewritten as:
$redis = Redis.new
$redis.sadd("work","the-first-task|1")
$redis.sadd("work", "another-task|2")
$redis.sadd("work", "yet-another-task|3")
To retrieve all the items of this collection, use the following code:
x = $redis.smembers("work")
If you need to keep track of the order of the items in your collection, it would be better to use a list instead of a set.
In any case, usage of the KEYS command should be restricted to tooling/debug code only. It is not meant to be used in a real application because of its linear complexity.
If you really need to build several collections, and retrieve items from all these collections, the best way is probably to introduce a new "catalog" collection to keep track of the keys corresponding to these collections.
For instance:
$redis = Redis.new
$redis.sadd("catalog:work", "work:the-first-task" )
$redis.sadd("catalog:work", "work:another-task" )
$redis.sadd("work:the-first-task", 1)
$redis.sadd("work:the-first-task", 2)
$redis.sadd("work:another-task", 3)
$redis.sadd("work:another-task", 4)
To efficiently retrieve all the items:
keys = $redis.smembers("catalog:work")
res = $redis.pipelined do
keys.each do |x|
$redis.smembers(x)
end
end
res.flatten!(1)
The idea is to perform a first query to get the content of catalog:work, and then iterate on the result using pipelining to fetch all the data. I'm not a Ruby user, so there is probably a more idiomatic way to implement it.
Another simpler option can be used if the number of collections you want to retrieve is limited, and if you do not care about the ownership of the items (in which set is stored each item)
keys = $redis.smembers("catalog:work")
res = $redis.sunion(*keys)
Here the SUNION command is used to build a set resulting of the union of all the sets you are interested in. It also filters out the duplicates in the result (this was not done in the previous example).
Well, I could get it by $redis.keys("work:*").

Twitter Ruby Gem - get friends names and ids, nothing more

Is it possible to simply get the people you are following with just an id and full name? I do not need any of the additional data, it's a waste of bandwidth.
Currently the only solution I have is:
twitter_client = Twitter::Client.new
friend_ids = twitter_client.friend_ids['ids']
friends = twitter_client.users(friend_ids).map { |f| {:twitter_id => f.id, :name => f.name} }
is there anyway to just have users returned be an array of ids and full names? better way of doing it than the way depicted above? preferably a way to not filter on the client side.
The users method uses the users/lookup API call. As you can see on the page, the only param available is include_entities. The only other method which helps you find users has the same limitation. So you cannot download only the needed attributes.
The only other thing I'd like to say is that you could directly use the friends variable, I don't see any benefit of running the map on it.

how to customize plone 4 collection to sort by multiple fields

I'm building a Plone 4.1 based site and am trying to find the best way to either sort a collection by multiple sort criteria, or at least customize a collection portlet to do so for the font page of the site. I believe the portlet uses the collection sort settings unless you choose random. Here is the section of code from the standard results in the portlet:
def _standard_results(self):
results = []
collection = self.collection()
if collection is not None:
limit = self.data.limit
if limit and limit > 0:
# pass on batching hints to the catalog
results = collection.queryCatalog(batch=True, b_size=limit)
results = results._sequence
else:
results = collection.queryCatalog()
if limit and limit > 0:
results = results[:limit]
return results
For example, I would like to be able to sort by Expiration Date if present, if not then use the Creation Date for example. Or sort by tags and Creation Date. Any feedback on the best approach to this would be appreciated.
as ross said you'll need AdvancedQuery to sort on multiple criteria.
if you just need this for the frontpage i'd suggest do create a custom portelt based on the collectionportlet.
where the collectionportlet calls collection.queryCatalog() you'll want to add some additional logic for your sorting:
>>> uids = [brain.UID for brain in collection.queryCatalog()]
>>> query = AdvancedQuery.In('UID', uids)
>>> results = catalog.evalAdvancedQuery(query, (('sortable_title', 'asc'), ('date', 'desc')
then you can use results instead of the results in your code sample above
As in this answer, multiple sort is only available through AdvancedQuery an there's no integration of AdvancedQuery into collections that I'm aware of. So basically this isn't possible unless you integrate AdvancedQuery into collections yourself, which would be a non-trivial task.
A hackish workaround might be to use plone.indexer to write an indexer that returns the right sort value according to your logic, create a new FieldIndex in the catalog (profiles/default/catalog.xml), register that new index as valid for sort criterion in profiles/default/portal_atct.xml, then use that as your sort index.

Resources