Skip to content

Instantly share code, notes, and snippets.

@antarestrader
Created August 24, 2010 04:42
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save antarestrader/546968 to your computer and use it in GitHub Desktop.
Save antarestrader/546968 to your computer and use it in GitHub Desktop.
A simple field and relation add-on for Mongomatic
module Models
class Base < Mongomatic::Base
def self.inherited(klass)
klass.extend ClassMethods
klass.extend ActiveModel::Naming
klass.send(:include, InstanceMethods)
end
module Models
attr_accessor :logger
@logger ||= Rails.logger
class Base < Mongomatic::Base
def self.inherited(klass)
klass.extend ClassMethods
klass.extend ActiveModel::Naming
klass.send(:include, InstanceMethods)
end
def parent
self
end
end
class Embedded
attr_accessor :parent
attr_accessor :doc
def self.inherited(klass)
klass.extend ClassMethods
klass.send(:include, InstanceMethods)
end
def [](index)
@doc[index]
end
def []=(index,val)
@doc[index] = val
end
def initialize(doc={},pnt=nil)
@doc = HashWithIndifferentAccess.new(doc)
@parent = pnt
end
end
module ClassMethods
public
def fields
@fields ||= []
end
protected
def field(name)
name = name.to_s
fields << name
class_eval(<<-EOF,__FILE__,__LINE__+1)
def #{name}
self["#{name}"]
end
def #{name}=(val)
self["#{name}"] = val
end
EOF
end
#options:
# :class_name default name.singularize.camelize
# :store_as default same as name
def embed(name, opts={})
name = name.to_s
class_name = opts[:class_name] || name.singularize.camelize
store_as = (opts[:store_at] || name).to_s
class_eval(<<-EOF,__FILE__,__LINE__+1)
def #{name}
@_#{name} ||= #{class_name}.new(self[:#{store_as}], self.parent) if self[:#{store_as}]
end
def #{name}=(val)
if val.nil?
unset(:#{store_as})
@_#{name} = nil
else
inst,hash = rationalize_instance(val,#{class_name})
inst.parent = self.parent
self[:#{store_as}] = hash
@_#{name} = inst
end
end
EOF
end
#options:
# :class_name default name.singularize.camelize
# :store_as default same as name
def embedded_set(name, opts={})
name = name.to_s
class_name = opts[:class_name] || name.singularize.camelize
store_as = (opts[:store_at] || name).to_s
class_eval(<<-EOF,__FILE__,__LINE__+1)
def #{name}
self[:#{store_as}] ||= []
@_#{name} ||= attach_intellegent_insertion(self[:#{store_as}].map {|i| #{class_name}.new(i,self)},self[:#{store_as}],self,#{class_name})
end
def #{name}=(arr)
self[:#{store_as}] = arr.dup
@_#{name} = nil
end
EOF
#
# example embedded_set(:cargo)
#def cargo
# self[:cargo] ||= []
# @cargo ||= attach_intellegent_insertion(self[:cargo].map {|i| Cargo.new(i,self)},self[:cargo],self,Cargo)
#end
#
#def cargo=(arr)
# self[:cargo] = arr.dup
# @cargo = nil
#end
end
#options:
# :class_name
# :store_as
#
def reference(name, opts={})
name = name.to_s
class_name = opts[:class_name] || name.singularize.camelize
store_as = (opts[:store_as] || name+"_id").to_s
class_eval(<<-EOF,__FILE__,__LINE__+1)
field :#{store_as}
def #{name}
@_#{name} ||= retrieve_reference(#{class_name},"#{store_as}")
@_#{name}
end
def #{name}=(val)
raise ArgumentError, "'\#{val.inspect}' is not an instance of #{class_name}" unless (val.nil? || val.kind_of?(#{class_name}) )
if val.nil?
unset("#{store_as}")
else
val.id ||= BSON::ObjectId.new
self["#{store_as}"] = val.id
end
@_#{name} = val
end
EOF
end
def reference_set(name,opts={})
name = name.to_s
class_name = opts[:class_name] || name.singularize.camelize
store_as = (opts[:store_at] || name.singularize+"_ids").to_s
class_eval(<<-EOF,__FILE__,__LINE__+1)
field :#{store_as}
def #{name}
self["#{store_as}"] ||= []
@_#{name} ||= attach_intellegent_insertion(
#{class_name}.find({ "_id" => { "$in" => (self["#{store_as}"]) } }).to_a,
self["#{store_as}"],
nil,
#{class_name},
:id
)
end
def #{name}=(val)
self["#{store_as}"] = val.map{|i| i.id}
@_#{name} = attach_intellegent_insertion(val.dup,self["#{store_as}"],nil,#{class_name},:id)
end
EOF
end
end
module InstanceMethods
def id
self[:_id]
end
def to_model
self
end
def to_s
id.to_s
end
def to_key
[self.id]
end
def errors
{}
end
def inspect
return "#<%s id:%s name:%s>" % [self.class.to_s, self.id.to_s, self.name] if self.respond_to? :name
"#<%s id:%s>" % [self.class.to_s, self.id.to_s]
end
private
def attach_intellegent_insertion(arr,doc,parent, klass, ref_method = :doc)
r_i = Proc.new do |v|
rationalize_instance(v,klass,ref_method)
end
arr.define_singleton_method '<<' do |val|
inst, ref = r_i.call(val)
doc << ref
inst.parent = parent if parent
super inst
end
arr
end
def rationalize_instance(val,klass, ref_method = :doc)
if klass === val
[val,val.send(ref_method)]
elsif Hash === val #do we dup?
val = HashWithIndifferentAccess.new val
[klass.new(val),val]
else
[klass.new(val),val]
end
end
def retrieve_reference(klass,store_as)
#eager load here?
if self[store_as]
val = klass.find_one(:_id=>self[store_as])
Models::Base.logger.info "Could not find referenced id #{self[store_as].to_s} in model #{klass.to_s}. Is the record saved? (#{__FILE__}:#{__LINE__-1})"
return val
else
nil
end
end
end
end
class Embedded
attr_accessor :parent
attr_accessor :doc
def self.inherited(klass)
klass.extend ClassMethods
klass.send(:include, InstanceMethods)
end
def [](index)
@doc[index]
end
def []=(index,val)
@doc[index] = val
end
def initialize(doc={},parent=nil)
@doc = HashWithIndifferentAccess.new(doc)
@parent = parent
end
end
module ClassMethods
protected
def field(name)
name = name.to_s
class_eval(<<-EOF,__FILE__,__LINE__+1)
def #{name}
self["#{name}"]
end
def #{name}=(val)
self["#{name}"] = val
end
EOF
end
#options:
# :class_name default name.singularize.camelize
# :store_as default same as name
def embed(name, opts={})
name = name.to_s
class_name = opts[:class_name] || name.singularize.camelize
store_as = (opts[:store_at] || name).to_s
class_eval(<<-EOF,__FILE__,__LINE__+1)
def #{name}
@_#{name} ||= #{class_name}.new(self[:#{store_as}]) if self[:#{store_as}]
end
def #{name}=(val)
if val.nil?
unset(:#{store_as})
@_#{name} = nil
else
inst,hash = rationalize_instance(val,#{class_name})
self[:#{store_as}] = hash
@_#{name} = inst
end
end
EOF
end
#options:
# :class_name default name.singularize.camelize
# :store_as default same as name
def embedded_set(name, opts={})
name = name.to_s
class_name = opts[:class_name] || name.singularize.camelize
store_as = (opts[:store_at] || name).to_s
class_eval(<<-EOF,__FILE__,__LINE__+1)
def #{name}
self[:#{store_as}] ||= []
@_#{name} ||= attach_intellegent_insertion(self[:#{store_as}].map {|i| #{class_name}.new(i,self)},self[:#{store_as}],self,#{class_name})
end
def #{name}=(arr)
self[:#{store_as}] = arr.dup
@_#{name} = nil
end
EOF
#
# example embedded_set(:cargo)
#def cargo
# self[:cargo] ||= []
# @cargo ||= attach_intellegent_insertion(self[:cargo].map {|i| Cargo.new(i,self)},self[:cargo],self,Cargo)
#end
#
#def cargo=(arr)
# self[:cargo] = arr.dup
# @cargo = nil
#end
end
#options:
# :class_name
# :store_as
#
def reference(name, opts={})
name = name.to_s
class_name = opts[:class_name] || name.singularize.camelize
store_as = (opts[:store_at] || name+"_id").to_s
class_eval(<<-EOF,__FILE__,__LINE__+1)
field :#{store_as}
def #{name}
@_#{name} ||= #{class_name}.find_one("_id"=>self["#{store_as}"]) if self["#{store_as}"]
end
def #{name}=(val)
raise ArgumentError, "'\#{val.inspect}' is not an instance of #{class_name}" unless (val.nil? || val.kind_of?(#{class_name}) )
if val.nil?
unset("#{store_as}")
else
self["#{store_as}"] = val.id
end
@_#{name} = val
end
EOF
end
def reference_set(name,opts={})
name = name.to_s
class_name = opts[:class_name] || name.singularize.camelize
store_as = (opts[:store_at] || name.singularize+"_ids").to_s
class_eval(<<-EOF,__FILE__,__LINE__+1)
field :ship_ids
def #{name}
self["#{store_as}"] ||= []
@_#{name} ||= attach_intellegent_insertion(
#{class_name}.find({ "_id" => { "$in" => (self["#{store_as}"]) } }).to_a,
self["#{store_as}"],
nil,
#{class_name},
:id
)
end
def #{name}=(val)
self["#{store_as}"] = val.map{|i| i.id}
@_#{name} = attach_intellegent_insertion(val.dup,self["#{store_as}"],nil,#{class_name},:id)
end
EOF
end
end
module InstanceMethods
def id
self[:_id]
end
def to_model
self
end
def to_s
id.to_s
end
def to_key
[self.id]
end
def errors
{}
end
def inspect
"#<%s id:%s name:%s>" % [self.class.to_s, self.id.to_s, self.name]
end
private
def attach_intellegent_insertion(arr,doc,parent, klass, ref_method = :doc)
r_i = Proc.new do |v|
rationalize_instance(v,klass,ref_method)
end
arr.define_singleton_method '<<' do |val|
inst, ref = r_i.call(val)
doc << ref
inst.parent = parent if parent
super inst
end
arr
end
def rationalize_instance(val,klass, ref_method = :doc)
if klass === val
[val,val.send(ref_method)]
elsif Hash === val #do we dup?
val = HashWithIndifferentAccess.new val
[klass.new(val),val]
else
[klass.new(val),val]
end
end
end
end
module Ship
class Cargo < Models::Embedded
field :name
field :quantity
reference :commodity
end
class Destination < Models::Embedded
field :name
field :eta
reference :location, :class_name=>"Planet::Base"
end
class Base < Models::Base
def self.collection_name
"ships"
end
def create_index
self.collection.create_index("name",:unique=>true)
end
def self.name
"Ship"
end
field :name
field :location_name
embedded_set :cargo, :class_name=>"Ship::Cargo"
embed :destination, :class_name=>"Ship::Destination"
reference :location,:class_name=>"Planet::Base"
def audit
audit_cargo
audit_destination
audit_location_name
update
end
private
def audit_cargo
self.cargo.each {|c| c.name = c.commodity.name if c.commodity}
end
def audit_destination
self.destination.name = self.destination.location.name if self.destination
end
def audit_location_name
self.location_name = location.name if location
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment