correlate 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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