dynameek 0.2.2 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dynameek (0.2.1)
4
+ dynameek (0.2.2)
5
5
  aws-sdk
6
6
 
7
7
  GEM
@@ -22,7 +22,7 @@ GEM
22
22
  i18n (0.6.1)
23
23
  json (1.7.7)
24
24
  multi_json (1.7.1)
25
- nokogiri (1.5.8)
25
+ nokogiri (1.5.9)
26
26
  rack (1.5.2)
27
27
  rack-protection (1.5.0)
28
28
  rack
@@ -23,20 +23,38 @@ module Dynameek
23
23
  value
24
24
  end
25
25
  end
26
-
26
+
27
+ def index_table_exists?
28
+ !index_table.nil? && index_table.exists?
29
+ end
30
+
27
31
  def exists?
28
32
  table.exists?
29
33
  end
30
34
 
31
35
  def build!
32
- return if dynamo_db.tables[table_name].exists?
36
+ return if dynamo_db.tables[table_name].exists?
33
37
  opts = {:hash_key => { hash_key_info.field => [:datetime, :integer, :float].include?(hash_key_info.type) ? :number : hash_key_info.type }}
34
38
  if range?
35
39
  opts[:range_key] = { range_info.field => [:datetime, :integer, :float].include?(range_info.type) ? :number : range_info.type }
36
40
  end
37
- new_table = dynamo_db.tables.create(table_name, read_units, write_units, opts)
38
- puts "Creating table, this may take a few minutes"
39
- while new_table.status == :creating
41
+ new_table = nil
42
+ idx_table = nil
43
+ if index_table? && !dynamo_db.tables[index_table_name].exists?
44
+ dynamo_db.tables.create(
45
+ table_name+"_INDEX", read_units,
46
+ write_units, opts)
47
+ new_table = dynamo_db.tables.create(
48
+ table_name, read_units, write_units, {
49
+ hash_key: {
50
+ hash_key_info.field.to_s+"_"+range_info.field.to_s => :string
51
+ },
52
+ range_key: {"dynameek_index" => :number}
53
+ })
54
+ else
55
+ new_table = dynamo_db.tables.create(table_name, read_units, write_units, opts)
56
+ end
57
+ while new_table.status == :creating || (!idx_table.nil? && idx_table.status == :creating)
40
58
  sleep 1
41
59
  end
42
60
  end
@@ -52,7 +70,14 @@ module Dynameek
52
70
  @table.load_schema if !@table.schema_loaded?
53
71
  @table
54
72
  end
55
-
73
+ def index_table
74
+ build!
75
+ @index_table ||= dynamo_db.tables[index_table_name]
76
+ if @index_table.exists? && !@index_table.schema_loaded?
77
+ @index_table.load_schema
78
+ end
79
+ @index_table
80
+ end
56
81
  end
57
82
  end
58
- end
83
+ end
@@ -28,7 +28,11 @@ module Dynameek
28
28
  def all
29
29
  run
30
30
  end
31
-
31
+
32
+ def size
33
+ all.size
34
+ end
35
+
32
36
  def each
33
37
  all.each do |item|
34
38
  yield(item)
@@ -83,13 +87,45 @@ module Dynameek
83
87
  range = range.reduce({}) {|memo, (key, val)| memo[RANGE_QUERY_MAP[key]] = @model.convert_to_dynamodb(@model.range_info.type, val); memo}
84
88
  end
85
89
  query_hash.merge!(range)
