tarantool-record 0.4.0
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/LICENSE +24 -0
- data/README_RECORD.md +0 -0
- data/lib/tarantool/record.rb +164 -0
- data/test/test_record.rb +89 -0
- metadata +93 -0
data/LICENSE
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright (C) 2011-2012 Mail.RU
|
3
|
+
*
|
4
|
+
* Redistribution and use in source and binary forms, with or without
|
5
|
+
* modification, are permitted provided that the following conditions
|
6
|
+
* are met:
|
7
|
+
* 1. Redistributions of source code must retain the above copyright
|
8
|
+
* notice, this list of conditions and the following disclaimer.
|
9
|
+
* 2. Redistributions in binary form must reproduce the above copyright
|
10
|
+
* notice, this list of conditions and the following disclaimer in the
|
11
|
+
* documentation and/or other materials provided with the distribution.
|
12
|
+
*
|
13
|
+
* THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
14
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
15
|
+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
16
|
+
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
|
17
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
18
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
19
|
+
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
20
|
+
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
21
|
+
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
22
|
+
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
23
|
+
* SUCH DAMAGE.
|
24
|
+
*/
|
data/README_RECORD.md
ADDED
File without changes
|
@@ -0,0 +1,164 @@
|
|
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, :int64, :varint
|
43
|
+
"v = v.to_i if String === v"
|
44
|
+
when :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/test_record.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require File.expand_path('../shared_record', __FILE__)
|
3
|
+
require 'tarantool/record'
|
4
|
+
|
5
|
+
describe 'Tarantool::Record' do
|
6
|
+
before { TConf.run(:master1) }
|
7
|
+
let(:base_class){ Tarantool::Record }
|
8
|
+
it_behaves_like :record
|
9
|
+
|
10
|
+
|
11
|
+
describe "update" do
|
12
|
+
let(:user) { user_class.create login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru' }
|
13
|
+
it "should not reload fields not reffered in operations" do
|
14
|
+
user.name = "Petr"
|
15
|
+
user.update(email: "prepor@ceo.ru")
|
16
|
+
user.name.must_equal "Petr"
|
17
|
+
user.email.must_equal "prepor@ceo.ru"
|
18
|
+
|
19
|
+
fetched = user_class.by_pk('prepor')
|
20
|
+
fetched.name.must_equal "Andrew"
|
21
|
+
fetched.email.must_equal "prepor@ceo.ru"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "validations" do
|
26
|
+
describe "with validator on login size" do
|
27
|
+
before do
|
28
|
+
user_class.validates_length_of(:login, minimum: 3)
|
29
|
+
end
|
30
|
+
it "should invalidate all records with login less then 3 chars" do
|
31
|
+
u = user_class.new login: 'pr', name: 'Andrew', email: 'ceo@prepor.ru'
|
32
|
+
u.save.must_equal false
|
33
|
+
u.valid?.must_equal false
|
34
|
+
u.errors.size.must_equal 1
|
35
|
+
u.login = 'prepor'
|
36
|
+
u.save.must_equal true
|
37
|
+
u.valid?.must_equal true
|
38
|
+
u.errors.size.must_equal 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "callbacks" do
|
44
|
+
let(:u) { user_class.new login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru '}
|
45
|
+
it "should run before / after create callbackss in right places" do
|
46
|
+
user_class.before_create :action_before_create
|
47
|
+
user_class.after_create :action_after_create
|
48
|
+
mock(u).action_before_create { u.new_record?.must_equal true }
|
49
|
+
mock(u).action_after_create { u.new_record?.must_equal false }
|
50
|
+
u.save
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "initialize" do
|
54
|
+
it "should run after_initialize after any initialization" do
|
55
|
+
user_class.after_initialize :action_after_initialize
|
56
|
+
any_instance_of(user_class) do |u|
|
57
|
+
mock(u).action_after_initialize.twice
|
58
|
+
end
|
59
|
+
u.save
|
60
|
+
user_class.find u.login
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should not run after_initialize after reload" do
|
64
|
+
user_class.after_initialize :action_after_initialize
|
65
|
+
any_instance_of(user_class) do |u|
|
66
|
+
mock(u).action_after_initialize.once
|
67
|
+
end
|
68
|
+
u.save
|
69
|
+
u.reload
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should properly save record inside after_initialize" do
|
73
|
+
user_class.after_initialize do |u|
|
74
|
+
u.save
|
75
|
+
end
|
76
|
+
u
|
77
|
+
user_class.find u.login
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe "serialization" do
|
83
|
+
it "should support AM serialization API" do
|
84
|
+
h = { login: 'prepor', name: 'Andrew', email: 'ceo@prepor.ru' }
|
85
|
+
u = user_class.create h
|
86
|
+
u.as_json.must_equal({ 'user' => h.merge(apples_count: 0) })
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
metadata
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tarantool-record
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Andrew Rudenko
|
9
|
+
- Sokolov Yura 'funny-falcon'
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-07-18 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: tarantool
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 0.4.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 0.4.0
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: active_model
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ! '>='
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '3.1'
|
39
|
+
- - <
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: '4.0'
|
42
|
+
type: :runtime
|
43
|
+
prerelease: false
|
44
|
+
version_requirements: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '3.1'
|
50
|
+
- - <
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '4.0'
|
53
|
+
description: Tarantool KV-storage ActiveModel-aware Record.
|
54
|
+
email:
|
55
|
+
- ceo@prepor.ru
|
56
|
+
- funny.falcon@gmail.com
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files:
|
60
|
+
- README_RECORD.md
|
61
|
+
- LICENSE
|
62
|
+
files:
|
63
|
+
- lib/tarantool/record.rb
|
64
|
+
- test/test_record.rb
|
65
|
+
- LICENSE
|
66
|
+
- README_RECORD.md
|
67
|
+
homepage: http://github.com/mailru/tarantool-ruby
|
68
|
+
licenses: []
|
69
|
+
post_install_message:
|
70
|
+
rdoc_options:
|
71
|
+
- --charset=UTF-8
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.24
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: Tarantool KV-storage ActiveModel-aware Record.
|
92
|
+
test_files:
|
93
|
+
- test/test_record.rb
|