ocean-dynamo 0.1.10 → 0.1.11
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.
- checksums.yaml +4 -4
- data/lib/ocean-dynamo.rb +17 -2
- data/lib/ocean-dynamo/attributes.rb +230 -0
- data/lib/ocean-dynamo/base.rb +8 -0
- data/lib/ocean-dynamo/callbacks.rb +15 -0
- data/lib/ocean-dynamo/class_variables.rb +38 -0
- data/lib/ocean-dynamo/exceptions.rb +39 -0
- data/lib/ocean-dynamo/persistence.rb +202 -0
- data/lib/ocean-dynamo/queries.rb +16 -0
- data/lib/ocean-dynamo/schema.rb +66 -0
- data/lib/ocean-dynamo/tables.rb +71 -0
- data/lib/ocean-dynamo/version.rb +1 -1
- metadata +11 -3
- data/lib/ocean-dynamo/dynamo.rb +0 -604
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2715c40f4987fe7ffe19eecd488bc67b5a9cebb3
|
4
|
+
data.tar.gz: 8fdae13f4f5d71632a0c938588e977807a53ec61
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b400e19b2a3b2707adfea3e415fe672352fe0e30267992ce14e70b616a7bce0ee1a569396278e0fe675ec2f4a7a4db65e4e7fd922ecd6d5112d7ce4aa5eff240
|
7
|
+
data.tar.gz: a0cc3aa3eb5866d64db1407267e5c9ac63606452eaeda01eb766f24888d6546b33768b80bcb3df6ccf8851f45876106a660026dee48ef7df6e3cd3b151c54610
|
data/lib/ocean-dynamo.rb
CHANGED
@@ -1,9 +1,24 @@
|
|
1
1
|
require "ocean-dynamo/engine"
|
2
2
|
|
3
|
-
require "
|
3
|
+
require "aws-sdk"
|
4
|
+
|
5
|
+
require "ocean-dynamo/base"
|
6
|
+
require "ocean-dynamo/exceptions"
|
7
|
+
require "ocean-dynamo/class_variables"
|
8
|
+
require "ocean-dynamo/tables"
|
9
|
+
require "ocean-dynamo/schema"
|
10
|
+
require "ocean-dynamo/callbacks"
|
11
|
+
require "ocean-dynamo/attributes"
|
12
|
+
require "ocean-dynamo/queries"
|
13
|
+
require "ocean-dynamo/persistence"
|
4
14
|
|
5
15
|
|
6
16
|
module OceanDynamo
|
7
17
|
|
8
|
-
|
18
|
+
DEFAULT_ATTRIBUTES = [
|
19
|
+
[:created_at, :datetime],
|
20
|
+
[:updated_at, :datetime],
|
21
|
+
[:lock_version, :integer, default: 0]
|
22
|
+
]
|
23
|
+
|
9
24
|
end
|
@@ -0,0 +1,230 @@
|
|
1
|
+
module OceanDynamo
|
2
|
+
class Base
|
3
|
+
include ActiveModel::DeprecatedMassAssignmentSecurity
|
4
|
+
include ActiveModel::ForbiddenAttributesProtection
|
5
|
+
|
6
|
+
attr_reader :attributes
|
7
|
+
attr_reader :destroyed
|
8
|
+
attr_reader :new_record
|
9
|
+
attr_reader :dynamo_item
|
10
|
+
|
11
|
+
|
12
|
+
def initialize(attrs={})
|
13
|
+
run_callbacks :initialize do
|
14
|
+
@attributes = Hash.new
|
15
|
+
fields.each do |name, md|
|
16
|
+
write_attribute(name, evaluate_default(md[:default], md[:type]))
|
17
|
+
self.class.class_eval "def #{name}; read_attribute('#{name.to_s}'); end"
|
18
|
+
self.class.class_eval "def #{name}=(value); write_attribute('#{name.to_s}', value); end"
|
19
|
+
if fields[name][:type] == :boolean
|
20
|
+
self.class.class_eval "def #{name}?; read_attribute('#{name.to_s}'); end"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
@dynamo_item = nil
|
24
|
+
@destroyed = false
|
25
|
+
@new_record = true
|
26
|
+
raise UnknownPrimaryKey unless table_hash_key
|
27
|
+
end
|
28
|
+
attrs && attrs.delete_if { |k, v| !fields.has_key?(k) }
|
29
|
+
super(attrs)
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def read_attribute_for_validation(key)
|
34
|
+
@attributes[key.to_s]
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def read_attribute(attr_name)
|
39
|
+
attr_name = attr_name.to_s
|
40
|
+
if attr_name == 'id' && fields[table_hash_key] != attr_name.to_sym
|
41
|
+
return read_attribute(table_hash_key)
|
42
|
+
end
|
43
|
+
@attributes[attr_name] # Type cast!
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def write_attribute(attr_name, value)
|
48
|
+
attr_name = attr_name.to_s
|
49
|
+
attr_name = table_hash_key.to_s if attr_name == 'id' && fields[table_hash_key]
|
50
|
+
if fields.has_key?(attr_name)
|
51
|
+
@attributes[attr_name] = type_cast_attribute_for_write(attr_name, value)
|
52
|
+
else
|
53
|
+
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{attr_name}'"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def type_cast_attribute_for_write(name, value, metadata=fields[name],
|
59
|
+
type: metadata[:type])
|
60
|
+
case type
|
61
|
+
when :string
|
62
|
+
return nil if value == nil
|
63
|
+
return value.collect(&:to_s) if value.is_a?(Array)
|
64
|
+
value
|
65
|
+
when :integer
|
66
|
+
return nil if value == nil
|
67
|
+
return value.collect(&:to_i) if value.is_a?(Array)
|
68
|
+
value.to_i
|
69
|
+
when :float
|
70
|
+
return nil if value == nil
|
71
|
+
return value.collect(&:to_f) if value.is_a?(Array)
|
72
|
+
value.to_f
|
73
|
+
when :boolean
|
74
|
+
return nil if value == nil
|
75
|
+
return true if value == true
|
76
|
+
return true if value == "true"
|
77
|
+
false
|
78
|
+
when :datetime
|
79
|
+
return nil if value == nil || !value.kind_of?(Time)
|
80
|
+
value
|
81
|
+
when :serialized
|
82
|
+
return nil if value == nil
|
83
|
+
value
|
84
|
+
else
|
85
|
+
raise UnsupportedType.new(type.to_s)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
def serialized_attributes
|
91
|
+
result = {}
|
92
|
+
fields.each do |attribute, metadata|
|
93
|
+
serialized = serialize_attribute(attribute, read_attribute(attribute), metadata)
|
94
|
+
result[attribute] = serialized unless serialized == nil
|
95
|
+
end
|
96
|
+
result
|
97
|
+
end
|
98
|
+
|
99
|
+
|
100
|
+
def serialize_attribute(attribute, value, metadata=fields[attribute],
|
101
|
+
type: metadata[:type])
|
102
|
+
return nil if value == nil
|
103
|
+
case type
|
104
|
+
when :string
|
105
|
+
["", []].include?(value) ? nil : value
|
106
|
+
when :integer
|
107
|
+
value == [] ? nil : value
|
108
|
+
when :float
|
109
|
+
value == [] ? nil : value
|
110
|
+
when :boolean
|
111
|
+
value ? "true" : "false"
|
112
|
+
when :datetime
|
113
|
+
value.to_i
|
114
|
+
when :serialized
|
115
|
+
value.to_json
|
116
|
+
else
|
117
|
+
raise UnsupportedType.new(type.to_s)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
def deserialized_attributes(consistent_read: false, hash: nil)
|
123
|
+
hash ||= dynamo_item.attributes.to_hash(consistent_read: consistent_read)
|
124
|
+
result = {}
|
125
|
+
fields.each do |attribute, metadata|
|
126
|
+
result[attribute] = deserialize_attribute(hash[attribute], metadata)
|
127
|
+
end
|
128
|
+
result
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
def deserialize_attribute(value, metadata, type: metadata[:type])
|
133
|
+
case type
|
134
|
+
when :string
|
135
|
+
return "" if value == nil
|
136
|
+
value.is_a?(Set) ? value.to_a : value
|
137
|
+
when :integer
|
138
|
+
return nil if value == nil
|
139
|
+
value.is_a?(Set) || value.is_a?(Array) ? value.collect(&:to_i) : value.to_i
|
140
|
+
when :float
|
141
|
+
return nil if value == nil
|
142
|
+
value.is_a?(Set) || value.is_a?(Array) ? value.collect(&:to_f) : value.to_f
|
143
|
+
when :boolean
|
144
|
+
case value
|
145
|
+
when "true"
|
146
|
+
true
|
147
|
+
when "false"
|
148
|
+
false
|
149
|
+
else
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
when :datetime
|
153
|
+
return nil if value == nil
|
154
|
+
Time.zone.at(value.to_i)
|
155
|
+
when :serialized
|
156
|
+
return nil if value == nil
|
157
|
+
JSON.parse(value)
|
158
|
+
else
|
159
|
+
raise UnsupportedType.new(type.to_s)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
|
164
|
+
def [](attribute)
|
165
|
+
read_attribute attribute
|
166
|
+
end
|
167
|
+
|
168
|
+
|
169
|
+
def []=(attribute, value)
|
170
|
+
write_attribute attribute, value
|
171
|
+
end
|
172
|
+
|
173
|
+
|
174
|
+
def id
|
175
|
+
read_attribute(table_hash_key)
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
def id=(value)
|
180
|
+
write_attribute(table_hash_key, value)
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
def to_key
|
185
|
+
return nil unless persisted?
|
186
|
+
key = respond_to?(:id) && id
|
187
|
+
return nil unless key
|
188
|
+
table_range_key ? [key, read_attribute(table_range_key)] : [key]
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
def assign_attributes(values)
|
193
|
+
return if values.blank?
|
194
|
+
values = values.stringify_keys
|
195
|
+
# if values.respond_to?(:permitted?)
|
196
|
+
# unless values.permitted?
|
197
|
+
# raise ActiveModel::ForbiddenAttributesError
|
198
|
+
# end
|
199
|
+
# end
|
200
|
+
values.each do |k, v|
|
201
|
+
#send("#{k}=", v)
|
202
|
+
_assign_attribute(k, v)
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
private
|
208
|
+
|
209
|
+
def _assign_attribute(k, v)
|
210
|
+
public_send("#{k}=", v)
|
211
|
+
rescue ActiveModel::NoMethodError
|
212
|
+
if respond_to?("#{k}=")
|
213
|
+
raise
|
214
|
+
else
|
215
|
+
raise ActiveModel::UnknownAttributeError, "unknown attribute: #{k}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
|
220
|
+
protected
|
221
|
+
|
222
|
+
def evaluate_default(default, type)
|
223
|
+
return default.call if default.is_a?(Proc)
|
224
|
+
return "" if default == nil && type == :string
|
225
|
+
return default.clone if default.is_a?(Array) || default.is_a?(String) # Instances need their own copies
|
226
|
+
default
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module OceanDynamo
|
2
|
+
class Base
|
3
|
+
|
4
|
+
include ActiveModel::Validations::Callbacks
|
5
|
+
|
6
|
+
define_model_callbacks :initialize, only: :after
|
7
|
+
define_model_callbacks :save
|
8
|
+
define_model_callbacks :create
|
9
|
+
define_model_callbacks :update
|
10
|
+
define_model_callbacks :destroy
|
11
|
+
define_model_callbacks :commit, only: :after
|
12
|
+
define_model_callbacks :touch
|
13
|
+
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module OceanDynamo
|
2
|
+
class Base
|
3
|
+
|
4
|
+
class_attribute :dynamo_client, instance_writer: false
|
5
|
+
self.dynamo_client = nil
|
6
|
+
|
7
|
+
class_attribute :dynamo_table, instance_writer: false
|
8
|
+
self.dynamo_table = nil
|
9
|
+
|
10
|
+
class_attribute :dynamo_items, instance_writer: false
|
11
|
+
self.dynamo_items = nil
|
12
|
+
|
13
|
+
class_attribute :table_name, instance_writer: false
|
14
|
+
self.table_name = nil
|
15
|
+
|
16
|
+
class_attribute :table_name_prefix, instance_writer: false
|
17
|
+
self.table_name_prefix = nil
|
18
|
+
|
19
|
+
class_attribute :table_name_suffix, instance_writer: false
|
20
|
+
self.table_name_suffix = nil
|
21
|
+
|
22
|
+
class_attribute :table_hash_key, instance_writer: false
|
23
|
+
self.table_hash_key = nil
|
24
|
+
|
25
|
+
class_attribute :table_range_key, instance_writer: false
|
26
|
+
self.table_range_key = nil
|
27
|
+
|
28
|
+
class_attribute :table_read_capacity_units, instance_writer: false
|
29
|
+
self.table_read_capacity_units = 10
|
30
|
+
|
31
|
+
class_attribute :table_write_capacity_units, instance_writer: false
|
32
|
+
self.table_write_capacity_units = 5
|
33
|
+
|
34
|
+
class_attribute :fields, instance_writer: false
|
35
|
+
self.fields = nil
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module OceanDynamo
|
2
|
+
|
3
|
+
class DynamoError < StandardError; end
|
4
|
+
|
5
|
+
class UnknownPrimaryKey < DynamoError; end
|
6
|
+
|
7
|
+
class UnknownTableStatus < DynamoError; end
|
8
|
+
|
9
|
+
class UnsupportedType < DynamoError; end
|
10
|
+
|
11
|
+
class SerializationTypeMismatch < DynamoError; end
|
12
|
+
|
13
|
+
class ConnectionNotEstablished < DynamoError; end
|
14
|
+
|
15
|
+
class RecordNotFound < DynamoError; end
|
16
|
+
|
17
|
+
class RecordNotSaved < DynamoError; end
|
18
|
+
|
19
|
+
class RecordInvalid < DynamoError; end
|
20
|
+
|
21
|
+
class RecordNotDestroyed < DynamoError; end
|
22
|
+
|
23
|
+
class StatementInvalid < DynamoError; end
|
24
|
+
class RecordNotUnique < StatementInvalid; end
|
25
|
+
class InvalidForeignKey < StatementInvalid; end
|
26
|
+
|
27
|
+
class StaleObjectError < DynamoError; end
|
28
|
+
|
29
|
+
class ReadOnlyRecord < DynamoError; end
|
30
|
+
|
31
|
+
class DangerousAttributeError < DynamoError; end
|
32
|
+
|
33
|
+
class UnknownAttributeError < NoMethodError; end
|
34
|
+
|
35
|
+
class AttributeAssignmentError < DynamoError; end
|
36
|
+
|
37
|
+
class MultiparameterAssignmentErrors < DynamoError; end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,202 @@
|
|
1
|
+
module OceanDynamo
|
2
|
+
class Base
|
3
|
+
|
4
|
+
# ---------------------------------------------------------
|
5
|
+
#
|
6
|
+
# Class methods
|
7
|
+
#
|
8
|
+
# ---------------------------------------------------------
|
9
|
+
|
10
|
+
|
11
|
+
def self.create(attributes = nil, &block)
|
12
|
+
object = new(attributes)
|
13
|
+
yield(object) if block_given?
|
14
|
+
object.save
|
15
|
+
object
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def self.create!(attributes = nil, &block)
|
20
|
+
object = new(attributes)
|
21
|
+
yield(object) if block_given?
|
22
|
+
object.save!
|
23
|
+
object
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def self.delete(hash, range=nil)
|
28
|
+
item = dynamo_items[hash, range]
|
29
|
+
return false unless item.exists?
|
30
|
+
item.delete
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
# ---------------------------------------------------------
|
36
|
+
#
|
37
|
+
# Instance variables and methods
|
38
|
+
#
|
39
|
+
# ---------------------------------------------------------
|
40
|
+
|
41
|
+
def destroyed?
|
42
|
+
@destroyed
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def new_record?
|
47
|
+
@new_record
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
def persisted?
|
52
|
+
!(new_record? || destroyed?)
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
def valid?(context = nil)
|
57
|
+
context ||= (new_record? ? :create : :update)
|
58
|
+
output = super(context)
|
59
|
+
errors.empty? && output
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def save
|
64
|
+
begin
|
65
|
+
create_or_update
|
66
|
+
rescue RecordInvalid
|
67
|
+
false
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
|
72
|
+
def save!(*)
|
73
|
+
create_or_update || raise(RecordNotSaved)
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def update_attributes(attributes={})
|
78
|
+
assign_attributes(attributes)
|
79
|
+
save
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def update_attributes!(attributes={})
|
84
|
+
assign_attributes(attributes)
|
85
|
+
save!
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def create_or_update
|
90
|
+
result = new_record? ? create : update
|
91
|
+
result != false
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def create
|
96
|
+
return false unless valid?(:create)
|
97
|
+
run_callbacks :commit do
|
98
|
+
run_callbacks :save do
|
99
|
+
run_callbacks :create do
|
100
|
+
k = read_attribute(table_hash_key)
|
101
|
+
write_attribute(table_hash_key, SecureRandom.uuid) if k == "" || k == nil
|
102
|
+
t = Time.zone.now
|
103
|
+
self.created_at ||= t
|
104
|
+
self.updated_at ||= t
|
105
|
+
dynamo_persist
|
106
|
+
true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
def update
|
114
|
+
return false unless valid?(:update)
|
115
|
+
run_callbacks :commit do
|
116
|
+
run_callbacks :save do
|
117
|
+
run_callbacks :update do
|
118
|
+
self.updated_at = Time.zone.now
|
119
|
+
dynamo_persist
|
120
|
+
true
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
def destroy
|
128
|
+
run_callbacks :commit do
|
129
|
+
run_callbacks :destroy do
|
130
|
+
delete
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
|
136
|
+
def destroy!
|
137
|
+
destroy || raise(RecordNotDestroyed)
|
138
|
+
end
|
139
|
+
|
140
|
+
|
141
|
+
def delete
|
142
|
+
if persisted?
|
143
|
+
@dynamo_item.delete
|
144
|
+
end
|
145
|
+
@destroyed = true
|
146
|
+
#freeze
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
def reload(**keywords)
|
151
|
+
range_key = table_range_key && attributes[table_range_key]
|
152
|
+
new_instance = self.class.find(id, range_key, **keywords)
|
153
|
+
assign_attributes(new_instance.attributes)
|
154
|
+
self
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
def touch(name=nil)
|
159
|
+
raise DynamoError, "can not touch on a new record object" unless persisted?
|
160
|
+
run_callbacks :touch do
|
161
|
+
attrs = ['updated_at']
|
162
|
+
attrs << name if name
|
163
|
+
t = Time.zone.now
|
164
|
+
attrs.each { |k| write_attribute k, t }
|
165
|
+
# TODO: handle lock_version
|
166
|
+
dynamo_item.attributes.update do |u|
|
167
|
+
attrs.each do |k|
|
168
|
+
u.set(k => serialize_attribute(k, t))
|
169
|
+
end
|
170
|
+
end
|
171
|
+
self
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
|
177
|
+
protected
|
178
|
+
|
179
|
+
def perform_validations(options={}) # :nodoc:
|
180
|
+
options[:validate] == false || valid?(options[:context])
|
181
|
+
end
|
182
|
+
|
183
|
+
|
184
|
+
def dynamo_persist
|
185
|
+
@dynamo_item = dynamo_items.put(serialized_attributes)
|
186
|
+
@new_record = false
|
187
|
+
end
|
188
|
+
|
189
|
+
|
190
|
+
def post_instantiate(item, consistent)
|
191
|
+
@dynamo_item = item
|
192
|
+
@new_record = false
|
193
|
+
assign_attributes(deserialized_attributes(
|
194
|
+
hash: nil,
|
195
|
+
consistent_read: consistent)
|
196
|
+
)
|
197
|
+
self
|
198
|
+
end
|
199
|
+
|
200
|
+
|
201
|
+
end
|
202
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module OceanDynamo
|
2
|
+
class Base
|
3
|
+
|
4
|
+
def self.find(hash, range=nil, consistent: false)
|
5
|
+
item = dynamo_items[hash, range]
|
6
|
+
raise RecordNotFound unless item.exists?
|
7
|
+
new.send(:post_instantiate, item, consistent)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def self.count
|
12
|
+
dynamo_table.item_count || -1 # The || -1 is for fake_dynamo specs.
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module OceanDynamo
|
2
|
+
class Base
|
3
|
+
|
4
|
+
def self.set_table_name(name)
|
5
|
+
self.table_name = name
|
6
|
+
true
|
7
|
+
end
|
8
|
+
|
9
|
+
|
10
|
+
def self.set_table_name_prefix(prefix)
|
11
|
+
self.table_name_prefix = prefix
|
12
|
+
true
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def self.set_table_name_suffix(suffix)
|
17
|
+
self.table_name_suffix = suffix
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
def self.compute_table_name
|
23
|
+
name.pluralize.underscore
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def self.table_full_name
|
28
|
+
"#{table_name_prefix}#{table_name}#{table_name_suffix}"
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
#
|
33
|
+
# This is where the class is initialized
|
34
|
+
#
|
35
|
+
def self.primary_key(hash_key, range_key=nil)
|
36
|
+
self.dynamo_client = nil
|
37
|
+
self.dynamo_table = nil
|
38
|
+
self.dynamo_items = nil
|
39
|
+
self.table_name = compute_table_name
|
40
|
+
self.table_name_prefix = nil
|
41
|
+
self.table_name_suffix = nil
|
42
|
+
self.fields = HashWithIndifferentAccess.new
|
43
|
+
self.table_hash_key = hash_key
|
44
|
+
self.table_range_key = range_key
|
45
|
+
DEFAULT_ATTRIBUTES.each { |k, name, **pairs| attribute k, name, **pairs }
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def self.read_capacity_units(units)
|
51
|
+
self.table_read_capacity_units = units
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def self.write_capacity_units(units)
|
56
|
+
self.table_write_capacity_units = units
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def self.attribute(name, type=:string, **pairs)
|
61
|
+
attr_accessor name
|
62
|
+
fields[name.to_s] = {type: type, default: pairs[:default]}
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module OceanDynamo
|
2
|
+
class Base
|
3
|
+
|
4
|
+
def self.establish_db_connection
|
5
|
+
setup_dynamo
|
6
|
+
if dynamo_table.exists?
|
7
|
+
wait_until_table_is_active
|
8
|
+
else
|
9
|
+
create_table
|
10
|
+
end
|
11
|
+
set_dynamo_table_keys
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def self.setup_dynamo
|
16
|
+
#self.dynamo_client = AWS::DynamoDB::Client.new(:api_version => '2012-08-10')
|
17
|
+
self.dynamo_client ||= AWS::DynamoDB.new
|
18
|
+
self.dynamo_table = dynamo_client.tables[table_full_name]
|
19
|
+
self.dynamo_items = dynamo_table.items
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def self.wait_until_table_is_active
|
24
|
+
loop do
|
25
|
+
case dynamo_table.status
|
26
|
+
when :active
|
27
|
+
set_dynamo_table_keys
|
28
|
+
return
|
29
|
+
when :updating, :creating
|
30
|
+
sleep 1
|
31
|
+
next
|
32
|
+
when :deleting
|
33
|
+
sleep 1 while dynamo_table.exists?
|
34
|
+
create_table
|
35
|
+
return
|
36
|
+
else
|
37
|
+
raise UnknownTableStatus.new("Unknown DynamoDB table status '#{dynamo_table.status}'")
|
38
|
+
end
|
39
|
+
sleep 1
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def self.set_dynamo_table_keys
|
45
|
+
dynamo_table.hash_key = [table_hash_key, fields[table_hash_key][:type]]
|
46
|
+
if table_range_key
|
47
|
+
dynamo_table.range_key = [table_range_key, fields[table_range_key][:type]]
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def self.create_table
|
53
|
+
self.dynamo_table = dynamo_client.tables.create(table_full_name,
|
54
|
+
table_read_capacity_units, table_write_capacity_units,
|
55
|
+
hash_key: { table_hash_key => fields[table_hash_key][:type]},
|
56
|
+
range_key: table_range_key && { table_range_key => fields[table_range_key][:type]}
|
57
|
+
)
|
58
|
+
sleep 1 until dynamo_table.status == :active
|
59
|
+
setup_dynamo
|
60
|
+
true
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
def self.delete_table
|
65
|
+
return false unless dynamo_table.exists? && dynamo_table.status == :active
|
66
|
+
dynamo_table.delete
|
67
|
+
true
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
data/lib/ocean-dynamo/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ocean-dynamo
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.11
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Bengtson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-09-
|
11
|
+
date: 2013-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: aws-sdk
|
@@ -134,8 +134,16 @@ extensions: []
|
|
134
134
|
extra_rdoc_files: []
|
135
135
|
files:
|
136
136
|
- config/routes.rb
|
137
|
-
- lib/ocean-dynamo/
|
137
|
+
- lib/ocean-dynamo/attributes.rb
|
138
|
+
- lib/ocean-dynamo/base.rb
|
139
|
+
- lib/ocean-dynamo/callbacks.rb
|
140
|
+
- lib/ocean-dynamo/class_variables.rb
|
138
141
|
- lib/ocean-dynamo/engine.rb
|
142
|
+
- lib/ocean-dynamo/exceptions.rb
|
143
|
+
- lib/ocean-dynamo/persistence.rb
|
144
|
+
- lib/ocean-dynamo/queries.rb
|
145
|
+
- lib/ocean-dynamo/schema.rb
|
146
|
+
- lib/ocean-dynamo/tables.rb
|
139
147
|
- lib/ocean-dynamo/version.rb
|
140
148
|
- lib/ocean-dynamo.rb
|
141
149
|
- MIT-LICENSE
|
data/lib/ocean-dynamo/dynamo.rb
DELETED
@@ -1,604 +0,0 @@
|
|
1
|
-
|
2
|
-
require "aws-sdk"
|
3
|
-
|
4
|
-
module OceanDynamo
|
5
|
-
|
6
|
-
DEFAULT_FIELDS = [
|
7
|
-
[:created_at, :datetime],
|
8
|
-
[:updated_at, :datetime],
|
9
|
-
[:lock_version, :integer, default: 0]
|
10
|
-
]
|
11
|
-
|
12
|
-
class DynamoError < StandardError; end
|
13
|
-
|
14
|
-
class UnknownPrimaryKey < DynamoError; end
|
15
|
-
|
16
|
-
class UnknownTableStatus < DynamoError; end
|
17
|
-
|
18
|
-
class UnsupportedType < DynamoError; end
|
19
|
-
|
20
|
-
class SerializationTypeMismatch < DynamoError; end
|
21
|
-
|
22
|
-
class ConnectionNotEstablished < DynamoError; end
|
23
|
-
|
24
|
-
class RecordNotFound < DynamoError; end
|
25
|
-
|
26
|
-
class RecordNotSaved < DynamoError; end
|
27
|
-
|
28
|
-
class RecordInvalid < DynamoError; end
|
29
|
-
|
30
|
-
class RecordNotDestroyed < DynamoError; end
|
31
|
-
|
32
|
-
class StatementInvalid < DynamoError; end
|
33
|
-
|
34
|
-
class WrappedDatabaseException < StatementInvalid; end
|
35
|
-
class RecordNotUnique < WrappedDatabaseException; end
|
36
|
-
class InvalidForeignKey < WrappedDatabaseException; end
|
37
|
-
|
38
|
-
class StaleObjectError < DynamoError; end
|
39
|
-
|
40
|
-
class ReadOnlyRecord < DynamoError; end
|
41
|
-
|
42
|
-
class DangerousAttributeError < DynamoError; end
|
43
|
-
|
44
|
-
class UnknownAttributeError < NoMethodError; end
|
45
|
-
|
46
|
-
class AttributeAssignmentError < DynamoError; end
|
47
|
-
|
48
|
-
class MultiparameterAssignmentErrors < DynamoError; end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
class Base
|
57
|
-
|
58
|
-
include ActiveModel::Model
|
59
|
-
include ActiveModel::Validations::Callbacks
|
60
|
-
|
61
|
-
|
62
|
-
# ---------------------------------------------------------
|
63
|
-
#
|
64
|
-
# Class variables and methods
|
65
|
-
#
|
66
|
-
# ---------------------------------------------------------
|
67
|
-
|
68
|
-
class_attribute :dynamo_client, instance_writer: false
|
69
|
-
self.dynamo_client = nil
|
70
|
-
|
71
|
-
class_attribute :dynamo_table, instance_writer: false
|
72
|
-
self.dynamo_table = nil
|
73
|
-
|
74
|
-
class_attribute :dynamo_items, instance_writer: false
|
75
|
-
self.dynamo_items = nil
|
76
|
-
|
77
|
-
class_attribute :table_name, instance_writer: false
|
78
|
-
self.table_name = nil
|
79
|
-
|
80
|
-
class_attribute :table_name_prefix, instance_writer: false
|
81
|
-
self.table_name_prefix = nil
|
82
|
-
|
83
|
-
class_attribute :table_name_suffix, instance_writer: false
|
84
|
-
self.table_name_suffix = nil
|
85
|
-
|
86
|
-
class_attribute :table_hash_key, instance_writer: false
|
87
|
-
self.table_hash_key = nil
|
88
|
-
|
89
|
-
class_attribute :table_range_key, instance_writer: false
|
90
|
-
self.table_range_key = nil
|
91
|
-
|
92
|
-
class_attribute :table_read_capacity_units, instance_writer: false
|
93
|
-
self.table_read_capacity_units = 10
|
94
|
-
|
95
|
-
class_attribute :table_write_capacity_units, instance_writer: false
|
96
|
-
self.table_write_capacity_units = 5
|
97
|
-
|
98
|
-
class_attribute :fields, instance_writer: false
|
99
|
-
self.fields = nil
|
100
|
-
|
101
|
-
|
102
|
-
def self.set_table_name(name)
|
103
|
-
self.table_name = name
|
104
|
-
true
|
105
|
-
end
|
106
|
-
|
107
|
-
|
108
|
-
def self.set_table_name_prefix(prefix)
|
109
|
-
self.table_name_prefix = prefix
|
110
|
-
true
|
111
|
-
end
|
112
|
-
|
113
|
-
|
114
|
-
def self.set_table_name_suffix(suffix)
|
115
|
-
self.table_name_suffix = suffix
|
116
|
-
true
|
117
|
-
end
|
118
|
-
|
119
|
-
|
120
|
-
def self.compute_table_name
|
121
|
-
name.pluralize.underscore
|
122
|
-
end
|
123
|
-
|
124
|
-
|
125
|
-
def self.table_full_name
|
126
|
-
"#{table_name_prefix}#{table_name}#{table_name_suffix}"
|
127
|
-
end
|
128
|
-
|
129
|
-
|
130
|
-
#
|
131
|
-
# This is where the class is initialized
|
132
|
-
#
|
133
|
-
def self.primary_key(hash_key, range_key=nil)
|
134
|
-
self.dynamo_client = nil
|
135
|
-
self.dynamo_table = nil
|
136
|
-
self.dynamo_items = nil
|
137
|
-
self.table_name = compute_table_name
|
138
|
-
self.table_name_prefix = nil
|
139
|
-
self.table_name_suffix = nil
|
140
|
-
self.fields = HashWithIndifferentAccess.new
|
141
|
-
self.table_hash_key = hash_key
|
142
|
-
self.table_range_key = range_key
|
143
|
-
DEFAULT_FIELDS.each { |k, name, **pairs| attribute k, name, **pairs }
|
144
|
-
nil
|
145
|
-
end
|
146
|
-
|
147
|
-
|
148
|
-
def self.read_capacity_units(units)
|
149
|
-
self.table_read_capacity_units = units
|
150
|
-
end
|
151
|
-
|
152
|
-
|
153
|
-
def self.write_capacity_units(units)
|
154
|
-
self.table_write_capacity_units = units
|
155
|
-
end
|
156
|
-
|
157
|
-
|
158
|
-
def self.attribute(name, type=:string, **pairs)
|
159
|
-
attr_accessor name
|
160
|
-
fields[name] = {type: type,
|
161
|
-
default: pairs[:default]}
|
162
|
-
end
|
163
|
-
|
164
|
-
|
165
|
-
def self.establish_db_connection
|
166
|
-
setup_dynamo
|
167
|
-
if dynamo_table.exists?
|
168
|
-
wait_until_table_is_active
|
169
|
-
else
|
170
|
-
create_table
|
171
|
-
end
|
172
|
-
set_dynamo_table_keys
|
173
|
-
end
|
174
|
-
|
175
|
-
|
176
|
-
def self.setup_dynamo
|
177
|
-
#self.dynamo_client = AWS::DynamoDB::Client.new(:api_version => '2012-08-10')
|
178
|
-
self.dynamo_client ||= AWS::DynamoDB.new
|
179
|
-
self.dynamo_table = dynamo_client.tables[table_full_name]
|
180
|
-
self.dynamo_items = dynamo_table.items
|
181
|
-
end
|
182
|
-
|
183
|
-
|
184
|
-
def self.wait_until_table_is_active
|
185
|
-
loop do
|
186
|
-
case dynamo_table.status
|
187
|
-
when :active
|
188
|
-
set_dynamo_table_keys
|
189
|
-
return
|
190
|
-
when :updating, :creating
|
191
|
-
sleep 1
|
192
|
-
next
|
193
|
-
when :deleting
|
194
|
-
sleep 1 while dynamo_table.exists?
|
195
|
-
create_table
|
196
|
-
return
|
197
|
-
else
|
198
|
-
raise UnknownTableStatus.new("Unknown DynamoDB table status '#{dynamo_table.status}'")
|
199
|
-
end
|
200
|
-
sleep 1
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
|
205
|
-
def self.set_dynamo_table_keys
|
206
|
-
dynamo_table.hash_key = [table_hash_key, fields[table_hash_key][:type]]
|
207
|
-
if table_range_key
|
208
|
-
dynamo_table.range_key = [table_range_key, fields[table_range_key][:type]]
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
|
213
|
-
def self.create_table
|
214
|
-
self.dynamo_table = dynamo_client.tables.create(table_full_name,
|
215
|
-
table_read_capacity_units, table_write_capacity_units,
|
216
|
-
hash_key: { table_hash_key => fields[table_hash_key][:type]},
|
217
|
-
range_key: table_range_key && { table_range_key => fields[table_range_key][:type]}
|
218
|
-
)
|
219
|
-
sleep 1 until dynamo_table.status == :active
|
220
|
-
setup_dynamo
|
221
|
-
true
|
222
|
-
end
|
223
|
-
|
224
|
-
|
225
|
-
def self.delete_table
|
226
|
-
return false unless dynamo_table.exists? && dynamo_table.status == :active
|
227
|
-
dynamo_table.delete
|
228
|
-
true
|
229
|
-
end
|
230
|
-
|
231
|
-
|
232
|
-
def self.create(attributes = nil, &block)
|
233
|
-
object = new(attributes)
|
234
|
-
yield(object) if block_given?
|
235
|
-
object.save
|
236
|
-
object
|
237
|
-
end
|
238
|
-
|
239
|
-
|
240
|
-
def self.create!(attributes = nil, &block)
|
241
|
-
object = new(attributes)
|
242
|
-
yield(object) if block_given?
|
243
|
-
object.save!
|
244
|
-
object
|
245
|
-
end
|
246
|
-
|
247
|
-
|
248
|
-
def self.find(hash, range=nil, consistent: false)
|
249
|
-
item = dynamo_items[hash, range]
|
250
|
-
raise RecordNotFound unless item.exists?
|
251
|
-
new.send(:post_instantiate, item, consistent)
|
252
|
-
end
|
253
|
-
|
254
|
-
|
255
|
-
def self.delete(hash, range=nil)
|
256
|
-
item = dynamo_items[hash, range]
|
257
|
-
return false unless item.exists?
|
258
|
-
item.delete
|
259
|
-
true
|
260
|
-
end
|
261
|
-
|
262
|
-
|
263
|
-
def self.count
|
264
|
-
dynamo_table.item_count || -1 # The || -1 is for fake_dynamo specs.
|
265
|
-
end
|
266
|
-
|
267
|
-
|
268
|
-
# ---------------------------------------------------------
|
269
|
-
#
|
270
|
-
# Callbacks
|
271
|
-
#
|
272
|
-
# ---------------------------------------------------------
|
273
|
-
|
274
|
-
define_model_callbacks :initialize, only: :after
|
275
|
-
define_model_callbacks :save
|
276
|
-
define_model_callbacks :create
|
277
|
-
define_model_callbacks :update
|
278
|
-
define_model_callbacks :destroy
|
279
|
-
define_model_callbacks :commit, only: :after
|
280
|
-
define_model_callbacks :touch
|
281
|
-
|
282
|
-
|
283
|
-
# ---------------------------------------------------------
|
284
|
-
#
|
285
|
-
# Instance variables and methods
|
286
|
-
#
|
287
|
-
# ---------------------------------------------------------
|
288
|
-
|
289
|
-
attr_reader :attributes
|
290
|
-
attr_reader :destroyed
|
291
|
-
attr_reader :new_record
|
292
|
-
attr_reader :dynamo_item
|
293
|
-
|
294
|
-
|
295
|
-
def initialize(attrs={})
|
296
|
-
run_callbacks :initialize do
|
297
|
-
@attributes = HashWithIndifferentAccess.new
|
298
|
-
fields.each do |name, md|
|
299
|
-
write_attribute(name, evaluate_default(md[:default], md[:type]))
|
300
|
-
self.class.class_eval "def #{name}; read_attribute('#{name}'); end"
|
301
|
-
self.class.class_eval "def #{name}=(value); write_attribute('#{name}', value); end"
|
302
|
-
if fields[name][:type] == :boolean
|
303
|
-
self.class.class_eval "def #{name}?; read_attribute('#{name}'); end"
|
304
|
-
end
|
305
|
-
end
|
306
|
-
@dynamo_item = nil
|
307
|
-
@destroyed = false
|
308
|
-
@new_record = true
|
309
|
-
raise UnknownPrimaryKey unless table_hash_key
|
310
|
-
end
|
311
|
-
attrs && attrs.delete_if { |k, v| !fields.has_key?(k) }
|
312
|
-
super(attrs)
|
313
|
-
end
|
314
|
-
|
315
|
-
|
316
|
-
def read_attribute(name)
|
317
|
-
@attributes[name]
|
318
|
-
end
|
319
|
-
|
320
|
-
|
321
|
-
def write_attribute(name, value)
|
322
|
-
@attributes[name] = value
|
323
|
-
end
|
324
|
-
|
325
|
-
|
326
|
-
def [](attribute)
|
327
|
-
read_attribute attribute
|
328
|
-
end
|
329
|
-
|
330
|
-
|
331
|
-
def []=(attribute, value)
|
332
|
-
write_attribute attribute, value
|
333
|
-
end
|
334
|
-
|
335
|
-
|
336
|
-
def id
|
337
|
-
read_attribute(table_hash_key)
|
338
|
-
end
|
339
|
-
|
340
|
-
|
341
|
-
def id=(value)
|
342
|
-
write_attribute(table_hash_key, value)
|
343
|
-
end
|
344
|
-
|
345
|
-
|
346
|
-
def to_key
|
347
|
-
return nil unless persisted?
|
348
|
-
key = respond_to?(:id) && id
|
349
|
-
return nil unless key
|
350
|
-
table_range_key ? [key, read_attribute(table_range_key)] : [key]
|
351
|
-
end
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
def assign_attributes(values)
|
356
|
-
# if values.respond_to?(:permitted?)
|
357
|
-
# unless values.permitted?
|
358
|
-
# raise ActiveModel::ForbiddenAttributesError
|
359
|
-
# end
|
360
|
-
# end
|
361
|
-
values.each do |k, v|
|
362
|
-
send("#{k}=", v)
|
363
|
-
end
|
364
|
-
end
|
365
|
-
|
366
|
-
|
367
|
-
def serialized_attributes
|
368
|
-
result = {}
|
369
|
-
fields.each do |attribute, metadata|
|
370
|
-
serialized = serialize_attribute(attribute, read_attribute(attribute), metadata)
|
371
|
-
result[attribute] = serialized unless serialized == nil
|
372
|
-
end
|
373
|
-
result
|
374
|
-
end
|
375
|
-
|
376
|
-
|
377
|
-
def serialize_attribute(attribute, value, metadata=fields[attribute],
|
378
|
-
type: metadata[:type])
|
379
|
-
return nil if value == nil
|
380
|
-
case type
|
381
|
-
when :string
|
382
|
-
["", []].include?(value) ? nil : value
|
383
|
-
when :integer
|
384
|
-
value == [] ? nil : value
|
385
|
-
when :float
|
386
|
-
value == [] ? nil : value
|
387
|
-
when :boolean
|
388
|
-
value ? "true" : "false"
|
389
|
-
when :datetime
|
390
|
-
value.to_i
|
391
|
-
when :serialized
|
392
|
-
value.to_json
|
393
|
-
else
|
394
|
-
raise UnsupportedType.new(type.to_s)
|
395
|
-
end
|
396
|
-
end
|
397
|
-
|
398
|
-
|
399
|
-
def deserialized_attributes(consistent_read: false, hash: nil)
|
400
|
-
hash ||= dynamo_item.attributes.to_hash(consistent_read: consistent_read)
|
401
|
-
result = {}
|
402
|
-
fields.each do |attribute, metadata|
|
403
|
-
result[attribute] = deserialize_attribute(hash[attribute], metadata)
|
404
|
-
end
|
405
|
-
result
|
406
|
-
end
|
407
|
-
|
408
|
-
|
409
|
-
def deserialize_attribute(value, metadata, type: metadata[:type])
|
410
|
-
case type
|
411
|
-
when :string
|
412
|
-
return "" if value == nil
|
413
|
-
value.is_a?(Set) ? value.to_a : value
|
414
|
-
when :integer
|
415
|
-
return nil if value == nil
|
416
|
-
value.is_a?(Set) || value.is_a?(Array) ? value.collect(&:to_i) : value.to_i
|
417
|
-
when :float
|
418
|
-
return nil if value == nil
|
419
|
-
value.is_a?(Set) || value.is_a?(Array) ? value.collect(&:to_f) : value.to_f
|
420
|
-
when :boolean
|
421
|
-
case value
|
422
|
-
when "true"
|
423
|
-
true
|
424
|
-
when "false"
|
425
|
-
false
|
426
|
-
else
|
427
|
-
nil
|
428
|
-
end
|
429
|
-
when :datetime
|
430
|
-
return nil if value == nil
|
431
|
-
Time.at(value.to_i)
|
432
|
-
when :serialized
|
433
|
-
return nil if value == nil
|
434
|
-
JSON.parse(value)
|
435
|
-
else
|
436
|
-
raise UnsupportedType.new(type.to_s)
|
437
|
-
end
|
438
|
-
end
|
439
|
-
|
440
|
-
|
441
|
-
def destroyed?
|
442
|
-
@destroyed
|
443
|
-
end
|
444
|
-
|
445
|
-
|
446
|
-
def new_record?
|
447
|
-
@new_record
|
448
|
-
end
|
449
|
-
|
450
|
-
|
451
|
-
def persisted?
|
452
|
-
!(new_record? || destroyed?)
|
453
|
-
end
|
454
|
-
|
455
|
-
|
456
|
-
def valid?(context = nil)
|
457
|
-
context ||= (new_record? ? :create : :update)
|
458
|
-
output = super(context)
|
459
|
-
errors.empty? && output
|
460
|
-
end
|
461
|
-
|
462
|
-
|
463
|
-
def save
|
464
|
-
begin
|
465
|
-
create_or_update
|
466
|
-
rescue RecordInvalid
|
467
|
-
false
|
468
|
-
end
|
469
|
-
end
|
470
|
-
|
471
|
-
|
472
|
-
def save!(*)
|
473
|
-
create_or_update || raise(RecordNotSaved)
|
474
|
-
end
|
475
|
-
|
476
|
-
|
477
|
-
def update_attributes(attributes={})
|
478
|
-
assign_attributes(attributes)
|
479
|
-
save
|
480
|
-
end
|
481
|
-
|
482
|
-
|
483
|
-
def update_attributes!(attributes={})
|
484
|
-
assign_attributes(attributes)
|
485
|
-
save!
|
486
|
-
end
|
487
|
-
|
488
|
-
|
489
|
-
def create_or_update
|
490
|
-
result = new_record? ? create : update
|
491
|
-
result != false
|
492
|
-
end
|
493
|
-
|
494
|
-
|
495
|
-
def create
|
496
|
-
return false unless valid?(:create)
|
497
|
-
run_callbacks :commit do
|
498
|
-
run_callbacks :save do
|
499
|
-
run_callbacks :create do
|
500
|
-
k = read_attribute(table_hash_key)
|
501
|
-
write_attribute(table_hash_key, SecureRandom.uuid) if k == "" || k == nil
|
502
|
-
t = Time.now
|
503
|
-
self.created_at ||= t
|
504
|
-
self.updated_at ||= t
|
505
|
-
dynamo_persist
|
506
|
-
true
|
507
|
-
end
|
508
|
-
end
|
509
|
-
end
|
510
|
-
end
|
511
|
-
|
512
|
-
|
513
|
-
def update
|
514
|
-
return false unless valid?(:update)
|
515
|
-
run_callbacks :commit do
|
516
|
-
run_callbacks :save do
|
517
|
-
run_callbacks :update do
|
518
|
-
self.updated_at = Time.now
|
519
|
-
dynamo_persist
|
520
|
-
true
|
521
|
-
end
|
522
|
-
end
|
523
|
-
end
|
524
|
-
end
|
525
|
-
|
526
|
-
def destroy
|
527
|
-
run_callbacks :commit do
|
528
|
-
run_callbacks :destroy do
|
529
|
-
delete
|
530
|
-
end
|
531
|
-
end
|
532
|
-
end
|
533
|
-
|
534
|
-
|
535
|
-
def delete
|
536
|
-
if persisted?
|
537
|
-
@dynamo_item.delete
|
538
|
-
end
|
539
|
-
@destroyed = true
|
540
|
-
#freeze
|
541
|
-
end
|
542
|
-
|
543
|
-
|
544
|
-
def reload(**keywords)
|
545
|
-
range_key = table_range_key && attributes[table_range_key]
|
546
|
-
new_instance = self.class.find(id, range_key, **keywords)
|
547
|
-
assign_attributes(new_instance.attributes)
|
548
|
-
self
|
549
|
-
end
|
550
|
-
|
551
|
-
|
552
|
-
def touch(name=nil)
|
553
|
-
run_callbacks :touch do
|
554
|
-
attrs = [:updated_at]
|
555
|
-
attrs << name if name
|
556
|
-
t = Time.now
|
557
|
-
attrs.each { |k| write_attribute name, t }
|
558
|
-
# TODO: handle lock_version
|
559
|
-
dynamo_item.attributes.update do |u|
|
560
|
-
attrs.each do |k|
|
561
|
-
u.set(k => serialize_attribute(k, t))
|
562
|
-
end
|
563
|
-
end
|
564
|
-
self
|
565
|
-
end
|
566
|
-
end
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
protected
|
571
|
-
|
572
|
-
def evaluate_default(default, type)
|
573
|
-
return default.call if default.is_a?(Proc)
|
574
|
-
return "" if default == nil && type == :string
|
575
|
-
return default.clone if default.is_a?(Array) || default.is_a?(String) # Instances need their own copies
|
576
|
-
default
|
577
|
-
end
|
578
|
-
|
579
|
-
|
580
|
-
def perform_validations(options={}) # :nodoc:
|
581
|
-
options[:validate] == false || valid?(options[:context])
|
582
|
-
end
|
583
|
-
|
584
|
-
|
585
|
-
def dynamo_persist
|
586
|
-
@dynamo_item = dynamo_items.put(serialized_attributes)
|
587
|
-
@new_record = false
|
588
|
-
end
|
589
|
-
|
590
|
-
|
591
|
-
def post_instantiate(item, consistent)
|
592
|
-
@dynamo_item = item
|
593
|
-
@new_record = false
|
594
|
-
assign_attributes(deserialized_attributes(
|
595
|
-
hash: nil,
|
596
|
-
consistent_read: consistent)
|
597
|
-
)
|
598
|
-
self
|
599
|
-
end
|
600
|
-
|
601
|
-
end # Base
|
602
|
-
|
603
|
-
end # Dynamo
|
604
|
-
|