tarantool 0.3.0.7 → 0.4.2.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.
- data/Gemfile +2 -3
- data/README.md +90 -30
- data/Rakefile +6 -1
- data/lib/tarantool.rb +93 -18
- data/lib/tarantool/base_record.rb +97 -10
- data/lib/tarantool/block_db.rb +104 -6
- data/lib/tarantool/callback_db.rb +7 -0
- data/lib/tarantool/core-ext.rb +24 -8
- data/lib/tarantool/em_db.rb +189 -20
- data/lib/tarantool/exceptions.rb +4 -0
- data/lib/tarantool/fiber_db.rb +15 -1
- data/lib/tarantool/light_record.rb +17 -0
- data/lib/tarantool/query.rb +15 -9
- data/lib/tarantool/record/select.rb +21 -3
- data/lib/tarantool/request.rb +130 -43
- data/lib/tarantool/response.rb +70 -7
- data/lib/tarantool/serializers.rb +26 -5
- data/lib/tarantool/serializers/ber_array.rb +14 -0
- data/lib/tarantool/shards_support.rb +204 -0
- data/lib/tarantool/space_array.rb +38 -13
- data/lib/tarantool/space_hash.rb +49 -27
- data/lib/tarantool/util.rb +96 -10
- data/lib/tarantool/version.rb +2 -1
- data/test/helper.rb +154 -4
- data/test/{tarant/init.lua → init.lua} +0 -0
- data/test/run_all.rb +2 -2
- data/test/shared_record.rb +59 -0
- data/test/shared_replicated_shard.rb +1018 -0
- data/test/shared_reshard.rb +380 -0
- data/test/tarantool.cfg +2 -0
- data/test/test_light_record.rb +2 -0
- data/test/test_light_record_callback.rb +92 -0
- data/test/test_query_block.rb +1 -0
- data/test/test_query_fiber.rb +1 -0
- data/test/test_reshard_block.rb +7 -0
- data/test/test_reshard_fiber.rb +11 -0
- data/test/test_shard_replication_block.rb +7 -0
- data/test/test_shard_replication_fiber.rb +11 -0
- data/test/test_space_array_block.rb +1 -0
- data/test/test_space_array_callback.rb +50 -121
- data/test/test_space_array_callback_nodef.rb +39 -96
- data/test/test_space_array_fiber.rb +1 -0
- data/test/test_space_hash_block.rb +1 -0
- data/test/test_space_hash_fiber.rb +1 -0
- metadata +54 -17
- data/lib/tarantool/record.rb +0 -164
- data/test/box.pid +0 -1
- data/test/tarantool.log +0 -6
- data/test/tarantool_repl.cfg +0 -53
- data/test/test_record.rb +0 -88
- data/test/test_record_composite_pk.rb +0 -77
@@ -1,6 +1,7 @@
|
|
1
1
|
require File.expand_path('../shared_space_array.rb', __FILE__)
|
2
2
|
|
3
3
|
describe 'Tarantool::FiberDB::SpaceArray' do
|
4
|
+
before { TConf.run(:master1) }
|
4
5
|
let(:tarantool) { Tarantool.new(TCONFIG.merge(type: :em)) }
|
5
6
|
alias blockrun fibrun
|
6
7
|
it_behaves_like :blocking_array_space
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tarantool
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-08-04 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: iproto
|
@@ -19,7 +19,7 @@ dependencies:
|
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 0.3.
|
22
|
+
version: 0.3.6
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -27,7 +27,39 @@ dependencies:
|
|
27
27
|
requirements:
|
28
28
|
- - ! '>='
|
29
29
|
- !ruby/object:Gem::Version
|
30
|
-
version: 0.3.
|
30
|
+
version: 0.3.6
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: murmurhash3
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: 0.1.1
|
39
|
+
type: :runtime
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.1.1
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: sumbur
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 0.0.2
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: 0.0.2
|
31
63
|
description: Tarantool KV-storage client.
|
32
64
|
email:
|
33
65
|
- ceo@prepor.ru
|
@@ -44,10 +76,11 @@ files:
|
|
44
76
|
- lib/tarantool/response.rb
|
45
77
|
- lib/tarantool/base_record.rb
|
46
78
|
- lib/tarantool/util.rb
|
47
|
-
- lib/tarantool/record.rb
|
48
79
|
- lib/tarantool/light_record.rb
|
49
80
|
- lib/tarantool/version.rb
|
81
|
+
- lib/tarantool/shards_support.rb
|
50
82
|
- lib/tarantool/core-ext.rb
|
83
|
+
- lib/tarantool/serializers/ber_array.rb
|
51
84
|
- lib/tarantool/serializers/bson.rb
|
52
85
|
- lib/tarantool/callback_db.rb
|
53
86
|
- lib/tarantool/exceptions.rb
|
@@ -60,26 +93,28 @@ files:
|
|
60
93
|
- lib/tarantool.rb
|
61
94
|
- test/test_light_record.rb
|
62
95
|
- test/shared_record.rb
|
63
|
-
- test/
|
96
|
+
- test/shared_replicated_shard.rb
|
64
97
|
- test/shared_space_array.rb
|
98
|
+
- test/test_shard_replication_fiber.rb
|
65
99
|
- test/test_space_hash_block.rb
|
66
100
|
- test/test_space_hash_fiber.rb
|
67
|
-
- test/
|
68
|
-
- test/test_record_composite_pk.rb
|
101
|
+
- test/test_reshard_block.rb
|
69
102
|
- test/tarantool.cfg
|
70
103
|
- test/helper.rb
|
71
104
|
- test/test_query_fiber.rb
|
72
105
|
- test/test_space_array_callback_nodef.rb
|
106
|
+
- test/init.lua
|
73
107
|
- test/run_all.rb
|
74
108
|
- test/test_space_array_callback.rb
|
75
109
|
- test/test_space_array_fiber.rb
|
110
|
+
- test/test_reshard_fiber.rb
|
76
111
|
- test/test_query_block.rb
|
77
|
-
- test/
|
78
|
-
- test/box.pid
|
112
|
+
- test/shared_reshard.rb
|
79
113
|
- test/shared_query.rb
|
114
|
+
- test/test_shard_replication_block.rb
|
80
115
|
- test/test_space_array_block.rb
|
81
116
|
- test/shared_space_hash.rb
|
82
|
-
- test/
|
117
|
+
- test/test_light_record_callback.rb
|
83
118
|
- Gemfile
|
84
119
|
- LICENSE
|
85
120
|
- Rakefile
|
@@ -112,23 +147,25 @@ summary: Tarantool KV-storage client.
|
|
112
147
|
test_files:
|
113
148
|
- test/test_light_record.rb
|
114
149
|
- test/shared_record.rb
|
115
|
-
- test/
|
150
|
+
- test/shared_replicated_shard.rb
|
116
151
|
- test/shared_space_array.rb
|
152
|
+
- test/test_shard_replication_fiber.rb
|
117
153
|
- test/test_space_hash_block.rb
|
118
154
|
- test/test_space_hash_fiber.rb
|
119
|
-
- test/
|
120
|
-
- test/test_record_composite_pk.rb
|
155
|
+
- test/test_reshard_block.rb
|
121
156
|
- test/tarantool.cfg
|
122
157
|
- test/helper.rb
|
123
158
|
- test/test_query_fiber.rb
|
124
159
|
- test/test_space_array_callback_nodef.rb
|
160
|
+
- test/init.lua
|
125
161
|
- test/run_all.rb
|
126
162
|
- test/test_space_array_callback.rb
|
127
163
|
- test/test_space_array_fiber.rb
|
164
|
+
- test/test_reshard_fiber.rb
|
128
165
|
- test/test_query_block.rb
|
129
|
-
- test/
|
130
|
-
- test/box.pid
|
166
|
+
- test/shared_reshard.rb
|
131
167
|
- test/shared_query.rb
|
168
|
+
- test/test_shard_replication_block.rb
|
132
169
|
- test/test_space_array_block.rb
|
133
170
|
- test/shared_space_hash.rb
|
134
|
-
- test/
|
171
|
+
- test/test_light_record_callback.rb
|
data/lib/tarantool/record.rb
DELETED
@@ -1,164 +0,0 @@
|
|
1
|
-
require 'active_model'
|
2
|
-
require 'tarantool/base_record'
|
3
|
-
|
4
|
-
module Tarantool
|
5
|
-
class Record < BaseRecord
|
6
|
-
extend ActiveModel::Naming
|
7
|
-
include ActiveModel::AttributeMethods
|
8
|
-
include ActiveModel::Validations
|
9
|
-
include ActiveModel::Serialization
|
10
|
-
extend ActiveModel::Callbacks
|
11
|
-
include ActiveModel::Dirty
|
12
|
-
|
13
|
-
include ActiveModel::Serializers::JSON
|
14
|
-
include ActiveModel::Serializers::Xml
|
15
|
-
|
16
|
-
define_model_callbacks :save, :create, :update, :destroy
|
17
|
-
define_model_callbacks :initialize, :only => :after
|
18
|
-
|
19
|
-
class << self
|
20
|
-
def set_space_no(val)
|
21
|
-
self.space_no = val
|
22
|
-
end
|
23
|
-
|
24
|
-
def set_tarantool(val)
|
25
|
-
case val.class.name
|
26
|
-
when 'Tarantool::BlockDB', 'Tarantool::FiberDB'
|
27
|
-
self.tarantool = val
|
28
|
-
else
|
29
|
-
raise "Tarantool should be blocking of fibered!!! (i.e. of class Tarantool::BlockDB or Tarantool::FiberDB) (got #{val.class})"
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
def define_field_accessor(name, type)
|
34
|
-
generated_attribute_methods.class_eval <<-"EOF", __FILE__, __LINE__ - 1
|
35
|
-
def #{name}
|
36
|
-
@attributes[:"#{name}"]
|
37
|
-
end
|
38
|
-
EOF
|
39
|
-
|
40
|
-
if Symbol === type
|
41
|
-
convert_code = case type
|
42
|
-
when :int, :integer
|
43
|
-
"v = v.to_i if String === v"
|
44
|
-
when :str, :string, :bytes
|
45
|
-
""
|
46
|
-
else
|
47
|
-
if serializer = Serializers::MAP[type]
|
48
|
-
"v = Serializers::MAP[#{type.inspect}].decode(v) if String === v"
|
49
|
-
else
|
50
|
-
raise ArgumentError, "unknown field type #{type.inspect}"
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
generated_attribute_methods.class_eval <<-"EOF", __FILE__, __LINE__ - 1
|
55
|
-
def #{name}=(v)
|
56
|
-
#{convert_code}
|
57
|
-
#{name}_will_change! unless v == @attributes[:"#{name}"] || new_record?
|
58
|
-
@attributes[:"#{name}"] = v
|
59
|
-
end
|
60
|
-
EOF
|
61
|
-
else
|
62
|
-
generated_attribute_methods.class_eval do
|
63
|
-
define_method("#{name}=") do |v|
|
64
|
-
v = type.decode(v) if String === v
|
65
|
-
send(:"#{name}_will_change!") unless v == @attributes[name]
|
66
|
-
@attributes[name] = v
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|
70
|
-
define_attribute_method name
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
def initialize(attributes = {})
|
75
|
-
@__new_record = true
|
76
|
-
@attributes = self.class.default_values.dup
|
77
|
-
run_callbacks(:initialize) do
|
78
|
-
init attributes
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
def init(attributes)
|
83
|
-
set_attributes(attributes)
|
84
|
-
end
|
85
|
-
|
86
|
-
def __fetched(attributes)
|
87
|
-
@__new_record = false
|
88
|
-
# well, initalize callback could call #attributes
|
89
|
-
@attributes = self.class.default_values.dup
|
90
|
-
run_callbacks(:initialize) do
|
91
|
-
@attributes = attributes
|
92
|
-
end
|
93
|
-
self
|
94
|
-
end
|
95
|
-
|
96
|
-
def _in_callbacks(&blk)
|
97
|
-
run_callbacks(:save) {
|
98
|
-
run_callbacks(new_record? ? :create : :update, &blk)
|
99
|
-
}
|
100
|
-
end
|
101
|
-
|
102
|
-
def save(and_reload = true)
|
103
|
-
_in_callbacks do
|
104
|
-
if valid?
|
105
|
-
changes = changes()
|
106
|
-
if new_record?
|
107
|
-
if and_reload
|
108
|
-
@attributes = space.insert(@attributes, return_tuple: true)
|
109
|
-
else
|
110
|
-
space.insert(@attributes)
|
111
|
-
end
|
112
|
-
else
|
113
|
-
return true if changes.size == 0
|
114
|
-
ops = []
|
115
|
-
changes.each do |k, (old, new)|
|
116
|
-
ops << [k.to_sym, :set, new]
|
117
|
-
end
|
118
|
-
if and_reload
|
119
|
-
unless new_attrs = space.update(id, ops, return_tuple: true)
|
120
|
-
_raise_doesnt_exists
|
121
|
-
end
|
122
|
-
else
|
123
|
-
if space.update(id, ops) == 0
|
124
|
-
_raise_doesnt_exists
|
125
|
-
end
|
126
|
-
end
|
127
|
-
end
|
128
|
-
@previously_changed = changes
|
129
|
-
@changed_attributes.clear
|
130
|
-
old_record!
|
131
|
-
true
|
132
|
-
else
|
133
|
-
false
|
134
|
-
end
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
# update record in db first, reload updated fileds then
|
139
|
-
# (Contrasting with LightRecord, where it reloads all fields)
|
140
|
-
# Consider that update operation does not count changes made by
|
141
|
-
# attr setters in your code, only field values in DB.
|
142
|
-
#
|
143
|
-
# record.update({:state => 'sleep', :sleep_count => [:+, 1]})
|
144
|
-
# record.update([[:state, 'sleep'], [:sleep_count, :+, 1]])
|
145
|
-
def update(ops)
|
146
|
-
raise UpdateNewRecord, "Could not call update on new record" if @__new_record
|
147
|
-
unless new_attrs = space.update(id, ops, return_tuple: true)
|
148
|
-
_raise_doesnt_exists
|
149
|
-
end
|
150
|
-
for op in ops
|
151
|
-
field = op.flatten.first
|
152
|
-
@attributes[field] = new_attrs[field]
|
153
|
-
end
|
154
|
-
self
|
155
|
-
end
|
156
|
-
|
157
|
-
def destroy
|
158
|
-
run_callbacks :destroy do
|
159
|
-
self.class.delete id
|
160
|
-
true
|
161
|
-
end
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
data/test/box.pid
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
2887
|
data/test/tarantool.log
DELETED
@@ -1,6 +0,0 @@
|
|
1
|
-
1341840795.677 2887 1/sched _ I> space 0 successfully configured
|
2
|
-
1341840795.677 2887 1/sched _ I> space 1 successfully configured
|
3
|
-
1341840795.677 2887 1/sched _ I> space 2 successfully configured
|
4
|
-
1341840795.677 2887 1/sched _ I> recovery start
|
5
|
-
1341840795.677 2887 1/sched _ recovery.m:274 E> can't find snapshot
|
6
|
-
1341840795.677 2887 1/sched _ C> didn't you forget to initialize storage with --init-storage switch?
|
data/test/tarantool_repl.cfg
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
slab_alloc_arena = 0.1
|
2
|
-
pid_file = "box.pid"
|
3
|
-
|
4
|
-
logger="cat - >> tarantool.log"
|
5
|
-
work_dir="tarant_repl"
|
6
|
-
|
7
|
-
primary_port = 34013
|
8
|
-
secondary_port = 34014
|
9
|
-
admin_port = 34015
|
10
|
-
replication_source = "127.0.0.1:33012"
|
11
|
-
|
12
|
-
rows_per_wal = 50000
|
13
|
-
|
14
|
-
space[0].enabled = 1
|
15
|
-
|
16
|
-
space[0].index[0].type = "TREE"
|
17
|
-
space[0].index[0].unique = 1
|
18
|
-
space[0].index[0].key_field[0].fieldno = 0
|
19
|
-
space[0].index[0].key_field[0].type = "STR"
|
20
|
-
|
21
|
-
space[0].index[1].type = "TREE"
|
22
|
-
space[0].index[1].unique = 0
|
23
|
-
space[0].index[1].key_field[0].fieldno = 1
|
24
|
-
space[0].index[1].key_field[0].type = "STR"
|
25
|
-
space[0].index[1].key_field[1].fieldno = 2
|
26
|
-
space[0].index[1].key_field[1].type = "STR"
|
27
|
-
|
28
|
-
space[0].index[2].type = "TREE"
|
29
|
-
space[0].index[2].unique = 0
|
30
|
-
space[0].index[2].key_field[0].fieldno = 3
|
31
|
-
space[0].index[2].key_field[0].type = "NUM"
|
32
|
-
|
33
|
-
space[1].enabled = 1
|
34
|
-
|
35
|
-
space[1].index[0].type = "HASH"
|
36
|
-
space[1].index[0].unique = 1
|
37
|
-
space[1].index[0].key_field[0].fieldno = 0
|
38
|
-
space[1].index[0].key_field[0].type = "NUM"
|
39
|
-
|
40
|
-
|
41
|
-
space[2].enabled = 1
|
42
|
-
|
43
|
-
space[2].index[0].type = "TREE"
|
44
|
-
space[2].index[0].unique = 1
|
45
|
-
space[2].index[0].key_field[0].fieldno = 0
|
46
|
-
space[2].index[0].key_field[0].type = "STR"
|
47
|
-
space[2].index[0].key_field[1].fieldno = 1
|
48
|
-
space[2].index[0].key_field[1].type = "STR"
|
49
|
-
|
50
|
-
space[2].index[1].type = "TREE"
|
51
|
-
space[2].index[1].unique = 0
|
52
|
-
space[2].index[1].key_field[0].fieldno = 2
|
53
|
-
space[2].index[1].key_field[0].type = "NUM"
|
data/test/test_record.rb
DELETED
@@ -1,88 +0,0 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
require File.expand_path('../shared_record', __FILE__)
|
3
|
-
require 'tarantool/record'
|
4
|
-
|
5
|
-
describe 'Tarantool::Record' do
|
6
|
-
let(:base_class){ Tarantool::Record }
|
7
|
-
it_behaves_like :record
|
8
|
-
|
9
|
-
|
10
|
-
describe "update" do
|
11
|
-
let(:user) { user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru' }
|
12
|
-
it "should not reload fields not reffered in operations" do
|
13
|
-
user.name = "Petr"
|
14
|
-
user.update(email: "prepor@ceo.ru")
|
15
|
-
user.name.must_equal "Petr"
|
16
|
-
user.email.must_equal "prepor@ceo.ru"
|
17
|
-
|
18
|
-
fetched = user_class.by_pk('prepor')
|
19
|
-
fetched.name.must_equal "Andrew"
|
20
|
-
fetched.email.must_equal "prepor@ceo.ru"
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
describe "validations" do
|
25
|
-
describe "with validator on login size" do
|
26
|
-
before do
|
27
|
-
user_class.validates_length_of(:login, minimum: 3)
|
28
|
-
end
|
29
|
-
it "should invalidate all records with login less then 3 chars" do
|
30
|
-
u = user_class.new login: 'pr', name: 'Andrew', email: 'ceo@prepor.ru'
|
31
|
-
u.save.must_equal false
|
32
|
-
u.valid?.must_equal false
|
33
|
-
u.errors.size.must_equal 1
|
34
|
-
u.login = 'prepor'
|
35
|
-
u.save.must_equal true
|
36
|
-
u.valid?.must_equal true
|
37
|
-
u.errors.size.must_equal 0
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
describe "callbacks" do
|
43
|
-
let(:u) { user_class.new login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru '}
|
44
|
-
it "should run before / after create callbackss in right places" do
|
45
|
-
user_class.before_create :action_before_create
|
46
|
-
user_class.after_create :action_after_create
|
47
|
-
mock(u).action_before_create { u.new_record?.must_equal true }
|
48
|
-
mock(u).action_after_create { u.new_record?.must_equal false }
|
49
|
-
u.save
|
50
|
-
end
|
51
|
-
|
52
|
-
describe "initialize" do
|
53
|
-
it "should run after_initialize after any initialization" do
|
54
|
-
user_class.after_initialize :action_after_initialize
|
55
|
-
any_instance_of(user_class) do |u|
|
56
|
-
mock(u).action_after_initialize.twice
|
57
|
-
end
|
58
|
-
u.save
|
59
|
-
user_class.find u.login
|
60
|
-
end
|
61
|
-
|
62
|
-
it "should not run after_initialize after reload" do
|
63
|
-
user_class.after_initialize :action_after_initialize
|
64
|
-
any_instance_of(user_class) do |u|
|
65
|
-
mock(u).action_after_initialize.once
|
66
|
-
end
|
67
|
-
u.save
|
68
|
-
u.reload
|
69
|
-
end
|
70
|
-
|
71
|
-
it "should properly save record inside after_initialize" do
|
72
|
-
user_class.after_initialize do |u|
|
73
|
-
u.save
|
74
|
-
end
|
75
|
-
u
|
76
|
-
user_class.find u.login
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
describe "serialization" do
|
82
|
-
it "should support AM serialization API" do
|
83
|
-
h = { login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru' }
|
84
|
-
u = user_class.create h
|
85
|
-
u.as_json.must_equal({ 'user' => h.merge(apples_count: 0) })
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|