dynamoid 3.3.0 → 3.7.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 (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