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