dymos 0.0.5 → 0.0.6

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 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