adept_dynamoid 0.5.0.6

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 (119) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/Dynamoid.gemspec +193 -0
  4. data/Gemfile +23 -0
  5. data/Gemfile.lock +86 -0
  6. data/LICENSE.txt +20 -0
  7. data/README.markdown +265 -0
  8. data/Rakefile +62 -0
  9. data/VERSION +1 -0
  10. data/doc/.nojekyll +0 -0
  11. data/doc/Dynamoid.html +312 -0
  12. data/doc/Dynamoid/Adapter.html +1385 -0
  13. data/doc/Dynamoid/Adapter/AwsSdk.html +1585 -0
  14. data/doc/Dynamoid/Adapter/Local.html +1574 -0
  15. data/doc/Dynamoid/Associations.html +131 -0
  16. data/doc/Dynamoid/Associations/Association.html +794 -0
  17. data/doc/Dynamoid/Associations/BelongsTo.html +158 -0
  18. data/doc/Dynamoid/Associations/ClassMethods.html +723 -0
  19. data/doc/Dynamoid/Associations/HasAndBelongsToMany.html +164 -0
  20. data/doc/Dynamoid/Associations/HasMany.html +164 -0
  21. data/doc/Dynamoid/Associations/HasOne.html +158 -0
  22. data/doc/Dynamoid/Associations/ManyAssociation.html +1640 -0
  23. data/doc/Dynamoid/Associations/SingleAssociation.html +598 -0
  24. data/doc/Dynamoid/Components.html +204 -0
  25. data/doc/Dynamoid/Config.html +395 -0
  26. data/doc/Dynamoid/Config/Options.html +609 -0
  27. data/doc/Dynamoid/Criteria.html +131 -0
  28. data/doc/Dynamoid/Criteria/Chain.html +1063 -0
  29. data/doc/Dynamoid/Criteria/ClassMethods.html +98 -0
  30. data/doc/Dynamoid/Document.html +666 -0
  31. data/doc/Dynamoid/Document/ClassMethods.html +937 -0
  32. data/doc/Dynamoid/Errors.html +118 -0
  33. data/doc/Dynamoid/Errors/DocumentNotValid.html +210 -0
  34. data/doc/Dynamoid/Errors/Error.html +130 -0
  35. data/doc/Dynamoid/Errors/InvalidField.html +133 -0
  36. data/doc/Dynamoid/Errors/MissingRangeKey.html +133 -0
  37. data/doc/Dynamoid/Fields.html +669 -0
  38. data/doc/Dynamoid/Fields/ClassMethods.html +309 -0
  39. data/doc/Dynamoid/Finders.html +128 -0
  40. data/doc/Dynamoid/Finders/ClassMethods.html +516 -0
  41. data/doc/Dynamoid/Indexes.html +308 -0
  42. data/doc/Dynamoid/Indexes/ClassMethods.html +353 -0
  43. data/doc/Dynamoid/Indexes/Index.html +1104 -0
  44. data/doc/Dynamoid/Persistence.html +651 -0
  45. data/doc/Dynamoid/Persistence/ClassMethods.html +670 -0
  46. data/doc/Dynamoid/Validations.html +399 -0
  47. data/doc/_index.html +461 -0
  48. data/doc/class_list.html +47 -0
  49. data/doc/css/common.css +1 -0
  50. data/doc/css/full_list.css +55 -0
  51. data/doc/css/style.css +322 -0
  52. data/doc/file.LICENSE.html +66 -0
  53. data/doc/file.README.html +312 -0
  54. data/doc/file_list.html +52 -0
  55. data/doc/frames.html +13 -0
  56. data/doc/index.html +312 -0
  57. data/doc/js/app.js +205 -0
  58. data/doc/js/full_list.js +173 -0
  59. data/doc/js/jquery.js +16 -0
  60. data/doc/method_list.html +1238 -0
  61. data/doc/top-level-namespace.html +105 -0
  62. data/lib/dynamoid.rb +47 -0
  63. data/lib/dynamoid/adapter.rb +177 -0
  64. data/lib/dynamoid/adapter/aws_sdk.rb +223 -0
  65. data/lib/dynamoid/associations.rb +106 -0
  66. data/lib/dynamoid/associations/association.rb +105 -0
  67. data/lib/dynamoid/associations/belongs_to.rb +44 -0
  68. data/lib/dynamoid/associations/has_and_belongs_to_many.rb +40 -0
  69. data/lib/dynamoid/associations/has_many.rb +39 -0
  70. data/lib/dynamoid/associations/has_one.rb +39 -0
  71. data/lib/dynamoid/associations/many_association.rb +191 -0
  72. data/lib/dynamoid/associations/single_association.rb +69 -0
  73. data/lib/dynamoid/components.rb +36 -0
  74. data/lib/dynamoid/config.rb +57 -0
  75. data/lib/dynamoid/config/options.rb +78 -0
  76. data/lib/dynamoid/criteria.rb +29 -0
  77. data/lib/dynamoid/criteria/chain.rb +243 -0
  78. data/lib/dynamoid/dirty.rb +41 -0
  79. data/lib/dynamoid/document.rb +184 -0
  80. data/lib/dynamoid/errors.rb +28 -0
  81. data/lib/dynamoid/fields.rb +130 -0
  82. data/lib/dynamoid/finders.rb +131 -0
  83. data/lib/dynamoid/identity_map.rb +96 -0
  84. data/lib/dynamoid/indexes.rb +69 -0
  85. data/lib/dynamoid/indexes/index.rb +103 -0
  86. data/lib/dynamoid/middleware/identity_map.rb +16 -0
  87. data/lib/dynamoid/persistence.rb +247 -0
  88. data/lib/dynamoid/validations.rb +36 -0
  89. data/spec/app/models/address.rb +10 -0
  90. data/spec/app/models/camel_case.rb +24 -0
  91. data/spec/app/models/magazine.rb +11 -0
  92. data/spec/app/models/message.rb +9 -0
  93. data/spec/app/models/sponsor.rb +8 -0
  94. data/spec/app/models/subscription.rb +12 -0
  95. data/spec/app/models/tweet.rb +12 -0
  96. data/spec/app/models/user.rb +26 -0
  97. data/spec/dynamoid/adapter/aws_sdk_spec.rb +186 -0
  98. data/spec/dynamoid/adapter_spec.rb +117 -0
  99. data/spec/dynamoid/associations/association_spec.rb +194 -0
  100. data/spec/dynamoid/associations/belongs_to_spec.rb +71 -0
  101. data/spec/dynamoid/associations/has_and_belongs_to_many_spec.rb +47 -0
  102. data/spec/dynamoid/associations/has_many_spec.rb +42 -0
  103. data/spec/dynamoid/associations/has_one_spec.rb +45 -0
  104. data/spec/dynamoid/associations_spec.rb +16 -0
  105. data/spec/dynamoid/config_spec.rb +27 -0
  106. data/spec/dynamoid/criteria/chain_spec.rb +140 -0
  107. data/spec/dynamoid/criteria_spec.rb +72 -0
  108. data/spec/dynamoid/dirty_spec.rb +49 -0
  109. data/spec/dynamoid/document_spec.rb +118 -0
  110. data/spec/dynamoid/fields_spec.rb +127 -0
  111. data/spec/dynamoid/finders_spec.rb +135 -0
  112. data/spec/dynamoid/identity_map_spec.rb +45 -0
  113. data/spec/dynamoid/indexes/index_spec.rb +104 -0
  114. data/spec/dynamoid/indexes_spec.rb +25 -0
  115. data/spec/dynamoid/persistence_spec.rb +176 -0
  116. data/spec/dynamoid/validations_spec.rb +36 -0
  117. data/spec/dynamoid_spec.rb +9 -0
  118. data/spec/spec_helper.rb +50 -0
  119. metadata +376 -0
