dynameek 0.1.1a → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +4 -0
- data/Gemfile.lock +50 -0
- data/LICENSE.txt +22 -0
- data/README.md +45 -0
- data/Rakefile +1 -0
- data/dynameek.gemspec +23 -0
- data/lib/dynameek/model/dynamo_db.rb +5 -4
- data/lib/dynameek/model/query.rb +22 -7
- data/lib/dynameek/model/structure.rb +76 -58
- data/lib/dynameek/model.rb +115 -78
- data/lib/dynameek/version.rb +3 -0
- data/lib/dynameek.rb +3 -0
- data/test/models/conversion.rb +13 -0
- data/test/models/simple.rb +13 -0
- data/test/spec/conversion_spec.rb +58 -0
- data/test/spec/simple_spec.rb +26 -0
- data/test/spec/spec_helper.rb +13 -0
- metadata +61 -12
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
dynameek (0.2.1)
|
5
|
+
aws-sdk
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: https://rubygems.org/
|
9
|
+
specs:
|
10
|
+
activesupport (3.2.13)
|
11
|
+
i18n (= 0.6.1)
|
12
|
+
multi_json (~> 1.0)
|
13
|
+
aws-sdk (1.8.5)
|
14
|
+
json (~> 1.4)
|
15
|
+
nokogiri (>= 1.4.4)
|
16
|
+
uuidtools (~> 2.1)
|
17
|
+
diff-lcs (1.2.1)
|
18
|
+
fake_dynamo (0.1.1)
|
19
|
+
activesupport
|
20
|
+
json
|
21
|
+
sinatra
|
22
|
+
i18n (0.6.1)
|
23
|
+
json (1.7.7)
|
24
|
+
multi_json (1.7.1)
|
25
|
+
nokogiri (1.5.8)
|
26
|
+
rack (1.5.2)
|
27
|
+
rack-protection (1.5.0)
|
28
|
+
rack
|
29
|
+
rspec (2.13.0)
|
30
|
+
rspec-core (~> 2.13.0)
|
31
|
+
rspec-expectations (~> 2.13.0)
|
32
|
+
rspec-mocks (~> 2.13.0)
|
33
|
+
rspec-core (2.13.1)
|
34
|
+
rspec-expectations (2.13.0)
|
35
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
36
|
+
rspec-mocks (2.13.0)
|
37
|
+
sinatra (1.4.1)
|
38
|
+
rack (~> 1.5, >= 1.5.2)
|
39
|
+
rack-protection (~> 1.4)
|
40
|
+
tilt (~> 1.3, >= 1.3.4)
|
41
|
+
tilt (1.3.6)
|
42
|
+
uuidtools (2.1.3)
|
43
|
+
|
44
|
+
PLATFORMS
|
45
|
+
ruby
|
46
|
+
|
47
|
+
DEPENDENCIES
|
48
|
+
dynameek!
|
49
|
+
fake_dynamo
|
50
|
+
rspec
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Max Dupenois
|
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,45 @@
|
|
1
|
+
# Dynameek
|
2
|
+
A very lightweight ORMish model thing for amazon's dynamo db, after initialising the aws-sdk with something like:
|
3
|
+
|
4
|
+
amazon_config_path = File.join(File.dirname(__FILE__), *%w[.. config amazon.config.yml])
|
5
|
+
amazon_config = YAML.load(File.read(amazon_config_path))
|
6
|
+
AWS.config(amazon_config[ENVIRONMENT])
|
7
|
+
|
8
|
+
##Models
|
9
|
+
|
10
|
+
You can create table models with this kind of syntax:
|
11
|
+
|
12
|
+
class Conversion < Dynameek::Model
|
13
|
+
|
14
|
+
field :client_id, :integer
|
15
|
+
field :channel_id, :string
|
16
|
+
field :goal_name, :string
|
17
|
+
field :time, :datetime
|
18
|
+
|
19
|
+
multi_column_hash_key [:client_id, :channel_id]
|
20
|
+
range :time
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
Creation of the models is as you'd expect
|
25
|
+
|
26
|
+
con = Conversion.create(:client_id => 1, :channel_id => "google", :goal_name=> "Some Goal", :time => DateTime.now)
|
27
|
+
|
28
|
+
The models can be edited like normal (_no update\_attributes yet though_)
|
29
|
+
|
30
|
+
con.goal_name="hello"
|
31
|
+
con.save
|
32
|
+
|
33
|
+
These models can be queried by find and query, although this is still undergoing some refactoring at the moment it currently looks something like this:
|
34
|
+
|
35
|
+
Conversion.find([1, "google"], DateTime.new([Some existing datetime]))
|
36
|
+
|
37
|
+
Conversion.query(["1", "google"]).where(DateTime.now, :lt).where(DateTime.now - 10, :gte).all
|
38
|
+
|
39
|
+
NB. The where clauses are only for referencing the range part of the composite hash key, there is currently no way to search by
|
40
|
+
hash contents as that felt like it was against the point of a document store.
|
41
|
+
|
42
|
+
### Disclaimery Bit
|
43
|
+
|
44
|
+
Delete is not currently supported because I don't need it. The gem is dynameek (unsurprisingly) but I wouldn't use it yet, far better
|
45
|
+
to clone down the project and modify it for your own use.
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/dynameek.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dynameek/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "dynameek"
|
8
|
+
gem.version = Dynameek::VERSION
|
9
|
+
gem.authors = ["Max Dupenois"]
|
10
|
+
gem.email = ["max.dupenois@forward.co.uk"]
|
11
|
+
gem.description = %q{A very lightweight model for DynamoDB tables in, certainly not in a finished state}
|
12
|
+
gem.summary = %q{Dynameek - A dynamodb model}
|
13
|
+
gem.homepage = "http://github.com/maxdupenois/dynameek"
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = ["lib"]
|
19
|
+
|
20
|
+
gem.add_dependency("aws-sdk")
|
21
|
+
gem.add_development_dependency("rspec")
|
22
|
+
gem.add_development_dependency("fake_dynamo")
|
23
|
+
end
|
@@ -30,10 +30,11 @@ module Dynameek
|
|
30
30
|
|
31
31
|
def build!
|
32
32
|
return if dynamo_db.tables[table_name].exists?
|
33
|
-
|
34
|
-
|
35
|
-
:range_key
|
36
|
-
|
33
|
+
opts = {:hash_key => { hash_key_info.field => [:datetime, :integer, :float].include?(hash_key_info.type) ? :number : hash_key_info.type }}
|
34
|
+
if range?
|
35
|
+
opts[:range_key] = { range_info.field => [:datetime, :integer, :float].include?(range_info.type) ? :number : range_info.type }
|
36
|
+
end
|
37
|
+
new_table = dynamo_db.tables.create(table_name, read_units, write_units, opts)
|
37
38
|
puts "Creating table, this may take a few minutes"
|
38
39
|
while new_table.status == :creating
|
39
40
|
sleep 1
|
data/lib/dynameek/model/query.rb
CHANGED
@@ -17,21 +17,29 @@ module Dynameek
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def where(value, op=:eq)
|
20
|
-
raise Exception("Op #{op.to_s} not recognised") if(!@range.keys.include?(op))
|
20
|
+
raise Exception.new("Op #{op.to_s} not recognised") if(!@range.keys.include?(op))
|
21
21
|
@range[op] = value
|
22
22
|
self
|
23
23
|
end
|
24
24
|
|
25
|
+
def delete
|
26
|
+
each(&:delete)
|
27
|
+
end
|
28
|
+
|
25
29
|
def all
|
26
30
|
run
|
27
31
|
end
|
28
32
|
|
29
33
|
def each
|
30
|
-
all.each
|
34
|
+
all.each do |item|
|
35
|
+
yield(item)
|
36
|
+
end
|
31
37
|
end
|
32
38
|
|
33
39
|
def each_with_index
|
34
|
-
all.each_with_index
|
40
|
+
all.each_with_index do |item, index|
|
41
|
+
yield(item, index)
|
42
|
+
end
|
35
43
|
end
|
36
44
|
|
37
45
|
RANGE_QUERY_MAP =
|
@@ -51,22 +59,29 @@ module Dynameek
|
|
51
59
|
hash_key = @hash_key
|
52
60
|
hash_key = hash_key.join(@model.multi_column_join) if(hash_key.is_a?(Array))
|
53
61
|
|
54
|
-
query_hash = {:hash_value => hash_key}
|
62
|
+
query_hash = {:hash_value => hash_key, :select => :all}
|
55
63
|
|
56
64
|
query_hash = @range.reduce(query_hash) do |hsh, (key, val)|
|
57
65
|
if(!val.nil?)
|
58
|
-
hsh[RANGE_QUERY_MAP[key]] = @model.convert_to_dynamodb(@model.
|
66
|
+
hsh[RANGE_QUERY_MAP[key]] = @model.convert_to_dynamodb(@model.range_info.type, val)
|
59
67
|
end
|
60
68
|
hsh
|
61
69
|
end
|
62
|
-
|
70
|
+
# p query_hash
|
71
|
+
# "CONTENTS"
|
72
|
+
# @model.table.items.each{|item| p item.inspect}
|
73
|
+
# p "_________"
|
74
|
+
@model.table.items.query(query_hash).map{|item|
|
75
|
+
# p item.inspect
|
76
|
+
@model.item_to_instance(item)
|
77
|
+
}
|
63
78
|
|
64
79
|
end
|
65
80
|
|
66
81
|
|
67
82
|
end
|
68
83
|
|
69
|
-
[:query, :where, :all, :each, :each_with_index].each do |method|
|
84
|
+
[:query, :where, :all, :each, :each_with_index, :delete].each do |method|
|
70
85
|
define_method(method) do |*args|
|
71
86
|
qc = QueryChain.new(self)
|
72
87
|
args = [] if !args
|
@@ -1,26 +1,70 @@
|
|
1
1
|
module Dynameek
|
2
2
|
module Model
|
3
3
|
module Structure
|
4
|
-
|
5
|
-
@fields = {}
|
6
|
-
@hash_key = OpenStruct.new
|
7
|
-
@hash_key.field = nil
|
8
|
-
@hash_key.type = nil
|
9
|
-
|
10
|
-
@range = OpenStruct.new
|
11
|
-
@range.field = nil
|
12
|
-
@range.type = nil
|
13
|
-
|
14
|
-
@read_write = [10, 5]
|
4
|
+
|
15
5
|
|
16
|
-
|
17
|
-
|
18
|
-
|
6
|
+
def fields
|
7
|
+
@fields ||= {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def hash_key_info
|
11
|
+
if(@hash_key.nil?)
|
12
|
+
@hash_key = OpenStruct.new
|
13
|
+
@hash_key.field = nil
|
14
|
+
@hash_key.type = nil
|
15
|
+
end
|
16
|
+
@hash_key
|
17
|
+
end
|
18
|
+
|
19
|
+
def range_info
|
20
|
+
if(@range.nil?)
|
21
|
+
@range = OpenStruct.new
|
22
|
+
@range.field = nil
|
23
|
+
@range.type = nil
|
24
|
+
end
|
25
|
+
@range
|
26
|
+
end
|
27
|
+
|
28
|
+
def range?
|
29
|
+
!range_info.field.nil?
|
30
|
+
end
|
31
|
+
|
32
|
+
def read_units
|
33
|
+
@read_write ||= [10, 5]
|
34
|
+
@read_write[0]
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_units
|
38
|
+
@read_write ||= [10, 5]
|
39
|
+
@read_write[1]
|
40
|
+
end
|
41
|
+
|
42
|
+
def read_write(vals)
|
43
|
+
@read_write = vals
|
44
|
+
end
|
45
|
+
|
46
|
+
def multi_column_hash_key_fields
|
47
|
+
@multi_column_hash_key_fields ||= []
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
def multi_column_join
|
53
|
+
@multi_column_join ||= "|"
|
54
|
+
end
|
55
|
+
|
56
|
+
def multi_column_join=(join)
|
57
|
+
@multi_column_join =join
|
58
|
+
end
|
59
|
+
|
60
|
+
def multi_column_hash_key?
|
61
|
+
!multi_column_hash_key_fields.empty?
|
62
|
+
end
|
19
63
|
|
20
64
|
def field fieldname, type
|
21
65
|
fieldname = fieldname.to_sym
|
22
66
|
type = type.to_sym
|
23
|
-
|
67
|
+
fields[fieldname] = type
|
24
68
|
define_method(fieldname.to_s) { read_attribute(fieldname) }
|
25
69
|
define_method("#{fieldname.to_s}?") { !read_attribute(fieldname).nil? }
|
26
70
|
define_method("#{fieldname.to_s}=") {|value| write_attribute(fieldname, value) }
|
@@ -31,74 +75,48 @@ module Dynameek
|
|
31
75
|
def hash_key fieldname
|
32
76
|
fieldname = fieldname.to_sym
|
33
77
|
check_field(fieldname)
|
34
|
-
|
35
|
-
|
78
|
+
hash_key_info.field = fieldname
|
79
|
+
hash_key_info.type = fields[fieldname]
|
36
80
|
define_method(:hash_key) { read_attribute(fieldname) }
|
37
81
|
end
|
38
82
|
|
39
|
-
|
40
|
-
@multi_column_join
|
41
|
-
end
|
42
|
-
def multi_column_join=(join)
|
43
|
-
@multi_column_join = join
|
44
|
-
end
|
45
|
-
def multi_column_hash_key?
|
46
|
-
!@multi_column_hash_key_fields.empty?
|
47
|
-
end
|
83
|
+
|
48
84
|
|
49
85
|
|
50
86
|
|
51
87
|
def multi_column_hash_key fieldnames
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
fieldname = fieldnames.map(&:to_s).join("_").
|
56
|
-
|
88
|
+
fieldnames = fieldnames.map(&:to_sym)
|
89
|
+
fieldnames.each{|f| check_field(f)}
|
90
|
+
fieldnames.each {|f| multi_column_hash_key_fields << f}
|
91
|
+
fieldname = fieldnames.map(&:to_s).join("_").to_sym
|
92
|
+
fields[fieldname] = :string
|
57
93
|
define_method(:hash_key) do
|
58
|
-
|
94
|
+
self.class.multi_column_hash_key_fields.reduce([]) do |memo, field|
|
59
95
|
memo << attributes[field]
|
60
96
|
memo
|
61
|
-
end.join(
|
97
|
+
end.join(self.class.multi_column_join)
|
62
98
|
end
|
63
99
|
alias_method fieldname, :hash_key
|
64
|
-
|
65
|
-
|
100
|
+
hash_key_info.field = fieldname
|
101
|
+
hash_key_info.type = :string
|
66
102
|
end
|
67
103
|
|
68
|
-
def hash_key_field
|
69
|
-
@hash_key
|
70
|
-
end
|
71
104
|
|
72
105
|
|
73
106
|
def range fieldname
|
74
107
|
fieldname = fieldname.to_sym
|
75
108
|
check_field(fieldname)
|
76
|
-
|
77
|
-
|
109
|
+
range_info.field = fieldname
|
110
|
+
range_info.type = fields[fieldname]
|
78
111
|
end
|
79
112
|
|
80
113
|
|
81
|
-
def read_write read, write
|
82
|
-
@read_write = [read, write]
|
83
|
-
end
|
84
114
|
|
85
|
-
|
86
|
-
|
87
|
-
end
|
88
|
-
def write_units
|
89
|
-
@read_write[1]
|
90
|
-
end
|
91
|
-
|
92
|
-
def fields
|
93
|
-
@fields
|
94
|
-
end
|
95
|
-
|
96
|
-
def range_field
|
97
|
-
@range.field.nil? ? nil : @range
|
98
|
-
end
|
115
|
+
|
116
|
+
|
99
117
|
|
100
118
|
def check_field(fieldname)
|
101
|
-
raise
|
119
|
+
raise ("#{fieldname} is not a recognised field") if fields[fieldname].nil?
|
102
120
|
end
|
103
121
|
|
104
122
|
end
|
data/lib/dynameek/model.rb
CHANGED
@@ -1,95 +1,132 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
attr_accessor :attributes
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
def self.find(hash_key, range_val=nil)
|
14
|
-
raise Exception("This has a composite hash with a range, the range val is required") if(range_val.nil? && !range_field.field.nil?)
|
15
|
-
#multicolumn
|
16
|
-
hash_key = hash_key.join(multi_column_join) if(hash_key.is_a?(Array))
|
17
|
-
|
18
|
-
items = if !range_val.nil?
|
19
|
-
range_val = convert_to_dynamodb(range_field.type, range_val)
|
20
|
-
table.batch_get(:all, [[hash_key, range_val]])
|
21
|
-
else
|
22
|
-
table.batch_get(:all, [hash_key])
|
1
|
+
module Dynameek
|
2
|
+
module Model
|
3
|
+
def self.included(base)
|
4
|
+
base.extend ClassMethods
|
23
5
|
end
|
24
|
-
# p items.methods - Object.new.methods
|
25
|
-
return nil if(items.entries.size == 0)
|
26
|
-
item_to_instance(items.first)
|
27
6
|
|
28
|
-
|
29
|
-
|
7
|
+
def save
|
8
|
+
attribs = self.class.fields.reduce({}) do |memo, (field, type)|
|
9
|
+
#Always call the read method in case it has been overwritten
|
10
|
+
#Note that this is required for the multicolumn key
|
11
|
+
val = self.send field
|
12
|
+
val = self.class.convert_to_dynamodb(type, val)
|
13
|
+
memo[field] = val
|
14
|
+
memo
|
15
|
+
end
|
16
|
+
self.class.before_save_callbacks.each{|method| self.send method}
|
17
|
+
self.class.table.batch_write(
|
18
|
+
:put => [
|
19
|
+
attribs
|
20
|
+
]
|
21
|
+
)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
|
27
|
+
def attributes
|
28
|
+
@attributes ||= {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def read_attribute(fieldname)
|
32
|
+
attributes[fieldname]
|
33
|
+
end
|
30
34
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
35
|
+
def write_attribute(fieldname, value)
|
36
|
+
attributes[fieldname] = value
|
37
|
+
end
|
38
|
+
|
39
|
+
def delete
|
40
|
+
if self.class.range?
|
41
|
+
range_val = self.class.convert_to_dynamodb(self.class.range_info.type, self.send(self.class.range_info.field))
|
42
|
+
#Rounding errors can be irritating here so if we have the actual item we'll use it's range_val, nope that makes things worse
|
43
|
+
# range_val = dynamo_item.range_value if !dynamo_item.nil?
|
44
|
+
# p "TRYING TO DELETE #{[[hash_key, range_val]]}.inspect"
|
45
|
+
# p "FINDING THAT THING: #{self.class.find(hash_key, self.send(self.class.range_info.field)).inspect}"
|
46
|
+
# p "VIA BATCH GET #{self.class.table.batch_get(:all, [[hash_key, range_val]]).entries.inspect}"
|
47
|
+
self.class.table.batch_delete([[hash_key, range_val]])
|
48
|
+
else
|
49
|
+
self.class.table.batch_delete([hash_key])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def dynamo_item
|
54
|
+
@dynamo_item ||= nil
|
38
55
|
end
|
39
|
-
instance
|
40
|
-
end
|
41
|
-
|
42
56
|
|
43
|
-
|
44
|
-
|
45
|
-
instance = self.new
|
46
|
-
attrib.each do |key, val|
|
47
|
-
instance.send "#{key.to_s}=", val
|
57
|
+
def dynamo_item=(item)
|
58
|
+
@dynamo_item = item
|
48
59
|
end
|
49
|
-
instance.save
|
50
|
-
end
|
51
60
|
|
52
|
-
|
53
|
-
|
54
|
-
|
61
|
+
module ClassMethods
|
62
|
+
include Dynameek::Model::DynamoDb
|
63
|
+
include Dynameek::Model::Structure
|
64
|
+
include Dynameek::Model::Query
|
65
|
+
|
55
66
|
|
56
|
-
def self.before_save method
|
57
|
-
before_save_callbacks << method.to_sym
|
58
|
-
end
|
59
67
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
val = self.class.convert_to_dynamodb(type, val)
|
66
|
-
memo[field] = val
|
67
|
-
memo
|
68
|
-
end
|
69
|
-
self.class.before_save_callbacks.each{|method| self.send method}
|
70
|
-
self.class.table.batch_write(
|
71
|
-
:put => [
|
72
|
-
attribs
|
73
|
-
]
|
74
|
-
)
|
75
|
-
self
|
76
|
-
end
|
68
|
+
|
69
|
+
def find(hash_key, range_val=nil)
|
70
|
+
raise Exception("This has a composite hash with a range, the range val is required") if(range_val.nil? && range?)
|
71
|
+
#multicolumn
|
72
|
+
hash_key = hash_key.join(multi_column_join) if(hash_key.is_a?(Array))
|
77
73
|
|
74
|
+
items = if range?
|
75
|
+
range_val = convert_to_dynamodb(range_info.type, range_val)
|
76
|
+
table.batch_get(:all, [[hash_key, range_val]])
|
77
|
+
else
|
78
|
+
table.batch_get(:all, [hash_key])
|
79
|
+
end
|
80
|
+
# p items.methods - Object.new.methods
|
81
|
+
return nil if(items.entries.size == 0)
|
82
|
+
item_to_instance(items.first)
|
83
|
+
|
84
|
+
end
|
78
85
|
|
79
|
-
|
80
|
-
|
81
|
-
|
86
|
+
def delete_table
|
87
|
+
table.delete
|
88
|
+
end
|
89
|
+
|
82
90
|
|
83
|
-
|
91
|
+
def item_to_instance(item)
|
92
|
+
item_hsh = (item.is_a?(AWS::DynamoDB::Item) || item.is_a?(AWS::DynamoDB::ItemData) ? item.attributes.to_hash : item)
|
93
|
+
instance = self.new
|
94
|
+
fields.each do |field, type|
|
95
|
+
next if multi_column_hash_key? && field == hash_key_info.field
|
96
|
+
instance.send "#{field.to_s}=", convert_from_dynamodb(type, item_hsh[field.to_s])
|
97
|
+
end
|
98
|
+
if item.is_a?(AWS::DynamoDB::Item) || item.is_a?(AWS::DynamoDB::ItemData)
|
99
|
+
instance.dynamo_item = item.is_a?(AWS::DynamoDB::ItemData) ? item.item : item
|
100
|
+
end
|
101
|
+
instance
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
def create(attrib)
|
107
|
+
instance = self.new
|
108
|
+
attrib.each do |key, val|
|
109
|
+
instance.send "#{key.to_s}=", val
|
110
|
+
end
|
111
|
+
instance.save
|
112
|
+
end
|
113
|
+
|
114
|
+
def before_save_callbacks
|
115
|
+
@before_save_callbacks ||= Set.new
|
116
|
+
end
|
117
|
+
|
118
|
+
def before_save method
|
119
|
+
before_save_callbacks << method.to_sym
|
120
|
+
end
|
84
121
|
|
85
|
-
def read_attribute(fieldname)
|
86
|
-
@attributes[fieldname]
|
87
|
-
end
|
88
122
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
123
|
+
|
124
|
+
def table_name
|
125
|
+
self.to_s
|
126
|
+
end
|
127
|
+
|
93
128
|
|
94
129
|
|
130
|
+
end
|
131
|
+
end
|
95
132
|
end
|
data/lib/dynameek.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[spec_helper])
|
2
|
+
|
3
|
+
describe Conversion do
|
4
|
+
before(:all) do
|
5
|
+
Conversion.delete_table
|
6
|
+
end
|
7
|
+
before(:each) do
|
8
|
+
Conversion.query([1, "google"]).delete
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should allow you to create a new conversion" do
|
12
|
+
con = nil
|
13
|
+
lambda{
|
14
|
+
con = Conversion.create(:client_id => 1, :channel_id => "google", :goal_name=> "new conversion", :time => DateTime.now)
|
15
|
+
}.should_not raise_error
|
16
|
+
con.hash_key.should == "1|google"
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
it "should allow you to find a conversion" do
|
21
|
+
now = DateTime.now
|
22
|
+
Conversion.create(:client_id => 1, :channel_id => "google", :goal_name=> "find conversion", :time => now)
|
23
|
+
con = Conversion.find([1, "google"], now)
|
24
|
+
con.should_not be_nil
|
25
|
+
con.hash_key.should == "1|google"
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
it "should allow you to delete a range" do
|
30
|
+
now = DateTime.now
|
31
|
+
Conversion.create(:client_id => 1, :channel_id => "google", :goal_name=> "delete conversion", :time => now)
|
32
|
+
Conversion.query([1, "google"]).all.size.should == 1
|
33
|
+
Conversion.query([1, "google"]).delete
|
34
|
+
Conversion.query([1, "google"]).all.size.should == 0
|
35
|
+
end
|
36
|
+
|
37
|
+
it "should allow you to find a query a conversion" do
|
38
|
+
now = DateTime.now
|
39
|
+
Conversion.create(:client_id => 1, :channel_id => "google", :goal_name=> "Some Goal", :time => now)
|
40
|
+
cons = Conversion.query([1, "google"]).all
|
41
|
+
cons.size.should == 1
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should allow you to find a query a conversion within a range" do
|
45
|
+
now = DateTime.now
|
46
|
+
Conversion.create(:client_id => 1, :channel_id => "google", :goal_name=> "Some Goal", :time => now - 3)
|
47
|
+
Conversion.create(:client_id => 1, :channel_id => "google", :goal_name=> "Some Goal", :time => now - 2)
|
48
|
+
Conversion.create(:client_id => 1, :channel_id => "google", :goal_name=> "Some Goal", :time => now - 1)
|
49
|
+
cons = Conversion.query([1, "google"]).where(now, :lt).all
|
50
|
+
cons.size.should == 3
|
51
|
+
cons = Conversion.query([1, "google"]).where(now - 4, :gt).all
|
52
|
+
cons.size.should == 3
|
53
|
+
# cons = Conversion.query([1, "google"]).where(now - 2, :gte).where(now, :lt).all
|
54
|
+
# cons.size.should == 2 Not supported on fake_dynamo, seems to work fine on prod
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), *%w[spec_helper])
|
2
|
+
|
3
|
+
describe Simple do
|
4
|
+
before(:all) do
|
5
|
+
Simple.delete_table
|
6
|
+
end
|
7
|
+
before(:each) do
|
8
|
+
simple = Simple.find(1)
|
9
|
+
simple.delete if(!simple.nil?)
|
10
|
+
end
|
11
|
+
it "should allow you to create a new simple" do
|
12
|
+
con = nil
|
13
|
+
lambda{
|
14
|
+
con = Simple.create(:my_id => 1, :some_value => "hello")
|
15
|
+
}.should_not raise_error
|
16
|
+
con.hash_key.should == 1
|
17
|
+
con.some_value.should == "hello"
|
18
|
+
end
|
19
|
+
it "should allow you to find a created simple" do
|
20
|
+
Simple.create(:my_id => 1, :some_value => "hello")
|
21
|
+
con = Simple.find(1)
|
22
|
+
con.hash_key.should == 1
|
23
|
+
con.some_value.should == "hello"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'aws-sdk'
|
3
|
+
|
4
|
+
require File.join(File.dirname(__FILE__), *%w[.. .. lib dynameek])
|
5
|
+
|
6
|
+
AWS.config(:use_ssl => false,
|
7
|
+
:dynamo_db_endpoint => 'localhost',
|
8
|
+
:dynamo_db_port => 4567,
|
9
|
+
:access_key_id => "xxx",
|
10
|
+
:secret_access_key => "xxx")
|
11
|
+
|
12
|
+
require File.join(File.dirname(__FILE__), *%w[.. models conversion])
|
13
|
+
require File.join(File.dirname(__FILE__), *%w[.. models simple])
|
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dynameek
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
version: 0.1
|
4
|
+
prerelease:
|
5
|
+
version: 0.2.1
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Max Dupenois
|
@@ -11,10 +11,43 @@ bindir: bin
|
|
11
11
|
cert_chain: []
|
12
12
|
|
13
13
|
date: 2013-03-20 00:00:00 Z
|
14
|
-
dependencies:
|
15
|
-
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: aws-sdk
|
17
|
+
prerelease: false
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
type: :runtime
|
25
|
+
version_requirements: *id001
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rspec
|
28
|
+
prerelease: false
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: "0"
|
35
|
+
type: :development
|
36
|
+
version_requirements: *id002
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: fake_dynamo
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: "0"
|
46
|
+
type: :development
|
47
|
+
version_requirements: *id003
|
16
48
|
description: A very lightweight model for DynamoDB tables in, certainly not in a finished state
|
17
|
-
email:
|
49
|
+
email:
|
50
|
+
- max.dupenois@forward.co.uk
|
18
51
|
executables: []
|
19
52
|
|
20
53
|
extensions: []
|
@@ -22,11 +55,23 @@ extensions: []
|
|
22
55
|
extra_rdoc_files: []
|
23
56
|
|
24
57
|
files:
|
58
|
+
- Gemfile
|
59
|
+
- Gemfile.lock
|
60
|
+
- LICENSE.txt
|
61
|
+
- README.md
|
62
|
+
- Rakefile
|
63
|
+
- dynameek.gemspec
|
64
|
+
- lib/dynameek.rb
|
65
|
+
- lib/dynameek/model.rb
|
25
66
|
- lib/dynameek/model/dynamo_db.rb
|
26
67
|
- lib/dynameek/model/query.rb
|
27
68
|
- lib/dynameek/model/structure.rb
|
28
|
-
- lib/dynameek/
|
29
|
-
-
|
69
|
+
- lib/dynameek/version.rb
|
70
|
+
- test/models/conversion.rb
|
71
|
+
- test/models/simple.rb
|
72
|
+
- test/spec/conversion_spec.rb
|
73
|
+
- test/spec/simple_spec.rb
|
74
|
+
- test/spec/spec_helper.rb
|
30
75
|
homepage: http://github.com/maxdupenois/dynameek
|
31
76
|
licenses: []
|
32
77
|
|
@@ -44,15 +89,19 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
44
89
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
90
|
none: false
|
46
91
|
requirements:
|
47
|
-
- - "
|
92
|
+
- - ">="
|
48
93
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
94
|
+
version: "0"
|
50
95
|
requirements: []
|
51
96
|
|
52
97
|
rubyforge_project:
|
53
98
|
rubygems_version: 1.8.24
|
54
99
|
signing_key:
|
55
100
|
specification_version: 3
|
56
|
-
summary: Dynameek
|
57
|
-
test_files:
|
58
|
-
|
101
|
+
summary: Dynameek - A dynamodb model
|
102
|
+
test_files:
|
103
|
+
- test/models/conversion.rb
|
104
|
+
- test/models/simple.rb
|
105
|
+
- test/spec/conversion_spec.rb
|
106
|
+
- test/spec/simple_spec.rb
|
107
|
+
- test/spec/spec_helper.rb
|