dynamoid 0.4.0 → 0.4.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 (50) hide show
  1. data/Dynamoid.gemspec +5 -3
  2. data/README.markdown +2 -2
  3. data/VERSION +1 -1
  4. data/doc/Dynamoid.html +26 -14
  5. data/doc/Dynamoid/Adapter.html +33 -35
  6. data/doc/Dynamoid/Adapter/AwsSdk.html +177 -153
  7. data/doc/Dynamoid/Adapter/Local.html +144 -57
  8. data/doc/Dynamoid/Associations.html +3 -3
  9. data/doc/Dynamoid/Associations/Association.html +104 -1016
  10. data/doc/Dynamoid/Associations/BelongsTo.html +11 -192
  11. data/doc/Dynamoid/Associations/ClassMethods.html +17 -17
  12. data/doc/Dynamoid/Associations/HasAndBelongsToMany.html +15 -190
  13. data/doc/Dynamoid/Associations/HasMany.html +15 -190
  14. data/doc/Dynamoid/Associations/HasOne.html +11 -192
  15. data/doc/Dynamoid/Associations/ManyAssociation.html +1640 -0
  16. data/doc/Dynamoid/Associations/SingleAssociation.html +598 -0
  17. data/doc/Dynamoid/Components.html +4 -2
  18. data/doc/Dynamoid/Config.html +10 -10
  19. data/doc/Dynamoid/Config/Options.html +1 -1
  20. data/doc/Dynamoid/Criteria.html +1 -1
  21. data/doc/Dynamoid/Criteria/Chain.html +326 -22
  22. data/doc/Dynamoid/Criteria/ClassMethods.html +1 -1
  23. data/doc/Dynamoid/Document.html +181 -27
  24. data/doc/Dynamoid/Document/ClassMethods.html +392 -36
  25. data/doc/Dynamoid/Errors.html +1 -1
  26. data/doc/Dynamoid/Errors/DocumentNotValid.html +1 -1
  27. data/doc/Dynamoid/Errors/Error.html +1 -1
  28. data/doc/Dynamoid/Errors/InvalidField.html +1 -1
  29. data/doc/Dynamoid/Errors/MissingRangeKey.html +1 -1
  30. data/doc/Dynamoid/Fields.html +44 -24
  31. data/doc/Dynamoid/Fields/ClassMethods.html +60 -15
  32. data/doc/Dynamoid/Finders.html +1 -1
  33. data/doc/Dynamoid/Finders/ClassMethods.html +45 -31
  34. data/doc/Dynamoid/Indexes.html +7 -7
  35. data/doc/Dynamoid/Indexes/ClassMethods.html +6 -4
  36. data/doc/Dynamoid/Indexes/Index.html +47 -32
  37. data/doc/Dynamoid/Persistence.html +47 -49
  38. data/doc/Dynamoid/Persistence/ClassMethods.html +149 -47
  39. data/doc/Dynamoid/Validations.html +1 -1
  40. data/doc/_index.html +35 -13
  41. data/doc/class_list.html +1 -1
  42. data/doc/file.LICENSE.html +1 -1
  43. data/doc/file.README.html +37 -4
  44. data/doc/index.html +37 -4
  45. data/doc/method_list.html +320 -136
  46. data/doc/top-level-namespace.html +1 -1
  47. data/lib/dynamoid/adapter.rb +28 -30
  48. data/lib/dynamoid/adapter/aws_sdk.rb +23 -22
  49. data/lib/dynamoid/persistence.rb +33 -27
  50. metadata +6 -4
@@ -96,7 +96,7 @@
96
96
  </div>
97
97
 
98
98
  <div id="footer">
99
- Generated on Tue Mar 27 17:53:07 2012 by
99
+ Generated on Thu Apr 26 01:26:26 2012 by
100
100
  <a href="http://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
101
101
  0.7.5 (ruby-1.9.3).
102
102
  </div>
@@ -1,12 +1,12 @@
1
1
  # encoding: utf-8
2
2
  module Dynamoid
3
3
 
4
- # Adapter provides a generic, write-through class that abstracts variations in the underlying connections to provide a uniform response
4
+ # Adapter provides a generic, write-through class that abstracts variations in the underlying connections to provide a uniform response
5
5
  # to Dynamoid.
6
6
  module Adapter
7
7
  extend self
8
8
  attr_accessor :tables
9
-
9
+
10
10
  # The actual adapter currently in use: presently, either AwsSdk or Local.
11
11
  #
12
12
  # @since 0.2.0
@@ -14,7 +14,7 @@ module Dynamoid
14
14
  reconnect! unless @adapter
15
15
  @adapter
