dymos 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 036d8dac33aac8670a6e98ebc1b5edd52acb193f
4
- data.tar.gz: bbcc269f35f7a4a717a62937e07281efd24e0f03
3
+ metadata.gz: 4e960079833a6e03ae80c5bb1374d504ccb8eb83
4
+ data.tar.gz: cf6b5b4cc3b06455ef702cde7aae8fef8694f4f8
5
5
  SHA512:
6
- metadata.gz: 6e60e93b0a7b52e8758d2e62e5c3a3d2c2a810edee67fa0986257651158e2b4e61dffd3617ab65da12709fdc964d534274082cffc9744a8fdfaf7c3affaf57f3
7
- data.tar.gz: ac6f103586f8d7d60f28670032e6c4bed0f0fd365dd950f4e739548865fccdaf2c5e7d8b93589283b20a9f45ddaf4e81a4d5d98d7c7399ca790217efe4eb5c6f
6
+ metadata.gz: 9e928ff07856ca7da1a51b51bd1a27c71502fdc6f6af9d52ea0d713ba02c2d9a07e744e73d1734841eef28c79d4067ac78c5d2882e1713f0d72c97801a37c200
7
+ data.tar.gz: 65cb61ad85949b518774863d0ddf01fcb5ee94143e5808f3631d2888bde4fd585e3d27d66fa9af0eeecf6916d9103c3514334c30d9252d65ae97a84fe30478f2
data/lib/dymos.rb CHANGED
@@ -3,12 +3,14 @@ require "dymos/query/expect"
3
3
  require "dymos/query/builder"
4
4
  require "dymos/query/put_item"
5
5
  require "dymos/query/update_item"
6
+ require "dymos/query/delete_item"
6
7
  require "dymos/query/get_item"
7
8
  require "dymos/query/describe"
8
9
  require "dymos/query/scan"
9
10
  require "dymos/query/query"
10
11
  require "dymos/attribute"
11
12
  require "dymos/command"
13
+ require "dymos/persistence"
12
14
  require "dymos/model"
13
15
  require "dymos/version"
14
16
 
data/lib/dymos/command.rb CHANGED
@@ -28,5 +28,9 @@ module Dymos
28
28
  def scan
29
29
  Dymos::Query::Scan.new(:scan, table_name, class_name)
30
30
  end
31
+
32
+ def delete
33
+ Dymos::Query::DeleteItem.new(:delete_item, table_name, class_name)
34
+ end
31
35
  end
32
36
  end
data/lib/dymos/model.rb CHANGED
@@ -4,30 +4,43 @@ require 'active_model'
4
4
  module Dymos
5
5
  class Model
6
6
  include ActiveModel::Model
7
+ include ActiveModel::Dirty
8
+ include Dymos::Persistence
7
9
  extend Dymos::Command
8
10
  attr_accessor :metadata
9
11
 
10
12
  def initialize(params={})
11
- @attributes = {}
13
+ @attributes={}
14
+ send :attributes=, params, true
12
15
  super
13
16
  end
14
17
 
15
- def self.field(attr, type = :string)
16
- define_method(attr) { read_attribute(attr) }
18
+ def self.field(attr, type, default: nil)
19
+ @fields ||= {}
20
+ @fields[attr]={
21
+ type: type,
22
+ default: default
23
+ }
24
+ define_attribute_methods attr
25
+ define_method(attr) { read_attribute(attr) || default }
17
26
  define_method("#{attr}_type") { type }
18
27
  define_method("#{attr}?") { !read_attribute(attr).nil? }
19
28
  define_method("#{attr}=") { |value| write_attribute(attr, value) }
20
29
  end
21
30
 
31
+ def self.fields
32
+ @fields
33
+ end
34
+
22
35
  def self.table(name)
23
36
  define_singleton_method('table_name') { name }
24
37
  define_method('table_name') { name }
25
38
  end
26
39
 
27
- def attributes=(attributes = {})
40
+ def attributes=(attributes = {}, initialize = false)
28
41
  if attributes
29
42
  attributes.each do |attr, value|
30
- write_attribute(attr, value)
43
+ write_attribute(attr, value, initialize)
31
44
  end
32
45
  end
33
46
  end
@@ -45,34 +58,30 @@ module Dymos
45
58
  end
46
59
 
47
60
  def self.find(key1, key2=nil)
48
- indexes = new.global_indexes
61
+ indexes = key_scheme
49
62
  keys={}
50
63
  keys[indexes.first[:attribute_name].to_sym] = key1
51
64
  keys[indexes.last[:attribute_name].to_sym] = key2 if indexes.size > 1
52
65
  self.get.key(keys).execute
