dynamoid 0.2.0 → 0.3.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 (97) hide show
  1. data/Dynamoid.gemspec +65 -3
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +6 -0
  4. data/README.markdown +117 -22
  5. data/Rakefile +22 -9
  6. data/VERSION +1 -1
  7. data/doc/.nojekyll +0 -0
  8. data/doc/Dynamoid.html +300 -0
  9. data/doc/Dynamoid/Adapter.html +1387 -0
  10. data/doc/Dynamoid/Adapter/AwsSdk.html +1561 -0
  11. data/doc/Dynamoid/Adapter/Local.html +1487 -0
  12. data/doc/Dynamoid/Associations.html +131 -0
  13. data/doc/Dynamoid/Associations/Association.html +1706 -0
  14. data/doc/Dynamoid/Associations/BelongsTo.html +339 -0
  15. data/doc/Dynamoid/Associations/ClassMethods.html +723 -0
  16. data/doc/Dynamoid/Associations/HasAndBelongsToMany.html +339 -0
  17. data/doc/Dynamoid/Associations/HasMany.html +339 -0
  18. data/doc/Dynamoid/Associations/HasOne.html +339 -0
  19. data/doc/Dynamoid/Components.html +202 -0
  20. data/doc/Dynamoid/Config.html +395 -0
  21. data/doc/Dynamoid/Config/Options.html +609 -0
  22. data/doc/Dynamoid/Criteria.html +131 -0
  23. data/doc/Dynamoid/Criteria/Chain.html +759 -0
  24. data/doc/Dynamoid/Criteria/ClassMethods.html +98 -0
  25. data/doc/Dynamoid/Document.html +512 -0
  26. data/doc/Dynamoid/Document/ClassMethods.html +581 -0
  27. data/doc/Dynamoid/Errors.html +118 -0
  28. data/doc/Dynamoid/Errors/DocumentNotValid.html +210 -0
  29. data/doc/Dynamoid/Errors/Error.html +130 -0
  30. data/doc/Dynamoid/Errors/InvalidField.html +133 -0
  31. data/doc/Dynamoid/Errors/MissingRangeKey.html +133 -0
  32. data/doc/Dynamoid/Fields.html +649 -0
  33. data/doc/Dynamoid/Fields/ClassMethods.html +264 -0
  34. data/doc/Dynamoid/Finders.html +128 -0
  35. data/doc/Dynamoid/Finders/ClassMethods.html +502 -0
  36. data/doc/Dynamoid/Indexes.html +308 -0
  37. data/doc/Dynamoid/Indexes/ClassMethods.html +351 -0
  38. data/doc/Dynamoid/Indexes/Index.html +1089 -0
  39. data/doc/Dynamoid/Persistence.html +653 -0
  40. data/doc/Dynamoid/Persistence/ClassMethods.html +568 -0
  41. data/doc/Dynamoid/Validations.html +399 -0
  42. data/doc/_index.html +439 -0
  43. data/doc/class_list.html +47 -0
  44. data/doc/css/common.css +1 -0
  45. data/doc/css/full_list.css +55 -0
  46. data/doc/css/style.css +322 -0
  47. data/doc/file.LICENSE.html +66 -0
  48. data/doc/file.README.html +279 -0
  49. data/doc/file_list.html +52 -0
  50. data/doc/frames.html +13 -0
  51. data/doc/index.html +279 -0
  52. data/doc/js/app.js +205 -0
  53. data/doc/js/full_list.js +173 -0
  54. data/doc/js/jquery.js +16 -0
  55. data/doc/method_list.html +1054 -0
  56. data/doc/top-level-namespace.html +105 -0
  57. data/lib/dynamoid.rb +2 -1
  58. data/lib/dynamoid/adapter.rb +77 -6
  59. data/lib/dynamoid/adapter/aws_sdk.rb +96 -16
  60. data/lib/dynamoid/adapter/local.rb +84 -15
  61. data/lib/dynamoid/associations.rb +53 -4
  62. data/lib/dynamoid/associations/association.rb +154 -26
  63. data/lib/dynamoid/associations/belongs_to.rb +32 -6
  64. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +29 -3
  65. data/lib/dynamoid/associations/has_many.rb +30 -4
  66. data/lib/dynamoid/associations/has_one.rb +26 -3
  67. data/lib/dynamoid/components.rb +7 -5
  68. data/lib/dynamoid/config.rb +15 -2
  69. data/lib/dynamoid/config/options.rb +8 -0
  70. data/lib/dynamoid/criteria.rb +7 -2
  71. data/lib/dynamoid/criteria/chain.rb +55 -8
  72. data/lib/dynamoid/document.rb +68 -7
  73. data/lib/dynamoid/errors.rb +17 -2
  74. data/lib/dynamoid/fields.rb +44 -1
  75. data/lib/dynamoid/finders.rb +32 -6
  76. data/lib/dynamoid/indexes.rb +22 -2
  77. data/lib/dynamoid/indexes/index.rb +48 -7
  78. data/lib/dynamoid/persistence.rb +111 -51
  79. data/lib/dynamoid/validations.rb +36 -0
  80. data/spec/app/models/address.rb +2 -1
  81. data/spec/app/models/camel_case.rb +11 -0
  82. data/spec/app/models/magazine.rb +4 -1
  83. data/spec/app/models/sponsor.rb +3 -1
  84. data/spec/app/models/subscription.rb +5 -1
  85. data/spec/app/models/user.rb +10 -1
  86. data/spec/dynamoid/associations/association_spec.rb +67 -1
  87. data/spec/dynamoid/associations/belongs_to_spec.rb +16 -1
  88. data/spec/dynamoid/associations/has_and_belongs_to_many_spec.rb +7 -0
  89. data/spec/dynamoid/associations/has_many_spec.rb +14 -0
  90. data/spec/dynamoid/associations/has_one_spec.rb +10 -1
  91. data/spec/dynamoid/criteria_spec.rb +5 -1
  92. data/spec/dynamoid/document_spec.rb +23 -3
  93. data/spec/dynamoid/fields_spec.rb +10 -1
  94. data/spec/dynamoid/indexes/index_spec.rb +19 -0
  95. data/spec/dynamoid/persistence_spec.rb +24 -0
  96. data/spec/dynamoid/validations_spec.rb +36 -0
  97. metadata +105 -4