16
16
  end
17
-
17
+
18
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
19
  #
20
20
  # @since 0.2.0
@@ -24,7 +24,7 @@ module Dynamoid
24
24
  @adapter.connect! if @adapter.respond_to?(:connect!)
25
25
  self.tables = benchmark('Cache Tables') {list_tables}
26
26
  end
27
-
27
+
28
28
  # Shows how long it takes a method to run on the adapter. Useful for generating logged output.
29
29
  #
30
30
  # @param [Symbol] method the name of the method to appear in the log
@@ -40,7 +40,7 @@ module Dynamoid
40
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
41
  return result
42
42
  end
43
-
43
+
44
44
  # Write an object to the adapter. Partition it to a randomly selected key first if necessary.
45
45
  #
46
46
  # @param [String] table the name of the table to write the object to
@@ -54,12 +54,12 @@ module Dynamoid
54
54
  object[:id] = "#{object[:id]}.#{Random.rand(Dynamoid::Config.partition_size)}"
55
55
  object[:updated_at] = Time.now.to_f
56
56
  end
57
- benchmark('Put Item', object) {put_item(table, object)}
57
+ put_item(table, object)
58
58
  end
59
-
59
+
60
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
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
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
63
  # correct record.
64
64
  #
65
65
  # @param [String] table the name of the table to write the object to
@@ -72,22 +72,22 @@ module Dynamoid
72
72
  if ids.respond_to?(:each)
73
73
  ids = ids.collect{|id| range_key ? [id, range_key] : id}
74
74
  if Dynamoid::Config.partitioning?
75
- results = benchmark('Partitioned Batch Get Item', ids) {batch_get_item(table => id_with_partitions(ids))}
75
+ results = batch_get_item(table => id_with_partitions(ids))
76
76
  {table => result_for_partition(results[table])}
77
77
  else
78
- benchmark('Batch Get Item', ids) {batch_get_item(table => ids)}
78
+ batch_get_item(table => ids)
79
79
  end
80
80
  else
81
81
  if Dynamoid::Config.partitioning?
82
82
  ids = range_key ? [[ids, range_key]] : ids
83
- results = benchmark('Partitioned Get Item', ids) {batch_get_item(table => id_with_partitions(ids))}
83
+ results = batch_get_item(table => id_with_partitions(ids))
84
84
  result_for_partition(results[table]).first
85
85
  else
86
- benchmark('Get Item', ids) {get_item(table, ids, options)}
86
+ get_item(table, ids, options)
87
87
  end
88
88
  end
89
89
  end
90
-
90
+
91
91
  # Delete an item from a table. If partitioning is turned on, deletes all partitioned keys as well.
92
92
  #
93
93
  # @param [String] table the name of the table to write the object to
@@ -97,29 +97,27 @@ module Dynamoid
97
97
  # @since 0.2.0
98
98
  def delete(table, id, options = {})
99
99
  if Dynamoid::Config.partitioning?
100
- benchmark('Delete Item', id) do
101
- id_with_partitions(id).each {|i| delete_item(table, i, options)}
102
- end
100
+ id_with_partitions(id).each {|i| delete_item(table, i, options)}
103
101
  else
104
- benchmark('Delete Item', id) {delete_item(table, id, options)}
102
+ delete_item(table, id, options)
105
103
  end
106
104
  end
107
-
105
+
108
106
  # Scans a table. Generally quite slow; try to avoid using scan if at all possible.
109
107
  #
110
108
  # @param [String] table the name of the table to write the object to
111
109
  # @param [Hash] scan_hash a hash of attributes: matching records will be returned by the scan
112
110
  #
113
- # @since 0.2.0
111
+ # @since 0.2.0
114
112
  def scan(table, query, opts = {})
115
113
  if Dynamoid::Config.partitioning?
116
114
  results = benchmark('Scan', table, query) {adapter.scan(table, query, opts)}
117
115
  result_for_partition(results)
118
116
  else
119
- adapter.scan(table, query, opts)
117
+ benchmark('Scan', table, query) {adapter.scan(table, query, opts)}
120
118
  end
121
119
  end
122
-
120
+
123
121
  [:batch_get_item, :create_table, :delete_item, :delete_table, :get_item, :list_tables, :put_item].each do |m|
124
122
  # Method delegation with benchmark to the underlying adapter. Faster than relying on method_missing.
125
123
  #
@@ -128,8 +126,8 @@ module Dynamoid
128
126
  benchmark("#{m.to_s}", args) {adapter.send(m, *args)}
129
127
  end
130
128
  end
131
-
132
- # 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
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
133
131
  # and pass it in unchanged.
