active_store 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .idea/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm use jruby-1.6.5@active_store --create
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in active_store.gemspec
4
+ gem 'activesupport', '3.0.11'
5
+
6
+ platforms :jruby do
7
+ gem 'json-jruby', :require => 'json'
8
+ end
9
+
10
+ gemspec
data/README ADDED
File without changes
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require "bundler/gem_tasks"
2
+ # Get your spec rake tasks working in RSpec 2.0
3
+
4
+ require 'rspec/core/rake_task'
5
+
6
+ desc "Run specs"
7
+ RSpec::Core::RakeTask.new do |t|
8
+ t.pattern = "./spec/**/*_spec.rb" # don't need this, it's default.
9
+ # Put spec opts in a file named .rspec in root
10
+ end
11
+
12
+
13
+ RUBIES = %w[jruby-1.6.5 1.9.2-p290]
14
+ desc "Run tests with ruby 1.8.7 and 1.9.2"
15
+ task :default do
16
+ RUBIES.each do |ruby|
17
+ sh "rvm #{ruby}@active_store rake spec"
18
+ end
19
+ end
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "active_store/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "active_store"
7
+ s.version = ActiveStore::VERSION
8
+ s.authors = ["Petter Remen", "Jean-Louis Giordano"]
9
+ s.email = ["petter@icehouse.se", "jean-louis@icehouse.se"]
10
+ s.homepage = ""
11
+ s.summary = %q{A active record-like wrapper for memcached protocol}
12
+ s.description = %q{}
13
+
14
+ s.rubyforge_project = "active_store"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "rake"
24
+ s.add_runtime_dependency "dalli", '1.1.3'
25
+ s.add_runtime_dependency "activesupport"
26
+ end
@@ -0,0 +1,188 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
3
+ module ActiveStore
4
+ class Base
5
+
6
+ def initialize(params = {})
7
+ set_attributes(params)
8
+ @created_at ||= Time.now
9
+ end
10
+
11
+ def ==(another_object)
12
+ (self.class.attributes - [:created_at]).all? do |attribute|
13
+ self.send(attribute) == another_object.send(attribute)
14
+ end
15
+ end
16
+
17
+ def attributes
18
+ self.class.attributes.inject(HashWithIndifferentAccess.new) do |accu, attribute|
19
+ send(attribute).nil? ? accu : accu.merge({attribute => send(attribute)})
20
+ end
21
+ end
22
+
23
+ def set_attributes(params = {})
24
+ params = params.with_indifferent_access
25
+ self.class.attributes.each do |attribute|
26
+ write_attribute(attribute, params[attribute])
27
+ end
28
+ end
29
+
30
+ def update_attribute(attribute, value)
31
+ self.send "#{attribute}=", value
32
+ save
33
+ end
34
+
35
+ def update_attributes(params)
36
+ params.each do |key, value|
37
+ send("#{key}=", value)
38
+ end
39
+ save
40
+ end
41
+
42
+ def reload
43
+ raise NoIdError.new("Could not reload without id.") if (id.nil? || id.empty?)
44
+ set_attributes(connection.get(id))
45
+ self
46
+ end
47
+
48
+ def save(ttl = self.class.default_ttl)
49
+ return false if (id.nil? || id.empty?)
50
+ connection.set(id, self.attributes, ttl)
51
+ true
52
+ end
53
+
54
+ def save!(ttl = self.class.default_ttl)
55
+ save(ttl) or raise NoIdError.new("Could not save without id.")
56
+ end
57
+
58
+ def connection
59
+ self.class.connection
60
+ end
61
+
62
+ protected
63
+
64
+ def write_attribute (attribute, value)
65
+ instance_variable_set("@#{attribute}", value)
66
+ end
67
+
68
+ def read_attribute (attribute)
69
+ instance_variable_get("@#{attribute}")
70
+ end
71
+
72
+ class << self
73
+ attr_reader :attributes
74
+
75
+ def define_attributes(*args)
76
+ @attributes ||= []
77
+ @attributes += args
78
+ @attributes |= [:id, :created_at]
79
+ attr_accessor *@attributes
80
+ end
81
+
82
+ def create(params = {})
83
+ obj = new(params)
84
+ obj.save!
85
+ obj
86
+ end
87
+
88
+ # Allows you to update an ActiveStore object within a
89
+ # CAS "transaction"
90
+ #
91
+ # Example:
92
+ # Model.cas_update(3) do |instance|
93
+ # instance.value += 5
94
+ # end
95
+ #
96
+ # Returns nil if the object could not be found.
97
+ # Returns false if the CAS operation failed due to
98
+ # the object being modified in the background.
99
+ def cas_update(id, ttl = default_ttl, &block)
100
+ block = attributes_block_wrapper(&block)
101
+ connection.cas(id, ttl, &block)
102
+ end
103
+
104
+ # Like cas_update, but also creates a new object if none could be
105
+ # found. The given block is guaranteed to be executed at most once.
106
+ #
107
+ # Example:
108
+ # Model.cas_update_or_create(3) do |instance|
109
+ # instance.value ||= 0
110
+ # instance.value += 5
111
+ # end
112
+ #
113
+ # Returns false if the CAS operation failed due to
114
+ # the object having been modified by another process, or if
115
+ # someone managed to create an object between the CAS and ADD
116
+ # operations
117
+ def cas_update_or_create(id, ttl = default_ttl, &block)
118
+ res = cas_update(id, ttl, &block)
119
+ res.nil? ? create_with_block(id, ttl, &block) : res
120
+ end
121
+
122
+ def stm_update_or_create (id, ttl = default_ttl, &block)
123
+ begin
124
+ success = cas_update_or_create(id, ttl = default_ttl, &block)
125
+ end until success
126
+ true
127
+ end
128
+
129
+ def create_with_block(id, ttl = default_ttl, &block)
130
+ block = attributes_block_wrapper(&block)
131
+ connection.add(id, block.call({:id => id}), ttl)
132
+ end
133
+
134
+ def find(id)
135
+ return nil if (id.nil? || id.empty?)
136
+ params = connection.get(id)
137
+ params ? new(params) : nil
138
+ end
139
+
140
+ def find_or_initialize_by_id(id)
141
+ find(id) || new(:id => id)
142
+ end
143
+
144
+ def connection
145
+ @connection ||= Connection.new(connection_string, namespace, default_ttl)
146
+ end
147
+
148
+ def connection_string= (connection_string)
149
+ @connection = nil
150
+ @connection_string = connection_string
151
+ end
152
+
153
+ def connection_string
154
+ @connection_string ||= (superclass.connection_string unless self == Base)
155
+ end
156
+
157
+ def namespace= (namespace)
158
+ @connection = nil
159
+ @namespace = namespace
160
+ end
161
+
162
+ def namespace
163
+ @namespace ||= self.name
164
+ end
165
+
166
+ def default_ttl= (default_ttl)
167
+ @connection = nil
168
+ @default_ttl = default_ttl
169
+ end
170
+
171
+ def default_ttl
172
+ @default_ttl ||= 30 * 24 * 3600
173
+ end
174
+
175
+ private
176
+ def attributes_block_wrapper(&block)
177
+ lambda do |attrs|
178
+ instance = new(attrs)
179
+ block.call(instance)
180
+ instance.attributes
181
+ end
182
+ end
183
+ end
184
+
185
+ class NoIdError < StandardError
186
+ end
187
+ end
188
+ end
@@ -0,0 +1,91 @@
1
+ require 'dalli'
2
+ require 'json'
3
+
4
+ module ActiveStore
5
+ class Connection
6
+ attr_accessor :connection_string, :namespace, :default_ttl
7
+
8
+ def initialize(connection_string, namespace, default_ttl)
9
+ @connection_string = connection_string
10
+ @namespace = namespace
11
+ @default_ttl = default_ttl
12
+ end
13
+
14
+ class ::Dalli::Client
15
+ # Monkey patch to avoid having a separate branch
16
+ def validate_key (key)
17
+ raise ArgumentError, "key cannot be blank" if key.nil? || key.strip.size == 0
18
+ raise ArgumentError, "key too long #{key.inspect}" if key.length > 250
19
+ end
20
+ end
21
+
22
+ attr_writer :store
23
+
24
+ def flush_all
25
+ store.flush_all
26
+ end
27
+
28
+ def incr(*args)
29
+ store.incr(*args)
30
+ end
31
+
32
+ def delete(*args)
33
+ store.delete(*args)
34
+ end
35
+
36
+ def get (key)
37
+ load(get_raw(key))
38
+ end
39
+
40
+ def set (key, value, ttl=default_ttl)
41
+ set_raw(key, dump(value), ttl)
42
+ end
43
+
44
+ def add (key, value, ttl=default_ttl)
45
+ add_raw(key, dump(value), ttl)
46
+ end
47
+
48
+ def cas (key, ttl=default_ttl)
49
+ store.cas(key, ttl, :raw => true) do |value|
50
+ raw_output = yield load(value)
51
+ dump(raw_output)
52
+ end
53
+ end
54
+
55
+ def get_raw(key)
56
+ store.get(key)
57
+ end
58
+
59
+ def set_raw(key, value, ttl=default_ttl)
60
+ store.set(key, value, ttl, :raw => true)
61
+ end
62
+
63
+ def add_raw(key, value, ttl=default_ttl)
64
+ store.add(key, value, ttl, :raw => true)
65
+ end
66
+
67
+ def store
68
+ unless @store
69
+ @store = Dalli::Client.new(connection_string, :namespace => namespace, :expires_in => default_ttl, :socket_timeout => 1)
70
+ begin
71
+ @store.get('test')
72
+ rescue Dalli::RingError => e
73
+ @store = nil
74
+ raise e
75
+ end
76
+ end
77
+ @store
78
+ end
79
+
80
+ private
81
+
82
+ # Ugly tricks to fool parsers that don't allow literals
83
+ def dump (value)
84
+ JSON.dump([value])[1...-1]
85
+ end
86
+
87
+ def load (value)
88
+ JSON.load("[#{value}]").first
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveStore
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,6 @@
1
+ require "active_store/version"
2
+ require "active_store/base"
3
+ require "active_store/connection"
4
+
5
+ module ActiveStore
6
+ end
@@ -0,0 +1,378 @@
1
+ require 'spec_helper'
2
+
3
+ describe ActiveStore::Base do
4
+ before do
5
+ class ItemStore < ActiveStore::Base
6
+ define_attributes :a1, :a2
7
+ self.namespace = "item"
8
+ self.default_ttl = 0
9
+ self.connection_string = nil
10
+ end
11
+ end
12
+
13
+ describe ".connection_string" do
14
+ it "defaults to Base value if nil" do
15
+ ActiveStore::Base.connection_string = "some_string"
16
+ ItemStore.connection_string.should == "some_string"
17
+ ItemStore.connection_string = "some other string"
18
+ ActiveStore::Base.connection_string.should == "some_string"
19
+ ItemStore.connection_string.should == "some other string"
20
+ end
21
+ end
22
+ describe ".namespace" do
23
+ it "forces a new connection" do
24
+ connection = ItemStore.connection
25
+ ItemStore.namespace = "item"
26
+ ItemStore.connection.should_not be_eql connection
27
+ end
28
+ it "sends itself as a parameter to Connection.new" do
29
+ ItemStore.connection.namespace.should == "item"
30
+ ItemStore.namespace = "foo"
31
+ ItemStore.connection.namespace.should == "foo"
32
+ end
33
+ it "defaults to the name of the class" do
34
+ ItemStore.namespace = nil
35
+ ItemStore.namespace.should == 'ItemStore'
36
+ end
37
+ end
38
+
39
+ describe ".default_ttl" do
40
+ it "forces a new connection" do
41
+ connection = ItemStore.connection
42
+ ItemStore.default_ttl = 2
43
+ ItemStore.connection.should_not be_eql connection
44
+ end
45
+ it "sends itself as a parameter to Connection.new" do
46
+ ItemStore.connection.default_ttl.should == 0
47
+ ItemStore.default_ttl = 2
48
+ ItemStore.connection.default_ttl.should == 2
49
+ end
50
+ it "defaults to 30 days" do
51
+ ItemStore.default_ttl = nil
52
+ ItemStore.default_ttl.should == 30 * 24 * 3600
53
+ end
54
+ end
55
+
56
+ it "lists attibute names" do
57
+ ItemStore.attributes.should include :a1, :a2
58
+ end
59
+
60
+ it "provides accessors for its attributes" do
61
+ item = ItemStore.new
62
+ item.a1.should be_nil
63
+ (item.a1 = "new_value").should == "new_value"
64
+ item.a1.should == "new_value"
65
+ end
66
+
67
+ describe ".define_attributes" do
68
+ it "should add id and created_at in attributes" do
69
+ ItemStore.attributes.should include :id, :created_at
70
+ end
71
+ it "should add id and created_at in its instance accessors" do
72
+ ItemStore.new.methods.map(&:to_sym).should include :id, :id=, :created_at, :created_at=
73
+ end
74
+ end
75
+
76
+ describe "#initialize" do
77
+ it "initializes given attributes" do
78
+ params = { :a1 => "one", :a2 => "two" }
79
+ item = ItemStore.new params
80
+ item.attributes[:a1].should == params[:a1]
81
+ item.attributes[:a2].should == params[:a2]
82
+ end
83
+ it "initializes attributes even when the params keys are strings" do
84
+ params = { "a1" => "one", "a2" => "two" }
85
+ item = ItemStore.new params
86
+ item.attributes[:a1].should == params["a1"]
87
+ item.attributes[:a2].should == params["a2"]
88
+ end
89
+ it "doesn't require attributes" do
90
+ ItemStore.new.a1.should be_nil
91
+ end
92
+ it "sets created_at if not given in params" do
93
+ ItemStore.new.created_at.should be_within(1).of(Time.now)
94
+ end
95
+ it "sets created_at if given" do
96
+ ItemStore.new(:created_at => "some time").created_at.should == "some time"
97
+ end
98
+ it "can set attribute to false" do
99
+ item = ItemStore.new "a1" => false
100
+ item.attributes[:a1].should == false
101
+ end
102
+ end
103
+
104
+ describe "#==" do
105
+ it "returns true if all attributes are the same" do
106
+ params = { :a1 => "one", :a2 => "two" }
107
+ (ItemStore.new(params) == ItemStore.new(params)).should be_true
108
+ end
109
+ it "returns false any of the attributes are different" do
110
+ params1 = { :a1 => "one", :a2 => "two"}
111
+ params2 = { :a1 => "different", :a2 => "two" }
112
+ (ItemStore.new(params1) == ItemStore.new(params2)).should be_false
113
+ end
114
+ end
115
+
116
+ describe "#update_attribute" do
117
+ it "updates given attribute and saves" do
118
+ item = ItemStore.new :id => "myid"
119
+ item.update_attribute(:a1, "new_value").should be_true
120
+ item.reload.a1.should be_true
121
+ end
122
+ it "returns false if id is missing" do
123
+ item = ItemStore.new
124
+ item.update_attribute(:a1, "new_value").should be_false
125
+ end
126
+ it "raises if attribute is missing" do
127
+ item = ItemStore.new
128
+ expect { item.update_attribute :apa, true }.to raise_exception(NoMethodError)
129
+ end
130
+ end
131
+
132
+ describe "#update_attributes" do
133
+ it "updates all given attributes" do
134
+ item = ItemStore.new :id => "myid"
135
+ item.update_attributes(:a1 => "one", :a2 => "two").should == true
136
+ item.reload
137
+ item.a1.should == "one"
138
+ item.a2.should == "two"
139
+ end
140
+ it "returns false if id is missing" do
141
+ item = ItemStore.new
142
+ item.update_attributes(:a1 => "new_value").should be_false
143
+ end
144
+ end
145
+
146
+ describe ".cas_update" do
147
+ context "when the given id does not exist" do
148
+ it "returns nil" do
149
+ ItemStore.cas_update("nonexistent").should be_nil
150
+ end
151
+ it "should not run the block" do
152
+ ItemStore.cas_update("nonexistent") do
153
+ true.should be_false
154
+ end
155
+ end
156
+ end
157
+ context "when the record has been updated before CAS set operation" do
158
+ it "returns false" do
159
+ ItemStore.create :id => "existent", :a1 => "foo"
160
+ ItemStore.cas_update("existent") do |item|
161
+ same_item = ItemStore.find("existent")
162
+ same_item.a1 = "bar"
163
+ same_item.save
164
+ item.a1 = "baz"
165
+ end.should be_false
166
+ end
167
+ it "doesn't modify the record" do
168
+ ItemStore.create :id => "existent", :a1 => "foo"
169
+ ItemStore.cas_update("existent") do |item|
170
+ same_item = ItemStore.find("existent")
171
+ same_item.a1 = "bar"
172
+ same_item.save
173
+ item.a1 = "baz"
174
+ end
175
+ ItemStore.find("existent").a1.should == "bar"
176
+ end
177
+ end
178
+ context "otherwise" do
179
+ it "returns true" do
180
+ ItemStore.create :id => "existent"
181
+ ItemStore.cas_update("existent") do |item|
182
+ item.a1 = "baz"
183
+ end.should be_true
184
+ end
185
+ it "updates the record" do
186
+ ItemStore.create :id => "existent"
187
+ ItemStore.cas_update("existent") do |item|
188
+ item.a1 = "baz"
189
+ end
190
+ ItemStore.find("existent").a1.should == "baz"
191
+ end
192
+ end
193
+ end
194
+
195
+ describe "cas_update_or_create" do
196
+ it "tries to update the record using cas_update" do
197
+ proc_mock = Proc.new {}
198
+ ItemStore.should_receive(:cas_update).with("old", ItemStore.default_ttl, &proc_mock).and_return(true)
199
+ ItemStore.cas_update_or_create("old", &proc_mock)
200
+ end
201
+
202
+ it "creates the record create_with_block if the CAS operation fails due to missing record" do
203
+ proc_mock = Proc.new {}
204
+ ItemStore.stub!(:cas_update).and_return(nil)
205
+ ItemStore.should_receive(:create_with_block).with("old", ItemStore.default_ttl, &proc_mock)
206
+ ItemStore.cas_update_or_create("old", &proc_mock)
207
+ end
208
+
209
+ it "returns false if update_cas returns false" do
210
+ proc_mock = Proc.new {}
211
+ ItemStore.stub!(:cas_update).and_return(false)
212
+ ItemStore.cas_update_or_create("foo", &proc_mock).should be_false
213
+ end
214
+
215
+ it "returns false if create_with_block returns false" do
216
+ proc_mock = Proc.new {}
217
+ ItemStore.stub!(:cas_update).and_return(nil)
218
+ ItemStore.stub!(:create_with_block).and_return(false)
219
+ ItemStore.cas_update_or_create("foo", &proc_mock).should be_false
220
+ end
221
+
222
+ it "returns true if the record was successfully created" do
223
+ ItemStore.cas_update_or_create("foo") {}.should be_true
224
+ end
225
+
226
+ it "returns true if the record was successfully modified" do
227
+ ItemStore.create(:id => "foo")
228
+ ItemStore.cas_update_or_create("foo") {}.should be_true
229
+ end
230
+
231
+ end
232
+
233
+ describe "stm_update_or_create" do
234
+ it "performs a cas_update_or_create" do
235
+ to_run = Proc.new(){}
236
+ ItemStore.should_receive(:cas_update_or_create) do |id, &block|
237
+ id.should == :some_id
238
+ block.should == to_run
239
+ true
240
+ end
241
+ ItemStore.stm_update_or_create(:some_id, &to_run).should be_true
242
+ end
243
+ it "performs a cas_update_or_create until success" do
244
+ ItemStore.should_receive(:cas_update_or_create).exactly(3).times.and_return(false, false, true)
245
+ ItemStore.stm_update_or_create(:some_id){}.should be_true
246
+ end
247
+ end
248
+
249
+ describe "#create_with_block" do
250
+ it "creates a new record if it doesn't exist" do
251
+ ItemStore.create_with_block("foo") do |item|
252
+ item.a1 = "bar"
253
+ end
254
+ ItemStore.find("foo").a1.should == "bar"
255
+ end
256
+
257
+ context "if the record already exists" do
258
+ before do
259
+ ItemStore.create(:id => "foo", :a1 => "bar")
260
+ end
261
+
262
+ it "returns false" do
263
+ ItemStore.create_with_block("foo") {}.should be_false
264
+ end
265
+
266
+ it "doesn't update the record" do
267
+ ItemStore.create_with_block("foo") do |item|
268
+ item.a1 = "baz"
269
+ end
270
+ ItemStore.find("foo").a1.should == "bar"
271
+ end
272
+ end
273
+ end
274
+
275
+ describe "#reload" do
276
+ it "reloads attributes from db" do
277
+ item = ItemStore.new :id => "myid"
278
+ ItemStore.connection.set("myid", :a1 => "new value")
279
+ item.reload
280
+ item.a1.should == "new value"
281
+ end
282
+ it "raises if no id is set" do
283
+ expect { ItemStore.new.reload }.to raise_exception(ActiveStore::Base::NoIdError)
284
+ end
285
+ it "raises if no id is empty string" do
286
+ expect { ItemStore.new(:id => "").reload }.to raise_exception(ActiveStore::Base::NoIdError)
287
+ end
288
+ end
289
+
290
+ describe "#save" do
291
+ it "saves attribute do db" do
292
+ ItemStore.find("myid").should be_nil
293
+ item = ItemStore.new :id =>"myid", :a1 => "one", :a2 => "two"
294
+ item.save.should be_true
295
+ item.created_at.should_not be_nil
296
+ ItemStore.find("myid").should == item
297
+ end
298
+ it "returns false if id is nil" do
299
+ item = ItemStore.new :id => nil
300
+ item.save.should be_false
301
+ end
302
+ it "saves with a given ttl" do
303
+ item = ItemStore.new :id => "myid", :a1 => "one", :a2 => "two"
304
+ ItemStore.connection.should_receive(:set).with(anything, anything, 2 * 24 * 3600)
305
+ item.save(2 * 24 * 3600)
306
+ end
307
+ it "saves with default ttl if not specified otherwise" do
308
+ item = ItemStore.new :id => "myid", :a1 => "one", :a2 => "two"
309
+ ItemStore.connection.should_receive(:set).with(anything, anything, ItemStore.default_ttl)
310
+ item.save
311
+ end
312
+ end
313
+
314
+ describe "#save!" do
315
+ it "calls save" do
316
+ item = ItemStore.new :id => "myid"
317
+ item.should_receive(:save).and_return(true)
318
+ item.save!
319
+ end
320
+ it "it returns true on successfull save" do
321
+ item = ItemStore.new :id => "myid"
322
+ item.save!.should be_true
323
+ end
324
+ it "raises if id is nil" do
325
+ item = ItemStore.new :id => nil
326
+ expect { item.save! }.to raise_exception(ActiveStore::Base::NoIdError)
327
+ end
328
+ it "saves with a given ttl" do
329
+ item = ItemStore.new :id => "myid"
330
+ ItemStore.connection.should_receive(:set).with(anything, anything, 2 * 24 * 3600)
331
+ item.save!(2 * 24 * 3600)
332
+ end
333
+ it "saves with default ttl if not specified otherwise" do
334
+ item = ItemStore.new :id => "myid"
335
+ ItemStore.connection.should_receive(:set).with(anything, anything, ItemStore.default_ttl)
336
+ item.save!
337
+ end
338
+ end
339
+
340
+ describe ".create" do
341
+ it "creates an item and saves it" do
342
+ item = ItemStore.create :id => "myid", :a1 => "one", :a2 => "two"
343
+ ItemStore.find("myid").should == item
344
+ end
345
+ it "returns the created campaign" do
346
+ ItemStore.create(:id => "myid").should be_a(ItemStore)
347
+ end
348
+ end
349
+
350
+ describe ".find" do
351
+ it "returns nil for nil" do
352
+ ItemStore.find(nil).should == nil
353
+ end
354
+ it "returns nil for empty string" do
355
+ ItemStore.find("").should == nil
356
+ end
357
+ it "returns nil if no match" do
358
+ ItemStore.find("no_match").should == nil
359
+ end
360
+ it "returns item if match" do
361
+ item = ItemStore.create :id => "match"
362
+ ItemStore.find("match").should == item
363
+ end
364
+ end
365
+
366
+ describe ".find_or_initialize_by_id" do
367
+ it "returns found dossier if present" do
368
+ ItemStore.should_receive(:find).with("some_id").and_return("found_dossier")
369
+ ItemStore.find_or_initialize_by_id("some_id").should == "found_dossier"
370
+ end
371
+ it "initializes a new dossier if none is present" do
372
+ ItemStore.should_receive(:find).with("some_id").and_return(nil)
373
+ ItemStore.find_or_initialize_by_id("some_id").id.should == "some_id"
374
+ end
375
+ end
376
+
377
+ end
378
+
@@ -0,0 +1,86 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe ActiveStore::Connection do
5
+ before do
6
+ @connection = ActiveStore::Connection.new(nil, 'test', 30 * 24 * 3600)
7
+ end
8
+
9
+ it "it sets and gets values from db" do
10
+ @connection.set "test", "foo" => "boo", "sha" => "bada"
11
+ @connection.get("test").should == {"foo" => "boo", "sha" => "bada"}
12
+ end
13
+
14
+ it "sets data as JSON" do
15
+ @connection.set "test", :foo => "boo", :sha => "bada"
16
+ @connection.get_raw("test").should == '{"foo":"boo","sha":"bada"}'
17
+ end
18
+
19
+ it "adds data as JSON" do
20
+ @connection.add("test", :foo => "boo", :sha => "bada").should be_true
21
+ @connection.get_raw("test").should == '{"foo":"boo","sha":"bada"}'
22
+ end
23
+
24
+ it "get returns nil if the key doesn't exist" do
25
+ @connection.get("asdfgasda").should be_nil
26
+ end
27
+
28
+ it "get does not create Time-like objects from Time-like strings" do
29
+ time_str = Time.now.to_s
30
+ date_str = Date.today.to_s
31
+ @connection.set("test", "foo" => time_str, "bar" => date_str)
32
+ @connection.get("test").should == {
33
+ "foo" => time_str,
34
+ "bar" => date_str
35
+ }
36
+ end
37
+
38
+ it "cas data as JSON" do
39
+ @connection.set_raw("test", '{"foo":"boo","sha":"bada"}', 1)
40
+ @connection.cas "test" do |data|
41
+ data.should == {"foo" => "boo", "sha" => "bada"}
42
+ data["shi"] = "bidi"
43
+ data
44
+ end
45
+ @connection.get_raw("test").should == '{"foo":"boo","sha":"bada","shi":"bidi"}'
46
+ end
47
+
48
+ it "gets data as JSON" do
49
+ @connection.set_raw("test", '{"foo":"boo","sha":"bada"}', 1)
50
+ @connection.get("test").should == {"foo" => "boo", "sha" => "bada"}
51
+ end
52
+
53
+ it "Works with increment" do
54
+ @connection.set("test", 10)
55
+ @connection.incr("test").should == 11
56
+ end
57
+
58
+ it "Works with strings" do
59
+ @connection.set("test", "some value")
60
+ @connection.get("test").should == "some value"
61
+ end
62
+
63
+ it "supports non-ascii keys" do
64
+ @connection.set("testäöå", "3")
65
+ @connection.get("testäöå").should == "3"
66
+ end
67
+
68
+ it "supports spaces in keys" do
69
+ @connection.set(" foo\n", "2")
70
+ @connection.get(" foo\n").should == "2"
71
+ end
72
+
73
+ describe "#store" do
74
+ it "returns a Dalli::Client" do
75
+ @connection.store = nil
76
+ @connection.store.should be_instance_of(Dalli::Client)
77
+ end
78
+
79
+ it "raises an exception when it cannot connect" do
80
+ connection = ActiveStore::Connection.new("localhost:1234", "test", 0)
81
+ expect {connection.store}.to raise_error
82
+ # Tests that we clear the memoization to try again
83
+ expect {connection.store}.to raise_error
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,9 @@
1
+ require 'active_store'
2
+
3
+ RSpec.configure do |config|
4
+ config.before(:each) do
5
+ ActiveStore::Base.connection_string = nil
6
+ ActiveStore::Base.connection.flush_all
7
+ end
8
+ end
9
+
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active_store
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Petter Remen
9
+ - Jean-Louis Giordano
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2012-03-27 00:00:00.000000000Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rspec
17
+ requirement: &70327179856160 !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: '0'
23
+ type: :development
24
+ prerelease: false
25
+ version_requirements: *70327179856160
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: &70327179855740 !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ! '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: *70327179855740
37
+ - !ruby/object:Gem::Dependency
38
+ name: dalli
39
+ requirement: &70327179855240 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - =
43
+ - !ruby/object:Gem::Version
44
+ version: 1.1.3
45
+ type: :runtime
46
+ prerelease: false
47
+ version_requirements: *70327179855240
48
+ - !ruby/object:Gem::Dependency
49
+ name: activesupport
50
+ requirement: &70327179854820 !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ! '>='
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ type: :runtime
57
+ prerelease: false
58
+ version_requirements: *70327179854820
59
+ description: ''
60
+ email:
61
+ - petter@icehouse.se
62
+ - jean-louis@icehouse.se
63
+ executables: []
64
+ extensions: []
65
+ extra_rdoc_files: []
66
+ files:
67
+ - .gitignore
68
+ - .rspec
69
+ - .rvmrc
70
+ - Gemfile
71
+ - README
72
+ - Rakefile
73
+ - active_store.gemspec
74
+ - lib/active_store.rb
75
+ - lib/active_store/base.rb
76
+ - lib/active_store/connection.rb
77
+ - lib/active_store/version.rb
78
+ - spec/lib/active_store/base_spec.rb
79
+ - spec/lib/active_store/connection_spec.rb
80
+ - spec/spec_helper.rb
81
+ homepage: ''
82
+ licenses: []
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ requirements: []
100
+ rubyforge_project: active_store
101
+ rubygems_version: 1.8.6
102
+ signing_key:
103
+ specification_version: 3
104
+ summary: A active record-like wrapper for memcached protocol
105
+ test_files:
106
+ - spec/lib/active_store/base_spec.rb
107
+ - spec/lib/active_store/connection_spec.rb
108
+ - spec/spec_helper.rb