dynamoid 1.3.4 → 2.0.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +3 -0
  4. data/.travis.yml +32 -7
  5. data/Appraisals +7 -0
  6. data/CHANGELOG.md +69 -2
  7. data/Gemfile +2 -0
  8. data/README.md +108 -28
  9. data/Rakefile +0 -24
  10. data/docker-compose.yml +7 -0
  11. data/dynamoid.gemspec +2 -3
  12. data/gemfiles/rails_4_0.gemfile +2 -3
  13. data/gemfiles/rails_4_1.gemfile +2 -3
  14. data/gemfiles/rails_4_2.gemfile +2 -3
  15. data/gemfiles/rails_5_0.gemfile +1 -1
  16. data/gemfiles/rails_5_1.gemfile +7 -0
  17. data/lib/dynamoid.rb +31 -31
  18. data/lib/dynamoid/adapter.rb +5 -5
  19. data/lib/dynamoid/adapter_plugin/aws_sdk_v2.rb +84 -57
  20. data/lib/dynamoid/associations.rb +21 -12
  21. data/lib/dynamoid/associations/association.rb +19 -3
  22. data/lib/dynamoid/associations/belongs_to.rb +26 -16
  23. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +0 -16
  24. data/lib/dynamoid/associations/has_many.rb +2 -17
  25. data/lib/dynamoid/associations/has_one.rb +0 -14
  26. data/lib/dynamoid/associations/many_association.rb +19 -6
  27. data/lib/dynamoid/associations/single_association.rb +25 -7
  28. data/lib/dynamoid/config.rb +18 -18
  29. data/lib/dynamoid/config/options.rb +1 -1
  30. data/lib/dynamoid/criteria/chain.rb +29 -21
  31. data/lib/dynamoid/dirty.rb +2 -2
  32. data/lib/dynamoid/document.rb +17 -5
  33. data/lib/dynamoid/errors.rb +4 -1
  34. data/lib/dynamoid/fields.rb +6 -6
  35. data/lib/dynamoid/finders.rb +19 -9
  36. data/lib/dynamoid/identity_map.rb +0 -1
  37. data/lib/dynamoid/indexes.rb +41 -54
  38. data/lib/dynamoid/persistence.rb +54 -24
  39. data/lib/dynamoid/railtie.rb +1 -1
  40. data/lib/dynamoid/validations.rb +4 -3
  41. data/lib/dynamoid/version.rb +1 -1
  42. metadata +14 -29
  43. data/gemfiles/rails_4_0.gemfile.lock +0 -150
  44. data/gemfiles/rails_4_1.gemfile.lock +0 -154
  45. data/gemfiles/rails_4_2.gemfile.lock +0 -175
  46. data/gemfiles/rails_5_0.gemfile.lock +0 -180
@@ -16,16 +16,16 @@ module Dynamoid
16
16
  # * has_one
17
17
  module Associations
18
18
  extend ActiveSupport::Concern
19
-
19
+
20
20
  # Create the association tracking attribute and initialize it to an empty hash.
21
21
  included do
22
22
  class_attribute :associations, instance_accessor: false
23
-
23
+
24
24
  self.associations = {}
25
25
  end
26
26
 
27
27
  module ClassMethods
28
-
28
+
29
29
  # create a has_many association for this document.
30
30
  #
31
31
  # @param [Symbol] name the name of the association
@@ -38,7 +38,7 @@ module Dynamoid
38
38
  def has_many(name, options = {})
39
39
  association(:has_many, name, options)
40
40
  end
41
-
41
+
42
42
  # create a has_one association for this document.
43
43
  #
44
44
  # @param [Symbol] name the name of the association
@@ -51,7 +51,7 @@ module Dynamoid
51
51
  def has_one(name, options = {})
52
52
  association(:has_one, name, options)
53
53
  end
54
-
54
+
55
55
  # create a belongs_to association for this document.
56
56
  #
57
57
  # @param [Symbol] name the name of the association
@@ -64,7 +64,7 @@ module Dynamoid
64
64
  def belongs_to(name, options = {})
65
65
  association(:belongs_to, name, options)
66
66
  end
67
-
67
+
68
68
  # create a has_and_belongs_to_many association for this document.
69
69
  #
70
70
  # @param [Symbol] name the name of the association
@@ -77,7 +77,7 @@ module Dynamoid
77
77
  def has_and_belongs_to_many(name, options = {})
78
78
  association(:has_and_belongs_to_many, name, options)
79
79
  end
80
-
80
+
81
81
  private
82
82
 
83
83
  # create getters and setters for an association.
