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 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