@@ -1,15 +1,24 @@
1
1
  # encoding: utf-8
2
2
  module Dynamoid #:nodoc:
3
3
 
4
- # The BelongsTo association.
4
+ # The belongs_to association. For belongs_to, we reference only a single target instead of multiple records; that target is the
5
+ # object to which the association object is associated.
5
6
  module Associations
6
7
  class BelongsTo
7
8
  include Dynamoid::Associations::Association
8
9
 
10
+ # Is this object equal to the association's target?
11
+ #
12
+ # @return [Boolean] true/false
13
+ #
14
+ # @since 0.2.0
9
15
  def ==(other)
10
16
  target == other
11
17
  end
12
18
 
19
+ # Delegate methods we don't find directly to the target.
20
+ #
21
+ # @since 0.2.0
13
22
  def method_missing(method, *args)
14
23
  if target.respond_to?(method)
15
24
  target.send(method, *args)
@@ -20,28 +29,45 @@ module Dynamoid #:nodoc:
20
29
 
21
30
  private
22
31
 
32
+ # Find the target of the belongs_to association.
33
+ #
34
+ # @return [Dynamoid::Document] the found target (or nil if nothing)
35
+ #
36
+ # @since 0.2.0
23
37
  def target
24
38
  records.first
25
39
  end
26
40
 
41
+ # Find the target association, either has_many or has_one. Uses either options[:inverse_of] or the source class name and default parsing to
42
+ # return the most likely name for the target association.
43
+ #
44
+ # @since 0.2.0
27
45
  def target_association
28
- has_many_key_name = source.class.to_s.pluralize.downcase.to_sym
29
- has_one_key_name = source.class.to_s.downcase.to_sym
46
+ has_many_key_name = options[:inverse_of] || source.class.to_s.underscore.pluralize.to_sym
47
+ has_one_key_name = options[:inverse_of] || source.class.to_s.underscore.to_sym
30
48
  if !target_class.associations[has_many_key_name].nil?
31
49
  return has_many_key_name if target_class.associations[has_many_key_name][:type] == :has_many
32
- elsif !target_class.associations[has_one_key_name].nil?
50
+ end
51
+
52
+ if !target_class.associations[has_one_key_name].nil?
33
53
  return has_one_key_name if target_class.associations[has_one_key_name][:type] == :has_one
34
54
  end
35
55
  end
