Skip to content

Instantly share code, notes, and snippets.

@ippeiukai
Last active August 29, 2015 14:08
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ippeiukai/df75196427c869454886 to your computer and use it in GitHub Desktop.
Save ippeiukai/df75196427c869454886 to your computer and use it in GitHub Desktop.
module MongoidEmbeddedObjectModification
# Custom field behaves more like an immutable value.
# Your changes to the object obtained with getter method will not take an effect unless you assign it back.
# https://github.com/mongoid/mongoid/issues/3341
def modify(mongoid_doc, field_name)
obj = mongoid_doc.public_send(field_name)
yield obj
mongoid_doc.public_send("#{field_name}=", obj)
end
end
# ================
module MongoidEmbeddedObject
extend ActiveSupport::Concern
extend MongoidEmbeddedObjectModification
def self.new_class(&block)
Class.new do
include MongoidEmbeddedObject
instance_exec(&block)
end
end
module Mongoization
extend ActiveSupport::Concern
module ClassMethods
def demongoize(object)
self.instantiate(object)
end
def mongoize(object)
object.mongoize
end
def evolve(object)
mongoize(object)
end
end
def mongoize
attributes.except('_id')
end
end
# HACK: Mongoid::Inspectable includes hardcoded _id field
module InspectableWithoutId
include Mongoid::Inspectable
def inspect
super.sub('_id: , ', '')
end
end
included do
include Mongoid::Attributes
include Mongoid::Changeable
include Mongoid::Fields
include Mongoid::Threaded::Lifecycle
include MongoidEmbeddedObject::InspectableWithoutId
include MongoidEmbeddedObject::Mongoization
fields.delete('_id') # Mongoid::Fields adds '_id' field; for now, its accessor methods are not removed
end
module ClassMethods
def instantiate(attributes = {})
self.new do
if !attributes.is_a?(Hash)
raise ArgumentError
elsif attributes.present?
@attributes = attributes.deep_dup
end
end
end
end
def initialize(params = {})
@attributes ||= {}
apply_defaults
if params.present?
params.each do |attr, value|
self.public_send("#{attr}=", value)
end
end
instance_eval(&Proc.new) if block_given?
super()
end
# HACK: method in Mongoid::Document needed by included Mongoid modules
def __selected_fields
nil
end
# HACK: method in Mongoid::Document needed by included Mongoid modules
def new_record?
true
end
end
@ippeiukai
Copy link
Author

What you do:

class Point
  include MongoidEmbeddedObject
  def self.at(x,y)
    self.new(x: x, y: y)
  end
  field :x, type: Integer, default: 0
  field :y, type: Integer, default: 0
end

class Profile
  include Mongoid::Document
  field :location, type: Point
end

or

class Profile
  include Mongoid::Document
  field :location, type: (Point = MongoidEmbeddedObject.new_class do
    field :x, type: Integer, default: 0
    field :y, type: Integer, default: 0
  end)
end

# compatibility with previous example
Point = Profile::Point
def Point.at(x,y)
  self.new(x: x, y: y)
end

What you get:

profile = Profile.new
profile.location = Point.at(12,24)
puts profile.location.inspect # => #<Point x: 12, y: 24>
puts profile.inspect # => #<Profile _id: 545c57fe4e3132c067000000, location: {"x"=>12, "y"=>24}>

or

profile = Profile.new(location: Point.at(12,24))
MongoidEmbeddedObject.modify(profile, :location) do |location|
  location.x = 10
end
puts profile.location.inspect # => #<Profile::Point x: 10, y: 24>
puts profile.inspect # => #<Profile _id: 545c5d7e4e3132c346010000, location: {"x"=>10, "y"=>24}>

@ippeiukai
Copy link
Author

Please see also the MongoidCustomArray class that should go well with this:
https://gist.github.com/ippeiukai/0dc7ec74abe62a599394

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment