dynamoid-edge 1.1.0

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.
@@ -0,0 +1,324 @@
1
+ require 'bigdecimal'
2
+ require 'securerandom'
3
+ require 'yaml'
4
+
5
+ # encoding: utf-8
6
+ module Dynamoid
7
+
8
+ # Persistence is responsible for dumping objects to and marshalling objects from the datastore. It tries to reserialize
9
+ # values to be of the same type as when they were passed in, based on the fields in the class.
10
+ module Persistence
11
+ extend ActiveSupport::Concern
12
+
13
+ attr_accessor :new_record
14
+ alias :new_record? :new_record
15
+
16
+ module ClassMethods
17
+
18
+ def table_name
19
+ @table_name ||= "#{Dynamoid::Config.namespace}_#{options[:name] || base_class.name.split('::').last.downcase.pluralize}"
20
+ end
21
+
22
+ # Creates a table.
23
+ #
24
+ # @param [Hash] options options to pass for table creation
25
+ # @option options [Symbol] :id the id field for the table
26
+ # @option options [Symbol] :table_name the actual name for the table
27
+ # @option options [Integer] :read_capacity set the read capacity for the table; does not work on existing tables
28
+ # @option options [Integer] :write_capacity set the write capacity for the table; does not work on existing tables
29
+ # @option options [Hash] {range_key => :type} a hash of the name of the range key and a symbol of its type
30
+ # @option options [Symbol] :hash_key_type the dynamo type of the hash key (:string or :number)
31
+ # @since 0.4.0
32
+ def create_table(options = {})
33
+ if self.range_key
34
+ range_key_hash = { range_key => dynamo_type(attributes[range_key][:type]) }
35
+ else
36
+ range_key_hash = nil
37
+ end
38
+ options = {
39
+ :id => self.hash_key,
40
+ :table_name => self.table_name,
41
+ :write_capacity => self.write_capacity,
42
+ :read_capacity => self.read_capacity,
43
+ :range_key => range_key_hash,
44
+ :hash_key_type => dynamo_type(attributes[self.hash_key][:type]),
45
+ :local_secondary_indexes => self.local_secondary_indexes.values,
46
+ :global_secondary_indexes => self.global_secondary_indexes.values
47
+ }.merge(options)
48
+
49
+ Dynamoid.adapter.create_table(options[:table_name], options[:id], options)
50
+ end
51
+
52
+ def from_database(attrs = {})
53
+ clazz = attrs[:type] ? obj = attrs[:type].constantize : self
54
+ clazz.new(attrs).tap { |r| r.new_record = false }
55
+ end
56
+
57
+ # Undump an object into a hash, converting each type from a string representation of itself into the type specified by the field.
58
+ #
59
+ # @since 0.2.0
60
+ def undump(incoming = nil)
61
+ incoming = (incoming || {}).symbolize_keys
62
+ Hash.new.tap do |hash|
63
+ self.attributes.each do |attribute, options|
64
+ hash[attribute] = undump_field(incoming[attribute], options)
65
+ end
66
+ incoming.each {|attribute, value| hash[attribute] = value unless hash.has_key? attribute }
67
+ end
68
+ end
69
+
70
+ # Undump a string value for a given type.
71
+ #
72
+ # @since 0.2.0
73
+ def undump_field(value, options)
74
+ if (field_class = options[:type]).is_a?(Class)
75
+ raise 'Dynamoid class-type fields do not support default values' if options[:default]
76
+
77
+ if field_class.respond_to?(:dynamoid_load)
78
+ field_class.dynamoid_load(value)
79
+ end
80
+ elsif options[:type] == :serialized
81
+ if value.is_a?(String)
82
+ options[:serializer] ? options[:serializer].load(value) : YAML.load(value)
83
+ else
84
+ value
85
+ end
86
+ else
87
+ if value.nil? && (default_value = options[:default])
88
+ value = default_value.respond_to?(:call) ? default_value.call : default_value
89
+ end
90
+
91
+ if !value.nil?
92
+ case options[:type]
93
+ when :string
94
+ value.to_s
95
+ when :integer
96
+ Integer(value)
97
+ when :number
98
+ BigDecimal.new(value.to_s)
99
+ when :array
100
+ value.to_a
101
+ when :set
102
+ Set.new(value)
103
+ when :datetime
104
+ if value.is_a?(Date) || value.is_a?(DateTime) || value.is_a?(Time)
105
+ value
106
+ else
107
+ Time.at(value).to_datetime
108
+ end
109
+ when :boolean
110
+ # persisted as 't', but because undump is called during initialize it can come in as true
111
+ if value == 't' || value == true
112
+ true
113
+ elsif value == 'f' || value == false
114
+ false
115
+ else
116
+ raise ArgumentError, "Boolean column neither true nor false"
117
+ end
118
+ else
119
+ raise ArgumentError, "Unknown type #{options[:type]}"
120
+ end
121
+ end
122
+ end
123
+ end
124
+
125
+ def dynamo_type(type)
126
+ if type.is_a?(Class)
127
+ type.respond_to?(:dynamoid_field_type) ? type.dynamoid_field_type : :string
128
+ else
129
+ case type
130
+ when :integer, :number, :datetime
131
+ :number
132
+ when :string, :serialized
133
+ :string
134
+ else
135
+ raise 'unknown type'
136
+ end
137
+ end
138
+ end
139
+
140
+ end
141
+
142
+ # Set updated_at and any passed in field to current DateTime. Useful for things like last_login_at, etc.
143
+ #
144
+ def touch(name = nil)
145
+ now = DateTime.now
146
+ self.updated_at = now
147
+ attributes[name] = now if name
148
+ save
149
+ end
150
+
151
+ # Is this object persisted in the datastore? Required for some ActiveModel integration stuff.
152
+ #
153
+ # @since 0.2.0
154
+ def persisted?
155
+ !new_record?
156
+ end
157
+
158
+ # Run the callbacks and then persist this object in the datastore.
159
+ #
160
+ # @since 0.2.0
161
+ def save(options = {})
162
+ self.class.create_table
163
+
164
+ if new_record?
165
+ conditions = { :unless_exists => [self.class.hash_key]}
166
+ conditions[:unless_exists] << range_key if(range_key)
167
+
168
+ run_callbacks(:create) { persist(conditions) }
169
+ else
170
+ persist
171
+ end
172
+
173
+ self
174
+ end
175
+
176
+ #
177
+ # update!() will increment the lock_version if the table has the column, but will not check it. Thus, a concurrent save will
178
+ # never cause an update! to fail, but an update! may cause a concurrent save to fail.
179
+ #
180
+ #
181
+ def update!(conditions = {}, &block)
182
+ run_callbacks(:update) do
183
+ options = range_key ? {:range_key => dump_field(self.read_attribute(range_key), self.class.attributes[range_key])} : {}
184
+
185
+ begin
186
+ new_attrs = Dynamoid.adapter.update_item(self.class.table_name, self.hash_key, options.merge(:conditions => conditions)) do |t|
187
+ if(self.class.attributes[:lock_version])
188
+ t.add(lock_version: 1)
189
+ end
190
+
191
+ yield t
192
+ end
193
+ load(new_attrs)
194
+ rescue Dynamoid::Errors::ConditionalCheckFailedException
195
+ raise Dynamoid::Errors::StaleObjectError.new(self, 'update')
196
+ end
197
+ end
198
+ end
199
+
200
+ def update(conditions = {}, &block)
201
+ update!(conditions, &block)
202
+ true
203
+ rescue Dynamoid::Errors::StaleObjectError
204
+ false
205
+ end
206
+
207
+ # Delete this object, but only after running callbacks for it.
208
+ #
209
+ # @since 0.2.0
210
+ def destroy
211
+ run_callbacks(:destroy) do
212
+ self.delete
213
+ end
214
+ self
215
+ end
216
+
217
+ # Delete this object from the datastore.
218
+ #
219
+ # @since 0.2.0
220
+ def delete
221
+ options = range_key ? {:range_key => dump_field(self.read_attribute(range_key), self.class.attributes[range_key])} : {}
222
+
223
+ # Add an optimistic locking check if the lock_version column exists
224
+ if(self.class.attributes[:lock_version])
225
+ conditions = {:if => {}}
226
+ conditions[:if][:lock_version] =
227
+ if changes[:lock_version].nil?
228
+ self.lock_version
229
+ else
230
+ changes[:lock_version][0]
231
+ end
232
+ options[:conditions] = conditions
233
+ end
234
+ Dynamoid.adapter.delete(self.class.table_name, self.hash_key, options)
235
+ rescue Dynamoid::Errors::ConditionalCheckFailedException
236
+ raise Dynamoid::Errors::StaleObjectError.new(self, 'delete')
237
+ end
238
+
239
+ # Dump this object's attributes into hash form, fit to be persisted into the datastore.
240
+ #
241
+ # @since 0.2.0
242
+ def dump
243
+ Hash.new.tap do |hash|
244
+ self.class.attributes.each do |attribute, options|
245
+ hash[attribute] = dump_field(self.read_attribute(attribute), options)
246
+ end
247
+ end
248
+ end
249
+
250
+ private
251
+
252
+ # Determine how to dump this field. Given a value, it'll determine how to turn it into a value that can be
253
+ # persisted into the datastore.
254
+ #
255
+ # @since 0.2.0
256
+ def dump_field(value, options)
257
+ if (field_class = options[:type]).is_a?(Class)
258
+ if value.respond_to?(:dynamoid_dump)
259
+ value.dynamoid_dump
260
+ elsif field_class.respond_to?(:dynamoid_dump)
261
+ field_class.dynamoid_dump(value)
262
+ else
263
+ raise ArgumentError, "Neither #{field_class} nor #{value} support serialization for Dynamoid."
264
+ end
265
+ else
266
+ case options[:type]
267
+ when :string
268
+ !value.nil? ? value.to_s : nil
269
+ when :integer
270
+ !value.nil? ? Integer(value) : nil
271
+ when :number
272
+ !value.nil? ? value : nil
273
+ when :set
274
+ !value.nil? ? Set.new(value) : nil
275
+ when :array
276
+ !value.nil? ? value : nil
277
+ when :datetime
278
+ !value.nil? ? value.to_time.to_f : nil
279
+ when :serialized
280
+ options[:serializer] ? options[:serializer].dump(value) : value.to_yaml
281
+ when :boolean
282
+ !value.nil? ? value.to_s[0] : nil
283
+ else
284
+ raise ArgumentError, "Unknown type #{options[:type]}"
285
+ end
286
+ end
287
+ end
288
+
289
+ # Persist the object into the datastore. Assign it an id first if it doesn't have one.
290
+ #
291
+ # @since 0.2.0
292
+ def persist(conditions = nil)
293
+ run_callbacks(:save) do
294
+ self.hash_key = SecureRandom.uuid if self.hash_key.nil? || self.hash_key.blank?
295
+
296
+ # Add an exists check to prevent overwriting existing records with new ones
297
+ if(new_record?)
298
+ conditions ||= {}
299
+ (conditions[:unless_exists] ||= []) << self.class.hash_key
300
+ end
301
+
302
+ # Add an optimistic locking check if the lock_version column exists
303
+ if(self.class.attributes[:lock_version])
304
+ conditions ||= {}
305
+ self.lock_version = (lock_version || 0) + 1
306
+ #Uses the original lock_version value from ActiveModel::Dirty in case user changed lock_version manually
307
+ (conditions[:if] ||= {})[:lock_version] = changes[:lock_version][0] if(changes[:lock_version][0])
308
+ end
309
+
310
+ begin
311
+ Dynamoid.adapter.write(self.class.table_name, self.dump, conditions)
312
+ @new_record = false
313
+ true
314
+ rescue Dynamoid::Errors::ConditionalCheckFailedException => e
315
+ if new_record?
316
+ raise Dynamoid::Errors::RecordNotUnique.new(e, self)
317
+ else
318
+ raise Dynamoid::Errors::StaleObjectError.new(self, 'persist')
319
+ end
320
+ end
321
+ end
322
+ end
323
+ end
324
+ end
@@ -0,0 +1,36 @@
1
+ # encoding: utf-8
2
+ module Dynamoid
3
+
4
+ # Provide ActiveModel validations to Dynamoid documents.
5
+ module Validations
6
+ extend ActiveSupport::Concern
7
+
8
+ include ActiveModel::Validations
9
+ include ActiveModel::Validations::Callbacks
10
+
11
+ # Override save to provide validation support.
12
+ #
13
+ # @since 0.2.0
14
+ def save(options = {})
15
+ options.reverse_merge!(:validate => true)
16
+ return false if options[:validate] and (not valid?)
17
+ super
18
+ end
19
+
20
+ # Is this object valid?
21
+ #
22
+ # @since 0.2.0
23
+ def valid?(context = nil)
24
+ context ||= (new_record? ? :create : :update)
25
+ super(context)
26
+ end
27
+
28
+ # Raise an error unless this object is valid.
29
+ #
30
+ # @since 0.2.0
31
+ def save!
32
+ raise Dynamoid::Errors::DocumentNotValid.new(self) unless valid?
33
+ save(:validate => false)
34
+ end
35
+ end
36
+ end
data/lib/dynamoid.rb ADDED
@@ -0,0 +1,50 @@
1
+ require "delegate"
2
+ require "time"
3
+ require "securerandom"
4
+ require "active_support"
5
+ require "active_support/core_ext"
6
+ require 'active_support/json'
7
+ require "active_support/inflector"
8
+ require "active_support/lazy_load_hooks"
9
+ require "active_support/time_with_zone"
10
+ require "active_model"
11
+
12
+ require 'dynamoid/errors'
13
+ require 'dynamoid/fields'
14
+ require 'dynamoid/indexes'
15
+ require 'dynamoid/associations'
16
+ require 'dynamoid/persistence'
17
+ require 'dynamoid/dirty'
18
+ require 'dynamoid/validations'
19
+ require 'dynamoid/criteria'
20
+ require 'dynamoid/finders'
21
+ require 'dynamoid/identity_map'
22
+ require 'dynamoid/config'
23
+ require 'dynamoid/components'
24
+ require 'dynamoid/document'
25
+ require 'dynamoid/adapter'
26
+
27
+ require 'dynamoid/middleware/identity_map'
28
+
29
+ module Dynamoid
30
+ extend self
31
+
32
+ MAX_ITEM_SIZE = 65_536
33
+
34
+ def configure
35
+ block_given? ? yield(Dynamoid::Config) : Dynamoid::Config
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
+ def adapter
48
+ @adapter ||= Adapter.new
49
+ end
50
+ end
metadata ADDED
@@ -0,0 +1,226 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dynamoid-edge
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Josh Symonds
8
+ - Logan Bowers
9
+ - Craig Heneveld
10
+ - Anatha Kumaran
11
+ - Jason Dew
12
+ - Luis Arias
13
+ - Stefan Neculai
14
+ - Philip White
15
+ - Peeyush Kumar
16
+ autorequire:
17
+ bindir: bin
18
+ cert_chain: []
19
+ date: 2016-03-10 00:00:00.000000000 Z
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: activemodel
23
+ requirement: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '4'
28
+ type: :runtime
29
+ prerelease: false
30
+ version_requirements: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '4'
35
+ - !ruby/object:Gem::Dependency
36
+ name: aws-sdk-resources
37
+ requirement: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '2'
42
+ type: :runtime
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '2'
49
+ - !ruby/object:Gem::Dependency
50
+ name: concurrent-ruby
51
+ requirement: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '1.0'
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '1.0'
63
+ - !ruby/object:Gem::Dependency
64
+ name: rake
65
+ requirement: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ - !ruby/object:Gem::Dependency
78
+ name: rspec
79
+ requirement: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3'
84
+ type: :development
85
+ prerelease: false
86
+ version_requirements: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '3'
91
+ - !ruby/object:Gem::Dependency
92
+ name: bundler
93
+ requirement: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ type: :development
99
+ prerelease: false
100
+ version_requirements: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ - !ruby/object:Gem::Dependency
106
+ name: yard
107
+ requirement: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ type: :development
113
+ prerelease: false
114
+ version_requirements: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ - !ruby/object:Gem::Dependency
120
+ name: github-markup
121
+ requirement: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ type: :development
127
+ prerelease: false
128
+ version_requirements: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ - !ruby/object:Gem::Dependency
134
+ name: pry
135
+ requirement: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ type: :development
141
+ prerelease: false
142
+ version_requirements: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ - !ruby/object:Gem::Dependency
148
+ name: coveralls
149
+ requirement: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ type: :development
155
+ prerelease: false
156
+ version_requirements: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ description: Dynamoid is an ORM for Amazon's DynamoDB that supports offline development,
162
+ associations, querying, and everything else you'd expect from an ActiveRecord-style
163
+ replacement.
164
+ email:
165
+ executables: []
166
+ extensions: []
167
+ extra_rdoc_files:
168
+ - LICENSE.txt
169
+ - README.markdown
170
+ files:
171
+ - CHANGELOG.md
172
+ - Gemfile
173
+ - LICENSE.txt
174
+ - README.markdown
175
+ - Rakefile
176
+ - dynamoid-edge.gemspec
177
+ - lib/dynamoid.rb
178
+ - lib/dynamoid/adapter.rb
179
+ - lib/dynamoid/adapter_plugin/aws_sdk_v2.rb
180
+ - lib/dynamoid/associations.rb
181
+ - lib/dynamoid/associations/association.rb
182
+ - lib/dynamoid/associations/belongs_to.rb
183
+ - lib/dynamoid/associations/has_and_belongs_to_many.rb
184
+ - lib/dynamoid/associations/has_many.rb
185
+ - lib/dynamoid/associations/has_one.rb
186
+ - lib/dynamoid/associations/many_association.rb
187
+ - lib/dynamoid/associations/single_association.rb
188
+ - lib/dynamoid/components.rb
189
+ - lib/dynamoid/config.rb
190
+ - lib/dynamoid/config/options.rb
191
+ - lib/dynamoid/criteria.rb
192
+ - lib/dynamoid/criteria/chain.rb
193
+ - lib/dynamoid/dirty.rb
194
+ - lib/dynamoid/document.rb
195
+ - lib/dynamoid/errors.rb
196
+ - lib/dynamoid/fields.rb
197
+ - lib/dynamoid/finders.rb
198
+ - lib/dynamoid/identity_map.rb
199
+ - lib/dynamoid/middleware/identity_map.rb
200
+ - lib/dynamoid/persistence.rb
201
+ - lib/dynamoid/validations.rb
202
+ homepage: http://github.com/Dynamoid/Dynamoid
203
+ licenses:
204
+ - MIT
205
+ metadata: {}
206
+ post_install_message:
207
+ rdoc_options: []
208
+ require_paths:
209
+ - lib
210
+ required_ruby_version: !ruby/object:Gem::Requirement
211
+ requirements:
212
+ - - ">="
213
+ - !ruby/object:Gem::Version
214
+ version: '0'
215
+ required_rubygems_version: !ruby/object:Gem::Requirement
216
+ requirements:
217
+ - - ">="
218
+ - !ruby/object:Gem::Version
219
+ version: '0'
220
+ requirements: []
221
+ rubyforge_project:
222
+ rubygems_version: 2.4.5.1
223
+ signing_key:
224
+ specification_version: 4
225
+ summary: Dynamoid is an ORM for Amazon's DynamoDB
226
+ test_files: []