134
132
  #
135
133
  # @example Partition id 1
@@ -139,15 +137,15 @@ module Dynamoid
139
137
  #
140
138
  # @param [Array] ids array of ids to partition
141
139
  #
142
- # @since 0.2.0
140
+ # @since 0.2.0
143
141
  def id_with_partitions(ids)
144
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)
145
143
  end
146
-
147
- # Takes an array of results that are partitioned, find the most recently updated one, and return only it. Compares each result by
144
+
145
+ # Takes an array of results that are partitioned, find the most recently updated one, and return only it. Compares each result by
148
146
  # their id and updated_at attributes; if the updated_at is the greatest, then it must be the correct result.
149
147
  #
150
- # @param [Array] returned partitioned results from a query
148
+ # @param [Array] returned partitioned results from a query
151
149
  #
152
150
  # @since 0.2.0
153
151
  def result_for_partition(results)
@@ -162,7 +160,7 @@ module Dynamoid
162
160
  end
163
161
  end.values
164
162
  end
165
-
163
+
166
164
  # Delegate all methods that aren't defind here to the underlying adapter.
167
165
  #
168
166
  # @since 0.2.0
@@ -170,7 +168,7 @@ module Dynamoid
170
168
  return benchmark(method, *args) {adapter.send(method, *args)} if @adapter.respond_to?(method)
171
169
  super
172
170
  end
173
-
171
+
174
172
  end
175
-
173
+
176
174
  end
@@ -3,15 +3,15 @@ require 'aws'
3
3
 
4
4
  module Dynamoid
5
5
  module Adapter
6
-
6
+
7
7
  # The AwsSdk adapter provides support for the AWS-SDK for Ruby gem.
8
8
  # More information is available at that Gem's Github page:
9
9
  # https://github.com/amazonwebservices/aws-sdk-for-ruby
10
- #
10
+ #
11
11
  module AwsSdk
12
12
  extend self
13
13
  @@connection = nil
14
-
14
+
15
15
  # Establish the connection to DynamoDB.
16
16
  #
17
17
  # @return [AWS::DynamoDB::Connection] the raw DynamoDB connection
@@ -20,7 +20,7 @@ module Dynamoid
20
20
  def connect!
21
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)
22
22
  end
23
-
23
+
24
24
  # Return the established connection.
25
25
  #
26
26
  # @return [AWS::DynamoDB::Connection] the raw DynamoDB connection
@@ -29,16 +29,16 @@ module Dynamoid
29
29
  def connection
30
30
  @@connection
31
31
  end
32
-
32
+
33
33
  # Get many items at once from DynamoDB. More efficient than getting each item individually.
34
- #
34
+ #
35
35
  # @example Retrieve IDs 1 and 2 from the table testtable
36
36
  # Dynamoid::Adapter::AwsSdk.batch_get_item('table1' => ['1', '2'])
37
37
  #
38
38
  # @param [Hash] options the hash of tables and IDs to retrieve
39
39
  #
40
40
  # @return [Hash] a hash where keys are the table names and the values are the retrieved items
41
- #
41
+ #
42
42
  # @since 0.2.0
43
43
  def batch_get_item(options)
44
44
  hash = Hash.new{|h, k| h[k] = []}
@@ -54,15 +54,16 @@ module Dynamoid
54
54
  end
55
55
  hash
56
56
  end
57
-
57
+
58
58
  # Create a table on DynamoDB. This usually takes a long time to complete.
59
59
  #
60
60
  # @param [String] table_name the name of the table to create
61
61
  # @param [Symbol] key the table's primary key (defaults to :id)
62
62
  # @param [Hash] options provide a range_key here if you want one for the table
63
- #
63
+ #
64
64
  # @since 0.2.0
65
65
  def create_table(table_name, key = :id, options = {})
66
+ Dynamite.logger.info "Creating #{table_name} table. This could take a while."
66
67
  options[:hash_key] ||= {key.to_sym => :string}
67
68
  read_capacity = options[:read_capacity] || Dynamoid::Config.read_capacity
68
69
  write_capacity = options[:write_capacity] || Dynamoid::Config.write_capacity
@@ -70,7 +71,7 @@ module Dynamoid
70
71
  sleep 0.5 while table.status == :creating
71
72
  return table
72
73
  end
73
-
74
+
74
75
  # Removes an item from DynamoDB.
75
76
  #
76
77
  # @param [String] table_name the name of the table
@@ -89,8 +90,8 @@ module Dynamoid
89
90
  result.delete unless result.attributes.to_h.empty?
90
91
  true
91
92
  end
