dynamoid 1.3.4 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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.