kvbean 0.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/MIT-LICENSE +20 -0
- data/README.md +62 -0
- data/Rakefile +45 -0
- data/lib/kvbean.rb +15 -0
- data/lib/kvbean/base.rb +233 -0
- data/lib/kvbean/callbacks.rb +23 -0
- data/lib/kvbean/ext/array.rb +7 -0
- data/lib/kvbean/observing.rb +29 -0
- data/lib/kvbean/redis.rb +109 -0
- data/lib/kvbean/timestamp.rb +51 -0
- data/lib/kvbean/validations.rb +19 -0
- data/lib/kvbean/version.rb +3 -0
- data/lib/tasks/kvbean_tasks.rake +4 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/message.rb +6 -0
- data/test/dummy/app/models/post.rb +15 -0
- data/test/dummy/app/observer/post_observer.rb +8 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +63 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +21 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +2 -0
- data/test/dummy/log/test.log +11064 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/kvbean/model/message_test.rb +150 -0
- data/test/kvbean/model/post_test.rb +39 -0
- data/test/kvbean/redis_test.rb +90 -0
- data/test/kvbean_test.rb +7 -0
- data/test/support/assertions.rb +17 -0
- data/test/test_helper.rb +17 -0
- metadata +166 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2012 Sen
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
Kvbean
|
2
|
+
======
|
3
|
+
|
4
|
+
Make redis on rails easier
|
5
|
+
|
6
|
+
Install
|
7
|
+
-------
|
8
|
+
|
9
|
+
put this code in Gemfile
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'kvbean'
|
13
|
+
```
|
14
|
+
|
15
|
+
|
16
|
+
Usage
|
17
|
+
-----
|
18
|
+
|
19
|
+
```ruby
|
20
|
+
class Message
|
21
|
+
include Kvbean::Base
|
22
|
+
|
23
|
+
kv_field :content, :title
|
24
|
+
end
|
25
|
+
|
26
|
+
Message.create(title: 'i am title', content: 'i am content')
|
27
|
+
|
28
|
+
message = Message.new(title: 'i am title', content: 'i am content')
|
29
|
+
message.save
|
30
|
+
```
|
31
|
+
|
32
|
+
Callbacks
|
33
|
+
---------
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
class Message
|
37
|
+
include Kvbean::Base
|
38
|
+
|
39
|
+
kv_field :content, :title
|
40
|
+
|
41
|
+
before_create :set_content
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def set_content
|
46
|
+
self.content = 'some text'
|
47
|
+
end
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
Validations
|
52
|
+
-----------
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
class Message
|
56
|
+
include Kvbean::Base
|
57
|
+
|
58
|
+
kv_field :content, :title
|
59
|
+
|
60
|
+
validates_presence_of :title
|
61
|
+
end
|
62
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
begin
|
3
|
+
require 'bundler/setup'
|
4
|
+
rescue LoadError
|
5
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'rdoc/task'
|
9
|
+
rescue LoadError
|
10
|
+
require 'rdoc/rdoc'
|
11
|
+
require 'rake/rdoctask'
|
12
|
+
RDoc::Task = Rake::RDocTask
|
13
|
+
end
|
14
|
+
|
15
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
16
|
+
rdoc.rdoc_dir = 'rdoc'
|
17
|
+
rdoc.title = 'Kvbean'
|
18
|
+
rdoc.options << '--line-numbers'
|
19
|
+
rdoc.rdoc_files.include('README.rdoc')
|
20
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
|
26
|
+
Bundler::GemHelper.install_tasks
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
|
30
|
+
Rake::TestTask.new(:benchmark) do |t|
|
31
|
+
t.libs << 'lib'
|
32
|
+
t.libs << 'test'
|
33
|
+
t.pattern = 'perf/*_test.rb'
|
34
|
+
t.verbose = false
|
35
|
+
end
|
36
|
+
|
37
|
+
Rake::TestTask.new(:test) do |t|
|
38
|
+
t.libs << 'lib'
|
39
|
+
t.libs << 'test'
|
40
|
+
t.pattern = 'test/**/*_test.rb'
|
41
|
+
t.verbose = false
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
task :default => :test
|
data/lib/kvbean.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
$:.unshift(File.dirname(__FILE__))
|
2
|
+
require "kvbean/ext/array"
|
3
|
+
|
4
|
+
module Kvbean
|
5
|
+
class InvalidRecord < StandardError; end
|
6
|
+
end
|
7
|
+
|
8
|
+
module Kvbean
|
9
|
+
autoload :Redis, 'kvbean/redis'
|
10
|
+
autoload :Base, 'kvbean/base'
|
11
|
+
autoload :Validations, 'kvbean/validations'
|
12
|
+
autoload :Callbacks, 'kvbean/callbacks'
|
13
|
+
autoload :Observing, 'kvbean/observing'
|
14
|
+
autoload :Timestamp, 'kvbean/timestamp'
|
15
|
+
end
|
data/lib/kvbean/base.rb
ADDED
@@ -0,0 +1,233 @@
|
|
1
|
+
require 'SecureRandom'
|
2
|
+
|
3
|
+
module Kvbean
|
4
|
+
module Base
|
5
|
+
module ClassMethods
|
6
|
+
attr_accessor :primary_key, :kv_fields
|
7
|
+
|
8
|
+
def kv_field(*attributes)
|
9
|
+
define_attribute_methods attributes
|
10
|
+
@kv_fields ||= []
|
11
|
+
attributes.each do |attribute|
|
12
|
+
@kv_fields << attribute
|
13
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
14
|
+
def #{attribute}
|
15
|
+
@#{attribute}
|
16
|
+
end
|
17
|
+
|
18
|
+
def #{attribute}=(val)
|
19
|
+
#{attribute}_will_change! unless val == @#{attribute}
|
20
|
+
@#{attribute} = val
|
21
|
+
end
|
22
|
+
EOS
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def collection(&block)
|
27
|
+
@collection ||= Class.new(Array)
|
28
|
+
@collection.class_eval(&block) if block_given?
|
29
|
+
@collection
|
30
|
+
end
|
31
|
+
|
32
|
+
def primary_key
|
33
|
+
@primary_key ||= 'id'
|
34
|
+
end
|
35
|
+
|
36
|
+
def records
|
37
|
+
@records ||= {}
|
38
|
+
end
|
39
|
+
|
40
|
+
def raw_find(id)
|
41
|
+
records[id] || raise(UnknownRecord, "Couldn't find #{self.name} with ID=#{id}")
|
42
|
+
end
|
43
|
+
|
44
|
+
def find(id)
|
45
|
+
item = raw_find(id)
|
46
|
+
item && item.dup
|
47
|
+
end
|
48
|
+
|
49
|
+
def first
|
50
|
+
item = records.values[0]
|
51
|
+
item && item.dup
|
52
|
+
end
|
53
|
+
|
54
|
+
def last
|
55
|
+
item = records.values[-1]
|
56
|
+
item && item.dup
|
57
|
+
end
|
58
|
+
|
59
|
+
def all
|
60
|
+
clear_all
|
61
|
+
super.each { |item| records[item.id] = item.dup }
|
62
|
+
collection.new(records.values.deep_dup)
|
63
|
+
end
|
64
|
+
|
65
|
+
def count
|
66
|
+
all.size
|
67
|
+
end
|
68
|
+
|
69
|
+
def update(id, attrs)
|
70
|
+
find(id).update_attributes(attrs)
|
71
|
+
end
|
72
|
+
|
73
|
+
def destroy_all
|
74
|
+
all.each { |item| item.destroy }
|
75
|
+
clear_all
|
76
|
+
end
|
77
|
+
|
78
|
+
def clear_all
|
79
|
+
records.clear
|
80
|
+
end
|
81
|
+
|
82
|
+
def create(attrs = {})
|
83
|
+
record = self.new(attrs)
|
84
|
+
record.save && record
|
85
|
+
end
|
86
|
+
|
87
|
+
def create!(attrs = {})
|
88
|
+
create(*attrs) || raise(Kvbean::InvalidRecord)
|
89
|
+
end
|
90
|
+
|
91
|
+
def find_by_attribute(name, value)
|
92
|
+
item = records.values.find {|r| r.send(name) == value }
|
93
|
+
item && item.dup
|
94
|
+
end
|
95
|
+
|
96
|
+
def find_all_by_attribute(name, value)
|
97
|
+
items = records.values.select {|r| r.send(name) == value }
|
98
|
+
collection.new(items.deep_dup)
|
99
|
+
end
|
100
|
+
|
101
|
+
def method_missing(method_symbol, *args)
|
102
|
+
method_name = method_symbol.to_s
|
103
|
+
|
104
|
+
if method_name =~ /^find_by_(\w+)!/
|
105
|
+
send("find_by_#{$1}", *args) || raise(UnknownRecord)
|
106
|
+
elsif method_name =~ /^find_by_(\w+)/
|
107
|
+
find_by_attribute($1, args.first)
|
108
|
+
elsif method_name =~ /^find_or_create_by_(\w+)/
|
109
|
+
send("find_by_#{$1}", *args) || create($1 => args.first)
|
110
|
+
elsif method_name =~ /^find_all_by_(\w+)/
|
111
|
+
find_all_by_attribute($1, args.first)
|
112
|
+
else
|
113
|
+
super
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
module InstanceMethods
|
119
|
+
attr_accessor :attributes, :new_record
|
120
|
+
|
121
|
+
def initialize(attributes = {})
|
122
|
+
@new_record = true
|
123
|
+
@attributes = {}.with_indifferent_access
|
124
|
+
@attributes.merge!(self.class.kv_fields.inject({}) {|h, n| h[n] = nil; h })
|
125
|
+
@changed_attributes = {}
|
126
|
+
load_to_instance(attributes)
|
127
|
+
end
|
128
|
+
|
129
|
+
def saved
|
130
|
+
@previously_changed = changes
|
131
|
+
@changed_attributes.clear
|
132
|
+
@new_record = false
|
133
|
+
end
|
134
|
+
|
135
|
+
def new_record?
|
136
|
+
@new_record
|
137
|
+
end
|
138
|
+
|
139
|
+
def load_to_instance(attributes)
|
140
|
+
return unless attributes
|
141
|
+
attributes.each do |key, value|
|
142
|
+
self.send "#{key}=".to_sym, value
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def reload_to_instance(attributes)
|
147
|
+
return unless attributes
|
148
|
+
attributes.each do |key, value|
|
149
|
+
self.send "#{key}=".to_sym, value
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def update_attributes(attributes)
|
154
|
+
load_to_instance(attributes) && save
|
155
|
+
end
|
156
|
+
|
157
|
+
def id
|
158
|
+
attributes[self.class.primary_key]
|
159
|
+
end
|
160
|
+
|
161
|
+
def id=(value)
|
162
|
+
attributes[self.class.primary_key] = value
|
163
|
+
end
|
164
|
+
|
165
|
+
def save
|
166
|
+
new_record? ? create : update
|
167
|
+
end
|
168
|
+
|
169
|
+
def raw_create
|
170
|
+
super
|
171
|
+
self.class.records[self.id] = self.dup
|
172
|
+
end
|
173
|
+
|
174
|
+
def raw_update
|
175
|
+
super
|
176
|
+
item = self.class.raw_find(id)
|
177
|
+
item.load_to_instance(attributes)
|
178
|
+
end
|
179
|
+
|
180
|
+
def raw_delete
|
181
|
+
super
|
182
|
+
self.class.records.delete(self.id)
|
183
|
+
end
|
184
|
+
|
185
|
+
def create
|
186
|
+
self.id ||= generate_id
|
187
|
+
@new_record = false
|
188
|
+
raw_create
|
189
|
+
self.id
|
190
|
+
end
|
191
|
+
|
192
|
+
def update
|
193
|
+
raw_update
|
194
|
+
true
|
195
|
+
end
|
196
|
+
|
197
|
+
def destroy
|
198
|
+
raw_destroy
|
199
|
+
true
|
200
|
+
end
|
201
|
+
|
202
|
+
def exists?
|
203
|
+
!new_record?
|
204
|
+
end
|
205
|
+
alias_method :persisted?, :exists?
|
206
|
+
|
207
|
+
def generate_id
|
208
|
+
SecureRandom.hex(13)
|
209
|
+
end
|
210
|
+
|
211
|
+
def ==(other)
|
212
|
+
other.equal?(self) || (other.instance_of?(self.class) && other.id == id)
|
213
|
+
end
|
214
|
+
|
215
|
+
def eql?(other)
|
216
|
+
self == other
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def self.included(base)
|
221
|
+
base.send :include, ActiveModel::Serialization
|
222
|
+
base.send :include, ActiveModel::Dirty
|
223
|
+
base.send :include, Kvbean::Redis
|
224
|
+
base.send :extend, ClassMethods
|
225
|
+
base.send :include, InstanceMethods
|
226
|
+
base.send :include, Kvbean::Validations
|
227
|
+
base.send :include, Kvbean::Callbacks
|
228
|
+
base.send :include, Kvbean::Observing
|
229
|
+
base.send :include, Kvbean::Timestamp
|
230
|
+
end
|
231
|
+
|
232
|
+
end
|
233
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Kvbean
|
2
|
+
module Callbacks
|
3
|
+
module InstanceMethods
|
4
|
+
%w( create save update destroy ).each do |method|
|
5
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
6
|
+
def #{method}
|
7
|
+
run_callbacks :#{method} do
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
EOS
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.included(base)
|
16
|
+
base.send :extend, ActiveModel::Callbacks
|
17
|
+
base.instance_eval do
|
18
|
+
define_model_callbacks :create, :save, :update, :destroy
|
19
|
+
end
|
20
|
+
base.send :include, InstanceMethods
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Kvbean
|
2
|
+
module Observing
|
3
|
+
#CALLBACKS = [
|
4
|
+
#:before_create, :before_destroy, :before_save, :before_update,
|
5
|
+
#:before_validation, :after_create, :after_destroy, :after_save,
|
6
|
+
#:after_update, :after_validation
|
7
|
+
#]
|
8
|
+
CALLBACKS = [
|
9
|
+
:before_create, :before_destroy, :before_save, :before_update,
|
10
|
+
:after_create, :after_destroy, :after_save,
|
11
|
+
:after_update
|
12
|
+
]
|
13
|
+
def self.included(base)
|
14
|
+
base.send :include, ActiveModel::Observing
|
15
|
+
CALLBACKS.each do |callback|
|
16
|
+
callback_method = :"notify_observers_#{callback}"
|
17
|
+
|
18
|
+
base.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
19
|
+
#{callback}(#{callback_method.inspect})
|
20
|
+
|
21
|
+
def #{callback_method}(&block)
|
22
|
+
notify_observers(#{callback.inspect}, &block)
|
23
|
+
end
|
24
|
+
private #{callback_method.inspect}
|
25
|
+
RUBY
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/kvbean/redis.rb
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
module Kvbean
|
2
|
+
module Redis
|
3
|
+
module ClassMethods
|
4
|
+
def redis
|
5
|
+
@redis ||= ::Redis.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def namespace
|
9
|
+
@namespace ||= self.name.downcase
|
10
|
+
end
|
11
|
+
|
12
|
+
def namespace=(namespace)
|
13
|
+
@namespace = namespace
|
14
|
+
end
|
15
|
+
|
16
|
+
def redis_key(*args)
|
17
|
+
args.unshift(self.namespace)
|
18
|
+
args.join(':')
|
19
|
+
end
|
20
|
+
|
21
|
+
def all
|
22
|
+
from_ids(redis.sort(redis_key))
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def from_ids(ids)
|
28
|
+
ids.map { |id| existing('id' => id) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def existing(attrs = {})
|
32
|
+
item = self.new(attrs)
|
33
|
+
item.new_record = false
|
34
|
+
item.redis_get
|
35
|
+
item
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module InstanceMethods
|
40
|
+
protected
|
41
|
+
|
42
|
+
def redis
|
43
|
+
self.class.redis
|
44
|
+
end
|
45
|
+
|
46
|
+
def redis_key(*args)
|
47
|
+
self.class.redis_key(id, *args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def redis_set
|
51
|
+
redis.set(redis_key, serializable_hash.to_json)
|
52
|
+
end
|
53
|
+
|
54
|
+
def redis_get
|
55
|
+
data = redis.get(redis_key)
|
56
|
+
if data
|
57
|
+
load_to_instance(ActiveSupport::JSON.decode(data))
|
58
|
+
else
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
public :redis_get
|
63
|
+
|
64
|
+
def destroy_indexes
|
65
|
+
self.class.kv_fields.each do |field|
|
66
|
+
old_value = changes[field].try(:first) || send(field)
|
67
|
+
redis.srem(self.class.redis_key(field, old_value), id)
|
68
|
+
if redis.smembers(self.class.redis_key(field, old_value)).blank?
|
69
|
+
redis.del(self.class.redis_key(field, old_value))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def create_indexes
|
75
|
+
self.class.kv_fields.each do |field|
|
76
|
+
new_value = send(field)
|
77
|
+
redis.sadd(self.class.redis_key(field, new_value), id)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def raw_create
|
82
|
+
redis_set
|
83
|
+
create_indexes
|
84
|
+
redis.sadd(self.class.redis_key, self.id)
|
85
|
+
saved
|
86
|
+
end
|
87
|
+
|
88
|
+
def raw_update
|
89
|
+
destroy_indexes
|
90
|
+
redis_set
|
91
|
+
create_indexes
|
92
|
+
saved
|
93
|
+
end
|
94
|
+
|
95
|
+
def raw_destroy
|
96
|
+
return if new_record?
|
97
|
+
|
98
|
+
destroy_indexes
|
99
|
+
redis.srem(self.class.redis_key, self.id)
|
100
|
+
redis.del(redis_key)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.included(base)
|
105
|
+
base.send :extend, ClassMethods
|
106
|
+
base.send :include, InstanceMethods
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|