dynamoid-edge 1.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,105 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ # The base association module which all associations include. Every association has two very important components: the source and
5
+ # the target. The source is the object which is calling the association information. It always has the target_ids inside of an attribute on itself.
6
+ # The target is the object which is referencing by this association.
7
+ module Associations
8
+ module Association
9
+ attr_accessor :name, :options, :source, :loaded
10
+
11
+ # Create a new association.
12
+ #
13
+ # @param [Class] source the source record of the association; that is, the record that you already have
14
+ # @param [Symbol] name the name of the association
15
+ # @param [Hash] options optional parameters for the association
16
+ # @option options [Class] :class the target class of the association; that is, the class to which the association objects belong
17
+ # @option options [Symbol] :class_name the name of the target class of the association; only this or Class is necessary
18
+ # @option options [Symbol] :inverse_of the name of the association on the target class
19
+ #
20
+ # @return [Dynamoid::Association] the actual association instance itself
21
+ #
22
+ # @since 0.2.0
23
+ def initialize(source, name, options)
24
+ @name = name
25
+ @options = options
26
+ @source = source
27
+ @loaded = false
28
+ end
29
+
30
+ def loaded?
31
+ @loaded
32
+ end
33
+
34
+ def find_target
35
+ end
36
+
37
+ def target
38
+ unless loaded?
39
+ @target = find_target
40
+ @loaded = true
41
+ end
42
+
43
+ @target
44
+ end
45
+
46
+ def reset
47
+ @target = nil
48
+ @loaded = false
49
+ end
50
+
51
+ private
52
+
53
+ # The target class name, either inferred through the association's name or specified in options.
54
+ #
55
+ # @since 0.2.0
56
+ def target_class_name
57
+ options[:class_name] || name.to_s.classify
58
+ end
59
+
60
+ # The target class, either inferred through the association's name or specified in options.
61
+ #
62
+ # @since 0.2.0
63
+ def target_class
64
+ options[:class] || target_class_name.constantize
65
+ end
66
+
67
+ # The target attribute: that is, the attribute on each object of the association that should reference the source.
68
+ #
69
+ # @since 0.2.0
70
+ def target_attribute
71
+ "#{target_association}_ids".to_sym if target_association
72
+ end
73
+
74
+ # The ids in the target association.
75
+ #
76
+ # @since 0.2.0
77
+ def target_ids
78
+ target.send(target_attribute) || Set.new
79
+ end
80
+
81
+ # The ids in the target association.
82
+ #
83
+ # @since 0.2.0
84
+ def source_class
85
+ source.class
86
+ end
87
+
88
+ # The source's association attribute: the name of the association with _ids afterwards, like "users_ids".
89
+ #
90
+ # @since 0.2.0
91
+ def source_attribute
92
+ "#{name}_ids".to_sym
93
+ end
94
+
95
+ # The ids in the source association.
96
+ #
97
+ # @since 0.2.0
98
+ def source_ids
99
+ source.send(source_attribute) || Set.new
100
+ end
101
+
102
+ end
103
+ end
104
+
105
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ # The belongs_to association. For belongs_to, we reference only a single target instead of multiple records; that target is the
5
+ # object to which the association object is associated.
6
+ module Associations
7
+ class BelongsTo
8
+ include SingleAssociation
9
+
10
+ private
11
+
12
+ # Find the target association, either has_many or has_one. Uses either options[:inverse_of] or the source class name and default parsing to
13
+ # return the most likely name for the target association.
14
+ #
15
+ # @since 0.2.0
16
+ def target_association
17
+ has_many_key_name = options[:inverse_of] || source.class.to_s.underscore.pluralize.to_sym
18
+ has_one_key_name = options[:inverse_of] || source.class.to_s.underscore.to_sym
19
+ if !target_class.associations[has_many_key_name].nil?
20
+ return has_many_key_name if target_class.associations[has_many_key_name][:type] == :has_many
21
+ end
22
+
23
+ if !target_class.associations[has_one_key_name].nil?
24
+ return has_one_key_name if target_class.associations[has_one_key_name][:type] == :has_one
25
+ end
26
+ end
27
+
28
+ # Associate a source object to this association.
29
+ #
30
+ # @since 0.2.0
31
+ def associate_target(object)
32
+ object.update_attribute(target_attribute, target_ids.merge(Array(source.id)))
33
+ end
34
+
35
+ # Disassociate a source object from this association.
36
+ #
37
+ # @since 0.2.0
38
+ def disassociate_target(object)
39
+ source.update_attribute(source_attribute, target_ids - Array(source.id))
40
+ end
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,40 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ # The has and belongs to many association.
5
+ module Associations
6
+ class HasAndBelongsToMany
7
+ include ManyAssociation
8
+
9
+ private
10
+
11
+ # Find the target association, always another :has_and_belongs_to_many association. Uses either options[:inverse_of] or the source class name
12
+ # and default parsing to return the most likely name for the target association.
13
+ #
14
+ # @since 0.2.0
15
+ def target_association
16
+ key_name = options[:inverse_of] || source.class.to_s.pluralize.underscore.to_sym
17
+ guess = target_class.associations[key_name]
18
+ return nil if guess.nil? || guess[:type] != :has_and_belongs_to_many
19
+ key_name
20
+ end
21
+
22
+ # Associate a source object to this association.
23
+ #
24
+ # @since 0.2.0
25
+ def associate_target(object)
26
+ ids = object.send(target_attribute) || Set.new
27
+ object.update_attribute(target_attribute, ids.merge(Array(source.id)))
28
+ end
29
+
30
+ # Disassociate a source object from this association.
31
+ #
32
+ # @since 0.2.0
33
+ def disassociate_target(object)
34
+ ids = object.send(target_attribute) || Set.new
35
+ object.update_attribute(target_attribute, ids - Array(source.id))
36
+ end
37
+ end
38
+ end
39
+
40
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ # The has_many association.
5
+ module Associations
6
+ class HasMany
7
+ include ManyAssociation
8
+
9
+ private
10
+
11
+ # Find the target association, always a :belongs_to association. Uses either options[:inverse_of] or the source class name
12
+ # and default parsing to return the most likely name for the target association.
13
+ #
14
+ # @since 0.2.0
15
+ def target_association
16
+ key_name = options[:inverse_of] || source.class.to_s.singularize.underscore.to_sym
17
+ guess = target_class.associations[key_name]
18
+ return nil if guess.nil? || guess[:type] != :belongs_to
19
+ key_name
20
+ end
21
+
22
+ # Associate a source object to this association.
23
+ #
24
+ # @since 0.2.0
25
+ def associate_target(object)
26
+ object.update_attribute(target_attribute, Set[source.id])
27
+ end
28
+
29
+ # Disassociate a source object from this association.
30
+ #
31
+ # @since 0.2.0
32
+ def disassociate_target(object)
33
+ object.update_attribute(target_attribute, nil)
34
+ end
35
+
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ # The HasOne association.
5
+ module Associations
6
+ class HasOne
7
+ include Association
8
+ include SingleAssociation
9
+
10
+ private
11
+
12
+ # Find the target association, always a :belongs_to association. Uses either options[:inverse_of] or the source class name
13
+ # and default parsing to return the most likely name for the target association.
14
+ #
15
+ # @since 0.2.0
16
+ def target_association
17
+ key_name = options[:inverse_of] || source.class.to_s.singularize.underscore.to_sym
18
+ guess = target_class.associations[key_name]
19
+ return nil if guess.nil? || guess[:type] != :belongs_to
20
+ key_name
21
+ end
22
+
23
+ # Associate a source object to this association.
24
+ #
25
+ # @since 0.2.0
26
+ def associate_target(object)
27
+ object.update_attribute(target_attribute, Set[source.id])
28
+ end
29
+
30
+ # Disassociate a source object from this association.
31
+ #
32
+ # @since 0.2.0
33
+ def disassociate_target(object)
34
+ source.update_attribute(source_attribute, nil)
35
+ end
36
+ end
37
+ end
38
+
39
+ end
@@ -0,0 +1,191 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ module Associations
5
+ module ManyAssociation
6
+ include Association
7
+
8
+ attr_accessor :query
9
+
10
+ def initialize(*args)
11
+ @query = {}
12
+ super
13
+ end
14
+
15
+ include Enumerable
16
+ # Delegate methods to the records the association represents.
17
+ delegate :first, :last, :empty?, :size, :class, :to => :records
18
+
19
+ # The records associated to the source.
20
+ #
21
+ # @return the association records; depending on which association this is, either a single instance or an array
22
+ #
23
+ # @since 0.2.0
24
+ def find_target
25
+ Array(target_class.find(source_ids.to_a))
26
+ end
27
+
28
+ def records
29
+ if query.empty?
30
+ target
31
+ else
32
+ results_with_query(target)
33
+ end
34
+ end
35
+
36
+ # Alias convenience methods for the associations.
37
+ alias :all :records
38
+ alias :count :size
39
+ alias :nil? :empty?
40
+
41
+ # Delegate include? to the records.
42
+ def include?(object)
43
+ records.include?(object)
44
+ end
45
+
46
+ # Deletes an object or array of objects from the association. This removes their records from the association field on the source,
47
+ # and attempts to remove the source from the target association if it is detected to exist.
48
+ #
49
+ # @param [Dynamoid::Document] object the object (or array of objects) to remove from the association
50
+ #
51
+ # @return [Dynamoid::Document] the deleted object
52
+ #
53
+ # @since 0.2.0
54
+ def delete(object)
55
+ source.update_attribute(source_attribute, source_ids - Array(object).collect(&:id))
56
+ Array(object).each {|o| self.send(:disassociate_target, o)} if target_association
57
+ object
58
+ end
59
+
60
+
61
+ # Add an object or array of objects to an association. This preserves the current records in the association (if any)
62
+ # and adds the object to the target association if it is detected to exist.
63
+ #
64
+ # @param [Dynamoid::Document] object the object (or array of objects) to add to the association
65
+ #
66
+ # @return [Dynamoid::Document] the added object
67
+ #
68
+ # @since 0.2.0
69
+ def <<(object)
70
+ source.update_attribute(source_attribute, source_ids.merge(Array(object).collect(&:id)))
71
+ Array(object).each {|o| self.send(:associate_target, o)} if target_association
72
+ object
73
+ end
74
+
75
+ # Replace an association with object or array of objects. This removes all of the existing associated records and replaces them with
76
+ # the passed object(s), and associates the target association if it is detected to exist.
77
+ #
78
+ # @param [Dynamoid::Document] object the object (or array of objects) to add to the association
79
+ #
80
+ # @return [Dynamoid::Document] the added object
81
+ #
82
+ # @since 0.2.0
83
+ def setter(object)
84
+ target.each {|o| delete(o)}
85
+ self << (object)
86
+ object
87
+ end
88
+
89
+ # Create a new instance of the target class and add it directly to the association. If the create fails an exception will be raised.
90
+ #
91
+ # @param [Hash] attribute hash for the new object
92
+ #
93
+ # @return [Dynamoid::Document] the newly-created object
94
+ #
95
+ # @since 0.2.0
96
+ def create!(attributes = {})
97
+ self << target_class.create!(attributes)
98
+ end
99
+
100
+ # Create a new instance of the target class and add it directly to the association.
101
+ #
102
+ # @param [Hash] attribute hash for the new object
103
+ #
104
+ # @return [Dynamoid::Document] the newly-created object
105
+ #
106
+ # @since 0.2.0
107
+ def create(attributes = {})
108
+ self << target_class.create(attributes)
109
+ end
110
+
111
+ # Create a new instance of the target class and add it directly to the association. If the create fails an exception will be raised.
112
+ #
113
+ # @param [Hash] attribute hash for the new object
114
+ #
115
+ # @return [Dynamoid::Document] the newly-created object
116
+ #
117
+ # @since 0.2.0
118
+ def each(&block)
119
+ records.each(&block)
120
+ end
121
+
122
+ # Destroys all members of the association and removes them from the association.
123
+ #
124
+ # @since 0.2.0
125
+ def destroy_all
126
+ objs = target
127
+ source.update_attribute(source_attribute, nil)
128
+ objs.each(&:destroy)
129
+ end
130
+
131
+ # Deletes all members of the association and removes them from the association.
132
+ #
133
+ # @since 0.2.0
134
+ def delete_all
135
+ objs = target
136
+ source.update_attribute(source_attribute, nil)
137
+ objs.each(&:delete)
138
+ end
139
+
140
+ # Naive association filtering.
141
+ #
142
+ # @param [Hash] A hash of attributes; each must match every returned object's attribute exactly.
143
+ #
144
+ # @return [Dynamoid::Association] the association this method was called on (for chaining purposes)
145
+ #
146
+ # @since 0.2.0
147
+ def where(args)
148
+ args.each {|k, v| query[k] = v}
149
+ self
150
+ end
151
+
152
+ # Is this array equal to the association's records?
153
+ #
154
+ # @return [Boolean] true/false
155
+ #
156
+ # @since 0.2.0
157
+ def ==(other)
158
+ records == Array(other)
159
+ end
160
+
161
+ # Delegate methods we don't find directly to the records array.
162
+ #
163
+ # @since 0.2.0
164
+ def method_missing(method, *args)
165
+ if records.respond_to?(method)
166
+ records.send(method, *args)
167
+ else
168
+ super
169
+ end
170
+ end
171
+
172
+ private
173
+
174
+ # If a query exists, filter all existing results based on that query.
175
+ #
176
+ # @param [Array] results the raw results for the association
177
+ #
178
+ # @return [Array] the filtered results for the query
179
+ #
180
+ # @since 0.2.0
181
+ def results_with_query(results)
182
+ results.find_all do |result|
183
+ query.all? do |attribute, value|
184
+ result.send(attribute) == value
185
+ end
186
+ end
187
+ end
188
+
189
+ end
190
+ end
191
+ end
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ module Associations
5
+ module SingleAssociation
6
+ include Association
7
+
8
+ delegate :class, :to => :target
9
+
10
+ def setter(object)
11
+ delete
12
+ source.update_attribute(source_attribute, Set[object.id])
13
+ self.send(:associate_target, object) if target_association
14
+ object
15
+ end
16
+
17
+ def delete
18
+ source.update_attribute(source_attribute, nil)
19
+ self.send(:disassociate_target, target) if target && target_association
20
+ target
21
+ end
22
+
23
+ def create!(attributes = {})
24
+ setter(target_class.create!(attributes))
25
+ end
26
+
27
+ def create(attributes = {})
28
+ setter(target_class.create!(attributes))
29
+ end
30
+
31
+
32
+ # Is this object equal to the association's target?
33
+ #
34
+ # @return [Boolean] true/false
35
+ #
36
+ # @since 0.2.0
37
+ def ==(other)
38
+ target == other
39
+ end
40
+
41
+ # Delegate methods we don't find directly to the target.
42
+ #
43
+ # @since 0.2.0
44
+ def method_missing(method, *args)
45
+ if target.respond_to?(method)
46
+ target.send(method, *args)
47
+ else
48
+ super
49
+ end
50
+ end
51
+
52
+ def nil?
53
+ target.nil?
54
+ end
55
+
56
+ private
57
+
58
+ # Find the target of the has_one association.
59
+ #
60
+ # @return [Dynamoid::Document] the found target (or nil if nothing)
61
+ #
62
+ # @since 0.2.0
63
+ def find_target
64
+ return if source_ids.empty?
65
+ target_class.find(source_ids.first)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+ require 'dynamoid/associations/association'
3
+ require 'dynamoid/associations/single_association'
4
+ require 'dynamoid/associations/many_association'
5
+ require 'dynamoid/associations/has_many'
6
+ require 'dynamoid/associations/belongs_to'
7
+ require 'dynamoid/associations/has_one'
8
+ require 'dynamoid/associations/has_and_belongs_to_many'
9
+
10
+ module Dynamoid
11
+
12
+ # Connects models together through the magic of associations. We enjoy four different kinds of associations presently:
13
+ # * belongs_to
14
+ # * has_and_belongs_to_many
15
+ # * has_many
16
+ # * has_one
17
+ module Associations
18
+ extend ActiveSupport::Concern
19
+
20
+ # Create the association tracking attribute and initialize it to an empty hash.
21
+ included do
22
+ class_attribute :associations
23
+
24
+ self.associations = {}
25
+ end
26
+
27
+ module ClassMethods
28
+
29
+ # create a has_many association for this document.
30
+ #
31
+ # @param [Symbol] name the name of the association
32
+ # @param [Hash] options options to pass to the association constructor
33
+ # @option options [Class] :class the target class of the has_many association; that is, the belongs_to class
34
+ # @option options [Symbol] :class_name the name of the target class of the association; that is, the name of the belongs_to class
35
+ # @option options [Symbol] :inverse_of the name of the association on the target class; that is, if the class has a belongs_to association, the name of that association
36
+ #
37
+ # @since 0.2.0
38
+ def has_many(name, options = {})
39
+ association(:has_many, name, options)
40
+ end
41
+
42
+ # create a has_one association for this document.
43
+ #
44
+ # @param [Symbol] name the name of the association
45
+ # @param [Hash] options options to pass to the association constructor
46
+ # @option options [Class] :class the target class of the has_one association; that is, the belongs_to class
47
+ # @option options [Symbol] :class_name the name of the target class of the association; that is, the name of the belongs_to class
48
+ # @option options [Symbol] :inverse_of the name of the association on the target class; that is, if the class has a belongs_to association, the name of that association
49
+ #
50
+ # @since 0.2.0
51
+ def has_one(name, options = {})
52
+ association(:has_one, name, options)
53
+ end
54
+
55
+ # create a belongs_to association for this document.
56
+ #
57
+ # @param [Symbol] name the name of the association
58
+ # @param [Hash] options options to pass to the association constructor
59
+ # @option options [Class] :class the target class of the has_one association; that is, the has_many or has_one class
60
+ # @option options [Symbol] :class_name the name of the target class of the association; that is, the name of the has_many or has_one class
61
+ # @option options [Symbol] :inverse_of the name of the association on the target class; that is, if the class has a has_many or has_one association, the name of that association
62
+ #
63
+ # @since 0.2.0
64
+ def belongs_to(name, options = {})
65
+ association(:belongs_to, name, options)
66
+ end
67
+
68
+ # create a has_and_belongs_to_many association for this document.
69
+ #
70
+ # @param [Symbol] name the name of the association
71
+ # @param [Hash] options options to pass to the association constructor
72
+ # @option options [Class] :class the target class of the has_and_belongs_to_many association; that is, the belongs_to class
73
+ # @option options [Symbol] :class_name the name of the target class of the association; that is, the name of the belongs_to class
74
+ # @option options [Symbol] :inverse_of the name of the association on the target class; that is, if the class has a belongs_to association, the name of that association
75
+ #
76
+ # @since 0.2.0
77
+ def has_and_belongs_to_many(name, options = {})
78
+ association(:has_and_belongs_to_many, name, options)
79
+ end
80
+
81
+ private
82
+
83
+ # create getters and setters for an association.
84
+ #
85
+ # @param [Symbol] symbol the type (:has_one, :has_many, :has_and_belongs_to_many, :belongs_to) of the association
86
+ # @param [Symbol] name the name of the association
87
+ # @param [Hash] options options to pass to the association constructor; see above for all valid options
88
+ #
89
+ # @since 0.2.0
90
+ def association(type, name, options = {})
91
+ field "#{name}_ids".to_sym, :set
92
+ self.associations[name] = options.merge(:type => type)
93
+ define_method(name) do
94
+ @associations[:"#{name}_ids"] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
95
+ end
96
+ define_method("#{name}=".to_sym) do |objects|
97
+ @associations[:"#{name}_ids"] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
98
+ @associations[:"#{name}_ids"].setter(objects)
99
+ end
100
+ end
101
+ end
102
+
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,37 @@
1
+ # encoding: utf-8
2
+ module Dynamoid
3
+
4
+ # All modules that a Document is composed of are defined in this
5
+ # module, to keep the document class from getting too cluttered.
6
+ module Components
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ extend ActiveModel::Translation
11
+ extend ActiveModel::Callbacks
12
+
13
+ define_model_callbacks :create, :save, :destroy, :initialize, :update
14
+
15
+ before_create :set_created_at
16
+ before_save :set_updated_at
17
+ after_initialize :set_type
18
+ end
19
+
20
+ include ActiveModel::AttributeMethods
21
+ include ActiveModel::Conversion
22
+ include ActiveModel::MassAssignmentSecurity if defined?(ActiveModel::MassAssignmentSecurity)
23
+ include ActiveModel::Naming
24
+ include ActiveModel::Observing if defined?(ActiveModel::Observing)
25
+ include ActiveModel::Serializers::JSON
26
+ include ActiveModel::Serializers::Xml
27
+ include Dynamoid::Fields
28
+ include Dynamoid::Indexes
29
+ include Dynamoid::Persistence
30
+ include Dynamoid::Finders
31
+ include Dynamoid::Associations
32
+ include Dynamoid::Criteria
33
+ include Dynamoid::Validations
34
+ include Dynamoid::IdentityMap
35
+ include Dynamoid::Dirty
36
+ end
37
+ end