adept_dynamoid 0.5.0.6
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.rspec +1 -0
- data/Dynamoid.gemspec +193 -0
- data/Gemfile +23 -0
- data/Gemfile.lock +86 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +265 -0
- data/Rakefile +62 -0
- data/VERSION +1 -0
- data/doc/.nojekyll +0 -0
- data/doc/Dynamoid.html +312 -0
- data/doc/Dynamoid/Adapter.html +1385 -0
- data/doc/Dynamoid/Adapter/AwsSdk.html +1585 -0
- data/doc/Dynamoid/Adapter/Local.html +1574 -0
- data/doc/Dynamoid/Associations.html +131 -0
- data/doc/Dynamoid/Associations/Association.html +794 -0
- data/doc/Dynamoid/Associations/BelongsTo.html +158 -0
- data/doc/Dynamoid/Associations/ClassMethods.html +723 -0
- data/doc/Dynamoid/Associations/HasAndBelongsToMany.html +164 -0
- data/doc/Dynamoid/Associations/HasMany.html +164 -0
- data/doc/Dynamoid/Associations/HasOne.html +158 -0
- data/doc/Dynamoid/Associations/ManyAssociation.html +1640 -0
- data/doc/Dynamoid/Associations/SingleAssociation.html +598 -0
- data/doc/Dynamoid/Components.html +204 -0
- data/doc/Dynamoid/Config.html +395 -0
- data/doc/Dynamoid/Config/Options.html +609 -0
- data/doc/Dynamoid/Criteria.html +131 -0
- data/doc/Dynamoid/Criteria/Chain.html +1063 -0
- data/doc/Dynamoid/Criteria/ClassMethods.html +98 -0
- data/doc/Dynamoid/Document.html +666 -0
- data/doc/Dynamoid/Document/ClassMethods.html +937 -0
- data/doc/Dynamoid/Errors.html +118 -0
- data/doc/Dynamoid/Errors/DocumentNotValid.html +210 -0
- data/doc/Dynamoid/Errors/Error.html +130 -0
- data/doc/Dynamoid/Errors/InvalidField.html +133 -0
- data/doc/Dynamoid/Errors/MissingRangeKey.html +133 -0
- data/doc/Dynamoid/Fields.html +669 -0
- data/doc/Dynamoid/Fields/ClassMethods.html +309 -0
- data/doc/Dynamoid/Finders.html +128 -0
- data/doc/Dynamoid/Finders/ClassMethods.html +516 -0
- data/doc/Dynamoid/Indexes.html +308 -0
- data/doc/Dynamoid/Indexes/ClassMethods.html +353 -0
- data/doc/Dynamoid/Indexes/Index.html +1104 -0
- data/doc/Dynamoid/Persistence.html +651 -0
- data/doc/Dynamoid/Persistence/ClassMethods.html +670 -0
- data/doc/Dynamoid/Validations.html +399 -0
- data/doc/_index.html +461 -0
- data/doc/class_list.html +47 -0
- data/doc/css/common.css +1 -0
- data/doc/css/full_list.css +55 -0
- data/doc/css/style.css +322 -0
- data/doc/file.LICENSE.html +66 -0
- data/doc/file.README.html +312 -0
- data/doc/file_list.html +52 -0
- data/doc/frames.html +13 -0
- data/doc/index.html +312 -0
- data/doc/js/app.js +205 -0
- data/doc/js/full_list.js +173 -0
- data/doc/js/jquery.js +16 -0
- data/doc/method_list.html +1238 -0
- data/doc/top-level-namespace.html +105 -0
- data/lib/dynamoid.rb +47 -0
- data/lib/dynamoid/adapter.rb +177 -0
- data/lib/dynamoid/adapter/aws_sdk.rb +223 -0
- data/lib/dynamoid/associations.rb +106 -0
- data/lib/dynamoid/associations/association.rb +105 -0
- data/lib/dynamoid/associations/belongs_to.rb +44 -0
- data/lib/dynamoid/associations/has_and_belongs_to_many.rb +40 -0
- data/lib/dynamoid/associations/has_many.rb +39 -0
- data/lib/dynamoid/associations/has_one.rb +39 -0
- data/lib/dynamoid/associations/many_association.rb +191 -0
- data/lib/dynamoid/associations/single_association.rb +69 -0
- data/lib/dynamoid/components.rb +36 -0
- data/lib/dynamoid/config.rb +57 -0
- data/lib/dynamoid/config/options.rb +78 -0
- data/lib/dynamoid/criteria.rb +29 -0
- data/lib/dynamoid/criteria/chain.rb +243 -0
- data/lib/dynamoid/dirty.rb +41 -0
- data/lib/dynamoid/document.rb +184 -0
- data/lib/dynamoid/errors.rb +28 -0
- data/lib/dynamoid/fields.rb +130 -0
- data/lib/dynamoid/finders.rb +131 -0
- data/lib/dynamoid/identity_map.rb +96 -0
- data/lib/dynamoid/indexes.rb +69 -0
- data/lib/dynamoid/indexes/index.rb +103 -0
- data/lib/dynamoid/middleware/identity_map.rb +16 -0
- data/lib/dynamoid/persistence.rb +247 -0
- data/lib/dynamoid/validations.rb +36 -0
- data/spec/app/models/address.rb +10 -0
- data/spec/app/models/camel_case.rb +24 -0
- data/spec/app/models/magazine.rb +11 -0
- data/spec/app/models/message.rb +9 -0
- data/spec/app/models/sponsor.rb +8 -0
- data/spec/app/models/subscription.rb +12 -0
- data/spec/app/models/tweet.rb +12 -0
- data/spec/app/models/user.rb +26 -0
- data/spec/dynamoid/adapter/aws_sdk_spec.rb +186 -0
- data/spec/dynamoid/adapter_spec.rb +117 -0
- data/spec/dynamoid/associations/association_spec.rb +194 -0
- data/spec/dynamoid/associations/belongs_to_spec.rb +71 -0
- data/spec/dynamoid/associations/has_and_belongs_to_many_spec.rb +47 -0
- data/spec/dynamoid/associations/has_many_spec.rb +42 -0
- data/spec/dynamoid/associations/has_one_spec.rb +45 -0
- data/spec/dynamoid/associations_spec.rb +16 -0
- data/spec/dynamoid/config_spec.rb +27 -0
- data/spec/dynamoid/criteria/chain_spec.rb +140 -0
- data/spec/dynamoid/criteria_spec.rb +72 -0
- data/spec/dynamoid/dirty_spec.rb +49 -0
- data/spec/dynamoid/document_spec.rb +118 -0
- data/spec/dynamoid/fields_spec.rb +127 -0
- data/spec/dynamoid/finders_spec.rb +135 -0
- data/spec/dynamoid/identity_map_spec.rb +45 -0
- data/spec/dynamoid/indexes/index_spec.rb +104 -0
- data/spec/dynamoid/indexes_spec.rb +25 -0
- data/spec/dynamoid/persistence_spec.rb +176 -0
- data/spec/dynamoid/validations_spec.rb +36 -0
- data/spec/dynamoid_spec.rb +9 -0
- data/spec/spec_helper.rb +50 -0
- metadata +376 -0
@@ -0,0 +1,96 @@
|
|
1
|
+
module Dynamoid
|
2
|
+
module IdentityMap
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def self.clear
|
6
|
+
models.each { |m| m.identity_map.clear }
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.models
|
10
|
+
Dynamoid::Config.included_models
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def identity_map
|
15
|
+
@identity_map ||= {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def from_database(attrs = {})
|
19
|
+
return super if identity_map_off?
|
20
|
+
|
21
|
+
key = identity_map_key(attrs)
|
22
|
+
document = identity_map[key]
|
23
|
+
|
24
|
+
if document.nil?
|
25
|
+
document = super
|
26
|
+
identity_map[key] = document
|
27
|
+
else
|
28
|
+
document.load(attrs)
|
29
|
+
end
|
30
|
+
|
31
|
+
document
|
32
|
+
end
|
33
|
+
|
34
|
+
def find_by_id(id, options = {})
|
35
|
+
return super if identity_map_off?
|
36
|
+
|
37
|
+
key = id.to_s
|
38
|
+
|
39
|
+
if range_key = options[:range_key]
|
40
|
+
key += "::#{range_key}"
|
41
|
+
end
|
42
|
+
|
43
|
+
if identity_map[key]
|
44
|
+
identity_map[key]
|
45
|
+
else
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def identity_map_key(attrs)
|
51
|
+
key = attrs[hash_key].to_s
|
52
|
+
if range_key
|
53
|
+
key += "::#{attrs[range_key]}"
|
54
|
+
end
|
55
|
+
key
|
56
|
+
end
|
57
|
+
|
58
|
+
def identity_map_on?
|
59
|
+
Dynamoid::Config.identity_map
|
60
|
+
end
|
61
|
+
|
62
|
+
def identity_map_off?
|
63
|
+
!identity_map_on?
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def identity_map
|
68
|
+
self.class.identity_map
|
69
|
+
end
|
70
|
+
|
71
|
+
def save(*args)
|
72
|
+
return super if self.class.identity_map_off?
|
73
|
+
|
74
|
+
if result = super
|
75
|
+
identity_map[identity_map_key] = self
|
76
|
+
end
|
77
|
+
result
|
78
|
+
end
|
79
|
+
|
80
|
+
def delete
|
81
|
+
return super if self.class.identity_map_off?
|
82
|
+
|
83
|
+
identity_map.delete(identity_map_key)
|
84
|
+
super
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def identity_map_key
|
89
|
+
key = hash_key.to_s
|
90
|
+
if self.class.range_key
|
91
|
+
key += "::#{range_value}"
|
92
|
+
end
|
93
|
+
key
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'dynamoid/indexes/index'
|
3
|
+
|
4
|
+
module Dynamoid #:nodoc:
|
5
|
+
|
6
|
+
# Indexes are quick ways of performing queries by anything other than id in DynamoDB. They are denormalized tables;
|
7
|
+
# that is, data is duplicated in the initial table (where the object is saved) and the index table (where
|
8
|
+
# we perform indexing).
|
9
|
+
module Indexes
|
10
|
+
extend ActiveSupport::Concern
|
11
|
+
|
12
|
+
# Make some helpful attributes to persist indexes.
|
13
|
+
included do
|
14
|
+
class_attribute :indexes
|
15
|
+
|
16
|
+
self.indexes = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
|
21
|
+
# The call to create an index. Generates a new index with the specified options -- for more information, see Dynamoid::Indexes::Index.
|
22
|
+
# This function also attempts to immediately create the indexing table if it does not exist already.
|
23
|
+
#
|
24
|
+
# @since 0.2.0
|
25
|
+
def index(name, options = {})
|
26
|
+
index = Dynamoid::Indexes::Index.new(self, name, options)
|
27
|
+
self.indexes[index.name] = index
|
28
|
+
create_indexes
|
29
|
+
end
|
30
|
+
|
31
|
+
# Helper function to find indexes.
|
32
|
+
#
|
33
|
+
# @since 0.2.0
|
34
|
+
def find_index(index)
|
35
|
+
self.indexes[Array(index).collect(&:to_s).sort.collect(&:to_sym)]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Helper function to create indexes (if they don't exist already).
|
39
|
+
#
|
40
|
+
# @since 0.2.0
|
41
|
+
def create_indexes
|
42
|
+
self.indexes.each do |name, index|
|
43
|
+
opts = {:table_name => index.table_name, :id => :id}
|
44
|
+
opts[:range_key] = { :range => :number } if index.range_key?
|
45
|
+
self.create_table(opts)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Callback for an object to save itself to each of a class' indexes.
|
51
|
+
#
|
52
|
+
# @since 0.2.0
|
53
|
+
def save_indexes
|
54
|
+
self.class.indexes.each do |name, index|
|
55
|
+
index.save(self)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Callback for an object to delete itself from each of a class' indexes.
|
60
|
+
#
|
61
|
+
# @since 0.2.0
|
62
|
+
def delete_indexes
|
63
|
+
self.class.indexes.each do |name, index|
|
64
|
+
index.delete(self)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Dynamoid #:nodoc:
|
3
|
+
module Indexes
|
4
|
+
|
5
|
+
# The class contains all the information an index contains, including its keys and which attributes it covers.
|
6
|
+
class Index
|
7
|
+
attr_accessor :source, :name, :hash_keys, :range_keys
|
8
|
+
alias_method :range_key?, :range_keys
|
9
|
+
|
10
|
+
# Create a new index. Pass either :range => true or :range => :column_name to create a ranged index on that column.
|
11
|
+
#
|
12
|
+
# @param [Class] source the source class for the index
|
13
|
+
# @param [Symbol] name the name of the index
|
14
|
+
#
|
15
|
+
# @since 0.2.0
|
16
|
+
def initialize(source, name, options = {})
|
17
|
+
@source = source
|
18
|
+
|
19
|
+
if options.delete(:range)
|
20
|
+
@range_keys = sort(name)
|
21
|
+
elsif options[:range_key]
|
22
|
+
@range_keys = sort(options[:range_key])
|
23
|
+
end
|
24
|
+
@hash_keys = sort(name)
|
25
|
+
@name = sort([hash_keys, range_keys])
|
26
|
+
|
27
|
+
raise Dynamoid::Errors::InvalidField, 'A key specified for an index is not a field' unless keys.all?{|n| source.attributes.include?(n)}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Sort objects into alphabetical strings, used for composing index names correctly (since we always assume they're alphabetical).
|
31
|
+
#
|
32
|
+
# @example find all users by first and last name
|
33
|
+
# sort([:gamma, :alpha, :beta, :omega]) # => [:alpha, :beta, :gamma, :omega]
|
34
|
+
#
|
35
|
+
# @since 0.2.0
|
36
|
+
def sort(objs)
|
37
|
+
Array(objs).flatten.compact.uniq.collect(&:to_s).sort.collect(&:to_sym)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Return the array of keys this index uses for its table.
|
41
|
+
#
|
42
|
+
# @since 0.2.0
|
43
|
+
def keys
|
44
|
+
[Array(hash_keys) + Array(range_keys)].flatten.uniq
|
45
|
+
end
|
46
|
+
|
47
|
+
# Return the table name for this index.
|
48
|
+
#
|
49
|
+
# @since 0.2.0
|
50
|
+
def table_name
|
51
|
+
"#{Dynamoid::Config.namespace}_index_" + source.table_name.sub("#{Dynamoid::Config.namespace}_", '').singularize + "_#{name.collect(&:to_s).collect(&:pluralize).join('_and_')}"
|
52
|
+
end
|
53
|
+
|
54
|
+
# Given either an object or a list of attributes, generate a hash key and a range key for the index. Optionally pass in
|
55
|
+
# true to changed_attributes for a list of all the object's dirty attributes in convenient index form (for deleting stale
|
56
|
+
# information from the indexes).
|
57
|
+
#
|
58
|
+
# @param [Object] attrs either an object that responds to :attributes, or a hash of attributes
|
59
|
+
#
|
60
|
+
# @return [Hash] a hash with the keys :hash_value and :range_value
|
61
|
+
#
|
62
|
+
# @since 0.2.0
|
63
|
+
def values(attrs, changed_attributes = false)
|
64
|
+
if changed_attributes
|
65
|
+
hash = {}
|
66
|
+
attrs.changes.each {|k, v| hash[k.to_sym] = (v.first || v.last)}
|
67
|
+
attrs = hash
|
68
|
+
end
|
69
|
+
attrs = attrs.send(:attributes) if attrs.respond_to?(:attributes)
|
70
|
+
{}.tap do |hash|
|
71
|
+
hash[:hash_value] = hash_keys.collect{|key| attrs[key]}.join('.')
|
72
|
+
hash[:range_value] = range_keys.inject(0.0) {|sum, key| sum + attrs[key].to_f} if self.range_key?
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Save an object to this index, merging it with existing ids if there's already something present at this index location.
|
77
|
+
# First, though, delete this object from its old indexes (so the object isn't listed in an erroneous index).
|
78
|
+
#
|
79
|
+
# @since 0.2.0
|
80
|
+
def save(obj)
|
81
|
+
self.delete(obj, true)
|
82
|
+
values = values(obj)
|
83
|
+
return true if values[:hash_value].blank? || (!values[:range_value].nil? && values[:range_value].blank?)
|
84
|
+
existing = Dynamoid::Adapter.read(self.table_name, values[:hash_value], { :range_key => values[:range_value] })
|
85
|
+
ids = ((existing and existing[:ids]) or Set.new)
|
86
|
+
Dynamoid::Adapter.write(self.table_name, {:id => values[:hash_value], :ids => ids.merge([obj.id]), :range => values[:range_value]})
|
87
|
+
end
|
88
|
+
|
89
|
+
# Delete an object from this index, preserving existing ids if there are any, and failing gracefully if for some reason the
|
90
|
+
# index doesn't already have this object in it.
|
91
|
+
#
|
92
|
+
# @since 0.2.0
|
93
|
+
def delete(obj, changed_attributes = false)
|
94
|
+
values = values(obj, changed_attributes)
|
95
|
+
return true if values[:hash_value].blank? || (!values[:range_value].nil? && values[:range_value].blank?)
|
96
|
+
existing = Dynamoid::Adapter.read(self.table_name, values[:hash_value], { :range_key => values[:range_value]})
|
97
|
+
return true unless existing && existing[:ids] && existing[:ids].include?(obj.id)
|
98
|
+
Dynamoid::Adapter.write(self.table_name, {:id => values[:hash_value], :ids => (existing[:ids] - Set[obj.id]), :range => values[:range_value]})
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,247 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
# encoding: utf-8
|
4
|
+
module Dynamoid
|
5
|
+
|
6
|
+
# Persistence is responsible for dumping objects to and marshalling objects from the datastore. It tries to reserialize
|
7
|
+
# values to be of the same type as when they were passed in, based on the fields in the class.
|
8
|
+
module Persistence
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
attr_accessor :new_record
|
12
|
+
alias :new_record? :new_record
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
# Returns the name of the table the class is for.
|
17
|
+
#
|
18
|
+
# @since 0.2.0
|
19
|
+
def table_name
|
20
|
+
"#{Dynamoid::Config.namespace}_#{options[:name] ? options[:name] : self.name.split('::').last.downcase.pluralize}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Creates a table.
|
24
|
+
#
|
25
|
+
# @param [Hash] options options to pass for table creation
|
26
|
+
# @option options [Symbol] :id the id field for the table
|
27
|
+
# @option options [Symbol] :table_name the actual name for the table
|
28
|
+
# @option options [Integer] :read_capacity set the read capacity for the table; does not work on existing tables
|
29
|
+
# @option options [Integer] :write_capacity set the write capacity for the table; does not work on existing tables
|
30
|
+
# @option options [Hash] {range_key => :type} a hash of the name of the range key and a symbol of its type
|
31
|
+
#
|
32
|
+
# @since 0.4.0
|
33
|
+
def create_table(options = {})
|
34
|
+
if self.range_key
|
35
|
+
range_key_hash = { range_key => dynamo_type(attributes[range_key][:type]) }
|
36
|
+
else
|
37
|
+
range_key_hash = nil
|
38
|
+
end
|
39
|
+
options = {
|
40
|
+
:id => self.hash_key,
|
41
|
+
:table_name => self.table_name,
|
42
|
+
:write_capacity => self.write_capacity,
|
43
|
+
:read_capacity => self.read_capacity,
|
44
|
+
:range_key => range_key_hash
|
45
|
+
}.merge(options)
|
46
|
+
|
47
|
+
return true if table_exists?(options[:table_name])
|
48
|
+
|
49
|
+
Dynamoid::Adapter.tables << options[:table_name] if Dynamoid::Adapter.create_table(options[:table_name], options[:id], options)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Does a table with this name exist?
|
53
|
+
#
|
54
|
+
# @since 0.2.0
|
55
|
+
def table_exists?(table_name)
|
56
|
+
Dynamoid::Adapter.tables ? Dynamoid::Adapter.tables.include?(table_name) : false
|
57
|
+
end
|
58
|
+
|
59
|
+
def from_database(attrs = {})
|
60
|
+
new(attrs).tap { |r| r.new_record = false }
|
61
|
+
end
|
62
|
+
|
63
|
+
# Undump an object into a hash, converting each type from a string representation of itself into the type specified by the field.
|
64
|
+
#
|
65
|
+
# @since 0.2.0
|
66
|
+
def undump(incoming = nil)
|
67
|
+
incoming = (incoming || {}).symbolize_keys
|
68
|
+
Hash.new.tap do |hash|
|
69
|
+
self.attributes.each do |attribute, options|
|
70
|
+
hash[attribute] = undump_field(incoming[attribute], options)
|
71
|
+
end
|
72
|
+
incoming.each {|attribute, value| hash[attribute] ||= value }
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Undump a value for a given type. Given a string, it'll determine (based on the type provided) whether to turn it into a
|
77
|
+
# string, integer, float, set, array, datetime, or serialized return value.
|
78
|
+
#
|
79
|
+
# @since 0.2.0
|
80
|
+
def undump_field(value, options)
|
81
|
+
if value.nil? && (default_value = options[:default])
|
82
|
+
value = default_value.respond_to?(:call) ? default_value.call : default_value
|
83
|
+
else
|
84
|
+
return if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
85
|
+
end
|
86
|
+
|
87
|
+
case options[:type]
|
88
|
+
when :string
|
89
|
+
value.to_s
|
90
|
+
when :integer
|
91
|
+
value.to_i
|
92
|
+
when :float
|
93
|
+
value.to_f
|
94
|
+
when :set, :array
|
95
|
+
if value.is_a?(Set) || value.is_a?(Array)
|
96
|
+
value
|
97
|
+
else
|
98
|
+
Set[value]
|
99
|
+
end
|
100
|
+
when :datetime
|
101
|
+
if value.is_a?(Date) || value.is_a?(DateTime) || value.is_a?(Time)
|
102
|
+
value
|
103
|
+
else
|
104
|
+
Time.at(value).to_datetime
|
105
|
+
end
|
106
|
+
when :serialized
|
107
|
+
if value.is_a?(String)
|
108
|
+
options[:serializer] ? options[:serializer].load(value) : YAML.load(value)
|
109
|
+
else
|
110
|
+
value
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def dynamo_type(type)
|
116
|
+
case type
|
117
|
+
when :integer, :float, :datetime
|
118
|
+
:number
|
119
|
+
when :string, :serialized
|
120
|
+
:string
|
121
|
+
else
|
122
|
+
raise 'unknown type'
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
# Set updated_at and any passed in field to current DateTime. Useful for things like last_login_at, etc.
|
129
|
+
#
|
130
|
+
def touch(name = nil)
|
131
|
+
now = DateTime.now
|
132
|
+
self.updated_at = now
|
133
|
+
attributes[name] = now if name
|
134
|
+
save
|
135
|
+
end
|
136
|
+
|
137
|
+
# Is this object persisted in the datastore? Required for some ActiveModel integration stuff.
|
138
|
+
#
|
139
|
+
# @since 0.2.0
|
140
|
+
def persisted?
|
141
|
+
!new_record?
|
142
|
+
end
|
143
|
+
|
144
|
+
# Run the callbacks and then persist this object in the datastore.
|
145
|
+
#
|
146
|
+
# @since 0.2.0
|
147
|
+
def save(options = {})
|
148
|
+
self.class.create_table
|
149
|
+
|
150
|
+
if new_record?
|
151
|
+
run_callbacks(:create) { persist }
|
152
|
+
else
|
153
|
+
persist
|
154
|
+
end
|
155
|
+
|
156
|
+
self
|
157
|
+
end
|
158
|
+
|
159
|
+
def update!(conditions = {}, &block)
|
160
|
+
options = range_key ? {:range_key => dump_field(self.read_attribute(range_key), self.class.attributes[range_key])} : {}
|
161
|
+
new_attrs = Dynamoid::Adapter.update_item(self.class.table_name, self.hash_key, options.merge(:conditions => conditions), &block)
|
162
|
+
load(new_attrs)
|
163
|
+
end
|
164
|
+
|
165
|
+
def update(conditions = {}, &block)
|
166
|
+
update!(conditions, &block)
|
167
|
+
true
|
168
|
+
rescue Dynamoid::Errors::ConditionalCheckFailedException
|
169
|
+
false
|
170
|
+
end
|
171
|
+
|
172
|
+
# Delete this object, but only after running callbacks for it.
|
173
|
+
#
|
174
|
+
# @since 0.2.0
|
175
|
+
def destroy
|
176
|
+
run_callbacks(:destroy) do
|
177
|
+
self.delete
|
178
|
+
end
|
179
|
+
self
|
180
|
+
end
|
181
|
+
|
182
|
+
# Delete this object from the datastore and all indexes.
|
183
|
+
#
|
184
|
+
# @since 0.2.0
|
185
|
+
def delete
|
186
|
+
delete_indexes
|
187
|
+
options = range_key ? {:range_key => dump_field(self.read_attribute(range_key), self.class.attributes[range_key])} : {}
|
188
|
+
Dynamoid::Adapter.delete(self.class.table_name, self.hash_key, options)
|
189
|
+
end
|
190
|
+
|
191
|
+
# Dump this object's attributes into hash form, fit to be persisted into the datastore.
|
192
|
+
#
|
193
|
+
# @since 0.2.0
|
194
|
+
def dump
|
195
|
+
Hash.new.tap do |hash|
|
196
|
+
self.class.attributes.each do |attribute, options|
|
197
|
+
hash[attribute] = dump_field(self.read_attribute(attribute), options)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
private
|
203
|
+
|
204
|
+
# Determine how to dump this field. Given a value, it'll determine how to turn it into a value that can be
|
205
|
+
# persisted into the datastore.
|
206
|
+
#
|
207
|
+
# @since 0.2.0
|
208
|
+
def dump_field(value, options)
|
209
|
+
return if value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
210
|
+
|
211
|
+
case options[:type]
|
212
|
+
when :string
|
213
|
+
value.to_s
|
214
|
+
when :integer
|
215
|
+
value.to_i
|
216
|
+
when :float
|
217
|
+
value.to_f
|
218
|
+
when :set, :array
|
219
|
+
if value.is_a?(Set) || value.is_a?(Array)
|
220
|
+
value
|
221
|
+
else
|
222
|
+
Set[value]
|
223
|
+
end
|
224
|
+
when :datetime
|
225
|
+
value.to_time.to_f
|
226
|
+
when :serialized
|
227
|
+
options[:serializer] ? options[:serializer].dump(value) : value.to_yaml
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
# Persist the object into the datastore. Assign it an id first if it doesn't have one; then afterwards,
|
232
|
+
# save its indexes.
|
233
|
+
#
|
234
|
+
# @since 0.2.0
|
235
|
+
def persist
|
236
|
+
run_callbacks(:save) do
|
237
|
+
self.hash_key = SecureRandom.uuid if self.hash_key.nil? || self.hash_key.blank?
|
238
|
+
Dynamoid::Adapter.write(self.class.table_name, self.dump)
|
239
|
+
save_indexes
|
240
|
+
@new_record = false
|
241
|
+
true
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|