tarantool 0.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 +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
|