36
56
 
57
+ # Associate a source object to this association.
58
+ #
59
+ # @since 0.2.0
37
60
  def associate_target(object)
38
61
  object.update_attribute(target_attribute, target_ids.merge(Array(source.id)))
39
62
  end
40
-
63
+
64
+ # Disassociate a source object from this association.
65
+ #
66
+ # @since 0.2.0
41
67
  def disassociate_target(object)
42
68
  source.update_attribute(source_attribute, target_ids - Array(source.id))
43
69
  end
44
70
  end
45
71
  end
46
72
 
47
- end
73
+ end
@@ -1,29 +1,55 @@
1
1
  # encoding: utf-8
2
2
  module Dynamoid #:nodoc:
3
3
 
4
- # The habtm association.
4
+ # The has and belongs to many association.
5
5
  module Associations
6
6
  class HasAndBelongsToMany
7
7
  include Dynamoid::Associations::Association
8
8
 
9
+ # Is this array equal to the association's records?
10
+ #
11
+ # @return [Boolean] true/false
12
+ #
13
+ # @since 0.2.0
9
14
  def ==(other)
10
15
  records == Array(other)
11
16
  end
12
17
 
18
+ # Delegate methods we don't find directly to the records array.
19
+ #
20
+ # @since 0.2.0
21
+ def method_missing(method, *args)
22
+ if records.respond_to?(method)
23
+ records.send(method, *args)
24
+ else
25
+ super
26
+ end
27
+ end
28
+
13
29
  private
14
30
 
31
+ # Find the target association, always another :has_and_belongs_to_many association. Uses either options[:inverse_of] or the source class name
32
+ # and default parsing to return the most likely name for the target association.
33
+ #
34
+ # @since 0.2.0
15
35
  def target_association
16
- key_name = source.class.to_s.pluralize.downcase.to_sym
36
+ key_name = options[:inverse_of] || source.class.to_s.pluralize.underscore.to_sym
17
37
  guess = target_class.associations[key_name]
18
38
  return nil if guess.nil? || guess[:type] != :has_and_belongs_to_many
19
39
  key_name
20
40
  end
21
41
 
42
+ # Associate a source object to this association.
43
+ #
44
+ # @since 0.2.0
22
45
  def associate_target(object)
23
46
  ids = object.send(target_attribute) || Set.new
24
47
  object.update_attribute(target_attribute, ids.merge(Array(source.id)))
25
48
  end
26
49
 
50
+ # Disassociate a source object from this association.
51
+ #
52
+ # @since 0.2.0
27
53
  def disassociate_target(object)
28
54
  ids = object.send(target_attribute) || Set.new
29
55
  object.update_attribute(target_attribute, ids - Array(source.id))
@@ -31,4 +57,4 @@ module Dynamoid #:nodoc:
31
57
  end
32
58
  end
33
59
 
34
- end
60
+ end
@@ -1,28 +1,54 @@
1
1
  # encoding: utf-8
2
2
  module Dynamoid #:nodoc:
3
3
 
4
- # The HasMany association.
4
+ # The has_many association.
5
5
  module Associations
6
6
  class HasMany
7
7
  include Dynamoid::Associations::Association
8
8
 
9
+ # Is this array equal to the association's records?
10
+ #
11
+ # @return [Boolean] true/false
12
+ #
13
+ # @since 0.2.0
9
14
  def ==(other)
10
15
  records == Array(other)
11
16
  end
12
17
 
18
+ # Delegate methods we don't find directly to the records array.
19
+ #
20
+ # @since 0.2.0
21
+ def method_missing(method, *args)
22
+ if records.respond_to?(method)
23
+ records.send(method, *args)
24
+ else
25
+ super
26
+ end
27
+ end
28
+
13
29
  private
14
30
 
31
+ # Find the target association, always a :belongs_to association. Uses either options[:inverse_of] or the source class name
32
+ # and default parsing to return the most likely name for the target association.
33
+ #
34
+ # @since 0.2.0
15
35
  def target_association
16
- key_name = source.class.to_s.singularize.downcase.to_sym
36
+ key_name = options[:inverse_of] || source.class.to_s.singularize.underscore.to_sym
17
37
  guess = target_class.associations[key_name]
18
38
  return nil if guess.nil? || guess[:type] != :belongs_to
19
39
  key_name
20
40
  end
