Created
August 24, 2010 04:42
-
-
Save antarestrader/546968 to your computer and use it in GitHub Desktop.
A simple field and relation add-on for Mongomatic
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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