How to rename a column in a collection in Mongodb using MongoMapper? - mongodb

Mongodb gives an option for renaming a column name as follows
db.collection.update({},{'$rename'=> {'old_name' => 'new_name'}}, false,true)
Is it possible for using MongoMapper to do the same? The documentation doesn't specify anything.
I also tried getting the Mongodb connection handle from MongoMapper as
connection = MongoMapper.connection
db = MongoMapper.database
collection = db.collection('collection_name')
collection .update(....)
and doing the same query but it doesn't work.

MongoMapper uses the 10gen Ruby driver, and MongoMapper::Document provides access to the underlying driver objects.
The following working test shows that you can use Model.collection.update to do what you want, to rename a field for the model 'Model'. Make sure to use the :multi => true option to update if you want to update/rename more than a single document.
app/models/model.rb
class Model
include MongoMapper::Document
end
test/unit/model_test.rb
require 'test_helper'
class ModelTest < ActiveSupport::TestCase
def setup
Model.remove
end
test "rename" do
puts "Model.collection.class: #{Model.collection.class}"
puts "Model.database.class: #{Model.database.class}"
Model.create( 'old_name' => 'name value 0', 'another_key' => 'another value 0' )
Model.create( 'old_name' => 'name value 1', 'another_key' => 'another value 1' )
assert_equal(2, Model.where( 'old_name' => { '$exists' => true } ).count)
Model.collection.update( {}, { '$rename' => { 'old_name' => 'new_name' } }, :multi => true )
assert_equal(0, Model.where( 'old_name' => { '$exists' => true } ).count)
p Model.all
end
end
$ rake test
Rack::File headers parameter replaces cache_control after Rack 1.5.
Run options:
# Running tests:
Model.collection.class: Mongo::Collection
Model.database.class: Mongo::DB
[DEPRECATED] The 'safe' write concern option has been deprecated in favor of 'w'.
[#<Model _id: BSON::ObjectId('5101809d7f11ba1256000001'), another_key: "another value 0", new_name: "name value 0">, #<Model _id: BSON::ObjectId('5101809d7f11ba1256000002'), another_key: "another value 1", new_name: "name value 1">]
.
Finished tests in 0.012344s, 81.0110 tests/s, 162.0220 assertions/s.
1 tests, 2 assertions, 0 failures, 0 errors, 0 skips

Related

Puppet - deep merge lookup default_value with hiera hash returned

I am trying to deep merge the lookup 'default_value' or 'default_values_hash' with the hash returned from a lookup. It will not merge and the default_value only appear to take effect if the hiera title isn't found at all. I cannot set resource defaults here as the values returned are later processed and not actual resource keys yet.
I've tried numerous variations including 'default_value', 'default_values_hash'. I'm seeking a way to just set a default hash in the manifest and have it deep merge with hiera to create a larger hash.
Manifest:
class test (
Hash $result = lookup('test::my_hash', {merge => 'deep', default_values_hash => {foo => 'bar', this => 'that', him => 'her'}}),
){
notice($result)
}
include test
Hiera:
---
test::my_hash:
foo: 'nobar'
this: 'then'
desired result (deep merge):
{ foo => 'nobar', this => 'then', him => 'her' }
actual result (returns hiera hash only):
{ foo => 'nobar', this => 'then' }
UPDATE:
I got it working with the code below. Still interested if anyone has a better solution.
class test (
$stuff = {
foo => 'bar',
this => 'that',
him => 'her'
},
Hash $result = deep_merge($stuff, lookup('test::my_hash')),
){
notice($result)
}
Unfortunately, that is the way lookup works. The default value is only used if no other value is found. The documentation for the default in lookup says
If present, lookup returns this when it can’t find a normal value. Default values are never merged with found values.
Your version using the deep_merge function from stdlib appears to be the best solution.
class foo {
$default_foo_attribute = {
foo => 'bar',
this => 'that',
him => 'her',
}
$attribute = deep_merge($default_foo_attribute,
lookup('foo::attribute',
Hash[String, String],
'deep',
{})
notice($attribute)
}

How to do atomic operations like $push $pull $set etc. in Ecto MongoDB

I'm using mongodb_ecto and I want to know how can I do operations like $push or $pull on a deeply nested field? At the moment I write back the whole document which sometimes causes false data to be in the DB due to a race-condition.
Ok, I kind of figured it out. Do not use Ecto for this. In some cases you need the MongoDB positional operator and this can only be done directly via the Mongo-Adapter. Now for some usage examples:
I have a doucment with a list of options. Options have an ID, a label and list of userIDs who voted for this option.
BTW to generate an ObjectID (which is needed for talking directly to the MongoDB-Adapter) use this:
id = "584a5b9419d51d724d146e3f" # string form
value = (for <<hex::16 <- id>>, into: <<>>, do: <<String.to_integer(<<hex::16>>, 16)::8>>)
object_id = %BSON.ObjectId{value: value}
And now for some examples:
# update label of option
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id, "options.id" => option_id}, # query
%{"$set" => %{"data.options.$.label" => new_label}} # update
)
# add new option to poll
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id},
%{"$addToSet" => %{"options" => %{label: label, id: random_new_id, votes: []}}}
)
# add user_id to option
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id, "options.id" => option_id},
%{"$addToSet" => %{"options.$.votes" => user_id}}
)
# remove user_id form option
Mongo.update_one(Repo.Pool, "polls",
%{"_id" => document_id, "options.id" => option_id},
%{"$pull" => %{"data.options.$.votes" => user_id}}
)