@@ -86,13 +86,23 @@ module Dynamoid
86
86
  # @param [Symbol] name the name of the association
87
87
  # @param [Hash] options options to pass to the association constructor; see above for all valid options
88
88
  #
89
- # @since 0.2.0
89
+ # @since 0.2.0
90
90
  def association(type, name, options = {})
91
- field "#{name}_ids".to_sym, :set
92
- self.associations[name] = options.merge(:type => type)
91
+ # Declare document field.
92
+ # In simple case it's equivalent to
93
+ # field "#{name}_ids".to_sym, :set
94
+ assoc = Dynamoid::Associations.const_get(type.to_s.camelcase).new(nil, name, options)
95
+ field_name = assoc.declaration_field_name
96
+ field_type = assoc.declaration_field_type
97
+
98
+ field field_name.to_sym, field_type
99
+
100
+ self.associations[name] = options.merge(type: type)
101
+
93
102
  define_method(name) do
94
103
  @associations[:"#{name}_ids"] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
95
104
  end
105
+
96
106
  define_method("#{name}=".to_sym) do |objects|
97
107
  @associations[:"#{name}_ids"] ||= Dynamoid::Associations.const_get(type.to_s.camelcase).new(self, name, options)
98
108
  @associations[:"#{name}_ids"].setter(objects)
@@ -100,7 +110,6 @@ module Dynamoid
100
110
  end
101
111
  end
102
112
 
103
-
104
113
  end
105
-
114
+
106
115
  end
@@ -16,6 +16,7 @@ module Dynamoid #:nodoc:
16
16
  # @option options [Class] :class the target class of the association; that is, the class to which the association objects belong
17
17
  # @option options [Symbol] :class_name the name of the target class of the association; only this or Class is necessary
18
18
  # @option options [Symbol] :inverse_of the name of the association on the target class
19
+ # @option options [Symbol] :foreign_key the name of the field for belongs_to association
19
20
  #
20
21
  # @return [Dynamoid::Association] the actual association instance itself
21
22
  #
@@ -48,6 +49,14 @@ module Dynamoid #:nodoc:
48
49
  @loaded = false
49
50
  end
50
51
 
52
+ def declaration_field_name
53
+ "#{name}_ids"
54
+ end
55
+
56
+ def declaration_field_type
57
+ :set
58
+ end
59
+
51
60
  private
52
61
 
53
62
  # The target class name, either inferred through the association's name or specified in options.
@@ -68,7 +77,13 @@ module Dynamoid #:nodoc:
68
77
  #
69
78
  # @since 0.2.0
70
79
  def target_attribute
71
- "#{target_association}_ids".to_sym if target_association
80
+ # In simple case it's equivalent to
81
+ # "#{target_association}_ids".to_sym if target_association
82
+ if target_association
83
+ target_options = target_class.associations[target_association]
84
+ assoc = Dynamoid::Associations.const_get(target_options[:type].to_s.camelcase).new(nil, target_association, target_options)
85
+ assoc.send(:source_attribute)
86
+ end
72
87
  end
73
88
 
74
89
  # The ids in the target association.
@@ -89,14 +104,15 @@ module Dynamoid #:nodoc:
89
104
  #
90
105
  # @since 0.2.0
91
106
  def source_attribute
92
- "#{name}_ids".to_sym
107
+ declaration_field_name.to_sym
93
108
  end
94
109
 
95
110
  # The ids in the source association.
96
111
  #
97
112
  # @since 0.2.0
98
113
  def source_ids
99
- source.send(source_attribute) || Set.new
114
+ # handle case when we store scalar value instead of collection (when foreign_key option is specified)
115
+ Array(source.send(source_attribute)).compact.to_set || Set.new
100
116
  end
101
117
 
102
118
  # Create a new instance of the target class without trying to add it to the association. This creates a base, that caller can update before setting or adding it.
@@ -1,12 +1,36 @@
1
1
  # encoding: utf-8
2
2
  module Dynamoid #:nodoc:
3
3
 
4
- # The belongs_to association. For belongs_to, we reference only a single target instead of multiple records; that target is the
4
+ # The belongs_to association. For belongs_to, we reference only a single target instead of multiple records; that target is the
5
5
  # object to which the association object is associated.
6
6
  module Associations
7
7
  class BelongsTo
8
8
  include SingleAssociation
9
9
 
