dynamoid 3.2.0 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -1
  3. data/README.md +580 -241
  4. data/lib/dynamoid.rb +2 -0
  5. data/lib/dynamoid/adapter.rb +15 -15
  6. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +82 -102
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +108 -0
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +29 -16
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +3 -2
  10. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/backoff.rb +2 -2
  11. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/limit.rb +2 -3
  12. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/middleware/start_key.rb +2 -2
  13. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/query.rb +15 -6
  14. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +15 -5
  15. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/table.rb +1 -0
  16. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/until_past_table_status.rb +5 -3
  17. data/lib/dynamoid/application_time_zone.rb +1 -0
  18. data/lib/dynamoid/associations.rb +182 -19
  19. data/lib/dynamoid/associations/association.rb +4 -2
  20. data/lib/dynamoid/associations/belongs_to.rb +2 -1
  21. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +2 -1
  22. data/lib/dynamoid/associations/has_many.rb +2 -1
  23. data/lib/dynamoid/associations/has_one.rb +2 -1
  24. data/lib/dynamoid/associations/many_association.rb +65 -22
  25. data/lib/dynamoid/associations/single_association.rb +28 -1
  26. data/lib/dynamoid/components.rb +8 -3
  27. data/lib/dynamoid/config.rb +16 -3
  28. data/lib/dynamoid/config/backoff_strategies/constant_backoff.rb +1 -0
  29. data/lib/dynamoid/config/backoff_strategies/exponential_backoff.rb +1 -0
  30. data/lib/dynamoid/config/options.rb +1 -0
  31. data/lib/dynamoid/criteria.rb +2 -1
  32. data/lib/dynamoid/criteria/chain.rb +418 -46
  33. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +3 -3
  34. data/lib/dynamoid/criteria/key_fields_detector.rb +109 -32
  35. data/lib/dynamoid/criteria/nonexistent_fields_detector.rb +3 -2
  36. data/lib/dynamoid/criteria/overwritten_conditions_detector.rb +1 -1
  37. data/lib/dynamoid/dirty.rb +239 -32
  38. data/lib/dynamoid/document.rb +130 -251
  39. data/lib/dynamoid/dumping.rb +9 -0
  40. data/lib/dynamoid/dynamodb_time_zone.rb +1 -0
  41. data/lib/dynamoid/fields.rb +246 -20
  42. data/lib/dynamoid/finders.rb +69 -32
  43. data/lib/dynamoid/identity_map.rb +6 -0
  44. data/lib/dynamoid/indexes.rb +76 -17
  45. data/lib/dynamoid/loadable.rb +31 -0
  46. data/lib/dynamoid/log/formatter.rb +26 -0
  47. data/lib/dynamoid/middleware/identity_map.rb +1 -0
  48. data/lib/dynamoid/persistence.rb +592 -122
  49. data/lib/dynamoid/persistence/import.rb +73 -0
  50. data/lib/dynamoid/persistence/save.rb +64 -0
  51. data/lib/dynamoid/persistence/update_fields.rb +63 -0
  52. data/lib/dynamoid/persistence/upsert.rb +60 -0
  53. data/lib/dynamoid/primary_key_type_mapping.rb +1 -0
  54. data/lib/dynamoid/railtie.rb +1 -0
  55. data/lib/dynamoid/tasks.rb +3 -1
  56. data/lib/dynamoid/tasks/database.rb +1 -0
  57. data/lib/dynamoid/type_casting.rb +12 -2
  58. data/lib/dynamoid/undumping.rb +8 -0
  59. data/lib/dynamoid/validations.rb +2 -0
  60. data/lib/dynamoid/version.rb +1 -1
  61. metadata +49 -71
  62. data/.coveralls.yml +0 -1
  63. data/.document +0 -5
  64. data/.gitignore +0 -74
  65. data/.rspec +0 -2
  66. data/.rubocop.yml +0 -71
  67. data/.rubocop_todo.yml +0 -55
  68. data/.travis.yml +0 -41
  69. data/Appraisals +0 -28
  70. data/Gemfile +0 -8
  71. data/Rakefile +0 -46
  72. data/Vagrantfile +0 -29
  73. data/docker-compose.yml +0 -7
  74. data/dynamoid.gemspec +0 -57
  75. data/gemfiles/rails_4_2.gemfile +0 -11
  76. data/gemfiles/rails_5_0.gemfile +0 -10
  77. data/gemfiles/rails_5_1.gemfile +0 -10
  78. data/gemfiles/rails_5_2.gemfile +0 -10