86
- @model.table.items.query(query_hash).map{|item|
87
- @model.item_to_instance(item)
88
- }
90
+ if @model.index_table?
91
+ rows = @model.index_table.items.query(query_hash).map do |item|
92
+ item_hsh = @model.aws_item_to_hash(item).reduce({}){|m, (k,v)| m[k.to_sym] = v; m}
93
+
94
+ # Need to convert from then back to because of the datetimes being
95
+ # screwy as number formats
96
+ hsh_val = @model.convert_from_dynamodb(@model.hash_key_info.type,
97
+ item_hsh[@model.hash_key_info.field])
98
+ rng_val = @model.convert_from_dynamodb(@model.range_info.type,
99
+ item_hsh[@model.range_info.field])
100
+ hsh_val = @model.convert_to_dynamodb(@model.hash_key_info.type, hsh_val)
101
+ rng_val = @model.convert_to_dynamodb(@model.range_info.type, rng_val)
102
+ item_hash_key = [hsh_val, @model.multi_column_join, rng_val].join('')
103
+ query_hsh_2 = {
104
+ hash_value: item_hash_key,
105
+ range_value: (1 .. item_hsh[:current_range_val].to_i),
106
+ select: :all
107
+ }
108
+ # p "QUERYING FOR #{query_hsh_2}"
109
+ # p "TABLE CONTENTS"
110
+ # p "--------------"
111
+ # @model.table.items.each do |i|
112
+ # p i.inspect
113
+ # end
114
+ internal_rows = @model.table.items.query(query_hsh_2).map{|act_item|
115
+ @model.item_to_instance(act_item)
116
+ }
117
+ internal_rows
118
+ end.flatten
119
+ rows
120
+ else
121
+ @model.table.items.query(query_hash).map{|item|
122
+ @model.item_to_instance(item)
123
+ }
124
+ end
89
125
  end
90
126
  end
91
127
 
92
- [:query, :where, :all, :each, :each_with_index, :delete].each do |method|
128
+ [:query, :where, :all, :each, :each_with_index, :size, :delete].each do |method|
93
129
  define_method(method) do |*args|
94
130
  qc = QueryChain.new(self)
95
131
  args = [] if !args
@@ -99,4 +135,4 @@ module Dynameek
99
135
 
100
136
  end
101
137
  end
102
- end
138
+ end
@@ -24,6 +24,11 @@ module Dynameek
24
24
  @range
25
25
  end
26
26
 
27
+ def index_table?
28
+ @uses_index_table|| false
29
+ end
30
+
31
+
27
32
  def range?
28
33
  !range_info.field.nil?
29
34
  end
@@ -74,7 +79,7 @@ module Dynameek
74
79
  hash_key_info.type = fields[fieldname]
75
80
  define_method(:hash_key) { read_attribute(fieldname) }
76
81
  end
77
-
82
+
78
83
  def multi_column_hash_key fieldnames
79
84
  fieldnames = fieldnames.map(&:to_sym)
80
85
  fieldnames.each{|f| check_field(f)}
@@ -97,6 +102,40 @@ module Dynameek
97
102
  check_field(fieldname)
98
103
  range_info.field = fieldname
99
104
  range_info.type = fields[fieldname]
105
+ define_method(:range) do
106
+ attributes[self.class.range_info.field]
107
+ end
108
+ end
109
+
110
+ def aws_item_to_hash(item)
111
+ (item.is_a?(AWS::DynamoDB::Item) || item.is_a?(AWS::DynamoDB::ItemData) ?
112
+ item.attributes.to_hash :
113
+ item
114
+ )
115
+ end
116
+
117
+ def current_range(key, range_val)
118
+ return nil if !index_table?
119
+ key.join!(multi_column_join) if key.is_a?(Array)
120
+ item = index_table.items.query(
121
+ {
122
+ hash_value: key,
123
+ select: :all,
124
+ range_value: range_val
125
+ }).first
126
+ return 0 if item.nil?
127
+ item_hsh = aws_item_to_hash(item)
128
+ item_hsh['current_range_val'].to_i
129
+ end
130
+
131
+ def use_index_table
132
+ define_method(:act_hash_key) do
133
+ [self.class.convert_to_dynamodb(self.class.hash_key_info.type, hash_key),
134
+ self.class.multi_column_join,
135
+ self.class.convert_to_dynamodb(self.class.range_info.type, range)].join('')
136
+ end
137
+ field :dynameek_index, :number
138
+ @uses_index_table = true
100
139
  end
101
140
 
102
141
  def check_field(fieldname)
@@ -107,4 +146,4 @@ module Dynameek
107
146
  end