10
+ def declaration_field_name
11
+ options[:foreign_key] || "#{name}_ids"
12
+ end
13
+
14
+ def declaration_field_type
15
+ if options[:foreign_key]
16
+ target_class.attributes[target_class.hash_key][:type]
17
+ else
18
+ :set
19
+ end
20
+ end
21
+
22
+ # Override default implementation
23
+ # to handle case when we store id as scalar value, not as collection
24
+ def associate(hash_key)
25
+ target.send(target_association).disassociate(source.hash_key) if target && target_association
26
+
27
+ if options[:foreign_key]
28
+ source.update_attribute(source_attribute, hash_key)
29
+ else
30
+ source.update_attribute(source_attribute, Set[hash_key])
31
+ end
32
+ end
33
+
10
34
  private
11
35
 
12
36
  # Find the target association, either has_many or has_one. Uses either options[:inverse_of] or the source class name and default parsing to
@@ -24,21 +48,7 @@ module Dynamoid #:nodoc:
24
48
  return has_one_key_name if target_class.associations[has_one_key_name][:type] == :has_one
25
49
  end
26
50
  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.hash_key)))
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.hash_key))
40
- end
41
51
  end
42
52
  end
43
-
53
+
44
54
  end
@@ -18,22 +18,6 @@ module Dynamoid #:nodoc:
18
18
  return nil if guess.nil? || guess[:type] != :has_and_belongs_to_many
19
19
  key_name
20
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.hash_key)))
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.hash_key))
36
- end
37
21
  end
38
22
  end
39
23
 
@@ -8,7 +8,7 @@ module Dynamoid #:nodoc:
8
8
 
9
9
  private
10
10
 
11
- # Find the target association, always a :belongs_to association. Uses either options[:inverse_of] or the source class name
11
+ # Find the target association, always a :belongs_to association. Uses either options[:inverse_of] or the source class name
12
12
  # and default parsing to return the most likely name for the target association.
13
13
  #
14
14
  # @since 0.2.0
@@ -18,22 +18,7 @@ module Dynamoid #:nodoc:
18
18
  return nil if guess.nil? || guess[:type] != :belongs_to
19
19
  key_name
20
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.hash_key])
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
21
  end
37
22
  end
38
-
23
+
39
24
  end
@@ -19,20 +19,6 @@ module Dynamoid #:nodoc:
19
19
  return nil if guess.nil? || guess[:type] != :belongs_to
20
20
  key_name
21
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.hash_key])
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
22
  end
37
23
  end
38
24
 
@@ -14,7 +14,7 @@ module Dynamoid #:nodoc:
14
14
 
15
15
  include Enumerable
16
16
  # Delegate methods to the records the association represents.
17
- delegate :first, :last, :empty?, :size, :class, :to => :records
17
+ delegate :first, :last, :empty?, :size, :class, to: :records
18
18
 
19
19
  # The records associated to the source.
20
20
  #
@@ -52,12 +52,13 @@ module Dynamoid #:nodoc:
52
52
  #
53
53
  # @since 0.2.0
54
54
  def delete(object)
55
- source.update_attribute(source_attribute, source_ids - Array(object).collect(&:hash_key))
56
- Array(object).each {|o| self.send(:disassociate_target, o)} if target_association
55
+ disassociate(Array(object).collect(&:hash_key))
56
+ if target_association
57
+ Array(object).each { |obj| obj.send(target_association).disassociate(source.hash_key) }
58
+ end
57
59
  object
58
60
  end
59
61
 
60
-
61
62
  # Add an object or array of objects to an association. This preserves the current records in the association (if any)
62
63
  # and adds the object to the target association if it is detected to exist.
63
64
  #
@@ -67,8 +68,12 @@ module Dynamoid #:nodoc:
67
68
  #
68
69
  # @since 0.2.0
69
70
  def <<(object)
70
- source.update_attribute(source_attribute, source_ids.merge(Array(object).collect(&:hash_key)))
71
- Array(object).each {|o| self.send(:associate_target, o)} if target_association
71
+ associate(Array(object).collect(&:hash_key))
72
+
73
+ if target_association
74
+ Array(object).each { |obj| obj.send(target_association).associate(source.hash_key) }
75
+ end
76
+
72
77
  object
73
78
  end
74
79
 
@@ -171,6 +176,14 @@ module Dynamoid #:nodoc:
171
176
  end
172
177
  end
173
178
 
179
+ def associate(hash_key)
180
+ source.update_attribute(source_attribute, source_ids.merge(Array(hash_key)))
181
+ end
182
+
183
+ def disassociate(hash_key)
184
+ source.update_attribute(source_attribute, source_ids - Array(hash_key))
185
+ end
186
+
174
187
  private
175
188
 
176
189
  # If a query exists, filter all existing results based on that query.
