RethinkDB: Updating documents - rethinkdb

This is a longshot, but I was wondering if in rethinkDB, let's say I update a document. Is there a magic function such that, if it is a field that is a string or int, it just updates it, but if the value of the field is an array, it appends it to the array?

In that case you'd need to use .branch and branch on the type. Something like .update(function(row) { return {field: r.branch(row('field').typeOf().eq('ARRAY'), row('field').add([el]), el)}; })

There is a magic function that does something similar. .forEach has the undocumented behaviour of adding numbers, combining arrays and drops strings:
>>> r.expr([{a:1, b:[2], c:"3"}, {a:2, b:[4], c:"6"}]).forEach(r.row)
{"a": 3, "b": [2,4], "c": "3"}

Related

Find documents where all array field values matches predicate

Given the following document schema
{
"type": ["A", "B"]
}
where field type is an indexed field of keyword type.
I want to find documents for which all values of type field should match some predicate p.
Basically I need to check if all values from type field are present in another array. E.g. for ["A", "B", "C] doc above matches, for ["A", "D"] not.
You can use scripts to workaround this. The idea here would be to check if all the elements of doc are present in input_array. Refer: subsets-discussion
In version 6.3, there's native support for this via terms_set query. Refer:
terms-set-query-dsl

Custom data search within an array

Is it possible to search an account's custom data to find a value contained in an array?
Something like:
?customData.[arrayName].{key}=value
The Stormpath docs don't mention array searching.
Yes, with Stormpath it is totally possible to search for custom data even if the values are stored as an array!
Please note that the field names are simple names, and the values are what are different data types like array, map, string etc... so the query is not as complex as one would think :-)
For example, if I want to store custom data called favoriteColors, which is an array like
"favoriteColors": [ "red", "black", "blue", "white" ]
Notice the field name is just like any other field name. The value is the array.
To search for accounts which have a value red in the favoriteColors array, you just need the normal query syntax:
?customData.favoriteColors=red
The full request (if searching a Directory of accounts), might look like this:
https://api.stormpath.com/v1/directories/<directory_uid>/accounts?customData.favoriteColors=red
You could also do the same search on the Tenant resource to search tenant-wide (across all accounts):
https://api.stormpath.com/v1/tenants/<tenant_uid>/accounts?customData.favoriteColors=red
This query would match an account that contains red in the favoriteColors array. If I changed the query to ?customData.favoriteColors=yellow it would not match unless yellow was also added to the array.
Searching for custom data in an array can definitely be done. The syntax is: customData.{fieldName}\[{index}\]=value where {index} can be the specific index you are looking for, or * if you want to find it anywhere in the array. (Note that the [] characters are escaped with a backslash or the query interpreter gets it confused with a range query.)
If you leave off the index entirely, then \[*\] is implied. More precisely, Stormpath will check for either the value in the fieldName or the value as an element in an array of fieldName. However, syntactic sugar can only work if the array field is the last element in your search. Since you can put literally any JSON object into your custom data, Stormpath cannot check every single possibility. Imagine something like customData.foo.bar.baz.qux=bingo. Stormpath would not try to guess that maybe foo is an array, maybe bar is an array or not, maybe baz is an array or not - only maybe qux is an array or not. So, if you want to search an array of objects, you cannot leave out the \[*\].
Here is an example. I have an account with the custom data:
{
"favoriteThings": [
{
"thing": "raindrops",
"location": "on roses"
},
{
"thing": "whiskers",
"location": "on kittens"
},
{
"thing": "snowflakes",
"location": "on my nose and eye lashes"
}
],
"favoriteColors": [
"blue",
"grey"
]
}
The following queries will yield the following results:
customData.favoriteColors=blue will include this account.
customData.favoriteColors\[1\]=blue will not include this account because blue is not at index 1.
customData.favoriteThings\[*\].thing=whiskers will include this account
customData.favoriteThings\[*\].thing=ponies will not include this account because it does not list ponies as one of his favorite things, but may include other accounts with custom data in the same structure.
customData.favoriteThings.thing=whiskers would not include this account or any other accounts with the same custom data structure because in that case, Stormpath would be looking for a single nested JSON favoriteThings object, not an array.

Iterate an array of hashes

I have a hash with a key of cities and the value is an array of hashes containing location data. It looks like this:
#locations = {
"cities"=>[
{"longitude"=>-77.2497049, "latitude"=>38.6581722, "country"=>"United States", "city"=>"Woodbridge, VA"},
{"longitude"=>-122.697236, "latitude"=>58.8050174, "country"=>"Canada", "city"=>"Fort Nelson, BC"},
...
]
}
I'd like to iterate through and print all the values for the key city:
Woodbridge, VA
Fort Nelson, BC
...
I can't say why would you have that structure, anyway, in the data format you have above, you would access it like
#locations[1].each { |c| p c["city"] }
Although, this implies that you should always expect second object in the array to be the required cities array. Further you need to put in required nil check.
For your corrected data format:
#locations = { "cities"=>[
{ "longitude"=>-77.2497049,
"latitude"=>38.6581722,
"country"=>"United States",
"city"=>"Woodbridge, VA"},
{ "longitude"=>-122.697236,
"latitude"=>58.8050174,
"country"=>"Canada",
"city"=>"Fort Nelson, BC" }] }
#locations["cities"].each { |h| puts h["city"] }
Woodbridge, VA
Fort Nelson, BC
or to save in an array:
#locations["cities"].each_with_object([]) { |h,a| a << h["city"] }
#=> ["Woodbridge, VA", "Fort Nelson, BC"]
As suggested by others, you have to do the exact same thing but let me explain whats happening in there.
Your example is an array and has multiple elements which could be just string like cities or an array of hashes like you mentioned.
So in order to iterate through the hashes and get the city values printed, you first of all have to access the array that has hashes. By doing so
#locations["cities"]
=> [{"longitude"=>-77.2497049, "latitude"=>38.6581722, "country"=>"United States", "city"=>"Woodbridge, VA"}, {"longitude"=>-122.697236, "latitude"=>58.8050174, "country"=>"Canada", "city"=>"Fort Nelson, BC"}]
Now that you have go the array you required, you can just integrate through them and get the result printed like this
#locations["cities"].map{|hash| p hash['city']}
In case your getting nil errors as you have stated in comments, just see what happens when you try to access the array of hashes. if you still are experiencing issues, then you may have to provide the full input so as to understand where the problem is.

