correlate 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,89 @@
1
+ module Correlate
2
+ # Thin proxy around the array of links.
3
+ class Links
4
+
5
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }
6
+
7
+ def initialize( klass, array = [] )
8
+ @klass = klass
9
+ @target_array = array
10
+ end
11
+
12
+ # Extract all the matching links for rel.
13
+ #
14
+ # @param [String] rel
15
+ # @return [ Correlate::Links ] for matching links
16
+ def rel( rel )
17
+ clone( @target_array.select { |link| link['rel'] == rel } )
18
+ end
19
+
20
+ # Add an object to the list
21
+ def <<( obj )
22
+ write_target.push({ 'rel' => rel_for_object( obj ), 'href' => id_for_object( obj ) })
23
+ end
24
+
25
+ alias :push :<<
26
+ alias :concat :<<
27
+
28
+ # Replace a matching +rel+ with this object
29
+ def replace( obj )
30
+ delete( obj )
31
+
32
+ self.<< obj
33
+ end
34
+
35
+ # Delete this object from the list
36
+ def delete( obj )
37
+ rel = rel_for_object( obj )
38
+
39
+ write_target.reject! { |l| l['rel'] == rel }
40
+ end
41
+
42
+ def __debug__
43
+ {
44
+ :object_id => object_id,
45
+ :klass => @klass,
46
+ :target_array => @target_array,
47
+ :original_copy => @original_copy,
48
+ :original_copy_object_id => @original_copy.object_id
49
+ }
50
+ end
51
+
52
+ protected
53
+
54
+ def clone( array )
55
+ Links.new( @klass, array ).subset_of( @original_copy || self )
56
+ end
57
+
58
+ def subset_of( links_instance )
59
+ @original_copy = links_instance
60
+ end
61
+
62
+ def rel_for_object( obj )
63
+ c = @klass.correlation_for( obj )
64
+
65
+ if c.nil? && obj.instance_of?( Hash )
66
+ obj['rel']
67
+ else
68
+ c.rel
69
+ end
70
+ end
71
+
72
+ def id_for_object( obj )
73
+ c = @klass.correlation_for( obj )
74
+ if c.nil? && obj.instance_of?( Hash )
75
+ obj['href']
76
+ else
77
+ obj.send( c.id_method )
78
+ end
79
+ end
80
+
81
+ def write_target
82
+ @original_copy || @target_array
83
+ end
84
+
85
+ def method_missing( name, *args, &block )
86
+ @target_array.send( name, *args, &block )
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,36 @@
1
+ module Correlate
2
+ module Relationships
3
+
4
+ autoload :CouchRest, 'correlate/relationships/couchrest'
5
+ autoload :ActiveRecord, 'correlate/relationships/active_record'
6
+
7
+ class << self
8
+
9
+ def configure!( klass, &block )
10
+ if klass.ancestors.include?( ::CouchRest::ExtendedDocument )
11
+ Correlate::Relationships::CouchRest.configure! klass, &block
12
+ else
13
+ if defined?( ::ActiveRecord ) && klass.ancestors.include?( ::ActiveRecord::Base )
14
+ Correlate::Relationships::ActiveRecord.configure! klass, &block
15
+ end
16
+ end
17
+ end
18
+
19
+ def build_correlation( name, type, opts )
20
+ correlation = Correlation.new
21
+ correlation.name = name
22
+ correlation.type = type
23
+ correlation.target = opts[:class]
24
+ correlation.source = opts[:source]
25
+ correlation.rel = opts[:rel]
26
+ correlation.id_method = opts[:id_method]
27
+ correlation.requires = opts[:requires]
28
+ correlation.required = opts[:required]
29
+
30
+ correlation
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
@@ -0,0 +1,146 @@
1
+ module Correlate
2
+ module Relationships
3
+
4
+ # Used to define relationships between ActiveRecord::Base models and
5
+ # CouchRest::ExtendedDocument classes.
6
+ #
7
+ # == Important note
8
+ #
9
+ # Unlike normal correlations, when correlating an ActiveRecord model with
10
+ # a CouchRest document, the 'reverse correlation' needs to be specified in
11
+ # in the CouchRest model.
12
+ #
13
+ # @see #a
14
+ # @see #some
15
+ class ActiveRecord
16
+
17
+ autoload :CollectionProxy, 'correlate/relationships/active_record/collection_proxy'
18
+
19
+ class << self
20
+
21
+ def configure!( klass, &block )
22
+
23
+ # Setup our conveniences
24
+ relationships = new( klass )
25
+ relationships.instance_eval( &block )
26
+ end
27
+
28
+ end
29
+
30
+ def initialize( klass )
31
+ @klass = klass
32
+ end
33
+
34
+ # Specify that this model will have multiple documents of said relationship.
35
+ # This is akin to ActiveRecord's +has_many associations+
36
+ #
37
+ # @example Defining correlations and using them
38
+ # class MyModel < ActiveRecord::Base
39
+ # include Correlate
40
+ #
41
+ # related_to do
42
+ # some :other_documents, :class => 'OtherDocument', :rel => 'my_model'
43
+ # end
44
+ # end
45
+ #
46
+ # class OtherDocument < CouchRest::ExtendedDocument
47
+ # include Correlate
48
+ #
49
+ # related_to do
50
+ # a :my_model, :class => 'MyModel'
51
+ # end
52
+ # end
53
+ #
54
+ # doc = OtherDocument.new
55
+ # doc.my_model # => nil
56
+ #
57
+ # me = MyModel.find( 1 )
58
+ # doc.my_model = me
59
+ #
60
+ # doc.me_model # => <MyModel#12121212>
61
+ #
62
+ # me.other_documents # => [ <OtherDocument#121212121> ]
63
+ #
64
+ # @param [Symbol] name of the relationship
65
+ # @param [Hash] options for the relationship
66
+ # @option options [String] :class the class of the related document/model
67
+ # @option options [String] :rel the value of the +rel+ key in the related hash
68
+ # @option options [Symbol] :id_method (:id) name of a method use to retrieve the 'foreign key' value from documents added to the relationship
69
+ # @option options [Symbol] :load_via (:get) name of the class method used to retreive related documents
70
+ def some( *args )
71
+ name = args.shift
72
+ opts = args.empty? ? {} : args.last
73
+ opts[:source] = @klass
74
+
75
+ correlation = Correlate::Relationships.build_correlation( name, :some, opts )
76
+ @klass.correlations << correlation
77
+
78
+ @klass.class_eval <<-EOF, __FILE__, __LINE__
79
+ def #{name}( raw = false )
80
+ local_correlation = self.class.correlations.detect { |c| c.name == :#{name} }
81
+
82
+ Correlate::Relationships::ActiveRecord::CollectionProxy.new self, local_correlation
83
+ end
84
+ EOF
85
+ end
86
+
87
+ # Specify that this model will have a single document of said relationship.
88
+ # This is akin to ActiveRecord's +belongs_to or has_one associations+.
89
+ #
90
+ # @example Defining correlations and using them
91
+ # class MyModel < ActiveRecord::Base
92
+ # include Correlate
93
+ #
94
+ # related_to do
95
+ # a :journal, :class => 'Journal', :rel => 'my_model'
96
+ # end
97
+ # end
98
+ #
99
+ # class Journal < CouchRest::ExtendedDocument
100
+ # include Correlate
101
+ #
102
+ # related_to do
103
+ # some :my_models, :class => 'MyModel', :rel => 'my_model'
104
+ # end
105
+ # end
106
+ #
107
+ # doc = Journal.new
108
+ # doc.my_models # => []
109
+ #
110
+ # me = MyModel.find( 1 )
111
+ # doc.my_models << me
112
+ #
113
+ # doc.my_models # => [ <MyModel#12121212> ]
114
+ #
115
+ # me.journal # => <Journal#121212121>
116
+ #
117
+ # @param [Symbol] name of the relationship
118
+ # @param [Hash] options for the relationship
119
+ # @option options [String] :class the class of the related document/model
120
+ # @option options [String] :rel (name) the value of the +rel+ key in the related hash
121
+ # @option options [Symbol] :id_method (:id) name of a method use to retrieve the 'foreign key' value from documents added to the relationship
122
+ # @option options [Symbol] :load_via (:get) name of the class method used to retreive related documents
123
+ def a( name, options = {} )
124
+ options[:source] = @klass
125
+
126
+ @klass.correlations << Correlate::Relationships.build_correlation( name, :a, options )
127
+
128
+ @klass.class_eval <<-EOF, __FILE__, __LINE__
129
+ def #{name}=( object )
130
+ self.links.replace( object )
131
+ end
132
+
133
+ def #{name}( raw = false )
134
+ correlation = self.links.rel( '#{name}' ).first
135
+ return if correlation.nil?
136
+
137
+ correlation = self.class.correlation_for( correlation ).correlate( correlation ) unless raw
138
+
139
+ correlation
140
+ end
141
+ EOF
142
+ end
143
+
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,32 @@
1
+ module Correlate
2
+ module Relationships
3
+ class ActiveRecord
4
+ class CollectionProxy
5
+
6
+ instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$)/ }
7
+
8
+ def initialize( source, correlation )
9
+ @source = source
10
+ @correlation = correlation
11
+ @collection = correlation.correlate( source )
12
+ end
13
+
14
+ def <<( object )
15
+ correlation = @correlation.opposite
16
+
17
+ object.send( "#{correlation.name}=", @source )
18
+ object.save
19
+ end
20
+
21
+ alias :push :<<
22
+ alias :concat :<<
23
+
24
+ protected
25
+
26
+ def method_missing( name, *args, &block )
27
+ @collection.send( name, *args, &block )
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,171 @@
1
+ module Correlate
2
+ module Relationships
3
+
4
+ # Configure relationships between CouchRest::ExtendedDocument classes, as
5
+ # well as between CouchRest::ExtendedDocument & ActiveRecord::Base classes.
6
+ #
7
+ # Using this correlation in a CouchRest::ExtendedDocument creates a +links+
8
+ # property that is used for storing the correlations between this document
9
+ # and others.
10
+ #
11
+ # It also creates a +rel+ view for the class that emits two keys: +rel+ &
12
+ # +href+, which is used for bulk lookups and loading of documents.
13
+ #
14
+ # == Notes
15
+ #
16
+ # To use the validations provided by Correlate you need to include
17
+ # +CouchRest::Validation+ into your classes.
18
+ #
19
+ # @see #a
20
+ # @see #some
21
+ class CouchRest
22
+
23
+ class << self
24
+
25
+ def configure!( klass, &block )
26
+
27
+ # Add our property
28
+ klass.property :links, :type => 'Correlate::Links', :default => Correlate::Links.new( klass )
29
+
30
+ # Setup our conveniences
31
+ relationships = new( klass )
32
+ relationships.instance_eval( &block )
33
+ relationships.build_validators
34
+ relationships.build_views
35
+
36
+ # Make sure our links array is properly casted
37
+ klass.class_eval <<-EOF, __FILE__, __LINE__
38
+ def links=( array )
39
+ self[:links] = Correlate::Links.new( self.class, array )
40
+ end
41
+ EOF
42
+ end
43
+
44
+ end
45
+
46
+ def initialize( klass )
47
+ @klass = klass
48
+ end
49
+
50
+ # Specify that this document will have multiple documents of said relationship.
51
+ #
52
+ # @example Defining correlations and using them
53
+ # class SomeDocument < CouchRest::ExtendedDocument
54
+ # include Correlate
55
+ #
56
+ # related_to do
57
+ # some :other_documents, :class => 'OtherDocument, :rel => 'other_document'
58
+ # end
59
+ # end
60
+ #
61
+ # doc = SomeDocument.new
62
+ # doc.other_documents # => []
63
+ #
64
+ # other_doc = OtherDocument.get( '885387e892e63f4b6d31dbc877533099' )
65
+ # doc.other_documents << other_doc
66
+ #
67
+ # doc.other_documents # => [ <OtherDocument#12121212> ]
68
+ #
69
+ # @param [Symbol] name of the relationship
70
+ # @param [Hash] options for the relationship
71
+ # @option options [String] :class the class of the related document/model
72
+ # @option options [String] :rel the value of the +rel+ key in the related hash
73
+ # @option options [Fixnum] :requires (nil) a number of documents required
74
+ # @option options [Symbol] :id_method (:id) name of a method use to retrieve the 'foreign key' value from documents added to the relationship
75
+ # @option options [Symbol] :load_via (:get/:find) name of the class method used to retreive related documents (defaults to :get for CouchRest::ExtendedDocument, :find for ActiveRecord::Base)
76
+ def some( name, options = {} )
77
+ options[:source] = @klass
78
+
79
+ @klass.correlations << Correlate::Relationships.build_correlation( name, :some, options )
80
+
81
+ @klass.class_eval <<-EOF, __FILE__, __LINE__
82
+ def #{name}( raw = false )
83
+ correlations = self.links.rel( '#{name}' )
84
+
85
+ #correlations.map! do |c|
86
+ # self.links.correlation_for_object( c ).correlate( c )
87
+ #end unless raw
88
+ unless raw
89
+ c = self.class.correlation_for( correlations.first )
90
+ correlations = c.bulk_correlate( correlations ) unless c.nil?
91
+ end
92
+
93
+ correlations
94
+ end
95
+ EOF
96
+ end
97
+
98
+ # Specify that this document will have a single document of said relationship.
99
+ #
100
+ # @example Defining correlations and using them
101
+ # class OtherDocument < CouchRest::ExtendedDocument
102
+ # include Correlate
103
+ #
104
+ # related_to do
105
+ # a :some_document, :class => 'SomeDocument'
106
+ # end
107
+ # end
108
+ #
109
+ # doc = OtherDocument.new
110
+ # doc.some_document # => nil
111
+ #
112
+ # some_doc = SomeDocument.get( '885387e892e63f4b6d31dbc877533099' )
113
+ # doc.some_document = some_doc
114
+ #
115
+ # doc.some_document # => [ <SomeDocument#12121212> ]
116
+ #
117
+ # @param [Symbol] name of the relationship
118
+ # @param [Hash] options for the relationship
119
+ # @option options [String] :class the class of the related document/model
120
+ # @option options [String] :rel (name) the value of the +rel+ key in the related hash
121
+ # @option options [Fixnum] :required (false) whether required or not
122
+ # @option options [Symbol] :id_method (:id) name of a method use to retrieve the 'foreign key' value from documents added to the relationship
123
+ # @option options [Symbol] :load_via (:get/:find) name of the class method used to retreive related documents (defaults to :get for CouchRest::ExtendedDocument, :find for ActiveRecord::Base)
124
+ def a( name, options = {} )
125
+ options[:source] = @klass
126
+
127
+ @klass.correlations << Correlate::Relationships.build_correlation( name, :a, options )
128
+
129
+ @klass.class_eval <<-EOF, __FILE__, __LINE__
130
+ def #{name}=( object )
131
+ self.links.replace( object )
132
+ end
133
+
134
+ def #{name}( raw = false )
135
+ correlation = self.links.rel( '#{name}' ).first
136
+ return if correlation.nil?
137
+
138
+ correlation = self.class.correlation_for( correlation ).correlate( correlation ) unless raw
139
+
140
+ correlation
141
+ end
142
+ EOF
143
+ end
144
+
145
+ # @private
146
+ def build_validators
147
+ if @klass.included_modules.include?( ::CouchRest::Validation )
148
+
149
+ fields = [ :links ]
150
+ opts = @klass.opts_from_validator_args( fields )
151
+ @klass.add_validator_to_context( opts, fields, Correlate::Validator )
152
+ end
153
+ end
154
+
155
+ # @private
156
+ def build_views
157
+ @klass.view_by :rel,
158
+ :map => <<-MAP
159
+ function( doc ) {
160
+ if( doc['couchrest-type'] == '#{@klass}' ) {
161
+ doc.links.forEach(function(link) {
162
+ emit([ link.rel, link.href ], 1);
163
+ });
164
+ }
165
+ }
166
+ MAP
167
+ end
168
+
169
+ end
170
+ end
171
+ end