dynamoid 3.3.0 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +104 -1
  3. data/README.md +146 -52
  4. data/lib/dynamoid.rb +1 -0
  5. data/lib/dynamoid/adapter.rb +20 -7
  6. data/lib/dynamoid/adapter_plugin/aws_sdk_v3.rb +70 -37
  7. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/batch_get_item.rb +3 -0
  8. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/create_table.rb +20 -12
  9. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/item_updater.rb +5 -4
  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 +4 -2
  14. data/lib/dynamoid/adapter_plugin/aws_sdk_v3/scan.rb +4 -2
  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 +2 -1
  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 +10 -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 +68 -23
  25. data/lib/dynamoid/associations/single_association.rb +31 -4
  26. data/lib/dynamoid/components.rb +2 -0
  27. data/lib/dynamoid/config.rb +15 -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 +9 -1
  32. data/lib/dynamoid/criteria/chain.rb +421 -46
  33. data/lib/dynamoid/criteria/ignored_conditions_detector.rb +3 -3
  34. data/lib/dynamoid/criteria/key_fields_detector.rb +31 -10
  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 +119 -64
  38. data/lib/dynamoid/document.rb +133 -46
  39. data/lib/dynamoid/dumping.rb +9 -0
  40. data/lib/dynamoid/dynamodb_time_zone.rb +1 -0
  41. data/lib/dynamoid/errors.rb +2 -0
  42. data/lib/dynamoid/fields.rb +251 -39
  43. data/lib/dynamoid/fields/declare.rb +86 -0
  44. data/lib/dynamoid/finders.rb +69 -32
  45. data/lib/dynamoid/identity_map.rb +6 -0
  46. data/lib/dynamoid/indexes.rb +86 -17
  47. data/lib/dynamoid/loadable.rb +2 -2
  48. data/lib/dynamoid/log/formatter.rb +26 -0
  49. data/lib/dynamoid/middleware/identity_map.rb +1 -0
  50. data/lib/dynamoid/persistence.rb +502 -104
  51. data/lib/dynamoid/persistence/import.rb +2 -1
  52. data/lib/dynamoid/persistence/save.rb +1 -0
  53. data/lib/dynamoid/persistence/update_fields.rb +5 -2
  54. data/lib/dynamoid/persistence/update_validations.rb +18 -0
  55. data/lib/dynamoid/persistence/upsert.rb +5 -3
  56. data/lib/dynamoid/primary_key_type_mapping.rb +1 -0
  57. data/lib/dynamoid/railtie.rb +1 -0
  58. data/lib/dynamoid/tasks.rb +3 -1
  59. data/lib/dynamoid/tasks/database.rb +1 -0
  60. data/lib/dynamoid/type_casting.rb +12 -2
  61. data/lib/dynamoid/undumping.rb +8 -0
  62. data/lib/dynamoid/validations.rb +6 -1
  63. data/lib/dynamoid/version.rb +1 -1
  64. metadata +48 -75
  65. data/.coveralls.yml +0 -1
  66. data/.document +0 -5
  67. data/.gitignore +0 -74
  68. data/.rspec +0 -2
  69. data/.rubocop.yml +0 -71
  70. data/.rubocop_todo.yml +0 -55
  71. data/.travis.yml +0 -44
  72. data/Appraisals +0 -22
  73. data/Gemfile +0 -8
  74. data/Rakefile +0 -46
  75. data/Vagrantfile +0 -29
  76. data/docker-compose.yml +0 -7
  77. data/dynamoid.gemspec +0 -57
  78. data/gemfiles/rails_4_2.gemfile +0 -9
  79. data/gemfiles/rails_5_0.gemfile +0 -8
  80. data/gemfiles/rails_5_1.gemfile +0 -8
  81. data/gemfiles/rails_5_2.gemfile +0 -8
  82. data/gemfiles/rails_6_0.gemfile +0 -8
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dynamoid
4
+ module Fields
5
+ # @private
6
+ class Declare
7
+ def initialize(source, name, type, options)
8
+ @source = source
9
+ @name = name.to_sym
10
+ @type = type
11
+ @options = options
12
+ end
13
+
14
+ def call
15
+ # Register new field metadata
16
+ @source.attributes = @source.attributes.merge(
17
+ @name => { type: @type }.merge(@options)
18
+ )
19
+
20
+ # Should be called before `define_attribute_methods` method because it
21
+ # defines an attribute getter itself
22
+ warn_about_method_overriding
23
+
24
+ # Dirty API
25
+ @source.define_attribute_method(@name)
26
+
27
+ # Generate getters and setters as well as other helper methods
28
+ generate_instance_methods
29
+
30
+ # If alias name specified - generate the same instance methods
31
+ if @options[:alias]
32
+ generate_instance_methods_for_alias
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def warn_about_method_overriding
39
+ warn_if_method_exists(@name)
40
+ warn_if_method_exists("#{@name}=")
41
+ warn_if_method_exists("#{@name}?")
42
+ warn_if_method_exists("#{@name}_before_type_cast?")
43
+ end
44
+
45
+ def generate_instance_methods
46
+ # only local variable is visible in `module_eval` block
47
+ name = @name
48
+
49
+ @source.generated_methods.module_eval do
50
+ define_method(name) { read_attribute(name) }
51
+ define_method("#{name}?") do
52
+ value = read_attribute(name)
53
+ case value
54
+ when true then true
55
+ when false, nil then false
56
+ else
57
+ !value.nil?
58
+ end
59
+ end
60
+ define_method("#{name}=") { |value| write_attribute(name, value) }
61
+ define_method("#{name}_before_type_cast") { read_attribute_before_type_cast(name) }
62
+ end
63
+ end
64
+
65
+ def generate_instance_methods_for_alias
66
+ # only local variable is visible in `module_eval` block
67
+ name = @name
68
+
69
+ alias_name = @options[:alias].to_sym
70
+
71
+ @source.generated_methods.module_eval do
72
+ alias_method alias_name, name
73
+ alias_method "#{alias_name}=", "#{name}="
74
+ alias_method "#{alias_name}?", "#{name}?"
75
+ alias_method "#{alias_name}_before_type_cast", "#{name}_before_type_cast"
76
+ end
77
+ end
78
+
79
+ def warn_if_method_exists(method)
80
+ if @source.instance_methods.include?(method.to_sym)
81
+ Dynamoid.logger.warn("Method #{method} generated for the field #{@name} overrides already existing method")
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
@@ -6,6 +6,7 @@ module Dynamoid
6
6
  module Finders
