dynamoid-moda 0.7.1

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 (136) hide show
  1. checksums.yaml +15 -0
  2. data/.document +5 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -0
  5. data/Gemfile +24 -0
  6. data/Gemfile.lock +118 -0
  7. data/Gemfile_activemodel4 +24 -0
  8. data/Gemfile_activemodel4.lock +88 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.markdown +360 -0
  11. data/Rakefile +93 -0
  12. data/VERSION +1 -0
  13. data/doc/.nojekyll +0 -0
  14. data/doc/Dynamoid.html +328 -0
  15. data/doc/Dynamoid/Adapter.html +1872 -0
  16. data/doc/Dynamoid/Adapter/AwsSdk.html +2101 -0
  17. data/doc/Dynamoid/Adapter/Local.html +1574 -0
  18. data/doc/Dynamoid/Associations.html +138 -0
  19. data/doc/Dynamoid/Associations/Association.html +847 -0
  20. data/doc/Dynamoid/Associations/BelongsTo.html +161 -0
  21. data/doc/Dynamoid/Associations/ClassMethods.html +766 -0
  22. data/doc/Dynamoid/Associations/HasAndBelongsToMany.html +167 -0
  23. data/doc/Dynamoid/Associations/HasMany.html +167 -0
  24. data/doc/Dynamoid/Associations/HasOne.html +161 -0
  25. data/doc/Dynamoid/Associations/ManyAssociation.html +1684 -0
  26. data/doc/Dynamoid/Associations/SingleAssociation.html +627 -0
  27. data/doc/Dynamoid/Components.html +242 -0
  28. data/doc/Dynamoid/Config.html +412 -0
  29. data/doc/Dynamoid/Config/Options.html +638 -0
  30. data/doc/Dynamoid/Criteria.html +138 -0
  31. data/doc/Dynamoid/Criteria/Chain.html +1471 -0
  32. data/doc/Dynamoid/Criteria/ClassMethods.html +105 -0
  33. data/doc/Dynamoid/Dirty.html +424 -0
  34. data/doc/Dynamoid/Dirty/ClassMethods.html +174 -0
  35. data/doc/Dynamoid/Document.html +1033 -0
  36. data/doc/Dynamoid/Document/ClassMethods.html +1116 -0
  37. data/doc/Dynamoid/Errors.html +125 -0
  38. data/doc/Dynamoid/Errors/ConditionalCheckFailedException.html +141 -0
  39. data/doc/Dynamoid/Errors/DocumentNotValid.html +221 -0
  40. data/doc/Dynamoid/Errors/Error.html +137 -0
  41. data/doc/Dynamoid/Errors/InvalidField.html +141 -0
  42. data/doc/Dynamoid/Errors/InvalidQuery.html +131 -0
  43. data/doc/Dynamoid/Errors/MissingRangeKey.html +141 -0
  44. data/doc/Dynamoid/Fields.html +686 -0
  45. data/doc/Dynamoid/Fields/ClassMethods.html +438 -0
  46. data/doc/Dynamoid/Finders.html +135 -0
  47. data/doc/Dynamoid/Finders/ClassMethods.html +943 -0
  48. data/doc/Dynamoid/IdentityMap.html +492 -0
  49. data/doc/Dynamoid/IdentityMap/ClassMethods.html +534 -0
  50. data/doc/Dynamoid/Indexes.html +321 -0
  51. data/doc/Dynamoid/Indexes/ClassMethods.html +369 -0
  52. data/doc/Dynamoid/Indexes/Index.html +1142 -0
  53. data/doc/Dynamoid/Middleware.html +115 -0
  54. data/doc/Dynamoid/Middleware/IdentityMap.html +264 -0
  55. data/doc/Dynamoid/Persistence.html +892 -0
  56. data/doc/Dynamoid/Persistence/ClassMethods.html +836 -0
  57. data/doc/Dynamoid/Validations.html +415 -0
  58. data/doc/_index.html +506 -0
  59. data/doc/class_list.html +53 -0
  60. data/doc/css/common.css +1 -0
  61. data/doc/css/full_list.css +57 -0
  62. data/doc/css/style.css +338 -0
  63. data/doc/file.LICENSE.html +73 -0
  64. data/doc/file.README.html +416 -0
  65. data/doc/file_list.html +58 -0
  66. data/doc/frames.html +28 -0
  67. data/doc/index.html +416 -0
  68. data/doc/js/app.js +214 -0
  69. data/doc/js/full_list.js +178 -0
  70. data/doc/js/jquery.js +4 -0
  71. data/doc/method_list.html +1144 -0
  72. data/doc/top-level-namespace.html +112 -0
  73. data/dynamoid-moda.gemspec +210 -0
  74. data/dynamoid.gemspec +208 -0
  75. data/lib/dynamoid.rb +46 -0
  76. data/lib/dynamoid/adapter.rb +267 -0
  77. data/lib/dynamoid/adapter/aws_sdk.rb +309 -0
  78. data/lib/dynamoid/associations.rb +106 -0
  79. data/lib/dynamoid/associations/association.rb +105 -0
  80. data/lib/dynamoid/associations/belongs_to.rb +44 -0
  81. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +40 -0
  82. data/lib/dynamoid/associations/has_many.rb +39 -0
  83. data/lib/dynamoid/associations/has_one.rb +39 -0
  84. data/lib/dynamoid/associations/many_association.rb +191 -0
  85. data/lib/dynamoid/associations/single_association.rb +69 -0
  86. data/lib/dynamoid/components.rb +37 -0
  87. data/lib/dynamoid/config.rb +57 -0
  88. data/lib/dynamoid/config/options.rb +78 -0
  89. data/lib/dynamoid/criteria.rb +29 -0
  90. data/lib/dynamoid/criteria/chain.rb +326 -0
  91. data/lib/dynamoid/dirty.rb +47 -0
  92. data/lib/dynamoid/document.rb +199 -0
  93. data/lib/dynamoid/errors.rb +28 -0
  94. data/lib/dynamoid/fields.rb +138 -0
  95. data/lib/dynamoid/finders.rb +133 -0
  96. data/lib/dynamoid/identity_map.rb +96 -0
  97. data/lib/dynamoid/indexes.rb +69 -0
  98. data/lib/dynamoid/indexes/index.rb +103 -0
  99. data/lib/dynamoid/middleware/identity_map.rb +16 -0
  100. data/lib/dynamoid/persistence.rb +292 -0
  101. data/lib/dynamoid/validations.rb +36 -0
  102. data/spec/app/models/address.rb +13 -0
  103. data/spec/app/models/camel_case.rb +34 -0
  104. data/spec/app/models/car.rb +6 -0
  105. data/spec/app/models/magazine.rb +11 -0
  106. data/spec/app/models/message.rb +9 -0
  107. data/spec/app/models/nuclear_submarine.rb +5 -0
  108. data/spec/app/models/sponsor.rb +8 -0
  109. data/spec/app/models/subscription.rb +12 -0
  110. data/spec/app/models/tweet.rb +12 -0
  111. data/spec/app/models/user.rb +26 -0
  112. data/spec/app/models/vehicle.rb +7 -0
  113. data/spec/dynamoid/adapter/aws_sdk_spec.rb +376 -0
  114. data/spec/dynamoid/adapter_spec.rb +155 -0
  115. data/spec/dynamoid/associations/association_spec.rb +194 -0
  116. data/spec/dynamoid/associations/belongs_to_spec.rb +71 -0
  117. data/spec/dynamoid/associations/has_and_belongs_to_many_spec.rb +47 -0
  118. data/spec/dynamoid/associations/has_many_spec.rb +42 -0
  119. data/spec/dynamoid/associations/has_one_spec.rb +45 -0
  120. data/spec/dynamoid/associations_spec.rb +16 -0
  121. data/spec/dynamoid/config_spec.rb +27 -0
  122. data/spec/dynamoid/criteria/chain_spec.rb +210 -0
  123. data/spec/dynamoid/criteria_spec.rb +75 -0
  124. data/spec/dynamoid/dirty_spec.rb +57 -0
  125. data/spec/dynamoid/document_spec.rb +180 -0
  126. data/spec/dynamoid/fields_spec.rb +156 -0
  127. data/spec/dynamoid/finders_spec.rb +147 -0
  128. data/spec/dynamoid/identity_map_spec.rb +45 -0
  129. data/spec/dynamoid/indexes/index_spec.rb +104 -0
  130. data/spec/dynamoid/indexes_spec.rb +25 -0
  131. data/spec/dynamoid/persistence_spec.rb +301 -0
  132. data/spec/dynamoid/validations_spec.rb +36 -0
  133. data/spec/dynamoid_spec.rb +14 -0
  134. data/spec/spec_helper.rb +55 -0
  135. data/spec/support/with_partitioning.rb +15 -0
  136. metadata +363 -0