108
147
  end
109
148
 
110
-
149
+
@@ -14,11 +14,38 @@ module Dynameek
14
14
  memo
15
15
  end
16
16
  self.class.before_save_callbacks.each{|method| self.send method}
17
- self.class.table.batch_write(
18
- :put => [
19
- attribs
20
- ]
21
- )
17
+ if self.class.index_table?
18
+ rng = self.class.convert_to_dynamodb(
19
+ self.class.range_info.type,
20
+ attributes[self.class.range_info.field]
21
+ )
22
+ curr_range = self.class.current_range(hash_key, rng) + 1
23
+ self.class.index_table.batch_write(
24
+ :put => [
25
+ {
26
+ self.class.hash_key_info.field => attribs[self.class.hash_key_info.field],
27
+ self.class.range_info.field => attribs[self.class.range_info.field],
28
+ current_range_val: curr_range
29
+ }
30
+ ]
31
+ )
32
+ attribs[self.class.hash_key_info.field.to_s+"_"+self.class.range_info.field.to_s] =
33
+ attribs[self.class.hash_key_info.field].to_s +
34
+ self.class.multi_column_join +
35
+ attribs[self.class.range_info.field].to_s
36
+ attribs[:dynameek_index] = curr_range
37
+ self.class.table.batch_write(
38
+ :put => [
39
+ attribs
40
+ ]
41
+ )
42
+ else
43
+ self.class.table.batch_write(
44
+ :put => [
45
+ attribs
46
+ ]
47
+ )
48
+ end
22
49
  self
23
50
  end
24
51
 
@@ -37,12 +64,11 @@ module Dynameek
37
64
  def delete
38
65
  if self.class.range?
39
66
  range_val = self.class.convert_to_dynamodb(self.class.range_info.type, self.send(self.class.range_info.field))
40
- #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
41
- # range_val = dynamo_item.range_value if !dynamo_item.nil?
42
- # p "TRYING TO DELETE #{[[hash_key, range_val]]}.inspect"
43
- # p "FINDING THAT THING: #{self.class.find(hash_key, self.send(self.class.range_info.field)).inspect}"
44
- # p "VIA BATCH GET #{self.class.table.batch_get(:all, [[hash_key, range_val]]).entries.inspect}"
45
- self.class.table.batch_delete([[hash_key, range_val]])
67
+ if self.class.index_table?
68
+ self.class.table.batch_delete([[act_hash_key, attributes[:dynameek_index]]])
69
+ else
70
+ self.class.table.batch_delete([[hash_key, range_val]])
71
+ end
46
72
  else
47
73
  self.class.table.batch_delete([hash_key])
48
74
  end
@@ -76,11 +102,17 @@ module Dynameek
76
102
  end
77
103
 
78
104
  def delete_table
79
- table.delete
105
+
106
+ index_table.delete if dynamo_db.tables[index_table_name].exists?
107
+ table.delete if dynamo_db.tables[table_name].exists?
108
+ while dynamo_db.tables[table_name].exists? &&
109
+ dynamo_db.tables[index_table_name].exists?
110
+ sleep 1
111
+ end
80
112
  end
81
113
 
82
114
  def item_to_instance(item)
83
- item_hsh = (item.is_a?(AWS::DynamoDB::Item) || item.is_a?(AWS::DynamoDB::ItemData) ? item.attributes.to_hash : item)
115
+ item_hsh = aws_item_to_hash(item)
84
116
  instance = self.new
85
117
  fields.each do |field, type|
86
118
  next if multi_column_hash_key? && field == hash_key_info.field
@@ -111,7 +143,11 @@ module Dynameek
111
143
  def table_name
112
144
  self.to_s
113
145
  end
114
-
146
+
147
+ def index_table_name
148
+ table_name + "_INDEX"
149
+ end
150
+
115
151
  end
116
152
  end
117
- end
153
+ end
@@ -1,3 +1,3 @@
1
1
  module Dynameek
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -0,0 +1,13 @@
1
+ class WithIndexTable
2
+ include Dynameek::Model
3
+
4
+ field :client_id, :integer
5
+ field :channel_id, :string
6
+ field :advert_id, :integer
7
+ field :time, :datetime
8
+
9
+ multi_column_hash_key [:client_id, :channel_id]
10
+ range :time
11
+
12
+ use_index_table
13
+ end
@@ -10,4 +10,5 @@ AWS.config(:use_ssl => false,
10
10
  :secret_access_key => "xxx")
11
11
 
12
12
  require File.join(File.dirname(__FILE__), *%w[.. models conversion])
13
- require File.join(File.dirname(__FILE__), *%w[.. models simple])
13
+ require File.join(File.dirname(__FILE__), *%w[.. models with_index_table])
14
+ require File.join(File.dirname(__FILE__), *%w[.. models simple])
@@ -0,0 +1,47 @@
1
+ require File.join(File.dirname(__FILE__), *%w[spec_helper])
2
+
3
+ describe WithIndexTable do
4
+ before(:all) do
5
+ WithIndexTable.delete_table
6
+ end
7
+ before(:each) do
8
+ WithIndexTable.query([1, "google"]).delete
9
+ end
10
+
11
+ it "should allow you to create a new row" do
12
+ con = nil
13
+ lambda{
14
+ con = WithIndexTable.create(:client_id => 1, :channel_id => "google", :advert_id=> 1, :time => DateTime.now)
15
+ }.should_not raise_error
16
+ con.hash_key.should == "1|google"
17
+ end
18
+
19
+ it "should allow you to have multiple rows for the same hash and range" do
20
+ row1, row2 = nil
21
+ time = DateTime.now
22
+ lambda{
23
+ row1 = WithIndexTable.create(:client_id => 1, :channel_id => "google",
24
+ :advert_id=> 1, :time => time)
25
+ row2 = WithIndexTable.create(:client_id => 1, :channel_id => "google",
26
+ :advert_id=> 2, :time => time)
27
+ }.should_not raise_error
28
+ row1.hash_key.should == "1|google"
29
+ row2.hash_key.should == "1|google"
30
+ end
31
+
32
+ it "should allow you to query for the some hash and range" do
33
+ row1, row2 = nil
34
+ time = DateTime.now
35
+ lambda{
36
+ row1 = WithIndexTable.create(:client_id => 1, :channel_id => "google",
37
+ :advert_id=> 1, :time => time)
38
+ row2 = WithIndexTable.create(:client_id => 1, :channel_id => "google",
39
+ :advert_id=> 2, :time => time)
40
+ }.should_not raise_error
41
+
42
+ query = WithIndexTable.query([1, "google"]).where(time, :eq)
43
+ query.size.should == 2
44
+
45
+ end
46
+
47
+ end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: dynameek
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.2.2
5
+ version: 0.3.1
6
6
  platform: ruby
7
7
  authors:
8
8
  - Max Dupenois
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2013-03-20 00:00:00 Z
13
+ date: 2013-04-02 00:00:00 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: aws-sdk
@@ -69,9 +69,11 @@ files:
69
69
  - lib/dynameek/version.rb
70
70
  - test/models/conversion.rb
71
71
  - test/models/simple.rb
72
+ - test/models/with_index_table.rb
72
73
  - test/spec/conversion_spec.rb
73
74
  - test/spec/simple_spec.rb
74
75
  - test/spec/spec_helper.rb
76
+ - test/spec/with_index_table_spec.rb
75
77
  homepage: http://github.com/maxdupenois/dynameek
76
78
  licenses: []
77
79
 
@@ -102,6 +104,8 @@ summary: Dynameek - A dynamodb model
102
104
  test_files:
103
105
  - test/models/conversion.rb
104
106
  - test/models/simple.rb
107
+ - test/models/with_index_table.rb
105
108
  - test/spec/conversion_spec.rb
106
109
  - test/spec/simple_spec.rb
107
110
  - test/spec/spec_helper.rb
111
+ - test/spec/with_index_table_spec.rb