53
66
  end
54
67
 
55
- def save
56
- items = {}
57
- attributes.each do |k, v|
58
- if v != nil
59
- items[k] =v
60
- end
61
- end
62
- result = dynamo.put_item(
63
- table_name: table_name,
64
- item: items,
65
- return_values: "ALL_OLD"
66
- )
67
- !result.error
68
+ def self.key_scheme
69
+ @key_scheme ||= new.describe_table[:table][:key_schema]
70
+ end
71
+
72
+ def reload!
73
+ reset_changes
68
74
  end
69
75
 
70
76
  def describe_table
71
- @scheme ||= dynamo.describe_table(table_name: table_name)
77
+ self.class.send(:describe).execute
72
78
  end
73
79
 
74
- def global_indexes
75
- describe_table.data[:table][:key_schema]
80
+ def indexes
81
+ scheme = self.class.key_scheme.map do |scheme|
82
+ [scheme[:attribute_name], send(scheme[:attribute_name])]
83
+ end
84
+ scheme.to_h
76
85
  end
77
86
 
78
87
  def dynamo
@@ -94,7 +103,8 @@ module Dymos
94
103
  @attributes[name.to_sym]
95
104
  end
96
105
 
97
- def write_attribute(name, value)
106
+ def write_attribute(name, value, initialize=false)
107
+ self.send "#{name}_will_change!" unless (initialize or value == @attributes[name.to_sym])
98
108
  @attributes[name.to_sym] = value
99
109
  end
100
110
 
@@ -0,0 +1,61 @@
1
+ module Dymos
2
+ module Persistence
3
+ attr_accessor :new_record
4
+
5
+ def initialize(params={})
6
+ @new_record = true
7
+ @destroyed = false
8
+ end
9
+
10
+ def new_record?
11
+ @new_record
12
+ end
13
+
14
+ def destroyed?
15
+ @destroyed
16
+ end
17
+
18
+ def persisted?
19
+ !(new_record? || destroyed?)
20
+ end
21
+
22
+ def save(*)
23
+ create_or_update
24
+ rescue
25
+ false
26
+ end
27
+
28
+ def save!(*)
29
+ create_or_update || raise(Dymos::RecordNotSaved)
30
+ end
31
+
32
+ def delete
33
+ self.class.delete.key(indexes).execute if persisted?
34
+ @destroyed = true
35
+ freeze
36
+ end
37
+
38
+ private
39
+
40
+ def create_or_update
41
+ result = new_record? ? _create_record : _update_record
42
+ result != false
43
+ end
44
+
45
+ def _update_record()
46
+ _create_record
47
+ end
48
+
49
+ def _create_record()
50
+ result = dynamo.put_item(
51
+ table_name: table_name,
52
+ item: attributes,
53
+ return_values: "ALL_OLD"
54
+ )
55
+ changes_applied
56
+ @new_record = false
57
+ !result.error
58
+ end
59
+
60
+ end
61
+ end
@@ -43,22 +43,22 @@ module Dymos
43
43
  if res.data.respond_to? :items # scan, query
44
44
  metadata = extract(res, :items)
45
45
  res.data[:items].map do |datum|
46
- obj = Object.const_get(@class_name).new
47
- obj.attributes = datum
46
+ obj = Object.const_get(@class_name).new(datum)
48
47
  obj.metadata = metadata
48
+ obj.new_record = false
49
49
  obj
50
50
  end
51
51
  elsif res.data.respond_to? :attributes # put_item, update_item
52
52
  return nil if res.attributes.nil?
53
- obj = Object.const_get(@class_name).new
54
- obj.attributes = res.attributes
53
+ obj = Object.const_get(@class_name).new(res.attributes)
55
54
  obj.metadata = extract(res, :attributes)
56
55
  obj
57
56
  elsif res.respond_to? :data
58
- if res.data.respond_to? :item # get_item
59
- obj = Object.const_get(@class_name).new
60
- obj.attributes = res.data.item
57
+ if res.data.respond_to? :item # get_item, delete_item
58
+ return nil if res.data.item.nil?
59
+ obj = Object.const_get(@class_name).new(res.data.item)
61
60
  obj.metadata = extract(res, :item)
61
+ obj.new_record = false
62
62
  obj
63
63
  else
64
64
  res.data.to_hash # describe
@@ -0,0 +1,47 @@
1
+ module Dymos
2
+ module Query
3
+ class DeleteItem < ::Dymos::Query::Builder
4
+
5
+ # @param [String] name
6
+ # @return [self]
7
+ def key(name)
8
+ @key = name
9
+ self
10
+ end
11
+
12
+ def expected(params)
13
+ @expected = Hash[params.map do |name, expression|
14
+ operator, values = expression.split(' ', 2)
15
+ if values.nil?
16
+ [name, ::Dymos::Query::Expect.new.condition(operator, nil).data]
17
+ else
18
+ value1, value2 = values.split(' ')
19
+ if value2.present?
20
+ [name, ::Dymos::Query::Expect.new.condition(operator, values).data]
21
+ else
22
+ [name, ::Dymos::Query::Expect.new.condition(operator, value1).data]
23
+ end
24
+ end
25
+
26
+ end]
27
+ self
28
+ end
29
+
30
+ def query
31
+ data = {
32
+ table_name: @table_name.to_s,
33
+ key: @key,
34
+ return_values: @return_values || 'ALL_OLD'
35
+ }
36
+
37
+ if @expected.present?
38
+ data[:expected] = @expected
39
+ if @expected.size > 1
40
+ data[:conditional_operator] = @conditional_operator || 'AND'
41
+ end
42
+ end
43
+ data
44
+ end
45
+ end
46
+ end
47
+ end
@@ -7,11 +7,17 @@ module Dymos
7
7
  self
8
8
  end
9
9
 
10
+ def exclusive_start_key(params)
11
+ @exclusive_start_key = params
12
+ self
13
+ end
14
+
10
15
  def query
11
16
  data = {
12
17
  table_name: @table_name.to_s,
13
18
  }
14
19
  data[:limit] = @limit if @limit.present?
20
+ data[:exclusive_start_key] = @exclusive_start_key if @exclusive_start_key.present?
15
21
  data
16
22
  end
17
23
  end
data/lib/dymos/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Dymos
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.6"
3
3
  end
@@ -46,6 +46,7 @@ describe Dymos::Model do
46
46
  client.put_item(table_name: 'dummy', item: {id: 'hoge', name: '太郎', list: Set['a', 'b', 'c']})
47
47
  client.put_item(table_name: 'dummy', item: {id: 'fuga', name: '次郎'})
48
48
  client.put_item(table_name: 'dummy', item: {id: 'piyo', name: '三郎'})
49
+ client.put_item(table_name: 'dummy', item: {id: 'musashi', name: '巴'}) #削除用
49
50
 
50
51
  client.delete_table(table_name: 'post') if client.list_tables[:table_names].include?('post')
51
52
  client.create_table(
@@ -66,6 +67,36 @@ describe Dymos::Model do
66
67
 
67
68
  # let(:model) { Dummy.new }
68
69
 
70
+ describe :fields do
71
+ it do
72
+ expect(DummyUser.fields.keys).to eq([:id, :name, :email, :list])
73
+ end
74
+ end
75
+ describe "クラスマクロのデフォルト値" do
76
+ it "設定されていなければnilを返す" do
77
+ expect(DummyPost.new.id).to eq(nil)
78
+ end
79
+
80
+ class DummyModel < Dymos::Model
81
+ field :id, :number, default: 0
82
+ field :body, :string, default: "none"
83
+ end
84
+
85
+ it "idは0を返す" do
86
+ expect(DummyModel.new.id).to eq(0)
87
+ end
88
+
89
+ it "bodyは'none'を返す" do
90
+ expect(DummyModel.new.body).to eq('none')
91
+ end
92
+
93
+ it "上書きすることができる" do
94
+ model = DummyModel.new
95
+ model.id=1
96
+ expect(model.id).to eq(1)
97
+ end
98
+ end
99
+
69
100
  describe :new do
70
101
 
71
102
  describe :field do
@@ -138,23 +169,21 @@ describe Dymos::Model do
138
169
  end
139
170
  end
140
171
 
141
- describe :global_indexes do
172
+ describe :key_scheme do
142
173
  it "キー情報" do
143
- model = DummyUser.new
144
- expect(model.global_indexes.first[:attribute_name]).to eq('id')
145
- expect(model.global_indexes.first[:key_type]).to eq('HASH')
174
+ expect(DummyUser.key_scheme.first[:attribute_name]).to eq('id')
175
+ expect(DummyUser.key_scheme.first[:key_type]).to eq('HASH')
146
176
 
147
- model = DummyPost.new
148
- expect(model.global_indexes.first[:attribute_name]).to eq('id')
149
- expect(model.global_indexes.first[:key_type]).to eq('HASH')
150
- expect(model.global_indexes.last[:attribute_name]).to eq('timestamp')
151
- expect(model.global_indexes.last[:key_type]).to eq('RANGE')
177
+ expect(DummyPost.key_scheme.first[:attribute_name]).to eq('id')
178
+ expect(DummyPost.key_scheme.first[:key_type]).to eq('HASH')
179
+ expect(DummyPost.key_scheme.last[:attribute_name]).to eq('timestamp')
180
+ expect(DummyPost.key_scheme.last[:key_type]).to eq('RANGE')
152
181
  end
153
182
  end
154
183
 
155
184
  describe :find do
156
185
  it "ユーザを抽出" do
157
- user = DummyUser.get.key(id:'hoge').execute
186
+ user = DummyUser.get.key(id: 'hoge').execute
158
187
  expect(user.id).to eq('hoge')
159
188
  expect(user.name).to eq('太郎')
160
189
  end
@@ -173,7 +202,7 @@ describe Dymos::Model do
173
202
  describe :all do
174
203
  it "すべてのユーザを抽出" do
175
204
  users = DummyUser.all
176
- expect(users.size).to eq(4)
205
+ expect(users.size).to eq(5)
177
206
  end
178
207
  end
179
208
 
@@ -185,5 +214,58 @@ describe Dymos::Model do
185
214
  end
186
215
  end
187
216
  end
217
+
218
+ describe "変更検知" do
219
+ class DummyModel < Dymos::Model
220
+ field :id, :number, default: 0
221
+ field :body, :string, default: "none"
222
+ end
223
+
224
+ it "新規モデル" do
225
+ user = DummyModel.new
226
+ expect(user.changes).to eq({})
227
+ expect(user.changed?).to eq(false)
228
+ user.id = 1
229
+ expect(user.changed?).to eq(true)
230
+ expect(user.changes).to eq({"id" => [0, 1]})
231
+ user.body = "hoge"
232
+ expect(user.changes).to eq({"id" => [0, 1], "body" => ['none', 'hoge']})
233
+ end
234
+
235
+ describe "DBから引いたモデル" do
236
+ it "" do
237
+ user = DummyUser.get.key(id: 'hoge').execute
238
+ expect(user.changes).to eq({})
239
+ expect(user.changed?).to eq(false)
240
+ user.id = 1
241
+ expect(user.changed?).to eq(true)
242
+ expect(user.id_changed?).to eq(true)
243
+ expect(user.changes).to eq({"id" => ["hoge", 1]})
244
+ end
245
+ end
246
+ end
247
+
248
+ describe :persistence do
249
+ describe "新規モデル" do
250
+ it do
251
+ user = DummyUser.new
252
+ expect(user.new_record?).to eq(true)
253
+ expect(user.destroyed?).to eq(false)
254
+ expect(user.persisted?).to eq(false)
255
+ end
256
+
257
+ describe "DBから引いたモデル" do
258
+ it do
259
+ user = DummyUser.get.key(id: 'musashi').execute
260
+ expect(user.new_record?).to eq(false)
261
+ expect(user.destroyed?).to eq(false)
262
+ expect(user.persisted?).to eq(true)
263
+ user.delete
264
+ expect(user.destroyed?).to eq(true)
265
+ expect(DummyUser.get.key(id: 'musashi').execute).to eq(nil)
266
+ end
267
+ end
268
+ end
269
+ end
188
270
  end
189
271
 
@@ -0,0 +1,76 @@
1
+ describe Dymos::Query::DeleteItem do
2
+ before :all do
3
+ Aws.config[:region] = 'us-west-1'
4
+ Aws.config[:endpoint] = 'http://localhost:4567'
5
+ Aws.config[:access_key_id] = 'XXX'
6
+ Aws.config[:secret_access_key] = 'XXX'
7
+
8
+ client = Aws::DynamoDB::Client.new
9
+ client.delete_table(table_name: 'test_delete_item') if client.list_tables[:table_names].include?('test_delete_item')
10
+ client.create_table(
11
+ table_name: 'test_delete_item',
12
+ attribute_definitions: [
13
+ {attribute_name: 'id', attribute_type: 'S'},
14
+ ],
15
+ key_schema: [
16
+ {attribute_name: 'id', key_type: 'HASH'}
17
+ ],
18
+ provisioned_throughput: {
19
+ read_capacity_units: 1,
20
+ write_capacity_units: 1,
21
+ })
22
+ client.put_item(table_name: 'test_delete_item', item: {id: 'hoge', name: '太郎'})
23
+ client.put_item(table_name: 'test_delete_item', item: {id: 'fuga', count: 0})
24
+ client.put_item(table_name: 'test_delete_item', item: {id: 'poyo', name: '可奈'})
25
+ client.put_item(table_name: 'test_delete_item', item: {id: 'piyo', name: '杏奈', count: 10})
26
+
27
+ class TestItem < Dymos::Model
28
+ table :test_delete_item
29
+ field :id, :string
30
+ field :name, :string
31
+ field :count, :string
32
+ end
33
+ end
34
+
35
+ let(:client) { Aws::DynamoDB::Client.new }
36
+ describe :put_item do
37
+ describe "クエリ生成" do
38
+ let(:query) { TestItem.delete.key(id: "hoge") }
39
+ it :query do
40
+ expect(query.query).to eq({
41
+ table_name: "test_delete_item",
42
+ key: {id: "hoge"},
43
+ return_values: "ALL_OLD",
44
+ })
45
+ end
46
+ it "成功すると削除したアイテムを返す" do
47
+ res = query.execute client
48
+ expect(res.id).to eq("hoge")
49
+ end
50
+
51
+ describe "既存のアイテムを削除" do
52
+ describe "return_values :ALL_OLD" do
53
+ it "条件なし" do
54
+ query = TestItem.delete.key(id: "poyo")
55
+ res = query.execute client
56
+ expect(res.id).to eq("poyo")
57
+ expect(TestItem.get.key(id: "poyo").execute).to eq(nil)
58
+ end
59
+
60
+ it "条件付き" do
61
+ query = TestItem.delete.key(id: "fuga").expected(count: "== 0")
62
+ res = query.execute
63
+ expect(res.id).to eq("fuga")
64
+ expect(TestItem.get.key(id: "fuga").execute).to eq(nil)
65
+ end
66
+ it "条件付き失敗" do
67
+ query = TestItem.delete.key(id: "piyo").expected(count: "== 1")
68
+ res = query.execute
69
+ expect(res).to eq(false)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+
@@ -27,6 +27,7 @@ describe Dymos::Query::GetItem do
27
27
  class TestItem < Dymos::Model
28
28
  table :test_get_item
29
29
  field :id, :string
30
+ field :category_id, :number
30
31
  end
31
32
  end
32
33
 
@@ -25,6 +25,7 @@ describe Dymos::Query::PutItem do
25
25
  class TestItem < Dymos::Model
26
26
  table :test_put_item
27
27
  field :id, :string
28
+ field :name, :string
28
29
  end
29
30
  end
30
31
 
@@ -48,6 +48,7 @@ describe Dymos::Query::Query do
48
48
  class TestItem < Dymos::Model
49
49
  table :test_query_item
50
50
  field :id, :string
51
+ field :other_id, :number
51
52
  end
52
53
  end
53
54
 
@@ -46,6 +46,10 @@ describe Dymos::Query::Scan do
46
46
  expect(res.first.metadata).to eq(count: 5, scanned_count: 5, last_evaluated_key: nil, consumed_capacity: nil)
47
47
  end
48
48
 
49
+
50
+ it :query do
51
+ expect(TestItem.scan.limit(100).exclusive_start_key('hoge').query).to eq(table_name: "test_get_item", exclusive_start_key:"hoge", limit: 100)
52
+ end
49
53
  end
50
54
  end
51
55
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dymos
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - hoshina85
@@ -127,8 +127,10 @@ files:
127
127
  - lib/dymos/attribute.rb
128
128
  - lib/dymos/command.rb
129
129
  - lib/dymos/model.rb
130
+ - lib/dymos/persistence.rb
130
131
  - lib/dymos/query/attribute.rb
131
132
  - lib/dymos/query/builder.rb
133
+ - lib/dymos/query/delete_item.rb
132
134
  - lib/dymos/query/describe.rb
133
135
  - lib/dymos/query/expect.rb
134
136
  - lib/dymos/query/get_item.rb
@@ -140,6 +142,7 @@ files:
140
142
  - spec/lib/dymos/model_spec.rb
141
143
  - spec/lib/dymos/query/attribute_spec.rb
142
144
  - spec/lib/dymos/query/builder_spec.rb
145
+ - spec/lib/dymos/query/delete_item_spec.rb
143
146
  - spec/lib/dymos/query/describe_spec.rb
144
147
  - spec/lib/dymos/query/expect_spec.rb
145
148
  - spec/lib/dymos/query/get_item_spec.rb
@@ -176,6 +179,7 @@ test_files:
176
179
  - spec/lib/dymos/model_spec.rb
177
180
  - spec/lib/dymos/query/attribute_spec.rb
178
181
  - spec/lib/dymos/query/builder_spec.rb
182
+ - spec/lib/dymos/query/delete_item_spec.rb
179
183
  - spec/lib/dymos/query/describe_spec.rb
180
184
  - spec/lib/dymos/query/expect_spec.rb
181
185
  - spec/lib/dymos/query/get_item_spec.rb