Mongoid - Array assignment - mongoid

I'm seeing some strange behaviour in Mongoid 2.3.4
class Student
has_and_belongs_to_many: teachers
end
class Teacher
has_and_belongs_to_many: students
end
Now in IRB
s = Student.first
s.teachers
=> []
s.teacher_ids = [Teacher.first.id, Teacher.last.id]
s.teacher_ids
=> [[BSON::ObjectId4f7c3300913417162c000008, BSON::ObjectId4f7c333b913417162c00000d]]
Not sure why this array is nested like that. I expected
[BSON::ObjectId4f7c3300913417162c000008, BSON::ObjectId4f7c333b913417162c00000d]
This breaks multi select fields in Rails, where the mass assignments of ids would happen like shown in IRB.

It may have to do with the fact that you're attempting to set the teachers_ids attribute to an array of Teacher objects.
You could try these as alternatives:
s.teachers = [Teacher.first, Teacher.last]
or
s.teachers << Teacher.first
s.teachers << Teacher.last
Update:
I've just run a little test and can confirm that your method of assignment works fine in Mongoid 2.4.6 (which is just what I happened to have installed) and 2.4.8.
If for some reason you can't upgrade to Mongoid 2.4, you could also try passing the IDs in as String objects instead of as ObjectId, which is how it would be handled if this was being passed in through POST parameters.
s.teacher_ids = [Teacher.first.id.to_s, Teacher.last.id.to_s]

Related

Rails update remove number from an array attribute?

Is there a way to remove a number from an attibute array in an update? For example, if I want to update all of an alchy's booze stashes if he runs out of a particular type of booze:
Alchy has_many :stashes
Stash.available_booze_types = [] (filled with booze.ids)
Booze is also a class
#booze.id = 7
if #booze.is_all_gone
#alchy.stashes.update(available_booze_types: "remove #booze.id")
end
update: #booze.id may or may not be present in the available_booze_types array
... so if #booze.id was in any of the Alchy.stash instances (in the available_booze_types attribute array), it would be removed.
I think you can do what you want in the following way:
if #booze.is_all_gone
#alchy.stashes.each do |stash|
stash.available_booze_types.delete(#booze.id)
end
end
However, it looks to me like there are better ways to do what you are trying to do. Rails gives you something like that array by using relations. Also, the data in the array will be lost if you reset the app (if as I understand available_booze_types is an attribute which is not stored in a database). If your application is correctly set up (an stash has many boozes), an scope like the following in Stash class seems to me like the correct approach:
scope :available_boozes, -> { joins(:boozes).where("number > ?", 0) }
You can use it in the following way:
#alchy.stashes.available_boozes
which would only return the ones that are available.

Mongoid 4 update array attribute in a document

What is the mistake in this code for not be able to update an array within a document?
Model
class Foo
include Mongoid::Document
include Mongoid::Timestamps::Created
field :myarray, type: Array
end
Controller
def add_item
#foo = Foo.find_by(uuid: params[:uuid])
unless #foo.nil?
unless #foo.has_attribute? :myarray
#foo[:myarray] = Array.new
end
#foo[:myarray] << params[:item]
#foo.save
end
end
I am using Rails 4 with MongoId 4 and if I do p #foo before #foo.save I can see #foo correctly changed but for any reason the update is not persisted.
When you say this:
#foo[:myarray] << params[:item]
You're modifying the myarray array in-place so Mongoid probably won't recognize that it has changed. Then when you say #foo.save, Mongoid will look at #foo to see what has changed; but the array reference in #foo[:myarray] won't change so Mongoid will decide that nothing has changed and #foo.save won't do anything.
If you force a new array reference to be created by saying:
#foo[:myarray] += [ params[:item] ] # Or anything else that creates a whole new array
then Mongoid will notice that #foo[:myarray] has changed and #foo.save will send the change down to MongoDB via $set operation on the underlying document.
This looks like the Mongoid version of this ActiveRecord problem with PostgreSQL array columns:
New data not persisting to Rails array column on Postgres
The rule of thumb is "don't edit mutable values in-place, create whole new values instead: copy, edit, replace". That way you don't have to worry about manually managing the "is dirty" flags.

ndb query by KeyProperty

I'm struggling with a KeyProperty query, and can't see what's wrong.
My model is
class MyList(ndb.Model):
user = ndb.KeyProperty(indexed=True)
status = ndb.BooleanProperty(default=True)
items = ndb.StructuredProperty(MyRef, repeated=True, indexed=False)
I create an instance of MyList with the appropriate data and can run the following properly
cls = MyList
lists = cls.query().fetch()
Returns
[MyList(key=Key('MyList', 12), status=True, items=..., user=Key('User', 11))]
But it fails when I try to filter by user, i.e. finding lists where the user equals a particular entity; even when using the one I've just used for insert, or from the previous query result.
key = lists[0].user
lists = cls.query(cls.user=key).fetch()
Returns
[]
But works fine with status=True as the filter, and I can't see what's missing?
I should add it happens in a unit testing environment with the following v3_stub
self.policy = datastore_stub_util.PseudoRandomHRConsistencyPolicy(probability=0)
self.testbed.init_datastore_v3_stub(
require_indexes=True,
root_path="%s/../"%(os.path.dirname(__file__)),
consistency_policy=self.policy
)
user=Key('User', 11) is a key to a different class: User. Not MyList
Perhaps you meant:
user = ndb.KeyProperty(kind='User', indexed=True)
Your code looks fine, but I have noticed some data integrity issues when developing locally with NDB. I copied your model and code, and I also got the empty list at first, but then after a few more attempts, the data is there.
Try it a few times?
edit: possibly related?
google app engine ndb: put() and then query(), there is always one less item

GoogleAppEngine - query with some custom filter

I am quite new with appEnginy and objectify. However I need to fetch a single row from db to get some value from it. I tried to fetch element by ofy().load().type(Branch.class).filter("parent_branch_id", 0).first() but the result is FirstRef(null). However though when I run following loop:
for(Branch b : ofy().load().type(Branch.class).list()) {
System.out.println(b.id +". "+b.tree_label+" - parent is " +b.parent_branch_id);
};
What do I do wrong?
[edit]
Ofcourse Branch is a database entity, if it matters parent_branch_id is of type long.
If you want a Branch as the result of your request, I think you miss a .now():
Branch branch = ofy().load().type(Branch.class).filter("parent_branch_id", 0).first().now();
It sounds like you don't have an #Index annotation on your parent_branch_id property. When you do ofy().load().type(Branch.class).list(), Objectify is effectively doing a batch get by kind (like doing Query("Branch") with the low-level API) so it doesn't need the property indexes. As soon as you add a filter(), it uses a query.
Assuming you are using Objectify 4, properties are not indexed by default. You can index all the properties in your entity by adding an #Index annotation to the class. The annotation reference provides useful info.
Example from the Objectify API reference:
LoadResult<Thing> th = ofy.load().type(Thing.class).filter("foo", foo).first();
Thing th = ofy.load().type(Thing.class).filter("foo", foo).first().now();
So you need to make sure member "foo" has an #Index and use the now() to fetch the first element. This will return a null if no element is found.
May be "parent_branch_id"in your case is a long, in which case the value must be 0L and not 0.

Underscore isObject() sometimes not work

When I use nodeUnit to write unit tests for Backbone models, something weird happened, the question can be summarized as:
var Player = Backbone.Model.extend({});
var player = new Player({name: 'Jacky'});
Then I update the player name with: player.set('name', 'Scott').
But I found that the name attribute is not updated at all(No validate function for this model), and there are weird attributes like: [0]: n, [1]: a, [2]: m, [3]: e.
It works fine if I changed the update method to player.set({name: 'Scott'}).
I did some quick investigation and found that Backbone.Model.set method depends on Underscore.isObject() method, and the latter is implemented as:
_.isObject = function(obj) {
return obj === Object(obj);
};
I called the isObject() method in nodeUnit test cases, and in the first case it works fine, but in latter cases, it does not work as expected, for example isObject('name') will return true instead of the expected false.
Anyone have any idea what caused this?
Can you make sure that you are using the latest version of Backbone? Backbone in older versions didn't support shortcut version for the set method (.set(key, value)) and allowed only object with attribute/value pairs as a parameter.
(i believe it was added in 0.9)

Resources