Find query with and operator in PHP

Hi i am working on backend of web application & want to find the documents from mongodb database that contain key active_status with value set to both 1 & 2. With mongodb PHP i am confused of how to find with both parameters in single query.
My query was this:
$mongoDb = MongoDbConnector::getCollection("endusers");
$endUserData = $mongoDb->find(array('active_status' => 1, '$and' => array('active_status' => 2)));
I have to fetch the users whose active_status should be 1 & 2. The above query doesnt seems to work. What is the right one for that?
Thanks on advance for quick response.
You have $and the wrong way around. Both arguments need to be included:
$endUserData = $mongoDb->find(array(
'$and' => array(
array( 'active_status' => 1 )
array( 'active_status' => 2 )
)
));
And since that would only make sense when looking for both elements within an array element, then you should instead use $all, which is shorter syntax:
$endUserData = $mongoDb->find(array(
'active_status' => array( '$all' => array(1,2) )
));
I should add that unless you intend to match a document like this:
{ "active_status" => [1,2] }
The you do not in fact want $and at all, but rather you want $or or better yet $in for multiple possible values on the same field:
$endUserData = $mongoDb->find(array(
'active_status' => array( '$in' => array(1,2) )
));
This matches documents like this:
{ "active_status": 1 },
{ "active_status": 2 }

How to keep orders in MongoDB?

In my MongoDB document I have object like this
[_id] => MongoId Object (
[$id] => 52a46b44aabacb5c218b4567
)
[results] => Array (
[http://google.com] => Array (
[position] => 1
[data] => 42672
)
[http://bing.com] => Array (
[position] => 2
[data] => 9423
)
[http://yandex.com] => Array (
[position] => 3
[data] => 5513
)
)
I would like to change data parameter in "bing.com" from 9423 to for instance 300. Moreover, I have to keep order of the sites. It have to looks like this
[_id] => MongoId Object (
[$id] => 52a46b44aabacb5c218b4567
)
[results] => Array (
[http://google.com] => Array (
[position] => 1
[data] => 42672
)
[http://bing.com] => Array (
[position] => 2
[data] => 300
)
[http://yandex.com] => Array (
[position] => 3
[data] => 5513
)
)
Is this achievable in Mongo?
The reordering of fields issue has been fixed as of MongoDB v2.5.2 (2.6 release). Having said that one way you can avoid the issue completely is having results as an array instead of a (sub)document. Also note you should not use "." as part of the key name either.
With 2.4, with the following you will see there is reodering in the case of _id=1 (subdocument) but not in the case of _id=2 (array).
$document = array("_id" => 1, "results" => array('http://google.com' => array('position' => 1, 'data' => 42672),
'http://bing.com' => array('position' => 2, 'data' => 9423),
'http://yandex.com' => array('position' => 3, 'data' => 5513)));
$coll->insert($document);
$document = array("_id" => 2, "results" => array(array('site' => 'http://google.com', 'data' => 42672),
array('site' => 'http://bing.com', 'data' => 9423),
array('site' => 'http://yandex.com', 'data' => 5513)));
$coll->insert($document);
$coll->update(array("_id" => 1), array('$set'=>array("results.http://bing.com.data"=>300)));
$coll->update(array("_id" => 2, 'results.site' => 'http://bing.com'), array('$set'=>array('results.$.data'=>300)));
I've included examples below using the mongo shell for clarity, but the PHP equivalent should be straightforward to work out.
I notice you originally modelled your list of sites as an embedded document, however the order of fields within an embedded document is currently not guaranteed to be preserved so you should instead use an array.
Additionally, you cannot use field names with embedded dots (.) in MongoDB so you should not plan to store urls as field names (see: Field name restrictions).
In order to find an element in an array you need to search by a value (not a field name) so your schema should look more like:
{
_id: ObjectId("52a46b44aabacb5c218b4567"),
results: [
{
site: 'http://google.com',
position: 1,
data: 42762
},
{
site: 'http://bing.com',
position: 2,
data: 9423
},
{
site: 'http://yandex.com',
position: 3,
data: 5513
}
]
}
Assuming the array site elements are unique, you can use the positional operator $ to find and update the matching embedded document in place.
For example, to perform your update of the "bing.com" data value:
db.sites.update(
// Match criteria
{
_id:ObjectId("52a46b44aabacb5c218b4567"),
'results.site':'http://bing.com'
},
// Update
{ $set: {
'results.$.data': 300 }
}
)
In MongoDB 2.4+ you have the option of pushing to a sorted array which could also be a useful approach to maintaining your array in sorted order when you add new entries.
It's worth noting that if you plan to store many (i.e. thousands) of items in an array this can impose a significant performance penalty due to document growth and the complexity of updating large arrays.
I am pretty sure that (as every other DBMS) you can't and should't rely on records orders.
Instead I would advice you to add index (on position, i.e. db.people.ensureIndex( { position: 1 } )) and query your record sorted by that field, i. e.: db.collection.find().sort( { position: 1 } )

Mongoid strange query result

I'm using MongoDB through Mongoid with Rails 3 and observe this strange behavior when doing query in rails console:
> Table.where(:field => {"$exists" => true}).count
=> 3735
> Table.where(:field => {"$exists" => true}, :field => {"$ne" => ""}).count
=> 14878 # wtf???
> Table.where(:field => {"$exists" => true}, :field => "").count
=> 0 # at least it's not negative
> Table.where(:field => {"$exists" => false}).count
=> 11143
Since 11143 + 3735 = 14878, I assume that where(:field => {"$exists" => true}, :field => {"$ne" => ""}) also counts those records in which :field is not present (because nil != ""?). However, I believed conditions listed in #where would be joined with and, so it should match only those records where :field is not empty string AND is present.
You say "However, I believed conditions listed in #where would be joined with 'and'," but this is not correct. The conditions are a hash, and you have a collision on the key :field. Ruby silently uses the last value.
Please review the documentation for selection in Mongoid http://mongoid.org/en/origin/docs/selection.html, and use #and for a proper 'and' conjunction. Note that you can #inspect your query and examine the returned Criteria object. For example:
puts Table.where(:field => {"$exists" => true}, :field => {"$ne" => ""}).inspect
Hope that this helps.