Skip to content

Commit

Permalink
'some' relationships from ActiveRecord to CouchDB implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
kennethkalmer committed Dec 12, 2009
1 parent d3c8870 commit 76614fe
Show file tree
Hide file tree
Showing 11 changed files with 350 additions and 85 deletions.
81 changes: 73 additions & 8 deletions lib/correlate/correlation.rb
@@ -1,15 +1,41 @@
module Correlate
class Correlation
attr_accessor :name, :type, :klass, :rel, :id_method, :requires
# Class owning the correlation
attr_accessor :source

# Name of the correlation
attr_accessor :name

# Type of relationship
attr_accessor :type

# Class the relationship targets
attr_accessor :target

# +rel+ attribute used to distinguish this correlation
attr_accessor :rel

# Method used to extract the 'id' from instances add to the correlation (defaults to +id+)
attr_accessor :id_method

# For +some+ relationships specify how many instances are required
attr_accessor :requires

# Class method on the target class used when loading single instances
# (defaults to +get+ for CouchRest docs, and +find+ for ActiveRecord models)
attr_accessor :load_via

# Name of a view used to load documents from ActiveRecord's side
attr_accessor :view

def matches?( obj )
case obj
when CouchRest::ExtendedDocument
klass == obj['couchrest-type']
target == obj['couchrest-type']
when Hash
obj['rel'] == rel
else
klass == obj.class.name
target == obj.class.name
end
end

Expand All @@ -21,16 +47,55 @@ def id_method
@id_method || :id
end

def correlate( obj )
classify.get( obj['href'] )
def correlate( *objects )
if direction == { :active_record => :couchdb }

raise ArgumentError, "#{target} doesn't correlate with #{source}" unless bidirectional?

if objects.size == 1
obj = objects.shift

return target_class.by_rel :key => [ opposite.rel, obj.send( opposite.id_method ) ]
end
end

if objects.size == 1
obj = objects.pop
return target_class.send( load_via, obj['href'] )
end
end

def bidirectional?
target_class.included_modules.include?( Correlate ) && !opposite.nil?
end

def opposite
target_class.correlations.detect { |c| c.target == source.name }
end

def direction
f = source_class.ancestors.include?( CouchRest::ExtendedDocument ) ? :couchdb : :active_record
t = target_class.ancestors.include?( CouchRest::ExtendedDocument ) ? :couchdb : :active_record

{ f => t }
end

private

def classify
raise "Class #{klass} not found" if !Object.const_defined?( klass.to_sym )
def load_via
@load_via ||= (
target_class.ancestors.include?( CouchRest::ExtendedDocument ) ? :get : :find
)
end

def target_class
raise "Class #{target} not found" if !Object.const_defined?( target.to_sym )

Object.const_get( target.to_sym )
end

Object.const_get( klass.to_sym )
def source_class
source
end

end
Expand Down
96 changes: 22 additions & 74 deletions lib/correlate/relationships.rb
@@ -1,86 +1,34 @@
module Correlate
class Relationships
module Relationships

autoload :CouchRest, 'correlate/relationships/couchrest'
autoload :ActiveRecord, 'correlate/relationships/active_record'

class << self

def configure!( klass, &block )

# Add our property
klass.property :links, :type => 'Correlate::Links', :default => Correlate::Links.new( klass )

# Setup our conveniences
relationships = new( klass )
relationships.instance_eval( &block )
relationships.build_validators
end

end

def initialize( klass )
@klass = klass
end

def some( *args )
name = args.shift
opts = args.empty? ? {} : args.last

@klass.correlations << build_correlation( name, :some, opts )

@klass.class_eval <<-EOF, __FILE__, __LINE__
def #{name}( raw = false )
correlations = self.links.rel( '#{name}' )
correlations.map! do |c|
self.links.correlation_for_object( c ).correlate( c )
end unless raw
correlations
if klass.ancestors.include?( ::CouchRest::ExtendedDocument )
Correlate::Relationships::CouchRest.configure! klass, &block
else
if defined?( ::ActiveRecord ) && klass.ancestors.include?( ::ActiveRecord::Base )
Correlate::Relationships::ActiveRecord.configure! klass, &block
end
end
EOF
end

def a( *args )
name = args.shift
opts = args.empty? ? {} : args.last

@klass.correlations << build_correlation( name, :a, opts )

@klass.class_eval <<-EOF, __FILE__, __LINE__
def #{name}=( object )
self.links.replace( object )
end
def #{name}( raw = false )
correlation = self.links.rel( '#{name}' ).first
return if correlation.nil?
correlation = self.links.correlation_for_object( correlation ).correlate( correlation ) unless raw
correlation
end
EOF
end

def build_validators
if @klass.included_modules.include?( CouchRest::Validation )
fields = [ :links ]
opts = @klass.opts_from_validator_args( fields )
@klass.add_validator_to_context( opts, fields, Correlate::Validator )
end
end

protected

def build_correlation( name, type, opts )
correlation = Correlation.new
correlation.name = name
correlation.type = type
correlation.klass = opts[:class]
correlation.rel = opts[:rel]
correlation.id_method = opts[:id_method]
correlation.requires = opts[:requires]
def build_correlation( name, type, opts )
correlation = Correlation.new
correlation.name = name
correlation.type = type
correlation.target = opts[:class]
correlation.source = opts[:source]
correlation.rel = opts[:rel]
correlation.id_method = opts[:id_method]
correlation.requires = opts[:requires]

correlation
end

correlation
end

end
Expand Down
64 changes: 64 additions & 0 deletions lib/correlate/relationships/active_record.rb
@@ -0,0 +1,64 @@
module Correlate
module Relationships
class ActiveRecord

autoload :CollectionProxy, 'correlate/relationships/active_record/collection_proxy'

class << self

def configure!( klass, &block )

# Setup our conveniences
relationships = new( klass )
relationships.instance_eval( &block )
end

end

def initialize( klass )
@klass = klass
end

def some( *args )
name = args.shift
opts = args.empty? ? {} : args.last
opts[:source] = @klass

correlation = Correlate::Relationships.build_correlation( name, :some, opts )
@klass.correlations << correlation

@klass.class_eval <<-EOF, __FILE__, __LINE__
def #{name}( raw = false )
local_correlation = self.class.correlations.detect { |c| c.name == :#{name} }
Correlate::Relationships::ActiveRecord::CollectionProxy.new self, local_correlation
end
EOF
end

def a( *args )
name = args.shift
opts = args.empty? ? {} : args.last
opts[:source] = @klass

@klass.correlations << Correlate::Relationships.build_correlation( name, :a, opts )

@klass.class_eval <<-EOF, __FILE__, __LINE__
def #{name}=( object )
self.links.replace( object )
end
def #{name}( raw = false )
correlation = self.links.rel( '#{name}' ).first
return if correlation.nil?
correlation = self.links.correlation_for_object( correlation ).correlate( correlation ) unless raw
correlation
end
EOF
end

end
end
end
29 changes: 29 additions & 0 deletions lib/correlate/relationships/active_record/collection_proxy.rb
@@ -0,0 +1,29 @@
module Correlate
module Relationships
class ActiveRecord
class CollectionProxy

instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }

def initialize( source, correlation )
@source = source
@correlation = correlation
@collection = correlation.correlate( source )
end

def <<( object )
correlation = @correlation.opposite

object.send( "#{correlation.name}=", @source )
object.save
end

protected

def method_missing( name, *args, &block )
@collection.send( name, *args, &block )
end
end
end
end
end

0 comments on commit 76614fe

Please sign in to comment.