Skip to content

Instantly share code, notes, and snippets.

@ippeiukai
Last active November 25, 2015 09:27
Show Gist options
  • Save ippeiukai/0dc7ec74abe62a599394 to your computer and use it in GitHub Desktop.
Save ippeiukai/0dc7ec74abe62a599394 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
# ================
class MongoidCustomArray < DelegateClass(Array)
extend MongoidEmbeddedObjectModification
module ArrayClasses
class << self
def find_or_add_for(type)
classes[type] ||= add_class(yield)
end
private
def classes
@classes ||= {}
end
def add_class(new_klass)
type_name = new_klass::ELEMENT_TYPE.name.demodulize
name = "ArrayOf#{type_name}"
if const_defined?(name)
i = 1;
begin
i += 1
name = "Array#{i}Of#{type_name}"
end while const_defined?(name)
end
const_set(name, new_klass)
new_klass
end
end
end
def self.of(type)
ArrayClasses.find_or_add_for(type) do
Class.new(self) do
const_set(:ELEMENT_TYPE, type)
end
end
end
module Mongoization
extend ActiveSupport::Concern
module ClassMethods
def demongoize(object)
case object
when Array
self[*object.map(&self::ELEMENT_TYPE.public_method(:demongoize))]
when nil
nil
end
end
def mongoize(object)
object.map(&self::ELEMENT_TYPE.public_method(:mongoize))
end
def evolve(object)
mongoize(object)
end
end
def mongoize
self.class.mongoize(self)
end
end
include Mongoization
def self.[](*elms)
self.new(elms)
end
end
@ippeiukai
Copy link
Author

What you do:

Note Point class uses MongoidEmbeddedObject from https://gist.github.com/ippeiukai/df75196427c869454886

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 :locations, type: MongoidCustomArray.of(Point)
end

What you get:

profile = Profile.new(locations:[Point.at(0,0), Point.at(12,24)])
MongoidCustomArray.modify(profile, :locations) do |locations|
  locations.first.x = 10
end
puts profile.inspect # => #<Profile _id: 545c61a34e3132c711000000, locations: [{"x"=>10, "y"=>0}, {"x"=>12, "y"=>24}]>
profile.save

profile = Profile.last
profile.locations # => [#<Point x: 10, y: 0>, #<Point x: 12, y: 24>]

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