cache2base 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source :rubygems
2
+
3
+ group :spec do
4
+ gem 'dalli'
5
+ gem 'rspec'
6
+ end
data/History.md ADDED
File without changes
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Jason Pearlman
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,57 @@
1
+ cache2base
2
+ ==========
3
+
4
+ cache2base is a high performance Ruby orm for memcache and membase. Just set your models up and stop worrying about keys!
5
+ Also supports maintaining collections of keys for you.
6
+
7
+ Installation and Usage
8
+ ------------------------
9
+
10
+ gem install cache2base
11
+ require 'cache2base'
12
+ Cache2base.init!(:server => Dalli::Client.new('localhost:11211'))
13
+
14
+ And create a model:
15
+
16
+ class MyModel4
17
+ include Cache2base
18
+ set_basename 'mm4'
19
+ set_ttl 300 # 300 seconds
20
+ set_fields :user_id, :first_name, :last_name
21
+
22
+ set_primary_key :user_id
23
+ member_of_collection :first_name, :hash_key => true # People with the same first name!
24
+ end
25
+
26
+ And use that model:
27
+
28
+ m = MyModel4.new(:last_name => "lname", :user_id => 5) # creates an in-memory instance
29
+ m.first_name = 'fname' # all set_fields are given accessors
30
+ m.save # saves to memcache/base
31
+
32
+ m2 = MyModel4.create(:last_name => "lname2", :first_name => 'fname', :user_id => 6) # auto create (.new, .save shortcut)
33
+
34
+ fnames = MyModel4.all(:first_name => "fname") # returns array of model instances that share the same first name
35
+ #=> [#<MyModel4:0x10133cd98 @last_name="lname", @new_instance=false, @first_name="fname", @user_id=5>,
36
+ #=> #<MyModel4:0x10133c4d8 @last_name="lname2", @new_instance=false, @first_name="fname", @user_id=6>]
37
+
38
+ m.delete # delete the first one
39
+
40
+ MyModel4.all(:first_name => "fname") #=> [#<MyModel4:0x101264560 @last_name="lname2", @new_instance=false, @first_name="fname", @user_id=6>]
41
+
42
+ Thanks
43
+ ------------
44
+
45
+ Mike Perham and the dalli project for making the best ruby memcached/membase library - [Dalli](https://github.com/mperham/dalli)
46
+
47
+ OMGPOP for providing a great environment for interesting ruby development - [OMGPOP](http://www.omgpop.com)
48
+
49
+ Author
50
+ ------------
51
+
52
+ Jason Pearlman, jason@omgpop.com / crash2burn@gmail.com
53
+
54
+ Copyright
55
+ -----------
56
+
57
+ Copyright (c) 2011 Jason Pearlman. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require 'rake/clean'
2
+ CLEAN.include "**/*.rbc"
3
+ CLEAN.include "**/.DS_Store"
4
+ CLEAN.include "cache2base-*.gem"
5
+
6
+ require File.expand_path("../lib/cache2base/version", __FILE__)
7
+
8
+ NAME = 'cache2base'
9
+
10
+ # Gem Packaging and Release
11
+
12
+ desc "Packages cache2base"
13
+ task :package=>[:clean] do |p|
14
+ sh %{gem build cache2base.gemspec}
15
+ end
16
+
17
+ desc "Install cache2base gem"
18
+ task :install=>[:package] do
19
+ sh %{sudo gem install ./#{NAME}-#{Cache2base::VERSION} --local}
20
+ end
21
+
22
+ desc "Uninstall cache2base gem"
23
+ task :uninstall=>[:clean] do
24
+ sh %{sudo gem uninstall #{NAME}}
25
+ end
26
+
27
+ desc "Upload cache2base gem to gemcutter"
28
+ task :release=>[:package] do
29
+ sh %{gem push ./#{NAME}-#{Cache2base::VERSION}.gem}
30
+ end
@@ -0,0 +1,26 @@
1
+ require './lib/cache2base/version'
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{cache2base}
5
+ s.version = Cache2base::VERSION
6
+
7
+ s.authors = ["Jason Pearlman"]
8
+ s.date = Time.now.utc.strftime("%Y-%m-%d")
9
+ s.description = %q{A ruby orm for memcache and membase}
10
+ s.email = %q{crash2burn@gmail.com}
11
+ s.files = Dir.glob("lib/**/*") + [
12
+ "LICENSE",
13
+ "README.md",
14
+ "History.md",
15
+ "Rakefile",
16
+ "Gemfile",
17
+ "cache2base.gemspec"
18
+ ]
19
+ s.homepage = %q{http://github.com/OMGPOP/cache2base}
20
+ s.has_rdoc = false
21
+ s.rdoc_options = ["--charset=UTF-8"]
22
+ s.require_paths = ["lib"]
23
+ s.summary = %q{A ruby orm for memcache and membase}
24
+ s.test_files = Dir.glob("test/**/*")
25
+ s.add_development_dependency(%q<rspec>, [">= 0"])
26
+ end
@@ -0,0 +1,253 @@
1
+ module Cache2base
2
+ def self.included(klass) # :nodoc:
3
+ klass.class_eval "@basename ||= self.to_s"
4
+ klass.class_eval "@ttl ||= 0"
5
+ klass.class_eval "@collections ||= []"
6
+ klass.class_eval "@server ||= Cache2base.server"
7
+ klass.extend(ClassMethods)
8
+ end
9
+
10
+ def self.init!(params = {})
11
+ @server = params[:server]
12
+ end
13
+
14
+ def self.server
15
+ @server||MEMBASE
16
+ end
17
+
18
+ def server
19
+ self.class.server
20
+ end
21
+
22
+ def valid_primary_key?
23
+ self.class.primary_key.each { |f| return false if self.send(f).nil? }
24
+ true
25
+ end
26
+
27
+ def initialize(hsh = {}, params = {})
28
+ @new_instance = params[:new_instance].nil? ? true : params[:new_instance]
29
+ hsh.each_pair do |k,v|
30
+ self.send(:"#{k}=", v)
31
+ end
32
+ end
33
+
34
+ def new?
35
+ @new_instance
36
+ end
37
+
38
+ def save
39
+ raise "Invalid Primary Key" unless valid_primary_key?
40
+ add_to_collections
41
+ result = @new_instance ? server.add(self.key, self.marshal, self.class.ttl) : server.set(self.key, self.marshal, self.class.ttl)
42
+ raise 'Duplicate Primary Key' unless result
43
+ @new_instance = false
44
+ self
45
+ end
46
+
47
+ def delete
48
+ remove_from_collections
49
+ server.delete(self.key)
50
+ end
51
+
52
+ def marshal
53
+ Marshal.dump(self.field_hash)
54
+ end
55
+
56
+ def field_hash
57
+ o = {}
58
+ self.class.fields.each do |field|
59
+ o[field] = self.send(field) if !self.send(field).nil?
60
+ end
61
+ o
62
+ end
63
+
64
+ def collection_key(field)
65
+ self.class.collection_key(Hash[Array(field).collect {|f| [f, self.send(f)]}])
66
+ end
67
+
68
+ def add_to_collections
69
+ self.class.collections.each do |field|
70
+ raise "Could not add field #{field} collection" unless add_to_collection(field)
71
+ end
72
+ end
73
+
74
+ def add_to_collection(field, loops = 0)
75
+ Array(field).each { |f| return 'could_not_add' if self.send(f).nil? } # still evaluates to true, so add_to_collections does not fail
76
+ success = server.cas(collection_key(field), self.class.ttl) do |value|
77
+ value << self.key unless value.include?(self.key)
78
+ value
79
+ end
80
+
81
+ unless success
82
+ if success.nil?
83
+ success = server.add(collection_key(field), [self.key], self.class.ttl)
84
+ if success
85
+ return true
86
+ else
87
+ return loops < 5 ? add_to_collection(field, loops+1) : false
88
+ end
89
+ else
90
+ return loops < 5 ? add_to_collection(field, loops+1) : false
91
+ end
92
+ end
93
+
94
+ success
95
+ end
96
+
97
+ def remove_from_collections
98
+ self.class.collections.each do |field|
99
+ raise "Could not remove field #{field} collection" unless remove_from_collection(field)
100
+ end
101
+ end
102
+
103
+ def remove_from_collection(field, loops=0)
104
+ Array(field).each { |f| return 'could_not_add' if self.send(f).nil? }
105
+ #return 'could_not_remove' if self.send(field).nil? # still evaluates to true, so remove_from_collections does not fail
106
+ success = server.cas(collection_key(field), self.class.ttl) do |value|
107
+ value.delete(self.key)
108
+ value
109
+ end
110
+
111
+ unless success
112
+ if success.nil?
113
+ return true # return true because theres no collection to remove from
114
+ else
115
+ return loops < 5 ? remove_from_collection(field, loops+1) : false # race conditions
116
+ end
117
+ end
118
+
119
+ success
120
+ end
121
+
122
+ module ClassMethods
123
+ def ttl
124
+ @ttl
125
+ end
126
+
127
+ def set_ttl(i)
128
+ @ttl = i.to_i
129
+ end
130
+
131
+ def set_basename(name)
132
+ @basename = name.to_s
133
+ end
134
+
135
+ def primary_key
136
+ @primary_key
137
+ end
138
+
139
+ def server
140
+ @server
141
+ end
142
+
143
+ def set_primary_key(mk, params = {})
144
+ @primary_key = Array(mk)
145
+ #o = '#{self.class}'
146
+ #c = "#{self}"
147
+ #h = "#{self}"
148
+ o = []
149
+ c = []
150
+ h = []
151
+ Array(mk).each_with_index do |v, i|
152
+ o << '#{self.send(:'+v.to_s+').to_s.gsub(\'_\',\'-\')}'
153
+ c << '#{Array(pk)['+i.to_s+'].to_s.gsub(\'_\',\'-\')}'
154
+ h << '#{pk[0][:'+v.to_s+'].to_s.gsub(\'_\',\'-\')}'
155
+ end
156
+
157
+ o = "#{@basename}_\#{#{params[:hash_key] ? "self.class.hash_key(\"#{o.join("_")}\")" : "\"#{o.join("_")}\""}}"
158
+ c = "#{@basename}_\#{#{params[:hash_key] ? "hash_key(\"#{c.join("_")}\")" : "\"#{c.join("_")}\""}}"
159
+ h = "#{@basename}_\#{#{params[:hash_key] ? "hash_key(\"#{h.join("_")}\")" : "\"#{h.join("_")}\""}}"
160
+
161
+ class_eval "def key; \"#{o}\"; end"
162
+ class_eval "def self.key(*pk); pk.first.is_a?(Hash) ? \"#{h}\" : \"#{c}\"; end"
163
+ end
164
+
165
+ def basename
166
+ @basename
167
+ end
168
+
169
+ def hash_key(k)
170
+ Digest::SHA1.hexdigest(k.to_s)
171
+ end
172
+
173
+ def set_fields(*fields)
174
+ @fields = @fields ? (@fields + (fields)) : (fields)
175
+ fields.each do |field|
176
+ class_eval "attr_accessor :#{field}"
177
+ end
178
+ end
179
+
180
+ def set_field(field, params)
181
+ @fields ||= []
182
+ @fields << field
183
+ @field_meta ||= {}
184
+ if params[:hash]
185
+ @field_meta[field] ||= {}
186
+ @field_meta[field][:hash] = true
187
+ end
188
+ class_eval "attr_accessor :#{field}"
189
+ end
190
+
191
+ def uses_hash?(field)
192
+ @field_meta[field] && @field_meta[field][:hash]
193
+ end
194
+
195
+ def member_of_collection(fields, params = {})
196
+ fields = Array(fields).sort { |a,b| a.to_s <=> b.to_s }
197
+ @collections ||= []
198
+ @collections << fields
199
+ @hashed_collections ||= {}
200
+ @hashed_collections[fields.join(",").to_s] = true if params[:hash_key]
201
+ end
202
+
203
+ def collections
204
+ @collections
205
+ end
206
+
207
+ def collection_key(vhsh)
208
+ keys = vhsh.keys.sort {|a,b| a.to_s <=> b.to_s}
209
+ "#{@basename}_c_#{hash_collection?(keys) ? hash_key(keys.collect {|field| vhsh[field].to_s.gsub('_','-') }.join("_")) : keys.collect {|field| vhsh[field].to_s.gsub('_','-') }.join("_")}"
210
+ end
211
+
212
+ def fields
213
+ @fields
214
+ end
215
+
216
+ def hash_collection?(field)
217
+ @hashed_collections[Array(field).join(',').to_s]
218
+ end
219
+
220
+ def find(fields, params = {})
221
+ o = server.get(key(fields))
222
+ return nil unless o
223
+ self.from_hash(Marshal.load(o))
224
+ end
225
+
226
+ def find_by_key(key)
227
+ o = server.get(key)
228
+ return nil unless o
229
+ self.from_hash(Marshal.load(o))
230
+ end
231
+
232
+ def find_by_keys(keys)
233
+ hsh = server.get_multi(keys)
234
+ keys.collect do |key| # to get it back in order since get_multi results in a hash
235
+ self.from_hash(Marshal.load(hsh[key]))
236
+ end.compact
237
+ end
238
+
239
+ def from_hash(hsh)
240
+ self.new(hsh, :new_instance => false)
241
+ end
242
+
243
+ def create(params)
244
+ o = self.new(params)
245
+ o.save
246
+ end
247
+
248
+ def all(fields, params = {})
249
+ arr = server.get(collection_key(fields))
250
+ find_by_keys(Array(arr)).compact
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,3 @@
1
+ module Cache2base
2
+ VERSION = '0.0.5'
3
+ end
data/lib/cache2base.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'digest/sha1'
2
+ require 'cache2base/core.rb'
3
+ require 'cache2base/version.rb'
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cache2base
3
+ version: !ruby/object:Gem::Version
4
+ hash: 21
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 5
10
+ version: 0.0.5
11
+ platform: ruby
12
+ authors:
13
+ - Jason Pearlman
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-05-23 00:00:00 -04:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 3
30
+ segments:
31
+ - 0
32
+ version: "0"
33
+ type: :development
34
+ version_requirements: *id001
35
+ description: A ruby orm for memcache and membase
36
+ email: crash2burn@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files: []
42
+
43
+ files:
44
+ - lib/cache2base/core.rb
45
+ - lib/cache2base/version.rb
46
+ - lib/cache2base.rb
47
+ - LICENSE
48
+ - README.md
49
+ - History.md
50
+ - Rakefile
51
+ - Gemfile
52
+ - cache2base.gemspec
53
+ has_rdoc: true
54
+ homepage: http://github.com/OMGPOP/cache2base
55
+ licenses: []
56
+
57
+ post_install_message:
58
+ rdoc_options:
59
+ - --charset=UTF-8
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ none: false
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ hash: 3
68
+ segments:
69
+ - 0
70
+ version: "0"
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ requirements: []
81
+
82
+ rubyforge_project:
83
+ rubygems_version: 1.3.7
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: A ruby orm for memcache and membase
87
+ test_files: []
88
+