dynamoid 0.4.0 → 0.4.1

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