21
-
41
+
42
+ # Associate a source object to this association.
43
+ #
44
+ # @since 0.2.0
22
45
  def associate_target(object)
23
46
  object.update_attribute(target_attribute, Set[source.id])
24
47
  end
25
48
 
49
+ # Disassociate a source object from this association.
50
+ #
51
+ # @since 0.2.0
26
52
  def disassociate_target(object)
27
53
  object.update_attribute(target_attribute, nil)
28
54
  end
@@ -30,4 +56,4 @@ module Dynamoid #:nodoc:
30
56
  end
31
57
  end
32
58
 
33
- end
59
+ end
@@ -6,10 +6,18 @@ module Dynamoid #:nodoc:
6
6
  class HasOne
7
7
  include Dynamoid::Associations::Association
8
8
 
9
+ # Is this object equal to the association's target?
10
+ #
11
+ # @return [Boolean] true/false
12
+ #
13
+ # @since 0.2.0
9
14
  def ==(other)
10
15
  target == other
11
16
  end
12
17
 
18
+ # Delegate methods we don't find directly to the target.
19
+ #
20
+ # @since 0.2.0
13
21
  def method_missing(method, *args)
14
22
  if target.respond_to?(method)
15
23
  target.send(method, *args)
@@ -20,21 +28,36 @@ module Dynamoid #:nodoc:
20
28
 
21
29
  private
22
30
 
31
+ # Find the target of the has_one association.
32
+ #
33
+ # @return [Dynamoid::Document] the found target (or nil if nothing)
34
+ #
35
+ # @since 0.2.0
23
36
  def target
24
37
  records.first
25
38
  end
26
-
39
+
40
+ # Find the target association, always a :belongs_to association. Uses either options[:inverse_of] or the source class name
41
+ # and default parsing to return the most likely name for the target association.
42
+ #
43
+ # @since 0.2.0
27
44
  def target_association
28
- key_name = source.class.to_s.singularize.downcase.to_sym
45
+ key_name = options[:inverse_of] || source.class.to_s.singularize.underscore.to_sym
29
46
  guess = target_class.associations[key_name]
30
47
  return nil if guess.nil? || guess[:type] != :belongs_to
31
48
  key_name
32
49
  end
33
50
 
51
+ # Associate a source object to this association.
52
+ #
53
+ # @since 0.2.0
34
54
  def associate_target(object)
35
- object.update_attribute(target_attribute, source.id)
55
+ object.update_attribute(target_attribute, Set[source.id])
36
56
  end
37
57
 
58
+ # Disassociate a source object from this association.
59
+ #
60
+ # @since 0.2.0
38
61
  def disassociate_target(object)
39
62
  source.update_attribute(source_attribute, nil)
40
63
  end
@@ -1,10 +1,11 @@
1
1
  # encoding: utf-8
2
- module Dynamoid #:nodoc
3
- module Components #:nodoc
2
+ module Dynamoid
3
+
4
+ # All modules that a Document is composed of are defined in this
5
+ # module, to keep the document class from getting too cluttered.
6
+ module Components
4
7
  extend ActiveSupport::Concern
5
8
 
6
- # All modules that a +Document+ is composed of are defined in this
7
- # module, to keep the document class from getting too cluttered.
8
9
  included do
9
10
  extend ActiveModel::Translation
10
11
  extend ActiveModel::Callbacks
@@ -16,10 +17,10 @@ module Dynamoid #:nodoc
16
17
  end
17
18
 
18
19
  include ActiveModel::Conversion
20
+ include ActiveModel::Dirty
19
21
  include ActiveModel::MassAssignmentSecurity
20
22
  include ActiveModel::Naming
21
23
  include ActiveModel::Observing
22
- include ActiveModel::Validations
23
24
  include ActiveModel::Serializers::JSON
24
25
  include ActiveModel::Serializers::Xml
25
26
  include Dynamoid::Fields
@@ -28,5 +29,6 @@ module Dynamoid #:nodoc
28
29
  include Dynamoid::Finders
29
30
  include Dynamoid::Associations
30
31
  include Dynamoid::Criteria
32
+ include Dynamoid::Validations
31
33
  end
32
34
  end
@@ -2,31 +2,44 @@
2
2
  require "uri"
3
3
  require "dynamoid/config/options"
4
4
 
5
- module Dynamoid #:nodoc
5
+ module Dynamoid
6
6
 
