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