92
-
93
- # Deletes an entire table from DynamoDB. Only 10 tables can be in the deleting state at once,
93
+
94
+ # Deletes an entire table from DynamoDB. Only 10 tables can be in the deleting state at once,
94
95
  # so if you have more this method may raise an exception.
95
96
  #
96
97
  # @param [String] table_name the name of the table to destroy
@@ -99,9 +100,9 @@ module Dynamoid
99
100
  def delete_table(table_name)
100
101
  @@connection.tables[table_name].delete
101
102
  end
102
-
103
+
103
104
  # @todo Add a DescribeTable method.
104
-
105
+
105
106
  # Fetches an item from DynamoDB.
106
107
  #
107
108
  # @param [String] table_name the name of the table
@@ -129,14 +130,14 @@ module Dynamoid
129
130
  result.symbolize_keys!
130
131
  end
131
132
  end
132
-
133
+
133
134
  # List all tables on DynamoDB.
134
135
  #
135
136
  # @since 0.2.0
136
137
  def list_tables
137
138
  @@connection.tables.collect(&:name)
138
139
  end
139
-
140
+
140
141
  # Persists an item on DynamoDB.
141
142
  #
142
143
  # @param [String] table_name the name of the table
@@ -147,9 +148,9 @@ module Dynamoid
147
148
  table = get_table(table_name)
148
149
  table.items.create(object.delete_if{|k, v| v.nil? || (v.respond_to?(:empty?) && v.empty?)})
149
150
  end
150
-
151
- # Query the DynamoDB table. This employs DynamoDB's indexes so is generally faster than scanning, but is
152
- # only really useful for range queries, since it can only find by one hash key at once. Only provide
151
+
152
+ # Query the DynamoDB table. This employs DynamoDB's indexes so is generally faster than scanning, but is
153
+ # only really useful for range queries, since it can only find by one hash key at once. Only provide
153
154
  # one range key to the hash.
154
155
  #
155
156
  # @param [String] table_name the name of the table
@@ -176,7 +177,7 @@ module Dynamoid
176
177
  get_item(table_name, opts[:hash_value])
177
178
  end
178
179
  end
179
-
180
+
180
181
  # Scan the DynamoDB table. This is usually a very slow operation as it naively filters all data on
181
182
  # the DynamoDB servers.
182
183
  #
@@ -194,9 +195,9 @@ module Dynamoid
194
195
  end
195
196
  results
196
197
  end
197
-
198
+
198
199
  # @todo Add an UpdateItem method.
199
-
200
+
200
201
  # @todo Add an UpdateTable method.
201
202
 
202
203
  def get_table(table_name)
@@ -3,23 +3,23 @@ require 'securerandom'
3
3
  # encoding: utf-8
4
4
  module Dynamoid
5
5
 
6
- # Persistence is responsible for dumping objects to and marshalling objects from the datastore. It tries to reserialize
6
+ # Persistence is responsible for dumping objects to and marshalling objects from the datastore. It tries to reserialize
7
7
  # values to be of the same type as when they were passed in, based on the fields in the class.
8
8
  module Persistence
9
9
  extend ActiveSupport::Concern
10
-
10
+
11
11
  attr_accessor :new_record
12
12
  alias :new_record? :new_record
13
-
13
+
14
14
  module ClassMethods
15
-
15
+
16
16
  # Returns the name of the table the class is for.
17
17
  #
18
18
  # @since 0.2.0
19
19
  def table_name
20
20
  "#{Dynamoid::Config.namespace}_#{options[:name] ? options[:name] : self.name.downcase.pluralize}"
21
21
  end
22
-
22
+
23
23
  # Creates a table.
24
24
  #
25
25
  # @param [Hash] options options to pass for table creation
@@ -31,26 +31,32 @@ module Dynamoid
31
31
  #
32
32
  # @since 0.4.0
33
33
  def create_table(options = {})
34
+ if self.range_key
35
+ range_key_type = [:integer, :float].include?(attributes[range_key][:type]) ? :number : attributes[range_key][:type]
36
+ range_key_hash = { range_key => range_key_type}
37
+ else
38
+ range_key_hash = nil
39
+ end
34
40
  options = {
35
41
  :id => self.hash_key,
36
42
  :table_name => self.table_name,
37
43
  :write_capacity => self.write_capacity,
38
44
  :read_capacity => self.read_capacity,
39
- :range_key => self.range_key ? {range_key => attributes[range_key][:type]} : nil
45
+ :range_key => range_key_hash
40
46
  }.merge(options)
41
-
47
+
42
48
  return true if table_exists?(options[:table_name])