7
+ # Contains all the basic configuration information required for Dynamoid: both sensible defaults and required fields.
7
8
  module Config
8
9
  extend self
9
10
  extend Options
10
11
  include ActiveModel::Observing
11
12
 
13
+ # All the default options.
12
14
  option :adapter, :default => 'local'
13
15
  option :namespace, :default => defined?(Rails) ? "dynamoid_#{Rails.application.class.parent_name}_#{Rails.env}" : "dynamoid"
14
16
  option :logger, :default => defined?(Rails)
15
17
  option :access_key
16
18
  option :secret_key
19
+ option :read_capacity, :default => 100
20
+ option :write_capacity, :default => 20
17
21
  option :warn_on_scan, :default => true
18
22
  option :partitioning, :default => false
19
23
  option :partition_size, :default => 200
20
24
  option :included_models, :default => []
21
25
 
26
+ # The default logger for Dynamoid: either the Rails logger or just stdout.
27
+ #
28
+ # @since 0.2.0
22
29
  def default_logger
23
30
  defined?(Rails) && Rails.respond_to?(:logger) ? Rails.logger : ::Logger.new($stdout)
24
31
  end
25
32
 
33
+ # Returns the assigned logger instance.
34
+ #
35
+ # @since 0.2.0
26
36
  def logger
27
37
  @logger ||= default_logger
28
38
  end
29
39
 
40
+ # If you want to, set the logger manually to any output you'd like. Or pass false or nil to disable logging entirely.
41
+ #
42
+ # @since 0.2.0
30
43
  def logger=(logger)
31
44
  case logger
32
45
  when false, nil then @logger = nil
@@ -37,4 +50,4 @@ module Dynamoid #:nodoc
37
50
  end
38
51
 
39
52
  end
40
- end
53
+ end
@@ -11,6 +11,8 @@ module Dynamoid #:nodoc
11
11
  # options.defaults
12
12
  #
13
13
  # @return [ Hash ] The default options.
14
+ #
15
+ # @since 0.2.0
14
16
  def defaults
15
17
  @defaults ||= {}
16
18
  end
@@ -24,6 +26,8 @@ module Dynamoid #:nodoc
24
26
  # @param [ Hash ] options Extras for the option.
25
27
  #
26
28
  # @option options [ Object ] :default The default value.
29
+ #
30
+ # @since 0.2.0
27
31
  def option(name, options = {})
28
32
  defaults[name] = settings[name] = options[:default]
29
33
 
@@ -52,6 +56,8 @@ module Dynamoid #:nodoc
52
56
  # config.reset
53
57
  #
54
58
  # @return [ Hash ] The defaults.
59
+ #
60
+ # @since 0.2.0
55
61
  def reset
56
62
  settings.replace(defaults)
57
63
  end
@@ -62,6 +68,8 @@ module Dynamoid #:nodoc
62
68
  # options.settings
63
69
  #
64
70
  # @return [ Hash ] The setting options.
71
+ #
72
+ # @since 0.2.0
65
73
  def settings
66
74
  @settings ||= {}
67
75
  end
@@ -1,14 +1,19 @@
1
1
  # encoding: utf-8
2
2
  require 'dynamoid/criteria/chain'
3
3
 
4
- module Dynamoid #:nodoc:
4
+ module Dynamoid
5
5
 
6
- # This module defines criteria and criteria chains.
6
+ # Allows classes to be queried by where, all, first, and each and return criteria chains.
7
7
  module Criteria
8
8
  extend ActiveSupport::Concern
9
9
 
10
10
  module ClassMethods
11
+
11
12
  [:where, :all, :first, :each].each do |meth|
13
+ # Return a criteria chain in response to a method that will begin or end a chain. For more information,
14
+ # see Dynamoid::Criteria::Chain.
15
+ #
16
+ # @since 0.2.0
12
17
  define_method(meth) do |*args|
13
18
  chain = Dynamoid::Criteria::Chain.new(self)
14
19
  if args
@@ -2,41 +2,75 @@
2
2
  module Dynamoid #:nodoc:
3
3
  module Criteria
4
4
 
5
- # The class object that gets passed around indicating state of a building query.
6
- # Also provides query execution.
5
+ # The criteria chain is equivalent to an ActiveRecord relation (and realistically I should change the name from
6
+ # chain to relation). It is a chainable object that builds up a query and eventually executes it either on an index
7
+ # or by a full table scan.
7
8
  class Chain
