kvbean 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +62 -0
  3. data/Rakefile +45 -0
  4. data/lib/kvbean.rb +15 -0
  5. data/lib/kvbean/base.rb +233 -0
  6. data/lib/kvbean/callbacks.rb +23 -0
  7. data/lib/kvbean/ext/array.rb +7 -0
  8. data/lib/kvbean/observing.rb +29 -0
  9. data/lib/kvbean/redis.rb +109 -0
  10. data/lib/kvbean/timestamp.rb +51 -0
  11. data/lib/kvbean/validations.rb +19 -0
  12. data/lib/kvbean/version.rb +3 -0
  13. data/lib/tasks/kvbean_tasks.rake +4 -0
  14. data/test/dummy/README.rdoc +261 -0
  15. data/test/dummy/Rakefile +7 -0
  16. data/test/dummy/app/assets/javascripts/application.js +13 -0
  17. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  18. data/test/dummy/app/controllers/application_controller.rb +3 -0
  19. data/test/dummy/app/helpers/application_helper.rb +2 -0
  20. data/test/dummy/app/models/message.rb +6 -0
  21. data/test/dummy/app/models/post.rb +15 -0
  22. data/test/dummy/app/observer/post_observer.rb +8 -0
  23. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  24. data/test/dummy/config.ru +4 -0
  25. data/test/dummy/config/application.rb +63 -0
  26. data/test/dummy/config/boot.rb +10 -0
  27. data/test/dummy/config/database.yml +21 -0
  28. data/test/dummy/config/environment.rb +5 -0
  29. data/test/dummy/config/environments/development.rb +37 -0
  30. data/test/dummy/config/environments/production.rb +67 -0
  31. data/test/dummy/config/environments/test.rb +37 -0
  32. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  33. data/test/dummy/config/initializers/inflections.rb +15 -0
  34. data/test/dummy/config/initializers/mime_types.rb +5 -0
  35. data/test/dummy/config/initializers/secret_token.rb +7 -0
  36. data/test/dummy/config/initializers/session_store.rb +8 -0
  37. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  38. data/test/dummy/config/locales/en.yml +5 -0
  39. data/test/dummy/config/routes.rb +2 -0
  40. data/test/dummy/log/test.log +11064 -0
  41. data/test/dummy/public/404.html +26 -0
  42. data/test/dummy/public/422.html +26 -0
  43. data/test/dummy/public/500.html +25 -0
  44. data/test/dummy/public/favicon.ico +0 -0
  45. data/test/dummy/script/rails +6 -0
  46. data/test/kvbean/model/message_test.rb +150 -0
  47. data/test/kvbean/model/post_test.rb +39 -0
  48. data/test/kvbean/redis_test.rb +90 -0
  49. data/test/kvbean_test.rb +7 -0
  50. data/test/support/assertions.rb +17 -0
  51. data/test/test_helper.rb +17 -0
  52. 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
@@ -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,7 @@
1
+ class Array
2
+ unless defined?(deep_dup)
3
+ def deep_dup
4
+ Marshal.load(Marshal.dump(self))
5
+ end
6
+ end
7
+ 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
@@ -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