@@ -3,6 +3,7 @@
3
3
  require_relative 'until_past_table_status'
4
4
 
5
5
  module Dynamoid
6
+ # @private
6
7
  module AdapterPlugin
7
8
  class AwsSdkV3
8
9
  class CreateTable
@@ -16,6 +17,7 @@ module Dynamoid
16
17
  end
17
18
 
18
19
  def call
20
+ billing_mode = options[:billing_mode]
19
21
  read_capacity = options[:read_capacity] || Dynamoid::Config.read_capacity
20
22
  write_capacity = options[:write_capacity] || Dynamoid::Config.write_capacity
21
23
 
@@ -41,14 +43,20 @@ module Dynamoid
41
43
 
42
44
  client_opts = {
43
45
  table_name: table_name,
44
- provisioned_throughput: {
45
- read_capacity_units: read_capacity,
46
- write_capacity_units: write_capacity
47
- },
48
46
  key_schema: key_schema,
49
47
  attribute_definitions: attribute_definitions
50
48
  }
51
49
 
50
+ if billing_mode == :on_demand
51
+ client_opts[:billing_mode] = 'PAY_PER_REQUEST'
52
+ else
53
+ client_opts[:billing_mode] = 'PROVISIONED'
54
+ client_opts[:provisioned_throughput] = {
55
+ read_capacity_units: read_capacity,
56
+ write_capacity_units: write_capacity
57
+ }
58
+ end
59
+
52
60
  if ls_indexes.present?
53
61
  client_opts[:local_secondary_indexes] = ls_indexes.map do |index|
54
62
  index_to_aws_hash(index)
@@ -62,11 +70,16 @@ module Dynamoid
62
70
  end
63
71
  resp = client.create_table(client_opts)
64
72
  options[:sync] = true if !options.key?(:sync) && ls_indexes.present? || gs_indexes.present?
65
- UntilPastTableStatus.new(table_name, :creating).call if options[:sync] &&
66
- (status = PARSE_TABLE_STATUS.call(resp, :table_description)) &&
67
- status == TABLE_STATUSES[:creating]
73
+
74
+ if options[:sync]
75
+ status = PARSE_TABLE_STATUS.call(resp, :table_description)
76
+ if status == TABLE_STATUSES[:creating]
77
+ UntilPastTableStatus.new(client, table_name, :creating).call
78
+ end
79
+ end
80
+
68
81
  # Response to original create_table, which, if options[:sync]
69
- # may have an outdated table_description.table_status of "CREATING"
82
+ # may have an outdated table_description.table_status of "CREATING"
70
83
  resp
71
84
  end
72
85
 
@@ -75,9 +88,9 @@ module Dynamoid
75
88
  # Builds aws attributes definitions based off of primary hash/range and
76
89
  # secondary indexes
77
90
  #
78
- # @param key_data
79
- # @option key_data [Hash] hash_key_schema - eg: {:id => :string}
80
- # @option key_data [Hash] range_key_schema - eg: {:created_at => :number}
91
+ # @param key_schema
92
+ # @option key_schema [Hash] hash_key_schema - eg: {:id => :string}
93
+ # @option key_schema [Hash] range_key_schema - eg: {:created_at => :number}
81
94
  # @param [Hash] secondary_indexes
82
95
  # @option secondary_indexes [Array<Dynamoid::Indexes::Index>] :local_secondary_indexes
83
96
  # @option secondary_indexes [Array<Dynamoid::Indexes::Index>] :global_secondary_indexes
@@ -118,8 +131,8 @@ module Dynamoid
118
131
  end
119
132
 
120
133
  # Builds an attribute definitions based on hash key and range key
121
- # @params [Hash] hash_key_schema - eg: {:id => :string}
122
- # @params [Hash] range_key_schema - eg: {:created_at => :datetime}
134
+ # @param [Hash] hash_key_schema - eg: {:id => :string}
135
+ # @param [Hash] range_key_schema - eg: {:created_at => :datetime}
123
136
  # @return [Array]
124
137
  def build_attribute_definitions(hash_key_schema, range_key_schema = nil)
125
138
  attrs = []
@@ -140,8 +153,8 @@ module Dynamoid
140
153
  end
141
154
 
142
155
  # Builds an aws attribute definition based on name and dynamoid type