8
9
  attr_accessor :query, :source, :index, :values
9
10
  include Enumerable
10
11
 
12
+ # Create a new criteria chain.
13
+ #
14
+ # @param [Class] source the class upon which the ultimate query will be performed.
11
15
  def initialize(source)
12
16
  @query = {}
13
17
  @source = source
14
18
  end
15
19
 
20
+ # The workhorse method of the criteria chain. Each key in the passed in hash will become another criteria that the
21
+ # ultimate query must match. A key can either be a symbol or a string, and should be an attribute name or
22
+ # an attribute name with a range operator.
23
+ #
24
+ # @example A simple criteria
25
+ # where(:name => 'Josh')
26
+ #
27
+ # @example A more complicated criteria
28
+ # where(:name => 'Josh', 'created_at.gt' => DateTime.now - 1.day)
29
+ #
30
+ # @since 0.2.0
16
31
  def where(args)
17
32
  args.each {|k, v| query[k] = v}
18
33
  self
19
34
  end
20
35
 
36
+ # Returns all the records matching the criteria.
37
+ #
38
+ # @since 0.2.0
21
39
  def all
22
40
  records
23
41
  end
24
-
42
+
43
+ # Returns the first record matching the criteria.
44
+ #
45
+ # @since 0.2.0
25
46
  def first
26
47
  records.first
27
48
  end
28
-
49
+
50
+ # Allows you to use the results of a search as an enumerable over the results found.
51
+ #
52
+ # @since 0.2.0
29
53
  def each(&block)
30
54
  records.each(&block)
31
55
  end
32
56
 
33
57
  private
34
58
 
59
+ # The actual records referenced by the association.
60
+ #
61
+ # @return [Array] an array of the found records.
62
+ #
63
+ # @since 0.2.0
35
64
  def records
36
65
  return records_with_index if index
37
66
  records_without_index
38
67
  end
39
-
68
+
69
+ # If the query matches an index on the associated class, then this method will retrieve results from the index table.
70
+ #
71
+ # @return [Array] an array of the found records.
72
+ #
73
+ # @since 0.2.0
40
74
  def records_with_index
41
75
  ids = if index.range_key?
42
76
  Dynamoid::Adapter.query(index.table_name, index_query).collect{|r| r[:ids]}.inject(Set.new) {|set, result| set + result}
@@ -55,14 +89,24 @@ module Dynamoid #:nodoc:
55
89
  end
56
90
  end
57
91
 
92
+ # If the query does not match an index, we'll manually scan the associated table to manually find results.
93
+ #
94
+ # @return [Array] an array of the found records.
95
+ #
96
+ # @since 0.2.0
58
97
  def records_without_index
59
98
  if Dynamoid::Config.warn_on_scan
60
99
  Dynamoid.logger.warn 'Queries without an index are forced to use scan and are generally much slower than indexed queries!'
61
100
  Dynamoid.logger.warn "You can index this query by adding this to #{source.to_s.downcase}.rb: index [#{source.attributes.sort.collect{|attr| ":#{attr}"}.join(', ')}]"
62
101
  end
63
- Dynamoid::Adapter.scan(source.table_name, query).collect {|hash| source.new(hash)}
102
+ Dynamoid::Adapter.scan(source.table_name, query).collect {|hash| source.new(hash).tap { |r| r.new_record = false } }
64
103
  end
65
104
 
105
+ # Format the provided query so that it can be used to query results from DynamoDB.
106
+ #
107
+ # @return [Hash] a hash with keys of :hash_value and :range_value
108
+ #
109
+ # @since 0.2.0
66
110
  def index_query
67
111
  values = index.values(query)
68
112
  {}.tap do |hash|
@@ -91,7 +135,10 @@ module Dynamoid #:nodoc:
91
135
  end
92
136
  end
93
137
  end
94
-
138
+
139
+ # Return an index that fulfills all the attributes the criteria is querying, or nil if none is found.
140
+ #
141
+ # @since 0.2.0
95
142
  def index
96
143
  index = source.find_index(query.keys.collect{|k| k.to_s.split('.').first})
97
144
  return nil if index.blank?
@@ -101,4 +148,4 @@ module Dynamoid #:nodoc:
101
148
 
102
149
  end
103
150
 
104
- end
151
+ end