tarantool-record 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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