143
- # @params [Symbol] name - eg: :id
144
- # @params [Symbol] dynamoid_type - eg: :string
156
+ # @param [Symbol] name - eg: :id
157
+ # @param [Symbol] dynamoid_type - eg: :string
145
158
  # @return [Hash]
146
159
  def attribute_definition_element(name, dynamoid_type)
147
160
  aws_type = api_type(dynamoid_type)
@@ -201,7 +214,7 @@ module Dynamoid
201
214
  end
202
215
 
203
216
  # Only global secondary indexes have a separate throughput.
204
- if index.type == :global_secondary
217
+ if index.type == :global_secondary && options[:billing_mode] != :on_demand
205
218
  hash[:provisioned_throughput] = {
206
219
  read_capacity_units: index.read_capacity,
207
220
  write_capacity_units: index.write_capacity
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
+ # @private
4
5
  module AdapterPlugin
5
6
  class AwsSdkV3
6
7
  # Mimics behavior of the yielded object on DynamoDB's update_item API (high level).
@@ -20,8 +21,8 @@ module Dynamoid
20
21
  # Adds the given values to the values already stored in the corresponding columns.
21
22
  # The column must contain a Set or a number.
22
23
  #
23
- # @param [Hash] vals keys of the hash are the columns to update, vals are the values to
24
- # add. values must be a Set, Array, or Numeric
24
+ # @param [Hash] values keys of the hash are the columns to update, values
25
+ # are the values to add. values must be a Set, Array, or Numeric
25
26
  #
26
27
  def add(values)
27
28
  @additions.merge!(sanitize_attributes(values))
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
+ # @private
4
5
  module AdapterPlugin
5
6
  class AwsSdkV3
6
7
  module Middleware
@@ -14,11 +15,10 @@ module Dynamoid
14
15
  response = @next_chain.call(request)
15
16
  @backoff.call if @backoff
16
17
 
17
- return response
18
+ response
18
19
  end
19
20
  end
20
21
  end
21
22
  end
22
23
  end
23
24
  end
24
-
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
+ # @private
4
5
  module AdapterPlugin
5
6
  class AwsSdkV3
6
7
  module Middleware
@@ -46,12 +47,10 @@ module Dynamoid
46
47
  @scan_count += response.scanned_count
47
48
  throw :stop_pagination if @scan_limit && @scan_count >= @scan_limit
48
49
 
49
- return response
50
+ response
50
51
  end
51
52
  end
52
-
53
53
  end
54
54
  end
55
55
  end
56
56
  end
57
-
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
+ # @private
4
5
  module AdapterPlugin
5
6
  class AwsSdkV3
6
7
  module Middleware
@@ -18,11 +19,10 @@ module Dynamoid
18
19
  throw :stop_pagination
19
20
  end
20
21
 
21
- return response
22
+ response
22
23
  end
23
24
  end
24
25
  end
25
26
  end
26
27
  end
27
28
  end
28
-
@@ -5,12 +5,14 @@ require_relative 'middleware/limit'
5
5
  require_relative 'middleware/start_key'
6
6
 
7
7
  module Dynamoid
8
+ # @private
8
9
  module AdapterPlugin
9
10
  class AwsSdkV3
10
11
  class Query
11
12
  OPTIONS_KEYS = %i[
12
13
  limit hash_key hash_value range_key consistent_read scan_index_forward
13
14
  select index_name batch_size exclusive_start_key record_limit scan_limit
15
+ project
14
16
  ].freeze
15
17
 
16
18
  attr_reader :client, :table, :options, :conditions
@@ -28,8 +30,8 @@ module Dynamoid
28
30
  request = build_request
29
31
 
30
32
  Enumerator.new do |yielder|
31
- api_call = -> (request) do
32
- client.query(request).tap do |response|
33
+ api_call = lambda do |req|
34
+ client.query(req).tap do |response|
33
35
  yielder << response
34
36
  end
35
37
  end
@@ -63,10 +65,11 @@ module Dynamoid
63
65
  batch_size = options[:batch_size]
64
66
  limit = [record_limit, scan_limit, batch_size].compact.min
65
67
 
66
- request[:limit] = limit if limit
67
- request[:table_name] = table.name
68
- request[:key_conditions] = key_conditions
69
- request[:query_filter] = query_filter
68
+ request[:limit] = limit if limit
69
+ request[:table_name] = table.name
70
+ request[:key_conditions] = key_conditions
71
+ request[:query_filter] = query_filter
72
+ request[:attributes_to_get] = attributes_to_get
70
73
 
71
74
  request
72
75
  end
@@ -117,6 +120,12 @@ module Dynamoid
117
120
  result
118
121
  end
119
122
  end
123
+
124
+ def attributes_to_get
125
+ return if options[:project].nil?
126
+
127
+ options[:project].map(&:to_s)
128
+ end
120
129
  end
121
130
  end
122
131
  end
@@ -5,6 +5,7 @@ require_relative 'middleware/limit'
5
5
  require_relative 'middleware/start_key'
6
6
 
7
7
  module Dynamoid
8
+ # @private
8
9
  module AdapterPlugin
9
10
  class AwsSdkV3
10
11
  class Scan
@@ -21,8 +22,8 @@ module Dynamoid
21
22
  request = build_request
22
23
 
23
24
  Enumerator.new do |yielder|
24
- api_call = -> (request) do
25
- client.scan(request).tap do |response|
25
+ api_call = lambda do |req|
26
+ client.scan(req).tap do |response|
26
27
  yielder << response
27
28
  end
28
29
  end
@@ -54,9 +55,10 @@ module Dynamoid
54
55
  batch_size = options[:batch_size]
55
56
  limit = [record_limit, scan_limit, batch_size].compact.min
56
57
 
57
- request[:limit] = limit if limit
58
- request[:table_name] = table.name
59
- request[:scan_filter] = scan_filter
58
+ request[:limit] = limit if limit
59
+ request[:table_name] = table.name
60
+ request[:scan_filter] = scan_filter
61
+ request[:attributes_to_get] = attributes_to_get
60
62
 
61
63
  request
62
64
  end
@@ -75,10 +77,18 @@ module Dynamoid
75
77
  comparison_operator: AwsSdkV3::FIELD_MAP[cond.keys[0]],
76
78
  attribute_value_list: AwsSdkV3.attribute_value_list(AwsSdkV3::FIELD_MAP[cond.keys[0]], cond.values[0].freeze)
77
79
  }
80
+ # nil means operator doesn't require attribute value list
81
+ conditions.delete(:attribute_value_list) if conditions[:attribute_value_list].nil?
78
82
  result[attr] = condition
79
83
  result
80
84
  end
81
85
  end
86
+
87
+ def attributes_to_get
88
+ return if options[:project].nil?
89
+
90
+ options[:project].map(&:to_s)
91
+ end
82
92
  end
83
93
  end
84
94
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
+ # @private
4
5
  module AdapterPlugin
5
6
  class AwsSdkV3
6
7
  # Represents a table. Exposes data from the "DescribeTable" API call, and also
@@ -1,12 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
+ # @private
4
5
  module AdapterPlugin
5
6
  class AwsSdkV3
6
7
  class UntilPastTableStatus
7
- attr_reader :table_name, :status
8
+ attr_reader :client, :table_name, :status
8
9
 
9
- def initialize(table_name, status = :creating)
10
+ def initialize(client, table_name, status = :creating)
11
+ @client = client
10
12
  @table_name = table_name
11
13
  @status = status
12
14
  end
@@ -31,7 +33,7 @@ module Dynamoid
31
33
  # See: http://docs.aws.amazon.com/sdkforruby/api/Aws/DynamoDB/Client.html#describe_table-instance_method
32
34
  rescue Aws::DynamoDB::Errors::ResourceNotFoundException => e
33
35
  case status
34
- when :creating then
36
+ when :creating
35
37
  if counter >= Dynamoid::Config.sync_retry_max_times
36
38
  Dynamoid.logger.warn "Waiting on table metadata for #{table_name} (check #{counter})"
37
39
  retry # start over at first line of begin, does not reset counter
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dynamoid
4
+ # @private
4
5
  module ApplicationTimeZone
5
6
  def self.at(value)
6
7
  case Dynamoid::Config.application_timezone
@@ -25,12 +25,55 @@ module Dynamoid
25
25
  end
26
26
 
27
27
  module ClassMethods
28
- # create a has_many association for this document.
28
+ # Declare a +has_many+ association for this document.
29
29
  #
30
- # @param [Symbol] name the name of the association
31
- # @param [Hash] options options to pass to the association constructor
30
+ # class Category
31
+ # include Dynamoid::Document
32
+ #
33
+ # has_many :posts
34
+ # end
35
+ #
36
+ # Association is an enumerable collection and supports following addition
37
+ # operations:
38
+ #
39
+ # * +create+
40
+ # * +create!+
41
+ # * +destroy_all+
42
+ # * +delete_all+
43
+ # * +delete+
44
+ # * +<<+
45
+ # * +where+
46
+ # * +all+
47
+ # * +empty?+
48
+ # * +size+
49
+ #
50
+ # When a name of an associated class doesn't match an association name a
51
+ # class name should be specified explicitly either with +class+ or
52
+ # +class_name+ option:
53
+ #
54
+ # has_many :labels, class: Tag
55
+ # has_many :labels, class_name: 'Tag'
56
+ #
57
+ # When associated class has own +belongs_to+ association to
58
+ # the current class and the name doesn't match a name of the current
59
+ # class this name can be specified with +inverse_of+ option:
60
+ #
61
+ # class Post
62
+ # include Dynamoid::Document
63
+ #
64
+ # belongs_to :item, class_name: 'Tag'
65
+ # end
66
+ #
67
+ # class Tag
68
+ # include Dynamoid::Document
69
+ #
70
+ # has_many :posts, inverse_of: :item
71
+ # end
72
+ #
73
+ # @param name [Symbol] the name of the association
74
+ # @param options [Hash] options to pass to the association constructor
32
75
  # @option options [Class] :class the target class of the has_many association; that is, the belongs_to class
33
- # @option options [Symbol] :class_name the name of the target class of the association; that is, the name of the belongs_to class
76
+ # @option options [String] :class_name the name of the target class of the association; that is, the name of the belongs_to class
34
77
  # @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
35
78
  #
36
79
  # @since 0.2.0
@@ -38,12 +81,47 @@ module Dynamoid
38
81
  association(:has_many, name, options)
39
82
  end
40
83
 
41
- # create a has_one association for this document.
84
+ # Declare a +has_one+ association for this document.
85
+ #
86
+ # class Image
87
+ # include Dynamoid::Document
42
88
  #
43
- # @param [Symbol] name the name of the association
44
- # @param [Hash] options options to pass to the association constructor
89
+ # has_one :post
90
+ # end
91
+ #
92
+ # Association supports following operations:
93
+ #
94
+ # * +create+
95
+ # * +create!+
96
+ # * +delete+
97
+ #
98
+ # When a name of an associated class doesn't match an association name a
99
+ # class name should be specified explicitly either with +class+ or
100
+ # +class_name+ option:
101
+ #
102
+ # has_one :item, class: Post
103
+ # has_one :item, class_name: 'Post'
104
+ #
105
+ # When associated class has own +belong_to+ association to the current
106
+ # class and the name doesn't match a name of the current class this name
107
+ # can be specified with +inverse_of+ option:
108
+ #
109
+ # class Post
110
+ # include Dynamoid::Document
111
+ #
112
+ # belongs_to :logo, class_name: 'Image'
113
+ # end
114
+ #
115
+ # class Image
116
+ # include Dynamoid::Document
117
+ #
118
+ # has_one :post, inverse_of: :logo
119
+ # end
120
+ #
121
+ # @param name [Symbol] the name of the association
122
+ # @param options [Hash] options to pass to the association constructor
45
123
  # @option options [Class] :class the target class of the has_one association; that is, the belongs_to class
46
- # @option options [Symbol] :class_name the name of the target class of the association; that is, the name of the belongs_to class
124
+ # @option options [String] :class_name the name of the target class of the association; that is, the name of the belongs_to class
47
125
  # @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
48
126
  #
49
127
  # @since 0.2.0
@@ -51,25 +129,110 @@ module Dynamoid
51
129
  association(:has_one, name, options)
52
130
  end
53
131
 
54
- # create a belongs_to association for this document.
132
+ # Declare a +belongs_to+ association for this document.
133
+ #
134
+ # class Post
135
+ # include Dynamoid::Document
55
136
  #
56
- # @param [Symbol] name the name of the association
57
- # @param [Hash] options options to pass to the association constructor
137
+ # belongs_to :categories
138
+ # end
139
+ #
140
+ # Association supports following operations:
141
+ #
142
+ # * +create+
143
+ # * +create!+
144
+ # * +delete+
145
+ #
146
+ # When a name of an associated class doesn't match an association name a
147
+ # class name should be specified explicitly either with +class+ or
148
+ # +class_name+ option:
149
+ #
150
+ # belongs_to :item, class: Post
151
+ # belongs_to :item, class_name: 'Post'
152
+ #
153
+ # When associated class has own +has_many+ or +has_one+ association to
154
+ # the current class and the name doesn't match a name of the current
155
+ # class this name can be specified with +inverse_of+ option:
156
+ #
157
+ # class Category
158
+ # include Dynamoid::Document
159
+ #
160
+ # has_many :items, class_name: 'Post'
161
+ # end
162
+ #
163
+ # class Post
164
+ # include Dynamoid::Document
165
+ #
166
+ # belongs_to :categories, inverse_of: :items
167
+ # end
168
+ #
169
+ # By default a hash key attribute name is +id+. If an associated class
170
+ # uses another name for a hash key attribute it should be specified in
171
+ # the +belongs_to+ association:
172
+ #
173
+ # belongs_to :categories, foreign_key: :uuid
174
+ #
175
+ # @param name [Symbol] the name of the association
176
+ # @param options [Hash] options to pass to the association constructor
58
177
  # @option options [Class] :class the target class of the has_one association; that is, the has_many or has_one class
59
- # @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
178
+ # @option options [String] :class_name the name of the target class of the association; that is, the name of the has_many or has_one class
60
179
  # @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
180
+ # @option options [Symbol] :foreign_key the name of a hash key attribute in the target class
61
181
  #
62
182
  # @since 0.2.0
63
183
  def belongs_to(name, options = {})
64
184
  association(:belongs_to, name, options)
65
185
  end
66
186
 
67
- # create a has_and_belongs_to_many association for this document.
187
+ # Declare a +has_and_belongs_to_many+ association for this document.
188
+ #
189
+ # class Post
190
+ # include Dynamoid::Document
191
+ #
192
+ # has_and_belongs_to_many :tags
193
+ # end
194
+ #
195
+ # Association is an enumerable collection and supports following addition
196
+ # operations:
197
+ #
198
+ # * +create+
199
+ # * +create!+
200
+ # * +destroy_all+
201
+ # * +delete_all+
202
+ # * +delete+
203
+ # * +<<+
204
+ # * +where+
205
+ # * +all+
206
+ # * +empty?+
207
+ # * +size+
208
+ #
209
+ # When a name of an associated class doesn't match an association name a
210
+ # class name should be specified explicitly either with +class+ or
211
+ # +class_name+ option:
212
+ #
213
+ # has_and_belongs_to_many :labels, class: Tag
214
+ # has_and_belongs_to_many :labels, class_name: 'Tag'
215
+ #
216
+ # When associated class has own +has_and_belongs_to_many+ association to
217
+ # the current class and the name doesn't match a name of the current
218
+ # class this name can be specified with +inverse_of+ option:
219
+ #
220
+ # class Tag
221
+ # include Dynamoid::Document
222
+ #
223
+ # has_and_belongs_to_many :items, class_name: 'Post'
224
+ # end
225
+ #
226
+ # class Post
227
+ # include Dynamoid::Document
228
+ #
229
+ # has_and_belongs_to_many :tags, inverse_of: :items
230
+ # end
68
231
  #
69
- # @param [Symbol] name the name of the association
70
- # @param [Hash] options options to pass to the association constructor
232
+ # @param name [Symbol] the name of the association
233
+ # @param options [Hash] options to pass to the association constructor
71
234
  # @option options [Class] :class the target class of the has_and_belongs_to_many association; that is, the belongs_to class
72
- # @option options [Symbol] :class_name the name of the target class of the association; that is, the name of the belongs_to class
235
+ # @option options [String] :class_name the name of the target class of the association; that is, the name of the belongs_to class
73
236
  # @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
74
237
  #
75
238
  # @since 0.2.0
@@ -81,9 +244,9 @@ module Dynamoid
81
244
 
82
245
  # create getters and setters for an association.
83
246
  #
84
- # @param [Symbol] symbol the type (:has_one, :has_many, :has_and_belongs_to_many, :belongs_to) of the association
85
- # @param [Symbol] name the name of the association
86
- # @param [Hash] options options to pass to the association constructor; see above for all valid options
247
+ # @param type [Symbol] the type (:has_one, :has_many, :has_and_belongs_to_many, :belongs_to) of the association
248
+ # @param name [Symbol] the name of the association
249
+ # @param options [Hash] options to pass to the association constructor; see above for all valid options
87
250
  #
88
251
  # @since 0.2.0
89
252
  def association(type, name, options = {})