dyna_model 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +81 -0
- data/Rakefile +11 -0
- data/dyna_model.gemspec +31 -0
- data/lib/dyna_model/attributes.rb +187 -0
- data/lib/dyna_model/aws/record/attributes/serialized_attr.rb +33 -0
- data/lib/dyna_model/config/options.rb +78 -0
- data/lib/dyna_model/config.rb +46 -0
- data/lib/dyna_model/document.rb +169 -0
- data/lib/dyna_model/extensions/symbol.rb +11 -0
- data/lib/dyna_model/persistence.rb +77 -0
- data/lib/dyna_model/query.rb +207 -0
- data/lib/dyna_model/response.rb +36 -0
- data/lib/dyna_model/schema.rb +251 -0
- data/lib/dyna_model/table.rb +453 -0
- data/lib/dyna_model/tasks.rb +81 -0
- data/lib/dyna_model/validations.rb +33 -0
- data/lib/dyna_model/version.rb +3 -0
- data/lib/dyna_model.rb +33 -0
- data/spec/app/models/cacher.rb +14 -0
- data/spec/app/models/callbacker.rb +46 -0
- data/spec/app/models/user.rb +28 -0
- data/spec/app/models/validez.rb +33 -0
- data/spec/dyna_model/attributes_spec.rb +54 -0
- data/spec/dyna_model/callbacks_spec.rb +35 -0
- data/spec/dyna_model/persistence_spec.rb +81 -0
- data/spec/dyna_model/query_spec.rb +118 -0
- data/spec/dyna_model/validations_spec.rb +61 -0
- data/spec/spec_helper.rb +58 -0
- metadata +197 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ca4e3bd3b871756a2b5fad4bf362c334bbf804af
|
4
|
+
data.tar.gz: ac13badc8bca36d88787d6cbdb6134af19c69a93
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 23243b46398ab0517a2f6a7c57e1fdbbe3f4f63c8d55b8c1a8a73abe8de75a297395ad8554d06101c02cb472de16a696e738a509b46002427812b4efa6279e49
|
7
|
+
data.tar.gz: 32f20b9e28c107825230eb53dfd18b092f6626e91e66a4b1fd859b0523c4f5c38c1566d2e77e9417ec80b8612c4c91ba24269f47a74379eecc1611ee37b0b38a
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Cary Dunn
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
# DynaModel
|
2
|
+
|
3
|
+
AWS DynamoDB ORM for Rails based on AWS::Record in the aws-sdk gem. Still a work in progress but very functional.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
```
|
7
|
+
gem 'dyna_model'
|
8
|
+
```
|
9
|
+
|
10
|
+
## Sample Model
|
11
|
+
```
|
12
|
+
class Dude
|
13
|
+
|
14
|
+
include DynaModel::Document
|
15
|
+
|
16
|
+
string_attr :hashy
|
17
|
+
integer_attr :ranger, default_value: 2
|
18
|
+
string_attr :name, default_value: lambda { "dude" }
|
19
|
+
boolean_attr :is_dude
|
20
|
+
datetime_attr :born
|
21
|
+
serialized_attr :cereal
|
22
|
+
timestamps
|
23
|
+
|
24
|
+
hash_key :hashy
|
25
|
+
range_key :ranger
|
26
|
+
|
27
|
+
set_shard_name "usery"
|
28
|
+
|
29
|
+
local_secondary_index :name
|
30
|
+
global_secondary_index(:name_index, { hash_key: :name, projection: [:name] })
|
31
|
+
|
32
|
+
read_provision 4
|
33
|
+
write_provision 4
|
34
|
+
guid_delimiter "!"
|
35
|
+
|
36
|
+
validates_presence_of :name
|
37
|
+
|
38
|
+
before_create :do_something
|
39
|
+
before_validation on: :create do
|
40
|
+
do_something
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
## Sample Methods
|
47
|
+
```
|
48
|
+
# Read a single object by Hash and (optionally) Range keys
|
49
|
+
Dude.read
|
50
|
+
|
51
|
+
# Query by Hash and (optionally) Range keys (compatible with Local and Global Secondary Indexes)
|
52
|
+
Dude.read_range
|
53
|
+
|
54
|
+
# Batch read
|
55
|
+
Dude.read_multiple
|
56
|
+
|
57
|
+
# Read by guid (helper for hash + guid_delimiter + range)
|
58
|
+
Dude.read_guid
|
59
|
+
|
60
|
+
# Get count of query
|
61
|
+
Dude.count_range
|
62
|
+
|
63
|
+
# Table scan with more complex filters
|
64
|
+
Dude.scan
|
65
|
+
|
66
|
+
# Create Table
|
67
|
+
Dude.create_table
|
68
|
+
|
69
|
+
# Delete Table
|
70
|
+
Dude.delete_table
|
71
|
+
|
72
|
+
# Rake tasks
|
73
|
+
rake ddb:create CLASS=Dude
|
74
|
+
rake ddb:create CLASS=all
|
75
|
+
rake ddb:destroy CLASS=Dude
|
76
|
+
rake ddb:destroy CLASS=all
|
77
|
+
```
|
78
|
+
|
79
|
+
# AWS::Record
|
80
|
+
* http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/Record.html
|
81
|
+
* https://github.com/aws/aws-sdk-ruby/blob/master/lib/aws/record/abstract_base.rb
|
data/Rakefile
ADDED
data/dyna_model.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dyna_model/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dyna_model"
|
8
|
+
spec.version = DynaModel::VERSION
|
9
|
+
spec.authors = ["Cary Dunn"]
|
10
|
+
spec.email = ["cary.dunn@gmail.com"]
|
11
|
+
spec.summary = %q{DyanmoDB ORM on AWS::Record}
|
12
|
+
spec.description = %q{DyanmoDB ORM on AWS::Record}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.5"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec"
|
24
|
+
spec.add_development_dependency "mocha"
|
25
|
+
|
26
|
+
spec.add_dependency 'activesupport', '>= 4.0.0'
|
27
|
+
spec.add_dependency 'activemodel', '>= 4.0.0'
|
28
|
+
spec.add_dependency 'rails', '>= 4.0.0'
|
29
|
+
spec.add_dependency 'aws-sdk', '>= 1.38.0'
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,187 @@
|
|
1
|
+
module DynaModel
|
2
|
+
module Attributes
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
|
7
|
+
# Adds a string attribute to this class.
|
8
|
+
#
|
9
|
+
# @example A standard string attribute
|
10
|
+
#
|
11
|
+
# class Recipe < AWS::Record::HashModel
|
12
|
+
# string_attr :name
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# recipe = Recipe.new(:name => "Buttermilk Pancakes")
|
16
|
+
# recipe.name #=> 'Buttermilk Pancakes'
|
17
|
+
#
|
18
|
+
# @example A string attribute with `:set` set to true
|
19
|
+
#
|
20
|
+
# class Recipe < AWS::Record::HashModel
|
21
|
+
# string_attr :tags, :set => true
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# recipe = Recipe.new(:tags => %w(popular dessert))
|
25
|
+
# recipe.tags #=> #<Set: {"popular", "desert"}>
|
26
|
+
#
|
27
|
+
# @param [Symbol] name The name of the attribute.
|
28
|
+
# @param [Hash] options
|
29
|
+
# @option options [Boolean] :set (false) When true this attribute
|
30
|
+
# can have multiple values.
|
31
|
+
def string_attr name, options = {}
|
32
|
+
add_attribute(AWS::Record::Attributes::StringAttr.new(name, options))
|
33
|
+
end
|
34
|
+
|
35
|
+
# Adds an integer attribute to this class.
|
36
|
+
#
|
37
|
+
# class Recipe < AWS::Record::HashModel
|
38
|
+
# integer_attr :servings
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# recipe = Recipe.new(:servings => '10')
|
42
|
+
# recipe.servings #=> 10
|
43
|
+
#
|
44
|
+
# @param [Symbol] name The name of the attribute.
|
45
|
+
# @param [Hash] options
|
46
|
+
# @option options [Boolean] :set (false) When true this attribute
|
47
|
+
# can have multiple values.
|
48
|
+
def integer_attr name, options = {}
|
49
|
+
add_attribute(AWS::Record::Attributes::IntegerAttr.new(name, options))
|
50
|
+
end
|
51
|
+
|
52
|
+
# Adds a float attribute to this class.
|
53
|
+
#
|
54
|
+
# class Listing < AWS::Record::HashModel
|
55
|
+
# float_attr :score
|
56
|
+
# end
|
57
|
+
#
|
58
|
+
# listing = Listing.new(:score => '123.456')
|
59
|
+
# listing.score # => 123.456
|
60
|
+
#
|
61
|
+
# @param [Symbol] name The name of the attribute.
|
62
|
+
# @param [Hash] options
|
63
|
+
# @option options [Boolean] :set (false) When true this attribute
|
64
|
+
# can have multiple values.
|
65
|
+
def float_attr name, options = {}
|
66
|
+
add_attribute(AWS::Record::Attributes::FloatAttr.new(name, options))
|
67
|
+
end
|
68
|
+
|
69
|
+
# Adds a boolean attribute to this class.
|
70
|
+
#
|
71
|
+
# @example
|
72
|
+
#
|
73
|
+
# class Book < AWS::Record::HashModel
|
74
|
+
# boolean_attr :read
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# b = Book.new
|
78
|
+
# b.read? # => false
|
79
|
+
# b.read = true
|
80
|
+
# b.read? # => true
|
81
|
+
#
|
82
|
+
# listing = Listing.new(:score => '123.456'
|
83
|
+
# listing.score # => 123.456
|
84
|
+
#
|
85
|
+
# @param [Symbol] name The name of the attribute.
|
86
|
+
def boolean_attr name, options = {}
|
87
|
+
|
88
|
+
attr = add_attribute(AWS::Record::Attributes::BooleanAttr.new(name, options))
|
89
|
+
|
90
|
+
# add the boolean question mark method
|
91
|
+
define_method("#{attr.name}?") do
|
92
|
+
!!__send__(attr.name)
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
|
97
|
+
# Adds a datetime attribute to this class.
|
98
|
+
#
|
99
|
+
# @example A standard datetime attribute
|
100
|
+
#
|
101
|
+
# class Recipe < AWS::Record::HashModel
|
102
|
+
# datetime_attr :invented
|
103
|
+
# end
|
104
|
+
#
|
105
|
+
# recipe = Recipe.new(:invented => Time.now)
|
106
|
+
# recipe.invented #=> <DateTime ...>
|
107
|
+
#
|
108
|
+
# If you add a datetime_attr for `:created_at` and/or `:updated_at` those
|
109
|
+
# will be automanaged.
|
110
|
+
#
|
111
|
+
# @param [Symbol] name The name of the attribute.
|
112
|
+
#
|
113
|
+
# @param [Hash] options
|
114
|
+
#
|
115
|
+
# @option options [Boolean] :set (false) When true this attribute
|
116
|
+
# can have multiple date times.
|
117
|
+
#
|
118
|
+
def datetime_attr name, options = {}
|
119
|
+
add_attribute(AWS::Record::Attributes::DateTimeAttr.new(name, options))
|
120
|
+
end
|
121
|
+
|
122
|
+
# Adds a date attribute to this class.
|
123
|
+
#
|
124
|
+
# @example A standard date attribute
|
125
|
+
#
|
126
|
+
# class Person < AWS::Record::HashModel
|
127
|
+
# date_attr :birthdate
|
128
|
+
# end
|
129
|
+
#
|
130
|
+
# baby = Person.new
|
131
|
+
# baby.birthdate = Time.now
|
132
|
+
# baby.birthdate #=> <Date: ....>
|
133
|
+
#
|
134
|
+
# @param [Symbol] name The name of the attribute.
|
135
|
+
#
|
136
|
+
# @param [Hash] options
|
137
|
+
#
|
138
|
+
# @option options [Boolean] :set (false) When true this attribute
|
139
|
+
# can have multiple dates.
|
140
|
+
#
|
141
|
+
def date_attr name, options = {}
|
142
|
+
add_attribute(AWS::Record::Attributes::DateAttr.new(name, options))
|
143
|
+
end
|
144
|
+
|
145
|
+
# Adds a DynamoDB binary attribute to this class. A binary
|
146
|
+
# attribute acts the same as a string attribute, except
|
147
|
+
#
|
148
|
+
# @param [Symbol] name The name of the attribute.
|
149
|
+
#
|
150
|
+
# @param [Hash] options
|
151
|
+
#
|
152
|
+
# @option options [Boolean] :set (false) When true this attribute
|
153
|
+
# can have multiple values.
|
154
|
+
#
|
155
|
+
# @note This should not be used for large objects.
|
156
|
+
#
|
157
|
+
def binary_attr name, options = {}
|
158
|
+
end
|
159
|
+
|
160
|
+
def serialized_attr name, options = {}
|
161
|
+
add_attribute(AWS::Record::Attributes::SerializedAttr.new(name, options))
|
162
|
+
end
|
163
|
+
|
164
|
+
# A convenience method for adding the standard two datetime attributes
|
165
|
+
# `:created_at` and `:updated_at`.
|
166
|
+
#
|
167
|
+
# @example
|
168
|
+
#
|
169
|
+
# class Recipe < AWS::Record::HashModel
|
170
|
+
# timestamps
|
171
|
+
# end
|
172
|
+
#
|
173
|
+
# recipe = Recipe.new
|
174
|
+
# recipe.save
|
175
|
+
# recipe.created_at #=> <DateTime ...>
|
176
|
+
# recipe.updated_at #=> <DateTime ...>
|
177
|
+
#
|
178
|
+
def timestamps
|
179
|
+
c = datetime_attr :created_at
|
180
|
+
u = datetime_attr :updated_at
|
181
|
+
[c, u]
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module AWS
|
2
|
+
module Record
|
3
|
+
module Attributes
|
4
|
+
class SerializedAttr < BaseAttr
|
5
|
+
|
6
|
+
def self.type_cast raw_value, options = {}
|
7
|
+
case raw_value
|
8
|
+
when nil then nil
|
9
|
+
when '' then nil
|
10
|
+
when String # assume binary
|
11
|
+
begin
|
12
|
+
Marshal.load(raw_value)
|
13
|
+
rescue
|
14
|
+
raw_value
|
15
|
+
end
|
16
|
+
else # object to serialize
|
17
|
+
raw_value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.serialize obj, options = {}
|
22
|
+
AWS::DynamoDB::Binary.new(Marshal.dump(obj))
|
23
|
+
end
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def self.allow_set?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Shamelessly stolen from Mongoid (and Dynamoid)!
|
2
|
+
module DynaModel #:nodoc
|
3
|
+
module Config
|
4
|
+
|
5
|
+
# Encapsulates logic for setting options.
|
6
|
+
module Options
|
7
|
+
|
8
|
+
# Get the defaults or initialize a new empty hash.
|
9
|
+
#
|
10
|
+
# @example Get the defaults.
|
11
|
+
# options.defaults
|
12
|
+
#
|
13
|
+
# @return [ Hash ] The default options.
|
14
|
+
#
|
15
|
+
# @since 0.2.0
|
16
|
+
def defaults
|
17
|
+
@defaults ||= {}
|
18
|
+
end
|
19
|
+
|
20
|
+
# Define a configuration option with a default.
|
21
|
+
#
|
22
|
+
# @example Define the option.
|
23
|
+
# Options.option(:persist_in_safe_mode, :default => false)
|
24
|
+
#
|
25
|
+
# @param [ Symbol ] name The name of the configuration option.
|
26
|
+
# @param [ Hash ] options Extras for the option.
|
27
|
+
#
|
28
|
+
# @option options [ Object ] :default The default value.
|
29
|
+
#
|
30
|
+
# @since 0.2.0
|
31
|
+
def option(name, options = {})
|
32
|
+
defaults[name] = settings[name] = options[:default]
|
33
|
+
|
34
|
+
class_eval <<-RUBY
|
35
|
+
def #{name}
|
36
|
+
settings[#{name.inspect}]
|
37
|
+
end
|
38
|
+
|
39
|
+
def #{name}=(value)
|
40
|
+
settings[#{name.inspect}] = value
|
41
|
+
end
|
42
|
+
|
43
|
+
def #{name}?
|
44
|
+
#{name}
|
45
|
+
end
|
46
|
+
|
47
|
+
def reset_#{name}
|
48
|
+
settings[#{name.inspect}] = defaults[#{name.inspect}]
|
49
|
+
end
|
50
|
+
RUBY
|
51
|
+
end
|
52
|
+
|
53
|
+
# Reset the configuration options to the defaults.
|
54
|
+
#
|
55
|
+
# @example Reset the configuration options.
|
56
|
+
# config.reset
|
57
|
+
#
|
58
|
+
# @return [ Hash ] The defaults.
|
59
|
+
#
|
60
|
+
# @since 0.2.0
|
61
|
+
def reset
|
62
|
+
settings.replace(defaults)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Get the settings or initialize a new empty hash.
|
66
|
+
#
|
67
|
+
# @example Get the settings.
|
68
|
+
# options.settings
|
69
|
+
#
|
70
|
+
# @return [ Hash ] The setting options.
|
71
|
+
#
|
72
|
+
# @since 0.2.0
|
73
|
+
def settings
|
74
|
+
@settings ||= {}
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "uri"
|
3
|
+
require "dyna_model/config/options"
|
4
|
+
|
5
|
+
# Shamelessly stolen from Dynamoid
|
6
|
+
module DynaModel
|
7
|
+
|
8
|
+
# Contains all the basic configuration information required for Dynamoid: both sensible defaults and required fields.
|
9
|
+
module Config
|
10
|
+
extend self
|
11
|
+
extend Options
|
12
|
+
|
13
|
+
# All the default options.
|
14
|
+
option :logger, :default => defined?(Rails)
|
15
|
+
option :read_provision, :default => 50
|
16
|
+
option :write_provision, :default => 10
|
17
|
+
# TODO - default adapter client based on config
|
18
|
+
#option :namespace, :default => defined?(Rails) ? "#{Rails.application.class.parent_name}_#{Rails.env}" : ""
|
19
|
+
option :endpoint, :default => 'dynamodb.us-west-2.amazonaws.com'
|
20
|
+
option :port, :default => 443
|
21
|
+
option :use_ssl, :default => true
|
22
|
+
option :default_guid_delimiter, :default => ":"
|
23
|
+
option :namespace, :default => ""
|
24
|
+
|
25
|
+
# The default logger: either the Rails logger or just stdout.
|
26
|
+
def default_logger
|
27
|
+
defined?(Rails) && Rails.respond_to?(:logger) ? Rails.logger : ::Logger.new($stdout)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the assigned logger instance.
|
31
|
+
def logger
|
32
|
+
@logger ||= default_logger
|
33
|
+
end
|
34
|
+
|
35
|
+
# If you want to, set the logger manually to any output you'd like. Or pass false or nil to disable logging entirely.
|
36
|
+
def logger=(logger)
|
37
|
+
case logger
|
38
|
+
when false, nil then @logger = nil
|
39
|
+
when true then @logger = default_logger
|
40
|
+
else
|
41
|
+
@logger = logger if logger.respond_to?(:info)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module DynaModel
|
2
|
+
module Document
|
3
|
+
|
4
|
+
MAX_ITEM_SIZE = 65_536
|
5
|
+
# These delimiters are also reserved characters and should not be used in
|
6
|
+
# hash or range keys
|
7
|
+
GUID_DELIMITER_PRECEDENCE = ["_", ":", "|", ",", "!", "~", "@", "^"]
|
8
|
+
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
included do
|
12
|
+
class_attribute :read_only_attributes, :base_class
|
13
|
+
self.base_class = self
|
14
|
+
|
15
|
+
AWS::Record.table_prefix = "#{DynaModel::Config.namespace}#{Rails.application.class.parent_name.to_s.underscore.dasherize}-#{Rails.env}-"
|
16
|
+
|
17
|
+
extend ActiveModel::Translation
|
18
|
+
extend ActiveModel::Callbacks
|
19
|
+
extend AWS::Record::AbstractBase
|
20
|
+
include DynaModel::Persistence
|
21
|
+
include DynaModel::Validations
|
22
|
+
|
23
|
+
define_model_callbacks :create, :save, :destroy, :initialize, :update, :validation
|
24
|
+
|
25
|
+
# OVERRIDE
|
26
|
+
# https://github.com/aws/aws-sdk-ruby/blob/master/lib/aws/record/abstract_base.rb#L258
|
27
|
+
# AWS::Record::AbstractBase for :select attributes
|
28
|
+
protected
|
29
|
+
def [] attribute_name
|
30
|
+
# Warn if using attributes that were not part of the :select (common with GSI/LSI projections)
|
31
|
+
# we do not want to give the impression they are nil
|
32
|
+
if (selected_attrs = self.instance_variable_get("@_selected_attributes"))
|
33
|
+
raise "Attribute '#{attribute_name}' was not part of the select '#{self.instance_variable_get("@_select")}' (available attributes: #{selected_attrs})" unless selected_attrs.include?(attribute_name)
|
34
|
+
end
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
include ActiveModel::Conversion
|
40
|
+
include ActiveModel::MassAssignmentSecurity if defined?(ActiveModel::MassAssignmentSecurity)
|
41
|
+
include ActiveModel::Naming
|
42
|
+
include ActiveModel::Observing if defined?(ActiveModel::Observing)
|
43
|
+
include ActiveModel::Serializers::JSON
|
44
|
+
include ActiveModel::Serializers::Xml
|
45
|
+
|
46
|
+
include DynaModel::Attributes
|
47
|
+
include DynaModel::Schema
|
48
|
+
include DynaModel::Query
|
49
|
+
|
50
|
+
def to_param
|
51
|
+
self.dynamo_db_guid
|
52
|
+
end
|
53
|
+
|
54
|
+
def dynamo_db_guid
|
55
|
+
_guid = [self.dynamo_db_item_key_values[:hash_value]]
|
56
|
+
_guid << self.dynamo_db_item_key_values[:range_value] if self.dynamo_db_item_key_values[:range_value]
|
57
|
+
_guid.join(self.class.guid_delimiter)
|
58
|
+
end
|
59
|
+
|
60
|
+
def dynamo_db_item_key_values
|
61
|
+
key_values = { hash_value: self[self.class.hash_key[:attribute_name]] }
|
62
|
+
key_values.merge!(range_value: self[self.class.range_key[:attribute_name]]) if self.class.range_key
|
63
|
+
key_values
|
64
|
+
end
|
65
|
+
|
66
|
+
def all_attributes_loaded?
|
67
|
+
self.instance_variable_get("@_select") == :all
|
68
|
+
end
|
69
|
+
|
70
|
+
# When only partial attributes were selected (via GSI or projected attributes on an index)
|
71
|
+
def load_attributes!
|
72
|
+
raise "All attributes already loaded!" if self.instance_variable_get("@_select") == :all
|
73
|
+
options = { shard_name: self.shard }
|
74
|
+
if self.class.range_key
|
75
|
+
obj = self.class.read(dynamo_db_item_key_values[:hash_value], dynamo_db_item_key_values[:range_value], options)
|
76
|
+
else
|
77
|
+
obj = self.class.read(dynamo_db_item_key_values[:hash_value], options)
|
78
|
+
end
|
79
|
+
raise "Could not find object" unless obj
|
80
|
+
self.instance_variable_set("@_select", :all)
|
81
|
+
self.remove_instance_variable("@_selected_attributes")
|
82
|
+
self.instance_variable_set("@_data", obj.instance_variable_get("@_data"))
|
83
|
+
self
|
84
|
+
end
|
85
|
+
|
86
|
+
def touch
|
87
|
+
self.send(:touch_timestamps, "updated_at")
|
88
|
+
end
|
89
|
+
|
90
|
+
def touch!
|
91
|
+
self.touch
|
92
|
+
self.save
|
93
|
+
end
|
94
|
+
|
95
|
+
module ClassMethods
|
96
|
+
|
97
|
+
def create_table options = {}
|
98
|
+
table_name = self.dynamo_db_table_name(options[:shard_name])
|
99
|
+
if self.dynamo_db_client.list_tables[:table_names].include?(table_name)
|
100
|
+
puts "Table #{table_name} already exists"
|
101
|
+
return false
|
102
|
+
end
|
103
|
+
self.dynamo_db_client.create_table(self.table_schema.merge({
|
104
|
+
table_name: table_name
|
105
|
+
}))
|
106
|
+
while (table_metadata = self.describe_table(options))[:table][:table_status] == "CREATING"
|
107
|
+
sleep 1
|
108
|
+
end
|
109
|
+
table_metadata
|
110
|
+
end
|
111
|
+
|
112
|
+
def describe_table(options={})
|
113
|
+
self.dynamo_db_client.describe_table(table_name: self.dynamo_db_table_name(options[:shard_name]))
|
114
|
+
end
|
115
|
+
|
116
|
+
def delete_table(options={})
|
117
|
+
table_name = self.dynamo_db_table_name(options[:shard_name])
|
118
|
+
return false unless self.dynamo_db_client.list_tables[:table_names].include?(table_name)
|
119
|
+
self.dynamo_db_client.delete_table(table_name: table_name)
|
120
|
+
begin
|
121
|
+
while (table_metadata = self.describe_table) && table_metadata[:table][:table_status] == "DELETING"
|
122
|
+
sleep 1
|
123
|
+
end
|
124
|
+
rescue AWS::DynamoDB::Errors::ResourceNotFoundException => e
|
125
|
+
DynaModel::Config.logger.info "Table deleted"
|
126
|
+
end
|
127
|
+
true
|
128
|
+
end
|
129
|
+
|
130
|
+
def resize_table(options={})
|
131
|
+
table_name = self.dynamo_db_table_name(options[:shard_name])
|
132
|
+
return false unless self.dynamo_db_client.list_tables[:table_names].include?(table_name)
|
133
|
+
self.dynamo_db_client.update_table({
|
134
|
+
provisioned_throughput: {
|
135
|
+
read_capacity_units: (options[:read_capacity_units] || self.table_schema[:provisioned_throughput][:read_capacity_units]).to_i,
|
136
|
+
write_capacity_units: (options[:write_capacity_units] || self.table_schema[:provisioned_throughput][:write_capacity_units]).to_i
|
137
|
+
},
|
138
|
+
table_name: table_name
|
139
|
+
})
|
140
|
+
while (table_metadata = self.describe_table) && table_metadata[:table][:table_status] == "UPDATING"
|
141
|
+
sleep 1
|
142
|
+
end
|
143
|
+
DynaModel::Config.logger.info "Table resized to #{table_metadata[:table][:provisioned_throughput]}"
|
144
|
+
true
|
145
|
+
end
|
146
|
+
|
147
|
+
def dynamo_db_table(shard_name = nil)
|
148
|
+
@table_map ||= {}
|
149
|
+
@table_map[self.dynamo_db_table_name(shard_name)] ||= Table.new(self)
|
150
|
+
end
|
151
|
+
|
152
|
+
def dynamo_db_table_name(shard_name = nil)
|
153
|
+
"#{AWS::Record.table_prefix}#{self.shard_name(shard_name)}"
|
154
|
+
end
|
155
|
+
|
156
|
+
def dynamo_db_client(config={})
|
157
|
+
options = {}
|
158
|
+
options[:use_ssl] = DynaModel::Config.use_ssl
|
159
|
+
options[:use_ssl] = config[:use_ssl] if config.has_key?(:use_ssl)
|
160
|
+
options[:dynamo_db_endpoint] = config[:endpoint] || DynaModel::Config.endpoint
|
161
|
+
options[:dynamo_db_port] = config[:port] || DynaModel::Config.port
|
162
|
+
options[:api_version] ||= config[:api_version] || '2012-08-10'
|
163
|
+
|
164
|
+
@dynamo_db_client ||= AWS::DynamoDB::Client.new(options)
|
165
|
+
end
|
166
|
+
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|