7
7
  extend ActiveSupport::Concern
8
8
 
9
+ # @private
9
10
  RANGE_MAP = {
10
11
  'gt' => :range_greater_than,
11
12
  'lt' => :range_less_than,
@@ -19,9 +20,25 @@ module Dynamoid
19
20
  module ClassMethods
20
21
  # Find one or many objects, specified by one id or an array of ids.
21
22
  #
22
- # @param [Array/String] *id an array of ids or one single id
23
- # @param [Hash] options
23
+ # By default it raises +RecordNotFound+ exception if at least one model
24
+ # isn't found. This behavior can be changed with +raise_error+ option. If
25
+ # specified +raise_error: false+ option then +find+ will not raise the
26
+ # exception.
24
27
  #
28
+ # When a document schema includes range key it always should be specified
29
+ # in +find+ method call. In case it's missing +MissingRangeKey+ exception
30
+ # will be raised.
31
+ #
32
+ # Please note that +find+ doesn't preserve order of models in result when
33
+ # passes multiple ids.
34
+ #
35
+ # Supported following options:
36
+ # * +consistent_read+
37
+ # * +range_key+
38
+ # * +raise_error+
39
+ #
40
+ # @param ids [String|Array] hash key or an array of hash keys
41
+ # @param options [Hash]
25
42
  # @return [Dynamoid::Document] one object or an array of objects, depending on whether the input was an array or not
26
43
  #
27
44
  # @example Find by partition key
@@ -45,36 +62,45 @@ module Dynamoid
45
62
  # @since 0.2.0
46
63
  def find(*ids, **options)
47
64
  if ids.size == 1 && !ids[0].is_a?(Array)
48
- _find_by_id(ids[0], options.merge(raise_error: true))
65
+ _find_by_id(ids[0], options.reverse_merge(raise_error: true))
49
66
  else
50
- _find_all(ids.flatten(1), options.merge(raise_error: true))
67
+ _find_all(ids.flatten(1), options.reverse_merge(raise_error: true))
51
68
  end
52
69
  end
53
70
 
54
- # Return objects found by the given array of ids, either hash keys, or hash/range key combinations using BatchGetItem.
71
+ # Find several models at once.
72
+ #
73
+ # Returns objects found by the given array of ids, either hash keys, or
74
+ # hash/range key combinations using +BatchGetItem+.
75
+ #
55
76
  # Returns empty array if no results found.
56
77
  #
57
- # Uses backoff specified by `Dynamoid::Config.backoff` config option
78
+ # Uses backoff specified by +Dynamoid::Config.backoff+ config option.
58
79
  #
59
- # @param [Array<ID>] ids
60
- # @param [Hash] options: Passed to the underlying query.
80
+ # @param ids [Array] array of primary keys
81
+ # @param options [Hash]
82
+ # @option options [true|false] :consistent_read
83
+ # @option options [true|false] :raise_error
61
84
  #
62
85
  # @example
63
- # find all the user with hash key
86
+ # # Find all the user with hash key
64
87
  # User.find_all(['1', '2', '3'])
65
88
  #
66
- # find all the tweets using hash key and range key with consistent read
67
- # Tweet.find_all([['1', 'red'], ['1', 'green']], :consistent_read => true)
89
+ # # Find all the tweets using hash key and range key with consistent read
90
+ # Tweet.find_all([['1', 'red'], ['1', 'green']], consistent_read: true)
68
91
  def find_all(ids, options = {})
69
92
  ActiveSupport::Deprecation.warn('[Dynamoid] .find_all is deprecated! Call .find instead of')
70
93
 
71
94
  _find_all(ids, options)
72
95
  end
73
96
 
74
- # Find one object directly by id.
75
- #
76
- # @param [String] id the id of the object to find
97
+ # Find one object directly by primary key.
77
98
  #
99
+ # @param id [String] the id of the object to find
100
+ # @param options [Hash]
101
+ # @option options [true|false] :consistent_read
102
+ # @option options [true|false] :raise_error
103
+ # @option options [Scalar value] :range_key
78
104
  # @return [Dynamoid::Document] the found object, or nil if nothing was found
79
105
  #
80
106
  # @example Find by partition key
@@ -90,7 +116,10 @@ module Dynamoid
90
116
  _find_by_id(id, options)
91
117
  end
92
118
 
119
+ # @private
93
120
  def _find_all(ids, options = {})
121
+ raise Errors::MissingRangeKey if range_key && ids.any? { |pk, sk| sk.nil? }
122
+
94
123
  if range_key
95
124
  ids = ids.map do |pk, sk|
96
125
  sk_casted = TypeCasting.cast_field(sk, attributes[range_key])
@@ -131,7 +160,10 @@ module Dynamoid
131
160
  end
132
161
  end
133
162
 
163
+ # @private
134
164
  def _find_by_id(id, options = {})
165
+ raise Errors::MissingRangeKey if range_key && options[:range_key].nil?
166
+
135
167
  if range_key
136
168
  key = options[:range_key]
137
169
  key_casted = TypeCasting.cast_field(key, attributes[range_key])
@@ -149,10 +181,10 @@ module Dynamoid
149
181
  end
150
182
  end
151
183
 
152
- # Find one object directly by hash and range keys
184
+ # Find one object directly by hash and range keys.
153
185
  #
154
- # @param [String] hash_key of the object to find
155
- # @param [String/Number] range_key of the object to find
186
+ # @param hash_key [Scalar value] hash key of the object to find
187
+ # @param range_key [Scalar value] range key of the object to find
156
188
  #
157
189
  def find_by_composite_key(hash_key, range_key, options = {})
158
190
  ActiveSupport::Deprecation.warn('[Dynamoid] .find_by_composite_key is deprecated! Call .find instead of')
@@ -169,6 +201,7 @@ module Dynamoid
169
201
  # range :level, :integer
170
202
  # table :key => :chamber_type
171
203
  # end
204
+ #
172
205
  # ChamberType.find_all_by_composite_key('DustVault', range_greater_than: 1)
173
206
  #
174
207
  # @param [String] hash_key of the objects to find
@@ -183,7 +216,7 @@ module Dynamoid
183
216
  def find_all_by_composite_key(hash_key, options = {})
184
217
  ActiveSupport::Deprecation.warn('[Dynamoid] .find_all_composite_key is deprecated! Call .where instead of')
185
218
 
186
- Dynamoid.adapter.query(table_name, options.merge(hash_value: hash_key)).flat_map{ |i| i }.collect do |item|
219
+ Dynamoid.adapter.query(table_name, options.merge(hash_value: hash_key)).flat_map { |i| i }.collect do |item|
187
220
  from_database(item)
188
221
  end
189
222
  end
@@ -193,20 +226,22 @@ module Dynamoid
193
226
  # @example
194
227
  # class User
195
228
  # include Dynamoid::Document
196
- # field :email, :string
197
- # field :age, :integer
198
- # field :gender, :string
199
- # field :rank :number
229
+ #
200
230
  # table :key => :email
201
- # global_secondary_index :hash_key => :age, :range_key => :rank
231
+ # global_secondary_index hash_key: :age, range_key: :rank
232
+ #
233
+ # field :email, :string
234
+ # field :age, :integer
235
+ # field :gender, :string
236
+ # field :rank :number
202
237
  # end
238
+ #
203
239
  # # NOTE: the first param and the second param are both hashes,
204
240
  # # so curly braces must be used on first hash param if sending both params
205
- # User.find_all_by_secondary_index({:age => 5}, :range => {"rank.lte" => 10})
241
+ # User.find_all_by_secondary_index({ age: 5 }, range: { "rank.lte": 10 })
206
242
  #
207
- # @param [Hash] eg: {:age => 5}
208
- # @param [Hash] eg: {"rank.lte" => 10}
209
- # @param [Hash] options - query filter, projected keys, scan_index_forward etc
243
+ # @param hash [Hash] conditions for the hash key e.g. +{ age: 5 }+
244
+ # @param options [Hash] conditions on range key e.g. +{ "rank.lte": 10 }, query filter, projected keys, scan_index_forward etc.
210
245
  # @return [Array] an array of all matching items
211
246
  def find_all_by_secondary_index(hash, options = {})
212
247
  ActiveSupport::Deprecation.warn('[Dynamoid] .find_all_by_secondary_index is deprecated! Call .where instead of')
@@ -240,12 +275,13 @@ module Dynamoid
240
275
  opts[range_op_mapped] = range_key_value
241
276
  end
242
277
  dynamo_options = opts.merge(options.reject { |key, _| key == :range })
243
- Dynamoid.adapter.query(table_name, dynamo_options).flat_map{ |i| i }.map do |item|
278
+ Dynamoid.adapter.query(table_name, dynamo_options).flat_map { |i| i }.map do |item|
244
279
  from_database(item)
245
280
  end
246
281
  end
247
282
 
248
- # Find using exciting method_missing finders attributes. Uses criteria chains under the hood to accomplish this neatness.
283
+ # Find using exciting method_missing finders attributes. Uses criteria
284
+ # chains under the hood to accomplish this neatness.
249
285
  #
250
286
  # @example find a user by a first name
251
287
  # User.find_by_first_name('Josh')
@@ -253,8 +289,9 @@ module Dynamoid
253
289
  # @example find all users by first and last name
254
290
  # User.find_all_by_first_name_and_last_name('Josh', 'Symonds')
255
291
  #
256
- # @return [Dynamoid::Document/Array] the found object, or an array of found objects if all was somewhere in the method
292
+ # @return [Dynamoid::Document|Array] the found object, or an array of found objects if all was somewhere in the method
257
293
  #
294
+ # @private
258
295
  # @since 0.2.0
259
296
  def method_missing(method, *args)
260
297
  if method =~ /find/
@@ -267,9 +304,9 @@ module Dynamoid
267
304
  chain = chain.where({}.tap { |h| attributes.each_with_index { |attr, index| h[attr.to_sym] = args[index] } })
268
305
 
269
306
  if finder =~ /all/
270
- return chain.all
307
+ chain.all
271
308
  else
272
- return chain.first
309
+ chain.first
273
310
  end
274
311
  else
275
312
  super
@@ -13,6 +13,7 @@ module Dynamoid
13
13
  @identity_map ||= {}
14
14
  end
15
15
 
16
+ # @private
16
17
  def from_database(attrs = {})
17
18
  return super if identity_map_off?
18
19
 
@@ -29,6 +30,7 @@ module Dynamoid
29
30
  document
30
31
  end
31
32
 
33
+ # @private
32
34
  def find_by_id(id, options = {})
33
35
  return super if identity_map_off?
34
36
 
@@ -41,6 +43,7 @@ module Dynamoid
41
43
  identity_map[key] || super
42
44
  end
43
45
 
46
+ # @private
44
47
  def identity_map_key(attrs)
45
48
  key = attrs[hash_key].to_s
46
49
  key += "::#{attrs[range_key]}" if range_key
@@ -60,6 +63,7 @@ module Dynamoid
60
63
  self.class.identity_map
61
64
  end
62
65
 
66
+ # @private
63
67
  def save(*args)
64
68
  return super if self.class.identity_map_off?
65
69
 
@@ -69,6 +73,7 @@ module Dynamoid
69
73
  result
70
74
  end
71
75
 
76
+ # @private
72
77
  def delete
73
78
  return super if self.class.identity_map_off?
74
79
 
@@ -76,6 +81,7 @@ module Dynamoid
76
81
  super
77
82
  end
78
83
 
84
+ # @private
79
85
  def identity_map_key
80
86
  key = hash_key.to_s
81
87
  key += "::#{range_value}" if self.class.range_key
@@ -15,19 +15,43 @@ module Dynamoid
15
15
  # Defines a Global Secondary index on a table. Keys can be specified as
16
16
  # hash-only, or hash & range.
17
17
  #
18
- # @param [Hash] options options to pass for this table
19
- # @option options [Symbol] :name the name for the index; this still gets
20
- # namespaced. If not specified, will use a default name.
21
- # @option options [Symbol] :hash_key the index hash key column.
22
- # @option options [Symbol] :range_key the index range key column (if
18
+ # class Post
19
+ # include Dynamoid::Document
20
+ #
21
+ # field :category
22
+ #
23
+ # global_secondary_indexes hash_key: :category
24
+ # end
25
+ #
26
+ # The full example with all the options being specified:
27
+ #
28
+ # global_secondary_indexes hash_key: :category,
29
+ # range_key: :created_at,
30
+ # name: 'posts_category_created_at_index',
31
+ # projected_attributes: :all,
32
+ # read_capacity: 100,
33
+ # write_capacity: 20
34
+ #
35
+ # Global secondary index should be declared after fields for mentioned
36
+ # hash key and optional range key are declared (with method +field+)
37
+ #
38
+ # The only mandatory option is +hash_key+. Raises
39
+ # +Dynamoid::Errors::InvalidIndex+ exception if passed incorrect
40
+ # options.
41
+ #
42
+ # @param [Hash] options the options to pass for this table
43
+ # @option options [Symbol] name the name for the index; this still gets
44
+ # namespaced. If not specified, will use a default name.
45
+ # @option options [Symbol] hash_key the index hash key column.
46
+ # @option options [Symbol] range_key the index range key column (if
23
47
  # applicable).
24
- # @option options [Symbol, Array<Symbol>] :projected_attributes table
25
- # attributes to project for this index. Can be :keys_only, :all
48
+ # @option options [Symbol, Array<Symbol>] projected_attributes table
49
+ # attributes to project for this index. Can be +:keys_only+, +:all+
26
50
  # or an array of included fields. If not specified, defaults to
27
- # :keys_only.
28
- # @option options [Integer] :read_capacity set the read capacity for the
51
+ # +:keys_only+.
52
+ # @option options [Integer] read_capacity set the read capacity for the
29
53
  # index; does not work on existing indexes.
30
- # @option options [Integer] :write_capacity set the write capacity for
54
+ # @option options [Integer] write_capacity set the write capacity for
31
55
  # the index; does not work on existing indexes.
32
56
  def global_secondary_index(options = {})
33
57
  unless options.present?
@@ -55,14 +79,38 @@ module Dynamoid
55
79
  # Defines a local secondary index on a table. Will use the same primary
56
80
  # hash key as the table.
57
81
  #
82
+ # class Comment
83
+ # include Dynamoid::Document
84
+ #
85
+ # table hash_key: :post_id
86
+ # range :created_at, :datetime
87
+ # field :author_id
88
+ #
89
+ # local_secondary_indexes hash_key: :author_id
90
+ # end
91
+ #
92
+ # The full example with all the options being specified:
93
+ #
94
+ # local_secondary_indexes range_key: :created_at,
95
+ # name: 'posts_created_at_index',
96
+ # projected_attributes: :all
97
+ #
98
+ # Local secondary index should be declared after fields for mentioned
99
+ # hash key and optional range key are declared (with method +field+) as
100
+ # well as after +table+ method call.
101
+ #
102
+ # The only mandatory option is +range_key+. Raises
103
+ # +Dynamoid::Errors::InvalidIndex+ exception if passed incorrect
104
+ # options.
105
+ #
58
106
  # @param [Hash] options options to pass for this index.
59
- # @option options [Symbol] :name the name for the index; this still gets
107
+ # @option options [Symbol] name the name for the index; this still gets
60
108
  # namespaced. If not specified, a name is automatically generated.
61
- # @option options [Symbol] :range_key the range key column for the index.
62
- # @option options [Symbol, Array<Symbol>] :projected_attributes table
63
- # attributes to project for this index. Can be :keys_only, :all
109
+ # @option options [Symbol] range_key the range key column for the index.
110
+ # @option options [Symbol, Array<Symbol>] projected_attributes table
111
+ # attributes to project for this index. Can be +:keys_only+, +:all+
64
112
  # or an array of included fields. If not specified, defaults to
65
- # :keys_only.
113
+ # +:keys_only+.
66
114
  def local_secondary_index(options = {})
67
115
  unless options.present?
68
116
  raise Dynamoid::Errors::InvalidIndex, 'empty index definition'
@@ -94,11 +142,28 @@ module Dynamoid
94
142
  self
95
143
  end
96
144
 
145
+ # Returns an index by its hash key and optional range key.
146
+ #
147
+ # It works only for indexes without explicit name declared.
148
+ #
149
+ # @param hash [scalar] the hash key used to declare an index
150
+ # @param range [scalar] the range key used to declare an index (optional)
151
+ # @return [Dynamoid::Indexes::Index, nil] index object or nil if it isn't found
97
152
  def find_index(hash, range = nil)
98
153
  index = indexes[index_key(hash, range)]
99
154
  index
100
155
  end
101
156
 
157
+ # Returns an index by its name
158
+ #
159
+ # @param name [string, symbol] the name of the index to lookup
160
+ # @return [Dynamoid::Indexes::Index, nil] index object or nil if it isn't found
161
+ def find_index_by_name(name)
162
+ string_name = name.to_s
163
+ indexes.each_value.detect{ |i| i.name.to_s == string_name }
164
+ end
165
+
166
+
102
167
  # Returns true iff the provided hash[,range] key combo is a local
103
168
  # secondary index.
104
169
  #
@@ -150,6 +215,10 @@ module Dynamoid
150
215
  local_secondary_indexes.merge(global_secondary_indexes)
151
216
  end
152
217
 
218
+ # Returns an array of hash keys for all the declared Glocal Secondary
219
+ # Indexes.
220
+ #
221
+ # @return [Array[String]] array of hash keys
153
222
  def indexed_hash_keys
154
223
  global_secondary_indexes.map do |_name, index|
155
224
  index.hash_key.to_s
@@ -165,8 +234,8 @@ module Dynamoid
165
234
  DEFAULT_PROJECTION_TYPE = :keys_only
166
235
 
167
236
  attr_accessor :name, :dynamoid_class, :type, :hash_key, :range_key,
168
- :hash_key_schema, :range_key_schema, :projected_attributes,
169
- :read_capacity, :write_capacity
237
+ :hash_key_schema, :range_key_schema, :projected_attributes,
238
+ :read_capacity, :write_capacity
170
239
 
171
240
  validate do
172
241
  validate_index_type