14

I'm using Mongoid in a Rails project (both 4.0.x), and I've got a document with a hash field that stores some schema-less data.

class Thing
  field :name, type: String
  field :mass, type: Integer
  field :info, type: Hash
end

With this setup, I can query for things, say, that have a key :endDate like so:

Thing.where("info.endDate"=>{'$exists'=>true})

And that's all nice and handy. Using a hash field for this :info field is nice because what I want to store doesn't have a fixed schema and varies from one thing to another.

Ok, but, I can't use the same dot syntax to $set key/value pairs in the :info hash. [1]

thing.set("info.endDate"=>Time.now) 

Raises a Mongoid::Errors::UnknownAttribute error.

It tells me I'd have to include Mongoid::Attributes::Dynamic in my model to do this, but that doesn't seem right to me. The point of the hash field type seems to be to allow you to work with data that doesn't have a fixed schema. It doesn't seem like I should have to include a special "dynamic attributes" module to use hash fields.

So right now, I'm updating values using regular old [] syntax, and then calling save on the model, like so:

thing.info[:endDate] = Time.now
thing.save

But a lot of the time it happens that it would be nicer to just $set the value. Is there some other syntax for setting hash field values? Am I wrong about the above error message and Dynamic Attributes being wrong-headed? Am I stuck doing two step updates to hash fields for now?

[1] admittedly, I've recently migrated from mongomapper, and so my expectations of this syntax are partly set by having been able to do this previously in mongomapper.

Community
  • 1
  • 1
Bee
  • 14,277
  • 6
  • 35
  • 49
  • 1
    I think that embeds_one , which will be defined as Mongoid::Attributes::Dynamic , will work for you (never tested this approach) – Vitali Mogilevsky Mar 31 '16 at 10:47
  • 2
    I don't want an embedded document. I want a Hash field because the data I'm storing there varies from document to document. And mongoid gives me a way to query on specific keys/value pairs within that hash. I would like to be able to update key/value pairs in that Hash as easily, using $set. – Bee Apr 02 '16 at 06:03
  • Isn't it supposed to be `.set(:info => {:endDate => Time.now })` Its been a while since I worked with MongoDB though. – Andy Gauge Apr 04 '16 at 23:20
  • 1
    @AndyGauge that would overwrite the entire `:info` field, rather than just writing the one key to the hash :-) – Bee Apr 08 '16 at 06:31

2 Answers2

13

The thing with Hash field is, it can be dynamic as much as you want. Therefore to prevent polluting your DB schema with unintended fields caused by bugs in your code this functionality is disabled by default.

No you are not stuck using 2-step updates for your hashes at all!

[],[]= are the shortcuts for read_attribute() and write_attribute() and should be used if you don't include Mongoid::Attributes::Dynamic. If you try to use $set without enabling dynamic attributes you will get a no-method error because it does not see your dynamic attributes as defined attributes.

If you'll read the source of Mongoid::Attributes::Dynamic then you'd find that this is required to add the dynamic attributes functionality.

To update the values by including Mongoid::Attributes::Dynamic you need to follow these steps:

thing = Thing.first
thing.set("info.endDate" => Time.now)
thing.reload # This will update the current variable 

Otherwise if you need you can easily skip this and do the value update by 2-step method

I hope this sheds some light over your query.

Source:

Rails mongoid dynamic fields - no method error

Dynamic attributes with Rails and Mongoid

Community
  • 1
  • 1
Jagjot
  • 5,816
  • 2
  • 24
  • 41
  • I'm still dissatisfied with this answer. The documentation on dynamic fields seems to be referring to dynamically adding fields to the document. It doesn't seem like setting a key/value pair within a declared Hash field is what they're referring to when they're talking about dynamic fields. What good is a Hash data type where you can't add arbitrary key/value pairs? And in fact I can add arbitrary values to the Hash, just not using $set, apparently. But maybe I'm just arguing with the design of mongoid here. I don't know. – Bee Apr 08 '16 at 06:21
  • 1
    @Bee I know it seems frustrating to not be able to use `$set` just when you want it on Hash fields. As I explained in my answer they have disabled the feature to use `$set` because it's values can be dynamic and if there are some errors in the code, it can lead to unintended fields. But when you are using [],[]= you are pretty sure what values you are using. This is just how it was designed. But we can enable `$set` for Hash fields by including that module and telling Mongoid that we are pretty sure of the code that we have coded. Think from author's viewpoint and it seems right. – Jagjot Apr 08 '16 at 06:28
  • 1
    Excellent!! Works perfectly :D – Sankalp Singha Apr 08 '16 at 20:15
1

I think you pass parameter in wrong way. Replace arrow symbol with comma

You can change to this and it will work

thing.set("info.endDate", Time.now) 
Sukanta
  • 585
  • 3
  • 6
  • Thanks! But that's the syntax for Mongoid 3.x sets. Starting with Mongoid 4.x they switched to passing a hash of field/value pairs to set. – Bee Apr 08 '16 at 05:58
  • Yes @Bee, actually i am using mongoid 3.x in my current project. and yes mongoid 4.x , set taking arguments as key-value pair. Thanks for your reply – Sukanta Apr 08 '16 at 06:04