@@ -0,0 +1,105 @@
1
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3
+ <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
4
+ <head>
5
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.7.5
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" media="screen" charset="utf-8" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" media="screen" charset="utf-8" />
16
+
17
+ <script type="text/javascript" charset="utf-8">
18
+ relpath = '';
19
+ if (relpath != '') relpath += '/';
20
+ </script>
21
+
22
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
23
+
24
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
25
+
26
+
27
+ </head>
28
+ <body>
29
+ <script type="text/javascript" charset="utf-8">
30
+ if (window.top.frames.main) document.body.className = 'frames';
31
+ </script>
32
+
33
+ <div id="header">
34
+ <div id="menu">
35
+
36
+ <a href="_index.html">Index</a> &raquo;
37
+
38
+
39
+ <span class="title">Top Level Namespace</span>
40
+
41
+
42
+ <div class="noframes"><span class="title">(</span><a href="." target="_top">no frames</a><span class="title">)</span></div>
43
+ </div>
44
+
45
+ <div id="search">
46
+
47
+ <a id="class_list_link" href="#">Class List</a>
48
+
49
+ <a id="method_list_link" href="#">Method List</a>
50
+
51
+ <a id="file_list_link" href="#">File List</a>
52
+
53
+ </div>
54
+ <div class="clear"></div>
55
+ </div>
56
+
57
+ <iframe id="search_frame"></iframe>
58
+
59
+ <div id="content"><h1>Top Level Namespace
60
+
61
+
62
+
63
+ </h1>
64
+
65
+ <dl class="box">
66
+
67
+
68
+
69
+
70
+
71
+
72
+
73
+
74
+ </dl>
75
+ <div class="clear"></div>
76
+
77
+ <h2>Defined Under Namespace</h2>
78
+ <p class="children">
79
+
80
+
81
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Dynamoid.html" title="Dynamoid (module)">Dynamoid</a></span>
82
+
83
+
84
+
85
+
86
+ </p>
87
+
88
+
89
+
90
+
91
+
92
+
93
+
94
+
95
+
96
+ </div>
97
+
98
+ <div id="footer">
99
+ Generated on Thu Apr 26 01:26:26 2012 by
100
+ <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
101
+ 0.7.5 (ruby-1.9.3).
102
+ </div>
103
+
104
+ </body>
105
+ </html>
data/lib/dynamoid.rb ADDED
@@ -0,0 +1,47 @@
1
+ require "delegate"
2
+ require "time"
3
+ require "securerandom"
4
+ require "active_support/core_ext"
5
+ require 'active_support/json'
6
+ require "active_support/inflector"
7
+ require "active_support/lazy_load_hooks"
8
+ require "active_support/time_with_zone"
9
+ require "active_model"
10
+
11
+ require 'dynamoid/errors'
12
+ require 'dynamoid/fields'
13
+ require 'dynamoid/indexes'
14
+ require 'dynamoid/associations'
15
+ require 'dynamoid/persistence'
16
+ require 'dynamoid/dirty'
17
+ require 'dynamoid/validations'
18
+ require 'dynamoid/criteria'
19
+ require 'dynamoid/finders'
20
+ require 'dynamoid/identity_map'
21
+ require 'dynamoid/config'
22
+ require 'dynamoid/components'
23
+ require 'dynamoid/document'
24
+ require 'dynamoid/adapter'
25
+
26
+ require 'dynamoid/middleware/identity_map'
27
+
28
+ module Dynamoid
29
+ extend self
30
+
31
+ MAX_ITEM_SIZE = 65_536
32
+
33
+ def configure
34
+ block_given? ? yield(Dynamoid::Config) : Dynamoid::Config
35
+ Dynamoid::Adapter.reconnect!
36
+ end
37
+ alias :config :configure
38
+
39
+ def logger
40
+ Dynamoid::Config.logger
41
+ end
42
+
43
+ def included_models
44
+ @included_models ||= []
45
+ end
46
+
47
+ end
@@ -0,0 +1,177 @@
1
+ # encoding: utf-8
2
+ module Dynamoid
3
+
4
+ # Adapter provides a generic, write-through class that abstracts variations in the underlying connections to provide a uniform response
5
+ # to Dynamoid.
6
+ module Adapter
7
+ extend self
8
+ attr_accessor :tables
9
+
10
+ # The actual adapter currently in use: presently AwsSdk.
11
+ #
12
+ # @since 0.2.0
13
+ def adapter
14
+ reconnect! unless @adapter
15
+ @adapter
16
+ end
17
+
18
+ # Establishes a connection to the underyling adapter and caches all its tables for speedier future lookups. Issued when the adapter is first called.
19
+ #
20
+ # @since 0.2.0
21
+ def reconnect!
22
+ require "dynamoid/adapter/#{Dynamoid::Config.adapter}" unless Dynamoid::Adapter.const_defined?(Dynamoid::Config.adapter.camelcase)
23
+ @adapter = Dynamoid::Adapter.const_get(Dynamoid::Config.adapter.camelcase)
24
+ @adapter.connect! if @adapter.respond_to?(:connect!)
25
+ self.tables = benchmark('Cache Tables') {list_tables}
26
+ end
27
+
28
+ # Shows how long it takes a method to run on the adapter. Useful for generating logged output.
29
+ #
30
+ # @param [Symbol] method the name of the method to appear in the log
31
+ # @param [Array] args the arguments to the method to appear in the log
32
+ # @yield the actual code to benchmark
33
+ #
34
+ # @return the result of the yield
35
+ #
36
+ # @since 0.2.0
37
+ def benchmark(method, *args)
38
+ start = Time.now
39
+ result = yield
40
+ Dynamoid.logger.info "(#{((Time.now - start) * 1000.0).round(2)} ms) #{method.to_s.split('_').collect(&:upcase).join(' ')}#{ " - #{args.inspect}" unless args.nil? || args.empty? }"
41
+ return result
42
+ end
43
+
44
+ # Write an object to the adapter. Partition it to a randomly selected key first if necessary.
45
+ #
46
+ # @param [String] table the name of the table to write the object to
47
+ # @param [Object] object the object itself
48
+ #
49
+ # @return [Object] the persisted object
50
+ #
51
+ # @since 0.2.0
52
+ def write(table, object)
53
+ if Dynamoid::Config.partitioning? && object[:id]
54
+ object[:id] = "#{object[:id]}.#{Random.rand(Dynamoid::Config.partition_size)}"
55
+ object[:updated_at] = Time.now.to_f
56
+ end
57
+ put_item(table, object)
58
+ end
59
+
60
+ # Read one or many keys from the selected table. This method intelligently calls batch_get or get on the underlying adapter depending on
61
+ # whether ids is a range or a single key: additionally, if partitioning is enabled, it batch_gets all keys in the partition space
62
+ # automatically. Finally, if a range key is present, it will also interpolate that into the ids so that the batch get will acquire the
63
+ # correct record.
64
+ #
65
+ # @param [String] table the name of the table to write the object to
66
+ # @param [Array] ids to fetch, can also be a string of just one id
67
+ # @param [Number] range_key the range key of the record
68
+ #
69
+ # @since 0.2.0
70
+ def read(table, ids, options = {})
71
+ range_key = options[:range_key]
72
+ if ids.respond_to?(:each)
73
+ ids = ids.collect{|id| range_key ? [id, range_key] : id}
74
+ if Dynamoid::Config.partitioning?
75
+ results = batch_get_item(table => id_with_partitions(ids))
76
+ {table => result_for_partition(results[table])}
77
+ else
78
+ batch_get_item(table => ids)
79
+ end
80
+ else
81
+ if Dynamoid::Config.partitioning?
82
+ ids = range_key ? [[ids, range_key]] : ids
83
+ results = batch_get_item(table => id_with_partitions(ids))
84
+ result_for_partition(results[table]).first
85
+ else
86
+ get_item(table, ids, options)
87
+ end
88
+ end
89
+ end
90
+
91
+ # Delete an item from a table. If partitioning is turned on, deletes all partitioned keys as well.
92
+ #
93
+ # @param [String] table the name of the table to write the object to
94
+ # @param [String] id the id of the record
95
+ # @param [Number] range_key the range key of the record
96
+ #
97
+ # @since 0.2.0
98
+ def delete(table, id, options = {})
99
+ if Dynamoid::Config.partitioning?
100
+ id_with_partitions(id).each {|i| delete_item(table, i, options)}
101
+ else
102
+ delete_item(table, id, options)
103
+ end
104
+ end
105
+
106
+ # Scans a table. Generally quite slow; try to avoid using scan if at all possible.
107
+ #
108
+ # @param [String] table the name of the table to write the object to
109
+ # @param [Hash] scan_hash a hash of attributes: matching records will be returned by the scan
110
+ #
111
+ # @since 0.2.0
112
+ def scan(table, query, opts = {})
113
+ if Dynamoid::Config.partitioning?
114
+ results = benchmark('Scan', table, query) {adapter.scan(table, query, opts)}
115
+ result_for_partition(results)
116
+ else
117
+ benchmark('Scan', table, query) {adapter.scan(table, query, opts)}
118
+ end
119
+ end
120
+
121
+ [:batch_get_item, :create_table, :delete_item, :delete_table, :get_item, :list_tables, :put_item].each do |m|
122
+ # Method delegation with benchmark to the underlying adapter. Faster than relying on method_missing.
123
+ #
124
+ # @since 0.2.0
125
+ define_method(m) do |*args|
126
+ benchmark("#{m.to_s}", args) {adapter.send(m, *args)}
127
+ end
128
+ end
129
+
130
+ # Takes a list of ids and returns them with partitioning added. If an array of arrays is passed, we assume the second key is the range key
131
+ # and pass it in unchanged.
132
+ #
133
+ # @example Partition id 1
134
+ # Dynamoid::Adapter.id_with_partitions(['1']) # ['1.0', '1.1', '1.2', ..., '1.199']
135
+ # @example Partition id 1 and range_key 1.0
136
+ # Dynamoid::Adapter.id_with_partitions([['1', 1.0]]) # [['1.0', 1.0], ['1.1', 1.0], ['1.2', 1.0], ..., ['1.199', 1.0]]
137
+ #
138
+ # @param [Array] ids array of ids to partition
139
+ #
140
+ # @since 0.2.0
141
+ def id_with_partitions(ids)
142
+ Array(ids).collect {|id| (0...Dynamoid::Config.partition_size).collect{|n| id.is_a?(Array) ? ["#{id.first}.#{n}", id.last] : "#{id}.#{n}"}}.flatten(1)
143
+ end
144
+
145
+ # Takes an array of results that are partitioned, find the most recently updated one, and return only it. Compares each result by
146
+ # their id and updated_at attributes; if the updated_at is the greatest, then it must be the correct result.
147
+ #
148
+ # @param [Array] returned partitioned results from a query
149
+ #
150
+ # @since 0.2.0
151
+ def result_for_partition(results)
152
+ {}.tap do |hash|
153
+ Array(results).each do |result|
154
+ next if result.nil?
155
+ #Need to find the value of id with out the . and partition number
156
+ partition = result[:id].split('.').last
157
+ id = result[:id].split(".#{partition}").first
158
+
159
+ if !hash[id] || (result[:updated_at] > hash[id][:updated_at])
160
+ result[:id] = id
161
+ hash[id] = result
162
+ end
163
+ end
164
+ end.values
165
+ end
166
+
167
+ # Delegate all methods that aren't defind here to the underlying adapter.
168
+ #
169
+ # @since 0.2.0
170
+ def method_missing(method, *args, &block)
171
+ return benchmark(method, *args) {adapter.send(method, *args, &block)} if @adapter.respond_to?(method)
172
+ super
173
+ end
174
+
175
+ end
176
+
177
+ end
@@ -0,0 +1,223 @@
1
+ # encoding: utf-8
2
+ require 'aws'
3
+
4
+ module Dynamoid
5
+ module Adapter
6
+
7
+ # The AwsSdk adapter provides support for the AWS-SDK for Ruby gem.
8
+ # More information is available at that Gem's Github page:
9
+ # https://github.com/amazonwebservices/aws-sdk-for-ruby
10
+ #
11
+ module AwsSdk
12
+ extend self
13
+ @@connection = nil
14
+
15
+ # Establish the connection to DynamoDB.
16
+ #
17
+ # @return [AWS::DynamoDB::Connection] the raw DynamoDB connection
18
+ #
19
+ # @since 0.2.0
20
+ def connect!
21
+ @@connection = AWS::DynamoDB.new(:access_key_id => Dynamoid::Config.access_key, :secret_access_key => Dynamoid::Config.secret_key, :dynamo_db_endpoint => Dynamoid::Config.endpoint, :use_ssl => Dynamoid::Config.use_ssl, :dynamo_db_port => Dynamoid::Config.port)
22
+ end
23
+
24
+ # Return the established connection.
25
+ #
26
+ # @return [AWS::DynamoDB::Connection] the raw DynamoDB connection
27
+ #
28
+ # @since 0.2.0
29
+ def connection
30
+ @@connection
31
+ end
32
+
33
+ # Get many items at once from DynamoDB. More efficient than getting each item individually.
34
+ #
35
+ # @example Retrieve IDs 1 and 2 from the table testtable
36
+ # Dynamoid::Adapter::AwsSdk.batch_get_item('table1' => ['1', '2'])
37
+ #
38
+ # @param [Hash] options the hash of tables and IDs to retrieve
39
+ #
40
+ # @return [Hash] a hash where keys are the table names and the values are the retrieved items
41
+ #
42
+ # @since 0.2.0
43
+ def batch_get_item(options)
44
+ hash = Hash.new{|h, k| h[k] = []}
45
+ return hash if options.all?{|k, v| v.empty?}
46
+ options.each do |t, ids|
47
+ Array(ids).in_groups_of(100, false) do |group|
48
+ batch = AWS::DynamoDB::BatchGet.new(:config => @@connection.config)
49
+ batch.table(t, :all, Array(group)) unless group.nil? || group.empty?
50
+ batch.each do |table_name, attributes|
51
+ hash[table_name] << attributes.symbolize_keys!
52
+ end
53
+ end
54
+ end
55
+ hash
56
+ end
57
+
58
+ # Create a table on DynamoDB. This usually takes a long time to complete.
59
+ #
60
+ # @param [String] table_name the name of the table to create
61
+ # @param [Symbol] key the table's primary key (defaults to :id)
62
+ # @param [Hash] options provide a range_key here if you want one for the table
63
+ #
64
+ # @since 0.2.0
65
+ def create_table(table_name, key = :id, options = {})
66
+ Dynamoid.logger.info "Creating #{table_name} table. This could take a while."
67
+ options[:hash_key] ||= {key.to_sym => :string}
68
+ read_capacity = options[:read_capacity] || Dynamoid::Config.read_capacity
69
+ write_capacity = options[:write_capacity] || Dynamoid::Config.write_capacity
70
+ table = @@connection.tables.create(table_name, read_capacity, write_capacity, options)
71
+ sleep 0.5 while table.status == :creating
72
+ return table
73
+ end
74
+
75
+ # Removes an item from DynamoDB.
76
+ #
77
+ # @param [String] table_name the name of the table
78
+ # @param [String] key the hash key of the item to delete
79
+ # @param [Number] range_key the range key of the item to delete, required if the table has a composite key
80
+ #
81
+ # @since 0.2.0
82
+ def delete_item(table_name, key, options = {})
83
+ range_key = options.delete(:range_key)
84
+ table = get_table(table_name)
85
+ result = table.items.at(key, range_key)
86
+ result.delete unless result.attributes.to_h.empty?
87
+ true
88
+ end
89
+
90
+ # Deletes an entire table from DynamoDB. Only 10 tables can be in the deleting state at once,
91
+ # so if you have more this method may raise an exception.
92
+ #
93
+ # @param [String] table_name the name of the table to destroy
94
+ #
95
+ # @since 0.2.0
96
+ def delete_table(table_name)
97
+ Dynamoid.logger.info "Deleting #{table_name} table. This could take a while."
98
+ table = @@connection.tables[table_name]
99
+ table.delete
100
+ sleep 0.5 while table.exists? == true
101
+ end
102
+
103
+ # @todo Add a DescribeTable method.
104
+
105
+ # Fetches an item from DynamoDB.
106
+ #
107
+ # @param [String] table_name the name of the table
108
+ # @param [String] key the hash key of the item to find
109
+ # @param [Number] range_key the range key of the item to find, required if the table has a composite key
110
+ #
111
+ # @return [Hash] a hash representing the raw item in DynamoDB
112
+ #
113
+ # @since 0.2.0
114
+
115
+
116
+
117
+ def get_item(table_name, key, options = {})
118
+ range_key = options.delete(:range_key)
119
+ table = get_table(table_name)
120
+
121
+ result = table.items.at(key, range_key).attributes.to_h(options)
122
+
123
+ if result.empty?
124
+ nil
125
+ else
126
+ result.symbolize_keys!
127
+ end
128
+ end
129
+
130
+ def update_item(table_name, key, options = {}, &block)
131
+ range_key = options.delete(:range_key)
132
+ conditions = options.delete(:conditions) || {}
133
+ table = get_table(table_name)
134
+ item = table.items.at(key, range_key)
135
+ item.attributes.update(conditions.merge(:return => :all_new), &block)
136
+ rescue AWS::DynamoDB::Errors::ConditionalCheckFailedException
137
+ raise Dynamoid::Errors::ConditionalCheckFailedException
138
+ end
139
+
140
+ # List all tables on DynamoDB.
141
+ #
142
+ # @since 0.2.0
143
+ def list_tables
144
+ @@connection.tables.collect(&:name)
145
+ end
146
+
147
+ # Persists an item on DynamoDB.
148
+ #
149
+ # @param [String] table_name the name of the table
150
+ # @param [Object] object a hash or Dynamoid object to persist
151
+ #
152
+ # @since 0.2.0
153
+ def put_item(table_name, object)
154
+ table = get_table(table_name)
155
+ table.items.create(object.delete_if{|k, v| v.nil? || (v.respond_to?(:empty?) && v.empty?)})
156
+ end
157
+
158
+ # Query the DynamoDB table. This employs DynamoDB's indexes so is generally faster than scanning, but is
159
+ # only really useful for range queries, since it can only find by one hash key at once. Only provide
160
+ # one range key to the hash.
161
+ #
162
+ # @param [String] table_name the name of the table
163
+ # @param [Hash] opts the options to query the table with
164
+ # @option opts [String] :hash_value the value of the hash key to find
165
+ # @option opts [Range] :range_value find the range key within this range
166
+ # @option opts [Number] :range_greater_than find range keys greater than this
167
+ # @option opts [Number] :range_less_than find range keys less than this
168
+ # @option opts [Number] :range_gte find range keys greater than or equal to this
169
+ # @option opts [Number] :range_lte find range keys less than or equal to this
170
+ #
171
+ # @return [Array] an array of all matching items
172
+ #
173
+ # @since 0.2.0
174
+ def query(table_name, opts = {})
175
+ table = get_table(table_name)
176
+
177
+ consistent_opts = { :consistent_read => opts[:consistent_read] || false }
178
+ if table.composite_key?
179
+ results = []
180
+ table.items.query(opts).each {|data| results << data.attributes.to_h(consistent_opts).symbolize_keys!}
181
+ results
182
+ else
183
+ get_item(table_name, opts[:hash_value])
184
+ end
185
+ end
186
+
187
+ # Scan the DynamoDB table. This is usually a very slow operation as it naively filters all data on
188
+ # the DynamoDB servers.
189
+ #
190
+ # @param [String] table_name the name of the table
191
+ # @param [Hash] scan_hash a hash of attributes: matching records will be returned by the scan
192
+ #
193
+ # @return [Array] an array of all matching items
194
+ #
195
+ # @since 0.2.0
196
+ def scan(table_name, scan_hash, select_opts)
197
+ table = get_table(table_name)
198
+ results = []
199
+ table.items.where(scan_hash).select(select_opts) do |data|
200
+ results << data.attributes.symbolize_keys!
201
+ end
202
+ results
203
+ end
204
+
205
+ # @todo Add an UpdateItem method.
206
+
207
+ # @todo Add an UpdateTable method.
208
+
209
+ def get_table(table_name)
210
+ unless table = table_cache[table_name]
211
+ table = @@connection.tables[table_name]
212
+ table.load_schema
213
+ table_cache[table_name] = table
214
+ end
215
+ table
216
+ end
217
+
218
+ def table_cache
219
+ @table_cache ||= {}
220
+ end
221
+ end
222
+ end
223
+ end