Mongoid 4 update array attribute in a document - arrays

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.

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.

pattern for updating a datastore object

I'm wondering what the right pattern should be to update an existing datastore object using endpoints-proto-datastore.
For example, given a model like the one from your GDL videos:
class Task(EndpointsModel):
detail = ndb.StringProperty(required=True)
owner = ndb.StringProperty()
imagine we'd like to update the 'detail' of a Task.
I considered something like:
#Task.method(name='task.update',
path='task/{id}',
request_fields=('id', 'detail'))
def updateTask(self, task):
pass
However, 'task' would presumably contain the previously-stored version of the object, and I'm not clear on how to access the 'new' detail variable with which to update the object and re-store it.
Put another way, I'd like to write something like this:
def updateTask(self, task_in_datastore, task_from_request):
task_in_datastore.detail = task_from_request.detail
task_in_datastore.put()
Is there a pattern for in-place updates of objects with endpoints-proto-datastore?
Thanks!
See the documentation for details on this
The property id is one of five helper properties provided by default
to help you perform common operations like this (retrieving by ID). In
addition there is an entityKey property which provides a base64
encoded version of a datastore key and can be used in a similar
fashion as id...
This means that if you use the default id property your current object will be retrieved and then any updates from the request will replace those on the current object. Hence doing the most trivial:
#Task.method(name='task.update',
path='task/{id}',
request_fields=('id', 'detail'))
def updateTask(self, task):
task.put()
return task
will perform exactly what you intended.
Task is your model, you can easily update like this:
#Task.method(name='task.update',
path='task/{id}',
request_fields=('id', 'detail'))
def updateTask(self, task):
# Task.get_by_id(task.id)
Task.detail = task.detail
Task.put()
return task

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.

Mongoid - Array assignment

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]

Queries with JDOQL Unique Result Set

In my GAE application I want to make a JDOQL which returns a List where every element exist at most once even in the database there are more. How can I do this?
I dont know about JDOQL, but if you want a list where each entity exists utmost once i.e each list element is unique, then you could do the following:
Asume you have an entit type / model class that we call Type, with attributes att1,attr2.
You want to have a list of unique elements based on one or more of the attributes, say attr2.
you can use the following method that I adapted from a good source on the issue:
def unique(seq, idfun=None):
''' A function that returns a list of unique items in a very efficient manner
Refer to : http://www.peterbe.com/plog/uniqifiers-benchmark '''
# order preserving
if idfun is None:
def idfun(x): return x
seen = {}
result = []
for item in seq:
marker = idfun(item)
# in old Python versions:
# if seen.has_key(marker)
# but in new ones:
if marker in seen: continue
seen[marker] = 1
result.append(item)
return result
the to get a list of unique elements from the datastore type Type based on attr2 i could do something like:
list = Type.all()
unique_list = unique(list,lambda t: t.attr2)
Hope this helps because it has been the best method for me so far.

Resources