To demonstrate the powers of Mongoid, we would start from a new rails application
rails new mongo_app
cd mongo_app
Installation
gem ‘mongoid’, ‘~> 5.1.0’
For you not to manually create the configuration file of mongoid in config/mongoid.yml, we use the command:
rails generate mongoid:config
Now, rails comes with the ActiveRecord ORM by default, to change that, we open config/application.rb and add:
config.generators do |g| g.orm :mongoid end
Models
To begin, lets create a new model called Phone.
rails generate model Phone number:string country:string
And then, we open the model file at app/models/phone.rb. Since we configured rails to use the mongoid ORM, we see our model file as:
class Phone
include Mongoid::Document
field :number, type: String
field :country, type: String
end
When you don’t specify a type, Mongoid considers this as an object type.
Reading/Writing in Mongoid
Now, for instance if we want to access the field, we do:
phone.number
phone[:number]
If we want to write,
phone.number = “911”
phone[:number] = “911”
Now, if we want to write multiple attributes at once, we do:
phone = Phone.new(number: “911”, country: “USA”)
phone.attribute = {number: “117”, country: “Philippines”}
Default Values
For instance, we want to have our models come with a default value (so that when there is no entry, we would have a value for the field), we modify the model
class Phone
include Mongoid::Document
field :number, type: String, default: “09228081190”
field :country, type: String, default: “jambytown”
end
Aliasing Fields
Since MongoDB is schemaless (unlike say, postgresql) it stores its schema along with the document. Remember that MongoDB, instead of storing rows of data in a table like a normal SQL dB would do, stores its data on BSON hashes. Since hashes are a key-value pair, this is where MongoDB stores its schema. Hence, it stores it in every document.
Since that could be expensive in terms of RAM, we can create aliases of fields:
class Phone
include Mongoid::Document
field :n, as: :number, type: String, default: “09228081190”
field :c, as: :country, type: String, default: “jambytown”
end
In creating aliases, mongoDB uses the real keys as the “key” in the key-value pairs, but we can still interface with mongoid using the alias.
Although it saves in memory, I do not advise its indiscriminate use as it can reduce the readability of the hashes once queried in the database.
Customizing DB
By default, Mongoid models references the default db and client placed in the configuration file. But you can define more databases and clients and give them a name. Mongoid also is preconfigured to connect to the collection that is the plural form of its model. For instance, for our Phone model, we are connected to the phones collection. (pretty much how ActiveRecord is, right?)
Now, if we want to customize what collection/database/client we want the model to refer to, we could do so by adding this line inside the Phone model class in app/models/phone.rb
store_in collection: "citizens", database: "other", client: "secondary"
Custom fields
For instance, we have the profile object. And we want to create a field with a custom type called Point.
class Profile
include Mongoid::Document
field :location, type: Point
end
For that, we define the class Point as another class inside the same file (or imported for better seperation of concern)
class Point
attr_reader :x, :y
def initialize(x, y)
@x, @y = x, y
end
# Converts an object of this instance into a database friendly value.
def mongoize
[ x, y ]
end
class << self
# Get the object as it was stored in the database, and instantiate
# this custom class from it.
def demongoize(object)
Point.new(object[0], object[1])
end
# Takes any possible object and converts it to how it would be
# stored in the database.
def mongoize(object)
case object
when Point then object.mongoize
when Hash then Point.new(object[:x], object[:y]).mongoize
else object
end
end
# Converts the object that was supplied to a criteria and converts it
# into a database friendly form.
def evolve(object)
case object
when Point then object.mongoize
else object
end
end
end
end
For us to be able to use the object as an attribute of the Profile model, we place 5 functions inside the Point class (the first two are instance method, and the last three are class methods as indicated by the meta-class class << self). The first mongoize (instance function), translate your object into a simple list (the way mongodb would store it).
The demongonize class function translates an object in a DB and instantiate a new point object out of it. Meanwhile, the mongonize class function is for passing points not in object form.
point = Point.new(12, 24)
Venue.where(location: point) #passed location in Object form
venue = Venue.new(location: [ 12, 24 ])
#passed location in array form, uses mongoinize
Dynamic Fields
When you haven't defined a field beforehand but you want anyone with an undefined field to be able to store in your DB, we allow dynamic fields. We do this simply by changing the model to
class Profile
include Mongoid::Document
include Mongoid::Attributes::Dynamic
field :location, type: Point
end
In this case, the normal dot syntax would raise an error
person.gender
person.gender = "Male"
So we instead use the methods below:
# Retrieve a dynamic field safely.
person[:gender]
person.read_attribute(:gender)
# Write a dynamic field safely.
person[:gender] = "Male"
person.write_attribute(:gender, "Male")
ReadOnly Attributes
If we want an attribute to be readonly, we simply add the code:
attr_readonly <tokenize names of fields seperated by comma>
class Profile
include Mongoid::Document
field :location, type: Point
attr_readonly :location
end
Inheritance
MongoDB supports model inheritance. In the example below, this means that Browser model has all the fields of the Canvas (but not the other way around).
class Canvas
include Mongoid::Document
field :name, type: String
embeds_many :shapes
end
class Browser < Canvas
field :version, type: Integer
scope :recent, where(:version.gt => 3)
end
class Firefox < Browser
end
Query Subclasses
For example, we queried the subclass Browser using the syntax Browser.where(name: “thing”). We would only return all objects that is of class Browser, not of canvass (parent) and not of firefox (child).
# Returns Canvas documents and subclasses
Canvas.where(name: "Paper")
# Returns only Firefox documents
Firefox.where(name: "Window 1")
Timestamping
ActiveRecord ORM automatically adds the timestamps in each record. For Mongoid, you have to manually include it using the methods below:
class Person
include Mongoid::Document
include Mongoid::Timestamps::Created
end
class Post
include Mongoid::Document
include Mongoid::Timestamps::Updated
end