43
-
49
+
44
50
  Dynamoid::Adapter.tables << options[:table_name] if Dynamoid::Adapter.create_table(options[:table_name], options[:id], options)
45
51
  end
46
52
 
47
53
  # Does a table with this name exist?
48
54
  #
49
- # @since 0.2.0
55
+ # @since 0.2.0
50
56
  def table_exists?(table_name)
51
57
  Dynamoid::Adapter.tables.include?(table_name)
52
58
  end
53
-
59
+
54
60
  # Undump an object into a hash, converting each type from a string representation of itself into the type specified by the field.
55
61
  #
56
62
  # @since 0.2.0
@@ -58,20 +64,20 @@ module Dynamoid
58
64
  incoming = (incoming || {}).symbolize_keys
59
65
  Hash.new.tap do |hash|
60
66
  self.attributes.each do |attribute, options|
61
- hash[attribute] = undump_field(incoming[attribute], options[:type])
67
+ hash[attribute] = undump_field(incoming[attribute], options)
62
68
  end
63
69
  incoming.each {|attribute, value| hash[attribute] ||= value }
64
70
  end
65
71
  end
66
72
 
67
- # Undump a value for a given type. Given a string, it'll determine (based on the type provided) whether to turn it into a
73
+ # Undump a value for a given type. Given a string, it'll determine (based on the type provided) whether to turn it into a
68
74
  # string, integer, float, set, array, datetime, or serialized return value.
69
75
  #
70
76
  # @since 0.2.0
71
- def undump_field(value, type)
77
+ def undump_field(value, options)
72
78
  return if value.nil? || (value.respond_to?(:empty?) && value.empty?)
73
79
 
74
- case type
80
+ case options[:type]
75
81
  when :string
76
82
  value.to_s
77
83
  when :integer
@@ -92,7 +98,7 @@ module Dynamoid
92
98
  end
93
99
  when :serialized
94
100
  if value.is_a?(String)
95
- YAML.load(value)
101
+ options[:serializer] ? options[:serializer].load(value) : YAML.load(value)
96
102
  else
97
103
  value
98
104
  end
@@ -100,14 +106,14 @@ module Dynamoid
100
106
  end
101
107
 
102
108
  end
103
-
109
+
104
110
  # Is this object persisted in the datastore? Required for some ActiveModel integration stuff.
105
111
  #
106
112
  # @since 0.2.0
107
113
  def persisted?
108
114
  !new_record?
109
115
  end
110
-
116
+
111
117
  # Run the callbacks and then persist this object in the datastore.
112
118
  #
113
119
  # @since 0.2.0
@@ -142,28 +148,28 @@ module Dynamoid
142
148
  delete_indexes
143
149
  Dynamoid::Adapter.delete(self.class.table_name, self.id)
144
150
  end
145
-
151
+
146
152
  # Dump this object's attributes into hash form, fit to be persisted into the datastore.
147
153
  #
148
154
  # @since 0.2.0
149
155
  def dump
150
156
  Hash.new.tap do |hash|
151
157
  self.class.attributes.each do |attribute, options|
152
- hash[attribute] = dump_field(self.read_attribute(attribute), options[:type])
158
+ hash[attribute] = dump_field(self.read_attribute(attribute), options)
153
159
  end
154
160
  end
155
161
  end
156
-
162
+
157
163
  private
158
164
 
159
- # Determine how to dump this field. Given a value, it'll determine how to turn it into a value that can be
165
+ # Determine how to dump this field. Given a value, it'll determine how to turn it into a value that can be
160
166
  # persisted into the datastore.
161
167
  #
162
168
  # @since 0.2.0
163
- def dump_field(value, type)
169
+ def dump_field(value, options)
164
170
  return if value.nil? || (value.respond_to?(:empty?) && value.empty?)
165
171
 
166
- case type
172
+ case options[:type]
167
173
  when :string
168
174
  value.to_s
169
175
  when :integer
@@ -179,11 +185,11 @@ module Dynamoid
179
185
  when :datetime
180
186
  value.to_time.to_f
181
187
  when :serialized
182
- value.to_yaml
188
+ options[:serializer] ? options[:serializer].dump(value) : value.to_yaml
183
189
  end
184
190
  end
185
191
 
186
- # Persist the object into the datastore. Assign it an id first if it doesn't have one; then afterwards,
192
+ # Persist the object into the datastore. Assign it an id first if it doesn't have one; then afterwards,
187
193
  # save its indexes.
188
194
  #
189
195
  # @since 0.2.0
@@ -196,7 +202,7 @@ module Dynamoid
196
202
  true
197
203
  end
198
204
  end
199
-
205
+
200
206
  end
201
-
207
+
202
208
  end