working on proxy association and handling
This commit is contained in:
parent
363461fc9d
commit
8fa7e87019
|
@ -12,22 +12,32 @@ module CouchRest
|
||||||
|
|
||||||
# Define an association that this object belongs to.
|
# Define an association that this object belongs to.
|
||||||
#
|
#
|
||||||
|
# An attribute will be created matching the name of the attribute
|
||||||
|
# with '_id' on the end, or the foreign key (:foreign_key) provided.
|
||||||
|
#
|
||||||
|
# Searching for the assocated object is performed using a string
|
||||||
|
# (:proxy) to be evaulated in the context of the owner. Typically
|
||||||
|
# this will be set to the class name (:class_name), or determined
|
||||||
|
# automatically if the owner belongs to a proxy object.
|
||||||
|
#
|
||||||
|
# If the association owner is proxied by another model, than an attempt will
|
||||||
|
# be made to automatically determine the correct place to request
|
||||||
|
# the documents. Typically, this is a method with the pluralized name of the
|
||||||
|
# association inside owner's owner, or proxy.
|
||||||
|
#
|
||||||
|
# For example, imagine a company acts as a proxy for invoices and clients.
|
||||||
|
# If an invoice belongs to a client, the invoice will need to access the
|
||||||
|
# list of clients via the proxy. So a request to search for the associated
|
||||||
|
# client from an invoice would look like:
|
||||||
|
#
|
||||||
|
# self.company.clients
|
||||||
|
#
|
||||||
|
# If the name of the collection proxy is not the pluralized assocation name,
|
||||||
|
# it can be set with the :proxy_name option.
|
||||||
|
#
|
||||||
def belongs_to(attrib, *options)
|
def belongs_to(attrib, *options)
|
||||||
opts = {
|
|
||||||
:foreign_key => attrib.to_s + '_id',
|
|
||||||
:class_name => attrib.to_s.camelcase,
|
|
||||||
:proxy => nil
|
|
||||||
}
|
|
||||||
case options.first
|
|
||||||
when Hash
|
|
||||||
opts.merge!(options.first)
|
|
||||||
end
|
|
||||||
|
|
||||||
begin
|
opts = merge_belongs_to_assocation_options(attrib, options.first)
|
||||||
opts[:class] = opts[:class_name].constantize
|
|
||||||
rescue NameError
|
|
||||||
raise NameError, "Unable to convert class name into Constant for #{self.name}##{attrib}"
|
|
||||||
end
|
|
||||||
|
|
||||||
prop = property(opts[:foreign_key], opts)
|
prop = property(opts[:foreign_key], opts)
|
||||||
|
|
||||||
|
@ -77,20 +87,9 @@ module CouchRest
|
||||||
# frequently! Use with prudence.
|
# frequently! Use with prudence.
|
||||||
#
|
#
|
||||||
def collection_of(attrib, *options)
|
def collection_of(attrib, *options)
|
||||||
opts = {
|
|
||||||
:foreign_key => attrib.to_s.singularize + '_ids',
|
opts = merge_belongs_to_assocation_options(attrib, options.first)
|
||||||
:class_name => attrib.to_s.singularize.camelcase,
|
opts[:foreign_key] = opts[:foreign_key].pluralize
|
||||||
: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
|
opts[:readonly] = true
|
||||||
|
|
||||||
prop = property(opts[:foreign_key], [], opts)
|
prop = property(opts[:foreign_key], [], opts)
|
||||||
|
@ -105,11 +104,42 @@ module CouchRest
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def merge_belongs_to_assocation_options(attrib, options = nil)
|
||||||
|
opts = {
|
||||||
|
:foreign_key => attrib.to_s + '_id',
|
||||||
|
:class_name => attrib.to_s.camelcase,
|
||||||
|
:proxy_name => attrib.to_s.pluralize
|
||||||
|
}
|
||||||
|
|
||||||
|
# merge the options
|
||||||
|
case options
|
||||||
|
when Hash
|
||||||
|
opts.merge!(options)
|
||||||
|
end
|
||||||
|
# Get a class name
|
||||||
|
begin
|
||||||
|
opts[:class] = opts[:class_name].constantize
|
||||||
|
rescue NameError
|
||||||
|
raise NameError, "Unable to convert class name into Constant for #{self.name}##{attrib}"
|
||||||
|
end
|
||||||
|
|
||||||
|
# Generate a string for the proxy method call
|
||||||
|
# Assumes that the proxy_owner_method from "proxyable" is available.
|
||||||
|
if opts[:proxy].to_s.empty?
|
||||||
|
opts[:proxy] = if proxy_owner_method
|
||||||
|
"self.#{proxy_owner_method}.#{opts[:proxy_name]}"
|
||||||
|
else
|
||||||
|
opts[:class_name].constantize
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
opts
|
||||||
|
end
|
||||||
|
|
||||||
def create_belongs_to_getter(attrib, property, options)
|
def create_belongs_to_getter(attrib, property, options)
|
||||||
base = options[:proxy] || options[:class_name]
|
|
||||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||||
def #{attrib}
|
def #{attrib}
|
||||||
@#{attrib} ||= #{options[:foreign_key]}.nil? ? nil : (model_proxy || #{base}).get(self.#{options[:foreign_key]})
|
@#{attrib} ||= #{options[:foreign_key]}.nil? ? nil : #{options[:proxy]}.get(self.#{options[:foreign_key]})
|
||||||
end
|
end
|
||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
|
@ -140,7 +170,7 @@ module CouchRest
|
||||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||||
def #{attrib}(reload = false)
|
def #{attrib}(reload = false)
|
||||||
return @#{attrib} unless @#{attrib}.nil? or reload
|
return @#{attrib} unless @#{attrib}.nil? or reload
|
||||||
ary = self.#{options[:foreign_key]}.collect{|i| (model_proxy || #{base}).get(i)}
|
ary = self.#{options[:foreign_key]}.collect{|i| #{base}.get(i)}
|
||||||
@#{attrib} = ::CouchRest::CollectionOfProxy.new(ary, self, '#{options[:foreign_key]}')
|
@#{attrib} = ::CouchRest::CollectionOfProxy.new(ary, self, '#{options[:foreign_key]}')
|
||||||
end
|
end
|
||||||
EOS
|
EOS
|
||||||
|
|
|
@ -54,13 +54,6 @@ module CouchRest::Model
|
||||||
alias :to_key :id
|
alias :to_key :id
|
||||||
alias :to_param :id
|
alias :to_param :id
|
||||||
|
|
||||||
# Fixes belongs_to assocations.
|
|
||||||
# See it "should allow to reference association by id in casted model" in
|
|
||||||
# assocations_spec.rb
|
|
||||||
def model_proxy
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
|
|
||||||
# Sets the attributes from a hash
|
# Sets the attributes from a hash
|
||||||
def update_attributes_without_saving(hash)
|
def update_attributes_without_saving(hash)
|
||||||
hash.each do |k, v|
|
hash.each do |k, v|
|
||||||
|
|
|
@ -4,29 +4,29 @@ module CouchRest
|
||||||
module Proxyable
|
module Proxyable
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
|
||||||
attr_accessor :model_proxy
|
|
||||||
end
|
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
|
|
||||||
|
attr_reader :proxy_owner_method
|
||||||
|
|
||||||
# Define a collection that will use the base model for the database connection
|
# Define a collection that will use the base model for the database connection
|
||||||
# details.
|
# details.
|
||||||
def proxy_for(model_name, options = {})
|
def proxy_for(assoc_name, options = {})
|
||||||
db_method = options[:database_method] || "proxy_database"
|
db_method = options[:database_method] || "proxy_database"
|
||||||
options[:class_name] ||= model_name.to_s.singularize.camelize
|
options[:class_name] ||= assoc_name.to_s.singularize.camelize
|
||||||
|
attr_accessor :model_proxy
|
||||||
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
class_eval <<-EOS, __FILE__, __LINE__ + 1
|
||||||
def #{model_name}
|
def #{assoc_name}
|
||||||
unless respond_to?('#{db_method}')
|
unless respond_to?('#{db_method}')
|
||||||
raise "Missing ##{db_method} method for proxy"
|
raise "Missing ##{db_method} method for proxy"
|
||||||
end
|
end
|
||||||
@#{model_name} ||= CouchRest::Model::Proxyable::ModelProxy.new(::#{options[:class_name]}, self, self.class.to_s.underscore, #{db_method})
|
@#{assoc_name} ||= CouchRest::Model::Proxyable::ModelProxy.new(::#{options[:class_name]}, self, self.class.to_s.underscore, #{db_method})
|
||||||
end
|
end
|
||||||
EOS
|
EOS
|
||||||
end
|
end
|
||||||
|
|
||||||
def proxied_by(model_name, options = {})
|
def proxied_by(model_name, options = {})
|
||||||
raise "Model can only be proxied once or ##{model_name} already defined" if method_defined?(model_name)
|
raise "Model can only be proxied once or ##{model_name} already defined" if method_defined?(model_name) || !proxy_owner_method.nil?
|
||||||
|
self.proxy_owner_method = model_name
|
||||||
attr_accessor model_name
|
attr_accessor model_name
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -15,7 +15,8 @@ module CouchRest
|
||||||
|
|
||||||
def validate_each(document, attribute, value)
|
def validate_each(document, attribute, value)
|
||||||
view_name = options[:view].nil? ? "by_#{attribute}" : options[:view]
|
view_name = options[:view].nil? ? "by_#{attribute}" : options[:view]
|
||||||
model = document.model_proxy || @model
|
|
||||||
|
model = (respond_to?(:model_proxy) && model_proxy ? model_proxy : @model)
|
||||||
# Determine the base of the search
|
# Determine the base of the search
|
||||||
base = options[:proxy].nil? ? model : document.instance_eval(options[:proxy])
|
base = options[:proxy].nil? ? model : document.instance_eval(options[:proxy])
|
||||||
|
|
||||||
|
|
|
@ -64,22 +64,6 @@ describe "Assocations" do
|
||||||
@invoice.alternate_client.id.should eql(@client.id)
|
@invoice.alternate_client.id.should eql(@client.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should allow to reference association by id in casted model" do
|
|
||||||
class Money < Hash
|
|
||||||
include CouchRest::Model::CastedModel
|
|
||||||
|
|
||||||
property :value, Integer
|
|
||||||
belongs_to :client
|
|
||||||
end
|
|
||||||
|
|
||||||
money = Money.new :value => 1000
|
|
||||||
|
|
||||||
money.client_id = @client.id
|
|
||||||
|
|
||||||
money.client.should eql(@client)
|
|
||||||
money.valid?.should be_true
|
|
||||||
end
|
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "of type collection_of" do
|
describe "of type collection_of" do
|
||||||
|
|
Loading…
Reference in a new issue