MongoDB Ruby driver typecasting while inserting document

While creating a document that is got from web interface, it does not rightly typecast integer date and other type. for example
{"_id": "<some_object_id>", "name": "hello", "age": "20", "dob": "1994-02-22"}
Since attributes are entered dynamically, their types can not be prejudged. Is there any way I can get them entered from client side, like
{"_id": "<some_object_id>", "name": "hello", "age": "$int:20", "dob": "$date:1994-02-22"}
Any help is highly appreciated.
Since you appear to be concerned about the strings that come in from a form such as a POST, the simple answer is that you cast them in Ruby.
If the field is what you expect to be a number then cast it to an int. And the same goes for dates as well.
Your mongo driver will correctly interpret these and store them as the corresponding BSON types in your MongoDB collection. The same goes in reverse, when you read collection data you will get it back cast into your native types.
"1234".to_i
Date.strptime("{ 2014, 2, 22 }", "{ %Y, %m, %d }")
But that's be basic Ruby part.
Now you could do something like you pseudo-suggested and store your information, not as native types but as strings with some form of type tagging. But see, I just don't see the point as you would have to
Detect the type at some stage and apply the tag
Live with the fact that you just ruined all the benefits of having the native types in the collection. Such as query and aggregation for date ranges and basic summing of values.
And while we seem to be going down the track of the anything type where users just arbitrarily insert data and something else has to work out what type it is, consider the following examples of MongoDB documents:
{
name: "Fred",
values: [ 1, 2, 3, 4],
}
{
name: "Sally",
values: "a"
}
So in Mongo terminology, that document structure is considered bad. Even though Mongo does have a flexible schema concept, this type of mixing will break things. So don't do it, but rather handle in the following way, which is quite acceptable even though the schema's are different:
{
name: "Fred",
values: [ 1, 2, 3, 4],
}
{
name: "Sally",
mystring: "a"
}
The long story short, Your application should be aware of the types of data that are coming in. If you allow user defined forms then your app needs to be able to attach a type to them. If you have a field that could be a string or a Date, then your app need to determine which type it is, and cast it, or otherwise store it correctly.
As it stands you will benefit from re-considering you use case, rather than waiting for something else to work all that out for you.

How do I extract a value from this Ruby hash?

I'm using the Foursquare API, and I want to extract the "id" value from this hash
[{"id"=>"4fe89779e4b09fd3748d3c5a", "name"=>"Hitcrowd", "contact"=>{"phone"=>"8662012805", "formattedPhone"=>"(866) 201-2805", "twitter"=>"hitcrowd"}, "location"=>{"address"=>"1275 Glenlivet Drive", "crossStreet"=>"Route 100", "lat"=>40.59089895083072, "lng"=>-75.6291255071468, "postalCode"=>"18106", "city"=>"Allentown", "state"=>"Pa", "country"=>"United States", "cc"=>"US"}, "categories"=>[{"id"=>"4bf58dd8d48988d125941735", "name"=>"Tech Startup", "pluralName"=>"Tech Startups", "shortName"=>"Tech Startup", "icon"=>"https://foursquare.com/img/categories/shops/technology.png", "parents"=>["Professional & Other Places", "Offices"], "primary"=>true}], "verified"=>true, "stats"=>{"checkinsCount"=>86, "usersCount"=>4, "tipCount"=>0}, "url"=>"http://www.hitcrowd.com", "likes"=>{"count"=>0, "groups"=>[]}, "beenHere"=>{"count"=>0}, "storeId"=>""}]
When I try to extract it by using ['id'], I get this error can't convert Symbol into Integer. How do I extract the value using ruby? Also, how do I do this for multiple hashes extracting the "id" value each time?
Please pardon my inexperience. Thanks!
It's wrapped in an array, that's what the [ and ] mean on the start and end. But it also looks like this array only one object in it, which is the hash you really want.
So assuming you want the first object in this array:
mydata[0]['id'] # or mydata.first['id'] as Factor Mystic suggests
But usually when an API returns an Array there is a reason (it might return many results instead of just one), and naively plucking the first item from it my not be what you want. So be sure you are getting the kind of data you really expect before hard coding this into your application.
For multiple hashes, if you want to do something with the id (run a procedure of some kind) then
resultsArray.each do |person|
id = person["id"] #then do something with the id
end
If you want to just get an array containing the ids then
resultsArray.map{|person| person["id"]}
# ["4fe89779e4b09fd3748d3c5a", "5df890079e4b09fd3748d3c5a"]
To just grab the one item from the array, see Alex Wayne's answer
To get an array of ids, try: resultsArray.map { |result| result["id"] }

Resources