@@ -5,18 +5,23 @@ module Dynamoid #:nodoc:
5
5
  module SingleAssociation
6
6
  include Association
7
7
 
8
- delegate :class, :to => :target
8
+ delegate :class, to: :target
9
9
 
10
10
  def setter(object)
11
- delete
12
- source.update_attribute(source_attribute, Set[object.hash_key])
13
- self.send(:associate_target, object) if target_association
11
+ if object.nil?
12
+ delete
13
+ return
14
+ end
15
+
16
+ associate(object.hash_key)
17
+ self.target = object
18
+ object.send(target_association).associate(source.hash_key) if target_association
14
19
  object
15
20
  end
16
21
 
17
22
  def delete
18
- source.update_attribute(source_attribute, nil)
19
- self.send(:disassociate_target, target) if target && target_association
23
+ target.send(target_association).disassociate(source.hash_key) if target && target_association
24
+ disassociate
20
25
  target
21
26
  end
22
27
 
@@ -28,7 +33,6 @@ module Dynamoid #:nodoc:
28
33
  setter(target_class.create(attributes))
29
34
  end
30
35
 
31
-
32
36
  # Is this object equal to the association's target?
33
37
  #
34
38
  # @return [Boolean] true/false
@@ -59,6 +63,15 @@ module Dynamoid #:nodoc:
59
63
  target.nil?
60
64
  end
61
65
 
66
+ def associate(hash_key)
67
+ target.send(target_association).disassociate(source.hash_key) if target && target_association
68
+ source.update_attribute(source_attribute, Set[hash_key])
69
+ end
70
+
71
+ def disassociate(hash_key=nil)
72
+ source.update_attribute(source_attribute, nil)
73
+ end
74
+
62
75
  private
63
76
 
64
77
  # Find the target of the has_one association.
@@ -70,6 +83,11 @@ module Dynamoid #:nodoc:
70
83
  return if source_ids.empty?
71
84
  target_class.find(source_ids.first)
72
85
  end
86
+
87
+ def target=(object)
88
+ @target = object
89
+ @loaded = true
90
+ end
73
91
  end
74
92
  end
75
93
  end
@@ -1,6 +1,6 @@
1
1
  # encoding: utf-8
2
- require "uri"
3
- require "dynamoid/config/options"
2
+ require 'uri'
3
+ require 'dynamoid/config/options'
4
4
 
5
5
  module Dynamoid
6
6
 
@@ -11,22 +11,22 @@ module Dynamoid
11
11
  include ActiveModel::Observing if defined?(ActiveModel::Observing)
12
12
 
13
13
  # All the default options.
14
- option :adapter, :default => 'aws_sdk_v2'
15
- option :namespace, :default => defined?(Rails) ? "dynamoid_#{Rails.application.class.parent_name}_#{Rails.env}" : "dynamoid"
16
- option :access_key, :default => nil
17
- option :secret_key, :default => nil
18
- option :region, :default => nil
19
- option :batch_size, :default => 100
20
- option :read_capacity, :default => 100
21
- option :write_capacity, :default => 20
22
- option :warn_on_scan, :default => true
23
- option :endpoint, :default => nil
24
- option :identity_map, :default => false
25
- option :timestamps, :default => true
26
- option :sync_retry_max_times, :default => 60 # a bit over 2 minutes
27
- option :sync_retry_wait_seconds, :default => 2
28
- option :convert_big_decimal, :default => false
29
- option :models_dir, :default => "app/models" # perhaps you keep your dynamoid models in a different directory?
14
+ option :adapter, default: 'aws_sdk_v2'
15
+ option :namespace, default: defined?(Rails) ? "dynamoid_#{Rails.application.class.parent_name}_#{Rails.env}" : 'dynamoid'
16
+ option :access_key, default: nil
17
+ option :secret_key, default: nil
18
+ option :region, default: nil
19
+ option :batch_size, default: 100
20
+ option :read_capacity, default: 100
21
+ option :write_capacity, default: 20
22
+ option :warn_on_scan, default: true
23
+ option :endpoint, default: nil
24
+ option :identity_map, default: false
25
+ option :timestamps, default: true
26
+ option :sync_retry_max_times, default: 60 # a bit over 2 minutes
27
+ option :sync_retry_wait_seconds, default: 2
28
+ option :convert_big_decimal, default: false
29
+ option :models_dir, default: 'app/models' # perhaps you keep your dynamoid models in a different directory?
30
30
  option :application_timezone, default: :local # available values - :utc, :local, time zone names
31
31
 
32
32
  # The default logger for Dynamoid: either the Rails logger or just stdout.