adept_dynamoid 0.5.0.6

Sign up to get free protection for your applications and to get access to all the features.
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