@@ -0,0 +1,47 @@
1
+ module Dynamoid
2
+ module Dirty
3
+ extend ActiveSupport::Concern
4
+ include ActiveModel::Dirty
5
+
6
+ module ClassMethods
7
+ def from_database(*)
8
+ super.tap { |d| d.changed_attributes.clear }
9
+ end
10
+ end
11
+
12
+ def save(*)
13
+ clear_changes { super }
14
+ end
15
+
16
+ def update!(*)
17
+ ret = super
18
+ clear_changes #update! completely reloads all fields on the class, so any extant changes are wiped out
19
+ ret
20
+ end
21
+
22
+ def reload
23
+ super.tap { clear_changes }
24
+ end
25
+
26
+ def clear_changes
27
+ previous = changes
28
+ (block_given? ? yield : true).tap do |result|
29
+ unless result == false #failed validation; nil is OK.
30
+ @previously_changed = previous
31
+ changed_attributes.clear
32
+ end
33
+ end
34
+ end
35
+
36
+ def write_attribute(name, value)
37
+ attribute_will_change!(name) unless self.read_attribute(name) == value
38
+ super
39
+ end
40
+
41
+ protected
42
+
43
+ def attribute_method?(attr)
44
+ super || self.class.attributes.has_key?(attr.to_sym)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,199 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ # This is the base module for all domain objects that need to be persisted to
5
+ # the database as documents.
6
+ module Document
7
+ extend ActiveSupport::Concern
8
+ include Dynamoid::Components
9
+
10
+ included do
11
+ class_attribute :options, :read_only_attributes, :base_class
12
+ self.options = {}
13
+ self.read_only_attributes = []
14
+ self.base_class = self
15
+
16
+ Dynamoid::Config.included_models << self
17
+ end
18
+
19
+ module ClassMethods
20
+ # Set up table options, including naming it whatever you want, setting the id key, and manually overriding read and
21
+ # write capacity.
22
+ #
23
+ # @param [Hash] options options to pass for this table
24
+ # @option options [Symbol] :name the name for the table; this still gets namespaced
25
+ # @option options [Symbol] :id id column for the table
26
+ # @option options [Integer] :read_capacity set the read capacity for the table; does not work on existing tables
27
+ # @option options [Integer] :write_capacity set the write capacity for the table; does not work on existing tables
28
+ #
29
+ # @since 0.4.0
30
+ def table(options = {})
31
+ self.options = options
32
+ super if defined? super
33
+ end
34
+
35
+ def attr_readonly(*read_only_attributes)
36
+ self.read_only_attributes.concat read_only_attributes.map(&:to_s)
37
+ end
38
+
39
+ # Returns the read_capacity for this table.
40
+ #
41
+ # @since 0.4.0
42
+ def read_capacity
43
+ options[:read_capacity] || Dynamoid::Config.read_capacity
44
+ end
45
+
46
+ # Returns the write_capacity for this table.
47
+ #
48
+ # @since 0.4.0
49
+ def write_capacity
50
+ options[:write_capacity] || Dynamoid::Config.write_capacity
51
+ end
52
+
53
+ # Returns the id field for this class.
54
+ #
55
+ # @since 0.4.0
56
+ def hash_key
57
+ options[:key] || :id
58
+ end
59
+
60
+ # Returns the number of items for this class.
61
+ #
62
+ # @since 0.6.1
63
+ def count
64
+ Dynamoid::Adapter.adapter.count(table_name)
65
+ end
66
+
67
+ # Initialize a new object and immediately save it to the database.
68
+ #
69
+ # @param [Hash] attrs Attributes with which to create the object.
70
+ #
71
+ # @return [Dynamoid::Document] the saved document
72
+ #
73
+ # @since 0.2.0
74
+ def create(attrs = {})
75
+ attrs[:type] ? attrs[:type].constantize.new(attrs).tap(&:save) : new(attrs).tap(&:save)
76
+ end
77
+
78
+ # Initialize a new object and immediately save it to the database. Raise an exception if persistence failed.
79
+ #
80
+ # @param [Hash] attrs Attributes with which to create the object.
81
+ #
82
+ # @return [Dynamoid::Document] the saved document
83
+ #
84
+ # @since 0.2.0
85
+ def create!(attrs = {})
86
+ attrs[:type] ? attrs[:type].constantize.new(attrs).tap(&:save!) : new(attrs).tap(&:save!)
87
+ end
88
+
89
+ # Initialize a new object.
90
+ #
91
+ # @param [Hash] attrs Attributes with which to create the object.
92
+ #
93
+ # @return [Dynamoid::Document] the new document
94
+ #
95
+ # @since 0.2.0
96
+ def build(attrs = {})
97
+ attrs[:type] ? attrs[:type].constantize.new(attrs) : new(attrs)
98
+ end
99
+
100
+ # Does this object exist?
101
+ #
102
+ # @param [Mixed] id_or_conditions the id of the object or a hash with the options to filter from.
103
+ #
104
+ # @return [Boolean] true/false
105
+ #
106
+ # @since 0.2.0
107
+ def exists?(id_or_conditions = {})
108
+ case id_or_conditions
109
+ when Hash then ! where(id_or_conditions).all.empty?
110
+ else !! find(id_or_conditions)
111
+ end
112
+ end
113
+ end
114
+
115
+ # Initialize a new object.
116
+ #
117
+ # @param [Hash] attrs Attributes with which to create the object.
118
+ #
119
+ # @return [Dynamoid::Document] the new document
120
+ #
121
+ # @since 0.2.0
122
+ def initialize(attrs = {})
123
+ run_callbacks :initialize do
124
+ @new_record = true
125
+ @attributes ||= {}
126
+ @associations ||= {}
127
+
128
+ load(attrs)
129
+ end
130
+ end
131
+
132
+ def load(attrs)
133
+ self.class.undump(attrs).each {|key, value| send "#{key}=", value }
134
+ end
135
+
136
+ # An object is equal to another object if their ids are equal.
137
+ #
138
+ # @since 0.2.0
139
+ def ==(other)
140
+ if self.class.identity_map_on?
141
+ super
142
+ else
143
+ return false if other.nil?
144
+ other.is_a?(Dynamoid::Document) && self.hash_key == other.hash_key && self.range_value == other.range_value
145
+ end
146
+ end
147
+
148
+ def eql?(other)
149
+ self == other
150
+ end
151
+
152
+ def hash
153
+ hash_key.hash ^ range_value.hash
154
+ end
155
+
156
+ # Reload an object from the database -- if you suspect the object has changed in the datastore and you need those
157
+ # changes to be reflected immediately, you would call this method. This is a consistent read.
158
+ #
159
+ # @return [Dynamoid::Document] the document this method was called on
160
+ #
161
+ # @since 0.2.0
162
+ def reload
163
+ range_key_value = range_value ? dumped_range_value : nil
164
+ self.attributes = self.class.find(hash_key, :range_key => range_key_value, :consistent_read => true).attributes
165
+ @associations.values.each(&:reset)
166
+ self
167
+ end
168
+
169
+ # Return an object's hash key, regardless of what it might be called to the object.
170
+ #
171
+ # @since 0.4.0
172
+ def hash_key
173
+ self.send(self.class.hash_key)
174
+ end
175
+
176
+ # Assign an object's hash key, regardless of what it might be called to the object.
177
+ #
178
+ # @since 0.4.0
179
+ def hash_key=(value)
180
+ self.send("#{self.class.hash_key}=", value)
181
+ end
182
+
183
+ def range_value
184
+ if range_key = self.class.range_key
185
+ self.send(range_key)
186
+ end
187
+ end
188
+
189
+ def range_value=(value)
190
+ self.send("#{self.class.range_key}=", value)
191
+ end
192
+
193
+ private
194
+
195
+ def dumped_range_value
196
+ dump_field(range_value, self.class.attributes[self.class.range_key])
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,28 @@
1
+ # encoding: utf-8
2
+ module Dynamoid
3
+
4
+ # All the error specific to Dynamoid.
5
+ module Errors
6
+
7
+ # Generic error class.
8
+ class Error < StandardError; end
9
+
10
+ # InvalidField is raised when an attribute is specified for an index, but the attribute does not exist.
11
+ class InvalidField < Error; end
12
+
13
+ # MissingRangeKey is raised when a table that requires a range key is quieried without one.
14
+ class MissingRangeKey < Error; end
15
+
16
+ # raised when the conditional check failed during update operation
17
+ class ConditionalCheckFailedException < Error; end
18
+
19
+ # DocumentNotValid is raised when the document fails validation.
20
+ class DocumentNotValid < Error
21
+ def initialize(document)
22
+ super("Validation failed: #{document.errors.full_messages.join(", ")}")
23
+ end
24
+ end
25
+
26
+ class InvalidQuery < Error; end
27
+ end
28
+ end
@@ -0,0 +1,138 @@
1
+ # encoding: utf-8
2
+ module Dynamoid #:nodoc:
3
+
4
+ # All fields on a Dynamoid::Document must be explicitly defined -- if you have fields in the database that are not
5
+ # specified with field, then they will be ignored.
6
+ module Fields
7
+ extend ActiveSupport::Concern
8
+
9
+ # Initialize the attributes we know the class has, in addition to our magic attributes: id, created_at, and updated_at.
10
+ included do
11
+ class_attribute :attributes
12
+ class_attribute :range_key
13
+
14
+ self.attributes = {}
15
+ field :created_at, :datetime
16
+ field :updated_at, :datetime
17
+
18
+ field :id #Default primary key
19
+ end
20
+
21
+ module ClassMethods
22
+
23
+ # Specify a field for a document. Its type determines how it is coerced when read in and out of the datastore:
24
+ # default is string, but you can also specify :integer, :float, :set, :array, :datetime, and :serialized.
25
+ #
26
+ # @param [Symbol] name the name of the field
27
+ # @param [Symbol] type the type of the field (one of :integer, :float, :set, :array, :datetime, or :serialized)
28
+ # @param [Hash] options any additional options for the field
29
+ #
30
+ # @since 0.2.0
31
+ def field(name, type = :string, options = {})
32
+ named = name.to_s
33
+ self.attributes = attributes.merge(name => {:type => type}.merge(options))
34
+
35
+ define_method(named) { read_attribute(named) }
36
+ define_method("#{named}?") { !read_attribute(named).nil? }
37
+ define_method("#{named}=") {|value| write_attribute(named, value) }
38
+ end
39
+
40
+ def range(name, type = :string)
41
+ field(name, type)
42
+ self.range_key = name
43
+ end
44
+
45
+ def table(options)
46
+ #a default 'id' column is created when Dynamoid::Document is included
47
+ unless(attributes.has_key? hash_key)
48
+ remove_field :id
49
+ field(hash_key)
50
+ end
51
+ end
52
+
53
+ def remove_field(field)
54
+ field = field.to_sym
55
+ attributes.delete(field) or raise "No such field"
56
+ remove_method field
57
+ remove_method :"#{field}="
58
+ remove_method :"#{field}?"
59
+ end
60
+ end
61
+
62
+ # You can access the attributes of an object directly on its attributes method, which is by default an empty hash.
63
+ attr_accessor :attributes
64
+ alias :raw_attributes :attributes
65
+
66
+ # Write an attribute on the object. Also marks the previous value as dirty.
67
+ #
68
+ # @param [Symbol] name the name of the field
69
+ # @param [Object] value the value to assign to that field
70
+ #
71
+ # @since 0.2.0
72
+ def write_attribute(name, value)
73
+ if (size = value.to_s.size) > MAX_ITEM_SIZE
74
+ Dynamoid.logger.warn "DynamoDB can't store items larger than #{MAX_ITEM_SIZE} and the #{name} field has a length of #{size}."
75
+ end
76
+
77
+ if association = @associations[name]
78
+ association.reset
79
+ end
80
+
81
+ attributes[name.to_sym] = value
82
+ end
83
+ alias :[]= :write_attribute
84
+
85
+ # Read an attribute from an object.
86
+ #
87
+ # @param [Symbol] name the name of the field
88
+ #
89
+ # @since 0.2.0
90
+ def read_attribute(name)
91
+ attributes[name.to_sym]
92
+ end
93
+ alias :[] :read_attribute
94
+
95
+ # Updates multiple attibutes at once, saving the object once the updates are complete.
96
+ #
97
+ # @param [Hash] attributes a hash of attributes to update
98
+ #
99
+ # @since 0.2.0
100
+ def update_attributes(attributes)
101
+ attributes.each {|attribute, value| self.write_attribute(attribute, value)} unless attributes.nil? || attributes.empty?
102
+ save
103
+ end
104
+
105
+ # Update a single attribute, saving the object afterwards.
106
+ #
107
+ # @param [Symbol] attribute the attribute to update
108
+ # @param [Object] value the value to assign it
109
+ #
110
+ # @since 0.2.0
111
+ def update_attribute(attribute, value)
112
+ write_attribute(attribute, value)
113
+ save
114
+ end
115
+
116
+ private
117
+
118
+ # Automatically called during the created callback to set the created_at time.
119
+ #
120
+ # @since 0.2.0
121
+ def set_created_at
122
+ self.created_at = DateTime.now
123
+ end
124
+
125
+ # Automatically called during the save callback to set the updated_at time.
126
+ #
127
+ # @since 0.2.0
128
+ def set_updated_at
129
+ self.updated_at = DateTime.now
130
+ end
131
+
132
+ def set_type
133
+ self.type ||= self.class.to_s if self.class.attributes[:type]
134
+ end
135
+
136
+ end
137
+
138
+ end
@@ -0,0 +1,133 @@
1
+ # encoding: utf-8
2
+ module Dynamoid
3
+
4
+ # This module defines the finder methods that hang off the document at the
5
+ # class level, like find, find_by_id, and the method_missing style finders.
6
+ module Finders
7
+ extend ActiveSupport::Concern
8
+
9
+ module ClassMethods
10
+
11
+ # Find one or many objects, specified by one id or an array of ids.
12
+ #
13
+ # @param [Array/String] *id an array of ids or one single id
14
+ #
15
+ # @return [Dynamoid::Document] one object or an array of objects, depending on whether the input was an array or not
16
+ #
17
+ # @since 0.2.0
18
+ def find(*ids)
19
+
20
+ options = if ids.last.is_a? Hash
21
+ ids.slice!(-1)
22
+ else
23
+ {}
24
+ end
25
+
26
+ ids = Array(ids.flatten.uniq)
27
+ if ids.count == 1
28
+ self.find_by_id(ids.first, options)
29
+ else
30
+ find_all(ids)
31
+ end
32
+ end
33
+
34
+ # Return objects found by the given array of ids, either hash keys, or hash/range key combinations using BatchGet.
35
+ # Returns empty array if no results found.
36
+ #
37
+ # @param [Array<ID>] ids
38
+ # @param [Hash] options: Passed to the underlying query.
39
+ #
40
+ # @example
41
+ # find all the user with hash key
42
+ # User.find_all(['1', '2', '3'])
43
+ #
44
+ # find all the tweets using hash key and range key with consistent read
45
+ # Tweet.find_all([['1', 'red'], ['1', 'green']], :consistent_read => true)
46
+ def find_all(ids, options = {})
47
+ items = Dynamoid::Adapter.read(self.table_name, ids, options)
48
+ items ? items[self.table_name].map{|i| from_database(i)} : []
49
+ end
50
+
51
+ # Find one object directly by id.
52
+ #
53
+ # @param [String] id the id of the object to find
54
+ #
55
+ # @return [Dynamoid::Document] the found object, or nil if nothing was found
56
+ #
57
+ # @since 0.2.0
58
+ def find_by_id(id, options = {})
59
+ if item = Dynamoid::Adapter.read(self.table_name, id, options)
60
+ from_database(item)
61
+ else
62
+ nil
63
+ end
64
+ end
65
+
66
+ # Find one object directly by hash and range keys
67
+ #
68
+ # @param [String] hash_key of the object to find
69
+ # @param [String/Integer/Float] range_key of the object to find
70
+ #
71
+ def find_by_composite_key(hash_key, range_key, options = {})
72
+ find_by_id(hash_key, options.merge({:range_key => range_key}))
73
+ end
74
+
75
+ # Find all objects by hash and range keys.
76
+ #
77
+ # @example find all ChamberTypes whose level is greater than 1
78
+ # class ChamberType
79
+ # include Dynamoid::Document
80
+ # field :chamber_type, :string
81
+ # range :level, :integer
82
+ # table :key => :chamber_type
83
+ # end
84
+ # ChamberType.find_all_by_composite_key('DustVault', range_greater_than: 1)
85
+ #
86
+ # @param [String] hash_key of the objects to find
87
+ # @param [Hash] options the options for the range key
88
+ # @option options [Range] :range_value find the range key within this range
89
+ # @option options [Number] :range_greater_than find range keys greater than this
90
+ # @option options [Number] :range_less_than find range keys less than this
91
+ # @option options [Number] :range_gte find range keys greater than or equal to this
92
+ # @option options [Number] :range_lte find range keys less than or equal to this
93
+ #
94
+ # @return [Array] an array of all matching items
95
+ #
96
+ def find_all_by_composite_key(hash_key, options = {})
97
+ Dynamoid::Adapter.query(self.table_name, options.merge({hash_value: hash_key})).collect do |item|
98
+ from_database(item)
99
+ end
100
+ end
101
+
102
+ # Find using exciting method_missing finders attributes. Uses criteria chains under the hood to accomplish this neatness.
103
+ #
104
+ # @example find a user by a first name
105
+ # User.find_by_first_name('Josh')
106
+ #
107
+ # @example find all users by first and last name
108
+ # User.find_all_by_first_name_and_last_name('Josh', 'Symonds')
109
+ #
110
+ # @return [Dynamoid::Document/Array] the found object, or an array of found objects if all was somewhere in the method
111
+ #
112
+ # @since 0.2.0
113
+ def method_missing(method, *args)
114
+ if method =~ /find/
115
+ finder = method.to_s.split('_by_').first
116
+ attributes = method.to_s.split('_by_').last.split('_and_')
117
+
118
+ chain = Dynamoid::Criteria::Chain.new(self)
119
+ chain.query = Hash.new.tap {|h| attributes.each_with_index {|attr, index| h[attr.to_sym] = args[index]}}
120
+
121
+ if finder =~ /all/
122
+ return chain.all
123
+ else
124
+ return chain.first
125
+ end
126
+ else
127
+ super
128
+ end
129
+ end
130
+ end
131
+ end
132
+
133
+ end