adding support for collection_of association
This commit is contained in:
parent
fa0ab968a8
commit
dd55466764
4 changed files with 281 additions and 11 deletions
|
@ -2,19 +2,18 @@ module CouchRest
|
|||
module CastedModel
|
||||
|
||||
def self.included(base)
|
||||
base.send(:include, ::CouchRest::Mixins::AttributeProtection)
|
||||
base.send(:include, ::CouchRest::Mixins::Attributes)
|
||||
base.send(:include, ::CouchRest::Mixins::Callbacks)
|
||||
base.send(:include, ::CouchRest::Mixins::Properties)
|
||||
base.send(:include, ::CouchRest::Mixins::Assocations)
|
||||
base.send(:include, ::CouchRest::Mixins::Associations)
|
||||
base.send(:attr_accessor, :casted_by)
|
||||
end
|
||||
|
||||
def initialize(keys={})
|
||||
def initialize(keys = {})
|
||||
raise StandardError unless self.is_a? Hash
|
||||
apply_all_property_defaults # defined in CouchRest::Mixins::Properties
|
||||
prepare_all_attributes(keys)
|
||||
super()
|
||||
keys.each do |k,v|
|
||||
write_attribute(k.to_s, v)
|
||||
end if keys
|
||||
end
|
||||
|
||||
def []= key, value
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
module CouchRest
|
||||
|
||||
|
||||
module Mixins
|
||||
module Associations
|
||||
|
||||
|
@ -10,10 +12,13 @@ module CouchRest
|
|||
|
||||
module ClassMethods
|
||||
|
||||
# Define an association that this object belongs to.
|
||||
#
|
||||
def belongs_to(attrib, *options)
|
||||
opts = {
|
||||
:foreign_key => attrib.to_s + '_id',
|
||||
:class_name => attrib.to_s.camelcase
|
||||
:class_name => attrib.to_s.camelcase,
|
||||
:proxy => nil
|
||||
}
|
||||
case options.first
|
||||
when Hash
|
||||
|
@ -23,10 +28,10 @@ module CouchRest
|
|||
begin
|
||||
opts[:class] = opts[:class_name].constantize
|
||||
rescue
|
||||
raise "Unable to convert belongs_to class name into Constant for #{self.name}##{attrib}"
|
||||
raise "Unable to convert class name into Constant for #{self.name}##{attrib}"
|
||||
end
|
||||
|
||||
prop = property(opts[:foreign_key])
|
||||
prop = property(opts[:foreign_key], opts)
|
||||
|
||||
create_belongs_to_getter(attrib, prop, opts)
|
||||
create_belongs_to_setter(attrib, prop, opts)
|
||||
|
@ -34,10 +39,76 @@ module CouchRest
|
|||
prop
|
||||
end
|
||||
|
||||
# Provide access to a collection of objects where the associated
|
||||
# property contains a list of the collection item ids.
|
||||
#
|
||||
# The following:
|
||||
#
|
||||
# collection_of :groups
|
||||
#
|
||||
# creates a pseudo property called "groups" which allows access
|
||||
# to a CollectionProxy object. Adding, replacing or removing entries in this
|
||||
# proxy will cause the matching property array, in this case "group_ids", to
|
||||
# be kept in sync.
|
||||
#
|
||||
# Any manual changes made to the collection ids property (group_ids), unless replaced, will require
|
||||
# a reload of the CollectionProxy for the two sets of data to be in sync:
|
||||
#
|
||||
# group_ids = ['123']
|
||||
# groups == [Group.get('123')]
|
||||
# group_ids << '321'
|
||||
# groups == [Group.get('123')]
|
||||
# groups(true) == [Group.get('123'), Group.get('321')]
|
||||
#
|
||||
# Of course, saving the parent record will store the collection ids as they are
|
||||
# found.
|
||||
#
|
||||
# The CollectionProxy supports the following array functions, anything else will cause
|
||||
# a mismatch between the collection objects and collection ids:
|
||||
#
|
||||
# groups << obj
|
||||
# groups.push obj
|
||||
# groups.unshift obj
|
||||
# groups[0] = obj
|
||||
# groups.pop == obj
|
||||
# groups.shift == obj
|
||||
#
|
||||
# Addtional options match those of the the belongs_to method.
|
||||
#
|
||||
def collection_of(attrib, *options)
|
||||
opts = {
|
||||
:foreign_key => attrib.to_s.singularize + '_ids',
|
||||
:class_name => attrib.to_s.singularize.camelcase,
|
||||
:proxy => nil
|
||||
}
|
||||
case options.first
|
||||
when Hash
|
||||
opts.merge!(options.first)
|
||||
end
|
||||
begin
|
||||
opts[:class] = opts[:class_name].constantize
|
||||
rescue
|
||||
raise "Unable to convert class name into Constant for #{self.name}##{attrib}"
|
||||
end
|
||||
opts[:readonly] = true
|
||||
|
||||
prop = property(opts[:foreign_key], [], opts)
|
||||
|
||||
create_collection_of_property_setter(attrib, prop, opts)
|
||||
create_collection_of_getter(attrib, prop, opts)
|
||||
create_collection_of_setter(attrib, prop, opts)
|
||||
|
||||
prop
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def create_belongs_to_getter(attrib, property, options)
|
||||
base = options[:proxy] || options[:class_name]
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def #{attrib}
|
||||
@#{attrib} ||= #{options[:class_name]}.get(self.#{options[:foreign_key]})
|
||||
@#{attrib} ||= #{base}.get(self.#{options[:foreign_key]})
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
@ -45,14 +116,92 @@ module CouchRest
|
|||
def create_belongs_to_setter(attrib, property, options)
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def #{attrib}=(value)
|
||||
@#{attrib} = value
|
||||
self.#{options[:foreign_key]} = value.nil? ? nil : value.id
|
||||
@#{attrib} = value
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
### collection_of support methods
|
||||
|
||||
def create_collection_of_property_setter(attrib, property, options)
|
||||
# ensure CollectionProxy is nil, ready to be reloaded on request
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def #{options[:foreign_key]}=(value)
|
||||
@#{attrib} = nil
|
||||
write_attribute("#{options[:foreign_key]}", value)
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
def create_collection_of_getter(attrib, property, options)
|
||||
base = options[:proxy] || options[:class_name]
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def #{attrib}(reload = false)
|
||||
return @#{attrib} unless @#{attrib}.nil? or reload
|
||||
ary = self.#{options[:foreign_key]}.collect{|i| #{base}.get(i)}
|
||||
@#{attrib} = ::CouchRest::CollectionProxy.new(ary, self, '#{options[:foreign_key]}')
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
def create_collection_of_setter(attrib, property, options)
|
||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||
def #{attrib}=(value)
|
||||
@#{attrib} = ::CouchRest::CollectionProxy.new(value, self, '#{options[:foreign_key]}')
|
||||
end
|
||||
EOS
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
# Special proxy for a collection of items so that adding and removing
|
||||
# to the list automatically updates the associated property.
|
||||
class CollectionProxy < Array
|
||||
attr_accessor :property
|
||||
attr_accessor :casted_by
|
||||
|
||||
def initialize(array, casted_by, property)
|
||||
self.property = property
|
||||
self.casted_by = casted_by
|
||||
array ||= []
|
||||
casted_by[property.to_s] = [] # replace the original array!
|
||||
array.each do |obj|
|
||||
casted_by[property.to_s] << obj.id
|
||||
end
|
||||
super(array)
|
||||
end
|
||||
def << obj
|
||||
casted_by[property.to_s] << obj.id
|
||||
super(obj)
|
||||
end
|
||||
def push(obj)
|
||||
casted_by[property.to_s].push obj.id
|
||||
super(obj)
|
||||
end
|
||||
def unshift(obj)
|
||||
casted_by[property.to_s].unshift obj.id
|
||||
super(obj)
|
||||
end
|
||||
|
||||
def []= index, obj
|
||||
casted_by[property.to_s][index] = obj.id
|
||||
super(index, obj)
|
||||
end
|
||||
|
||||
def pop
|
||||
casted_by[property.to_s].pop
|
||||
super
|
||||
end
|
||||
def shift
|
||||
casted_by[property.to_s].shift
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue