tarantool 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +14 -0
- data/Gemfile.lock +55 -0
- data/LICENSE +24 -0
- data/README.md +116 -0
- data/Rakefile +131 -0
- data/examples/em_simple.rb +21 -0
- data/examples/record.rb +56 -0
- data/examples/synchrony_simple.rb +13 -0
- data/lib/em/protocols/fixed_header_and_body.rb +67 -0
- data/lib/tarantool.rb +44 -0
- data/lib/tarantool/connection.rb +54 -0
- data/lib/tarantool/exceptions.rb +11 -0
- data/lib/tarantool/record.rb +316 -0
- data/lib/tarantool/request.rb +94 -0
- data/lib/tarantool/requests.rb +19 -0
- data/lib/tarantool/requests/call.rb +20 -0
- data/lib/tarantool/requests/delete.rb +18 -0
- data/lib/tarantool/requests/insert.rb +19 -0
- data/lib/tarantool/requests/ping.rb +16 -0
- data/lib/tarantool/requests/select.rb +22 -0
- data/lib/tarantool/requests/update.rb +35 -0
- data/lib/tarantool/response.rb +58 -0
- data/lib/tarantool/serializers.rb +9 -0
- data/lib/tarantool/serializers/bson.rb +15 -0
- data/lib/tarantool/serializers/integer.rb +14 -0
- data/lib/tarantool/serializers/string.rb +14 -0
- data/lib/tarantool/space.rb +39 -0
- data/lib/tarantool/synchrony.rb +13 -0
- data/spec/helpers/let.rb +11 -0
- data/spec/helpers/truncate.rb +12 -0
- data/spec/spec_helper.rb +28 -0
- data/spec/tarantool.cfg +32 -0
- data/spec/tarantool/record_spec.rb +247 -0
- data/spec/tarantool/request_spec.rb +114 -0
- data/tarantool.gemspec +70 -0
- metadata +126 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Tarantool
|
2
|
+
require 'tarantool/request'
|
3
|
+
module Requests
|
4
|
+
REQUEST_TYPES = {
|
5
|
+
insert: 13,
|
6
|
+
select: 17,
|
7
|
+
update: 19,
|
8
|
+
delete: 21,
|
9
|
+
call: 22,
|
10
|
+
ping: 65280
|
11
|
+
}
|
12
|
+
BOX_RETURN_TUPLE = 1
|
13
|
+
BOX_ADD = 2
|
14
|
+
|
15
|
+
%w{insert select update delete call ping}.each do |v|
|
16
|
+
require "tarantool/requests/#{v}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Tarantool
|
2
|
+
module Requests
|
3
|
+
class Call < Request
|
4
|
+
request_type :call
|
5
|
+
|
6
|
+
attr_reader :flags, :proc_name, :tuple
|
7
|
+
def parse_args
|
8
|
+
@flags = params[:return_tuple] ? 1 : 0
|
9
|
+
@proc_name = params[:proc_name]
|
10
|
+
@tuple = params[:args] || []
|
11
|
+
end
|
12
|
+
|
13
|
+
def make_body
|
14
|
+
[flags].pack('L') +
|
15
|
+
self.class.pack_field(proc_name) +
|
16
|
+
self.class.pack_tuple(*tuple)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Tarantool
|
2
|
+
module Requests
|
3
|
+
class Delete < Request
|
4
|
+
request_type :delete
|
5
|
+
|
6
|
+
attr_reader :flags, :key
|
7
|
+
def parse_args
|
8
|
+
@flags = params[:return_tuple] ? 1 : 0
|
9
|
+
@key = params[:key] || args.first
|
10
|
+
end
|
11
|
+
|
12
|
+
def make_body
|
13
|
+
[space_no, flags].pack('LL') +
|
14
|
+
self.class.pack_tuple(key)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Tarantool
|
2
|
+
module Requests
|
3
|
+
class Insert < Request
|
4
|
+
request_type :insert
|
5
|
+
|
6
|
+
attr_reader :flags, :values
|
7
|
+
def parse_args
|
8
|
+
@flags = BOX_ADD
|
9
|
+
@flags |= BOX_RETURN_TUPLE if params[:return_tuple]
|
10
|
+
@values = params[:values] || args
|
11
|
+
end
|
12
|
+
|
13
|
+
def make_body
|
14
|
+
[space_no, flags].pack('LL') +
|
15
|
+
self.class.pack_tuple(*values)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Tarantool
|
2
|
+
module Requests
|
3
|
+
class Select < Request
|
4
|
+
request_type :select
|
5
|
+
|
6
|
+
attr_reader :index_no, :offset, :limit, :count, :tuples
|
7
|
+
def parse_args
|
8
|
+
@index_no = params[:index_no] || 0
|
9
|
+
@offset = params[:offset] || 0
|
10
|
+
@limit = params[:limit] || -1
|
11
|
+
@tuples = params[:values] || args
|
12
|
+
raise(ArgumentError.new('values are required')) if tuples.empty?
|
13
|
+
params[:return_tuple] = true
|
14
|
+
end
|
15
|
+
|
16
|
+
def make_body
|
17
|
+
[space_no, index_no, offset, limit, tuples.size].pack('LLLLL') +
|
18
|
+
tuples.map { |tuple| self.class.pack_tuple(*tuple) }.join
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Tarantool
|
2
|
+
module Requests
|
3
|
+
class Update < Request
|
4
|
+
request_type :update
|
5
|
+
|
6
|
+
OP_CODES = { set: 0, add: 1, and: 2, or: 3, xor: 4, splice: 5 }
|
7
|
+
|
8
|
+
def self.pack_ops(ops)
|
9
|
+
ops.map do |op|
|
10
|
+
raise ArgumentError.new('Operation should be array of size 3') unless op.size == 3
|
11
|
+
|
12
|
+
field_no, op_symbol, op_arg = op
|
13
|
+
op_code = OP_CODES[op_symbol] || raise(ArgumentError.new("Unsupported operation symbol '#{op_symbol}'"))
|
14
|
+
|
15
|
+
[field_no, op_code].pack('LC') + self.pack_field(op_arg)
|
16
|
+
end.join
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :flags, :key, :ops
|
20
|
+
def parse_args
|
21
|
+
@flags = params[:return_tuple] ? 1 : 0
|
22
|
+
@key = params[:key] || args.first
|
23
|
+
@ops = params[:ops]
|
24
|
+
raise ArgumentError.new('Key is required') unless key
|
25
|
+
end
|
26
|
+
|
27
|
+
def make_body
|
28
|
+
[space_no, flags].pack('LL') +
|
29
|
+
self.class.pack_tuple(key) +
|
30
|
+
[ops.size].pack('L') +
|
31
|
+
self.class.pack_ops(ops)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Tarantool
|
2
|
+
class Field
|
3
|
+
attr_reader :data
|
4
|
+
def initialize(data)
|
5
|
+
@data = data
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_i
|
9
|
+
if data.bytesize == 4
|
10
|
+
data.unpack('L')[0]
|
11
|
+
elsif data.bytesize == 8
|
12
|
+
data.unpack('Q')[0]
|
13
|
+
else
|
14
|
+
raise ValueError.new("Unable to cast field to int: length must be 4 or 8 bytes, field length is #{data.size}")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
data.dup.force_encoding('utf-8')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
class Response
|
23
|
+
attr_reader :tuples_affected, :offset, :tuples
|
24
|
+
def initialize(data, params = {})
|
25
|
+
@offset = 0
|
26
|
+
@tuples_affected, = data[0, 4].unpack('L')
|
27
|
+
@offset += 4
|
28
|
+
if params[:return_tuple]
|
29
|
+
@tuples = (1..tuples_affected).map do
|
30
|
+
unpack_tuple(data)
|
31
|
+
end
|
32
|
+
else
|
33
|
+
tuples_affected
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Only select request can return many tuples
|
38
|
+
def tuple
|
39
|
+
tuples.first
|
40
|
+
end
|
41
|
+
|
42
|
+
def unpack_tuple(data)
|
43
|
+
byte_size, cardinality = data[offset, 8].unpack("LL")
|
44
|
+
@offset += 8
|
45
|
+
tuple_data = data[offset, byte_size]
|
46
|
+
@offset += byte_size
|
47
|
+
(1..cardinality).map do
|
48
|
+
Field.new unpack_field(tuple_data)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def unpack_field(data)
|
53
|
+
byte_size, = data.unpack('w')
|
54
|
+
data.slice!(0, [byte_size].pack('w').bytesize) # ololo
|
55
|
+
data.slice!(0, byte_size)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bson'
|
2
|
+
module Tarantool
|
3
|
+
module Serializers
|
4
|
+
class BSON
|
5
|
+
Serializers::MAP[:bson] = self
|
6
|
+
def self.encode(value)
|
7
|
+
::BSON.serialize(value).to_s
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.decode(field)
|
11
|
+
::BSON.deserialize(field.to_s)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Tarantool
|
2
|
+
class Space
|
3
|
+
attr_accessor :space_no
|
4
|
+
attr_reader :connection
|
5
|
+
def initialize(connection, space_no = nil)
|
6
|
+
@connection = connection
|
7
|
+
@space_no = space_no
|
8
|
+
end
|
9
|
+
|
10
|
+
def select(*args)
|
11
|
+
request Requests::Select, args
|
12
|
+
end
|
13
|
+
|
14
|
+
def call(*args)
|
15
|
+
request Requests::Call, args
|
16
|
+
end
|
17
|
+
|
18
|
+
def insert(*args)
|
19
|
+
request Requests::Insert, args
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def delete(*args)
|
24
|
+
request Requests::Delete, args
|
25
|
+
end
|
26
|
+
|
27
|
+
def update(*args)
|
28
|
+
request Requests::Update, args
|
29
|
+
end
|
30
|
+
|
31
|
+
def ping(*args)
|
32
|
+
request Requests::Ping, args
|
33
|
+
end
|
34
|
+
|
35
|
+
def request(cls, args)
|
36
|
+
cls.new(self, *args).perform
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/spec/helpers/let.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
module Helpers
|
2
|
+
module Truncate
|
3
|
+
def teardown
|
4
|
+
while (res = Tarantool.call(proc_name: 'box.select_range', args: [Tarantool.singleton_space.space_no.to_s, '0', '100'], return_tuple: true)) && res.tuples.size > 0
|
5
|
+
res.tuples.each do |k, *_|
|
6
|
+
Tarantool.delete key: k
|
7
|
+
end
|
8
|
+
end
|
9
|
+
super
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
ENV['BUNDLE_GEMFILE'] = File.expand_path('../../Gemfile', __FILE__)
|
3
|
+
Bundler.setup
|
4
|
+
|
5
|
+
require 'minitest/spec'
|
6
|
+
|
7
|
+
require 'helpers/let'
|
8
|
+
require 'helpers/truncate'
|
9
|
+
require 'rr'
|
10
|
+
|
11
|
+
require 'tarantool/synchrony'
|
12
|
+
|
13
|
+
config = { host: '10.211.55.3', port: 33013, space_no: 0 }
|
14
|
+
|
15
|
+
Tarantool.configure config
|
16
|
+
|
17
|
+
class MiniTest::Unit::TestCase
|
18
|
+
extend Helpers::Let
|
19
|
+
include RR::Adapters::MiniTest
|
20
|
+
end
|
21
|
+
|
22
|
+
at_exit {
|
23
|
+
EM.synchrony do
|
24
|
+
exit_code = MiniTest::Unit.new.run(ARGV)
|
25
|
+
EM.stop
|
26
|
+
exit_code
|
27
|
+
end
|
28
|
+
}
|
data/spec/tarantool.cfg
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
slab_alloc_arena = 0.1
|
2
|
+
pid_file = "box.pid"
|
3
|
+
|
4
|
+
logger="cat - >> tarantool.log"
|
5
|
+
|
6
|
+
primary_port = 33013
|
7
|
+
secondary_port = 33014
|
8
|
+
admin_port = 33015
|
9
|
+
|
10
|
+
rows_per_wal = 50
|
11
|
+
|
12
|
+
space[0].enabled = 1
|
13
|
+
|
14
|
+
space[0].index[0].type = "HASH"
|
15
|
+
space[0].index[0].unique = 1
|
16
|
+
space[0].index[0].key_field[0].fieldno = 0
|
17
|
+
space[0].index[0].key_field[0].type = "STR"
|
18
|
+
|
19
|
+
space[0].index[1].type = "TREE"
|
20
|
+
space[0].index[1].unique = 1
|
21
|
+
space[0].index[1].key_field[0].fieldno = 1
|
22
|
+
space[0].index[1].key_field[0].type = "STR"
|
23
|
+
space[0].index[1].key_field[1].fieldno = 2
|
24
|
+
space[0].index[1].key_field[1].type = "STR"
|
25
|
+
|
26
|
+
|
27
|
+
space[1].enabled = 1
|
28
|
+
|
29
|
+
space[1].index[0].type = "HASH"
|
30
|
+
space[1].index[0].unique = 1
|
31
|
+
space[1].index[0].key_field[0].fieldno = 0
|
32
|
+
space[1].index[0].key_field[0].type = "NUM"
|
@@ -0,0 +1,247 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'tarantool/record'
|
4
|
+
require 'yajl'
|
5
|
+
require 'tarantool/serializers/bson'
|
6
|
+
describe Tarantool::Record do
|
7
|
+
include Helpers::Truncate
|
8
|
+
before do
|
9
|
+
Tarantool.singleton_space.space_no = 0
|
10
|
+
end
|
11
|
+
let(:user_class) do
|
12
|
+
Class.new(Tarantool::Record) do
|
13
|
+
def self.name # For naming
|
14
|
+
"User"
|
15
|
+
end
|
16
|
+
|
17
|
+
field :login, :string
|
18
|
+
field :name, :string
|
19
|
+
field :email, :string
|
20
|
+
field :apples_count, :integer, default: 0
|
21
|
+
index :name, :email
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
let(:user) { user_class.new }
|
26
|
+
it "should set and get attributes" do
|
27
|
+
user.name = 'Andrew'
|
28
|
+
user.name.must_equal 'Andrew'
|
29
|
+
end
|
30
|
+
|
31
|
+
describe "inheritance" do
|
32
|
+
let(:author_class) do
|
33
|
+
Class.new(user_class) do
|
34
|
+
field :best_book, Integer
|
35
|
+
end
|
36
|
+
end
|
37
|
+
let(:artist_class) do
|
38
|
+
Class.new(user_class) do
|
39
|
+
field :imdb_id, Integer
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "Artist from User" do
|
44
|
+
it "should has only itself field" do
|
45
|
+
artist_class.fields.keys.must_equal [:login, :name, :email, :apples_count, :imdb_id]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should has same space no as parent" do
|
50
|
+
user_class.space_no = 1
|
51
|
+
artist_class.space_no.must_equal 1
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should has different space no to parent if setted" do
|
55
|
+
artist_class.space_no = 1
|
56
|
+
user_class.space_no.must_equal 0
|
57
|
+
artist_class.space_no.must_equal 1
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe "detect_index_no" do
|
62
|
+
let(:select) { user_class.select }
|
63
|
+
it "should return 0 for :login" do
|
64
|
+
select.detect_index_no([:login]).must_equal 0
|
65
|
+
end
|
66
|
+
it "should return 1 for :name" do
|
67
|
+
select.detect_index_no([:name]).must_equal 1
|
68
|
+
end
|
69
|
+
it "should return 1 for :name, :email" do
|
70
|
+
select.detect_index_no([:name, :email]).must_equal 1
|
71
|
+
end
|
72
|
+
it "should return nil for :email" do
|
73
|
+
select.detect_index_no([:email]).must_be_nil
|
74
|
+
end
|
75
|
+
it "should return nil for :login, :name" do
|
76
|
+
select.detect_index_no([:login, :name]).must_be_nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "save" do
|
81
|
+
it "should save and select record" do
|
82
|
+
u = user_class.new login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
|
83
|
+
u.save
|
84
|
+
u = user_class.find 'prepor'
|
85
|
+
u.id.must_equal 'prepor'
|
86
|
+
u.email.must_equal 'ceo@prepor.ru'
|
87
|
+
u.name.must_equal 'Andrew'
|
88
|
+
u.apples_count.must_equal 0
|
89
|
+
end
|
90
|
+
|
91
|
+
it "should update dirty attributes" do
|
92
|
+
u = user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
|
93
|
+
u.name = 'Petr'
|
94
|
+
u.save
|
95
|
+
u = user_class.find 'prepor'
|
96
|
+
u.email.must_equal 'ceo@prepor.ru'
|
97
|
+
u.name.must_equal 'Petr'
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "with nils" do
|
101
|
+
before do
|
102
|
+
user_class.field :info, :bson
|
103
|
+
end
|
104
|
+
it "should work properly with nils values" do
|
105
|
+
u = user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru', apples_count: nil
|
106
|
+
u.info.must_be_nil
|
107
|
+
u.apples_count.must_be_nil
|
108
|
+
u = u.reload
|
109
|
+
u.info.must_be_nil
|
110
|
+
u.apples_count.must_be_nil
|
111
|
+
u.info = {'bio' => 'hi!'}
|
112
|
+
u.apples_count = 1
|
113
|
+
u.save
|
114
|
+
u = u.reload
|
115
|
+
u.info.must_equal({ 'bio' => 'hi!' })
|
116
|
+
u.apples_count.must_equal 1
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "reload" do
|
122
|
+
it "should reload current record" do
|
123
|
+
u = user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
|
124
|
+
u.name = 'Petr'
|
125
|
+
u.reload.name.must_equal 'Andrew'
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "increment" do
|
130
|
+
let(:user) { user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru' }
|
131
|
+
it "should increment apples count by 1" do
|
132
|
+
user.increment :apples_count
|
133
|
+
user.reload.apples_count.must_equal 1
|
134
|
+
end
|
135
|
+
|
136
|
+
it "should increment apples count by 3" do
|
137
|
+
user.increment :apples_count, 3
|
138
|
+
user.reload.apples_count.must_equal 3
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
describe "destroy" do
|
143
|
+
it "should destroy record" do
|
144
|
+
u = user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
|
145
|
+
u.destroy
|
146
|
+
u.reload.must_be_nil
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "validations" do
|
151
|
+
describe "with validator on login size" do
|
152
|
+
before do
|
153
|
+
user_class.validates_length_of(:login, minimum: 3)
|
154
|
+
end
|
155
|
+
it "should invalidate all records with login less then 3 chars" do
|
156
|
+
u = user_class.new login: 'pr', name: 'Andrew', email: 'ceo@prepor.ru'
|
157
|
+
u.save.must_equal false
|
158
|
+
u.valid?.must_equal false
|
159
|
+
u.errors.size.must_equal 1
|
160
|
+
u.login = 'prepor'
|
161
|
+
u.save.must_equal true
|
162
|
+
u.valid?.must_equal true
|
163
|
+
u.errors.size.must_equal 0
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
describe "callbacks" do
|
169
|
+
it "should run before / after create callbackss in right places" do
|
170
|
+
user_class.before_create :action_before_create
|
171
|
+
user_class.after_create :action_after_create
|
172
|
+
|
173
|
+
u = user_class.new login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
|
174
|
+
mock(u).action_before_create { u.new_record?.must_equal true }
|
175
|
+
mock(u).action_after_create { u.new_record?.must_equal false }
|
176
|
+
u.save
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
describe "serialization" do
|
181
|
+
it "should support AM serialization API" do
|
182
|
+
h = { login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru' }
|
183
|
+
u = user_class.create h
|
184
|
+
u.as_json.must_equal({ 'user' => h.merge(apples_count: 0) })
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "fields serilizers" do
|
188
|
+
before do
|
189
|
+
user_class.field :info, :bson
|
190
|
+
end
|
191
|
+
|
192
|
+
it "should serialise and deserialize info field" do
|
193
|
+
info = { 'bio' => "hi!", 'age' => 23, 'hobbies' => ['mufa', 'tuka'] }
|
194
|
+
u = user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru', info: info
|
195
|
+
u.info['hobbies'].must_equal ['mufa', 'tuka']
|
196
|
+
u = u.reload
|
197
|
+
u.info['hobbies'].must_equal ['mufa', 'tuka']
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe "select" do
|
203
|
+
describe "by name Andrew" do
|
204
|
+
let(:select) { user_class.where(name: 'Andrew') }
|
205
|
+
before do
|
206
|
+
user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru'
|
207
|
+
user_class.create login: 'petro', name: 'Petr', email: 'petro@gmail.com'
|
208
|
+
user_class.create login: 'ruden', name: 'Andrew', email: 'rudenkoco@gmail.com'
|
209
|
+
end
|
210
|
+
it "should select all records with name == 'Andrew'" do
|
211
|
+
select.all.map(&:login).must_equal ['prepor', 'ruden']
|
212
|
+
end
|
213
|
+
|
214
|
+
it "should select first record with name == 'Andrew'" do
|
215
|
+
select.first.login.must_equal 'prepor'
|
216
|
+
end
|
217
|
+
|
218
|
+
it "should select 1 record by name and email" do
|
219
|
+
user_class.where(name: 'Andrew', email: 'rudenkoco@gmail.com').map(&:login).must_equal ['ruden']
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should select 2 record by name and email" do
|
223
|
+
user_class.where(name: ['Andrew', 'Andrew'], email: ['ceo@prepor.ru', 'rudenkoco@gmail.com']).map(&:login).must_equal ['prepor', 'ruden']
|
224
|
+
end
|
225
|
+
|
226
|
+
it "should select 3 record by names" do
|
227
|
+
user_class.where(name: ['Andrew', 'Petr']).map(&:login).must_equal ['prepor', 'ruden', 'petro']
|
228
|
+
end
|
229
|
+
|
230
|
+
describe "with limit 1" do
|
231
|
+
let(:select) { super().limit(1) }
|
232
|
+
it "should select first record with name == 'Andrew'" do
|
233
|
+
select.map(&:login).must_equal ['prepor']
|
234
|
+
end
|
235
|
+
|
236
|
+
describe "with offset 1" do
|
237
|
+
let(:select) { super().offset(1) }
|
238
|
+
it "should select last record with name == 'Andrew'" do
|
239
|
+
select.map(&:login).must_equal ['ruden']
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
end
|