toystore 0.6.3 → 0.6.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -1
- data/README.rdoc +4 -0
- data/examples/attributes_abbreviation.rb +26 -0
- data/examples/attributes_virtual.rb +41 -0
- data/examples/identity_map.rb +45 -0
- data/examples/mongo.rb +1 -0
- data/examples/read_write_caching.rb +50 -0
- data/lib/toy/attributes.rb +3 -3
- data/lib/toy/exceptions.rb +1 -13
- data/lib/toy/identity.rb +4 -0
- data/lib/toy/identity/object_id_key_factory.rb +2 -2
- data/lib/toy/mass_assignment_security.rb +3 -6
- data/lib/toy/querying.rb +1 -3
- data/lib/toy/reference.rb +1 -1
- data/lib/toy/validations.rb +1 -1
- data/lib/toy/version.rb +1 -1
- data/spec/toy/attributes_spec.rb +22 -0
- data/spec/toy/exceptions_spec.rb +2 -2
- data/spec/toy/identity/object_id_key_factory_spec.rb +5 -1
- data/spec/toy/identity/uuid_key_factory_spec.rb +4 -0
- data/spec/toy/identity_spec.rb +7 -0
- data/spec/toy/mass_assignment_security_spec.rb +19 -3
- data/spec/toy/reference_spec.rb +15 -0
- data/spec/toy/validations_spec.rb +4 -4
- data/specs.watchr +1 -1
- metadata +8 -5
- data/.bundle/config +0 -2
data/.gitignore
CHANGED
data/README.rdoc
CHANGED
@@ -4,6 +4,10 @@ An object mapper for anything that can read, write and delete data.
|
|
4
4
|
|
5
5
|
See examples/ for potential direction. The idea is that any key-value store (via adapters) that supports read, write, delete will work (memcache, membase, mongo, redis, couch, toyko. Potentially even RESTFUL services or sqlite with a single key-value table?)
|
6
6
|
|
7
|
+
== Mailing List
|
8
|
+
|
9
|
+
https://groups.google.com/forum/#!forum/toystoreadapter
|
10
|
+
|
7
11
|
== Identity Map
|
8
12
|
|
9
13
|
By default, Toystore has identity map turned on. It assumes that any Toystore model has a unique id across all models. This means you either need to use the default uuid id's, object_id's (included), some other unique id, or create your own key factory that namespaces to model (see examples).
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'pathname'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'adapter/memory'
|
5
|
+
|
6
|
+
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
7
|
+
lib_path = root_path.join('lib')
|
8
|
+
$:.unshift(lib_path)
|
9
|
+
require 'toystore'
|
10
|
+
|
11
|
+
class User
|
12
|
+
include Toy::Store
|
13
|
+
store :memory, {}
|
14
|
+
|
15
|
+
attribute :email, String
|
16
|
+
attribute :my_really_long_field_name, String, :abbr => :my
|
17
|
+
end
|
18
|
+
|
19
|
+
user = User.create({
|
20
|
+
:email => 'nunemaker@gmail.com',
|
21
|
+
:my_really_long_field_name => 'something',
|
22
|
+
})
|
23
|
+
|
24
|
+
pp Marshal.load(User.store.client[user.id])
|
25
|
+
# Abbreviated attributes are stored in the database as the abbreviation for when you want to conserve space. The abbreviation and the full attribute name work exactly the same in Ruby, the only difference is how they get persisted.
|
26
|
+
# {"my"=>"something", "email"=>"nunemaker@gmail.com"}
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'pathname'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'adapter/memory'
|
5
|
+
|
6
|
+
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
7
|
+
lib_path = root_path.join('lib')
|
8
|
+
$:.unshift(lib_path)
|
9
|
+
require 'toystore'
|
10
|
+
|
11
|
+
class User
|
12
|
+
include Toy::Store
|
13
|
+
store :memory, {}
|
14
|
+
|
15
|
+
attribute :email, String
|
16
|
+
attribute :crypted_password, String
|
17
|
+
|
18
|
+
attribute :password, String, :virtual => true
|
19
|
+
attribute :password_confirmation, String, :virtual => true
|
20
|
+
|
21
|
+
before_validation :encrypt_password
|
22
|
+
|
23
|
+
private
|
24
|
+
def encrypt_password
|
25
|
+
self.crypted_password = encrypt(password)
|
26
|
+
end
|
27
|
+
|
28
|
+
def encrypt(password)
|
29
|
+
password # do something magical here
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
user = User.create({
|
34
|
+
:email => 'nunemaker@gmail.com',
|
35
|
+
:password => 'testing',
|
36
|
+
:password_confirmation => 'testing',
|
37
|
+
})
|
38
|
+
|
39
|
+
pp Marshal.load(User.store.client[user.id])
|
40
|
+
# Virtual attributes are never persisted. In the data store, only email and crypted_password are stored.
|
41
|
+
# {"crypted_password"=>"testing", "email"=>"nunemaker@gmail.com"}
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'pathname'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'adapter/memory'
|
5
|
+
|
6
|
+
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
7
|
+
lib_path = root_path.join('lib')
|
8
|
+
$:.unshift(lib_path)
|
9
|
+
require 'toystore'
|
10
|
+
|
11
|
+
# Identity map is turned on by default for Toystore. To turn it off for an entire model, just call identity_map_off.
|
12
|
+
#
|
13
|
+
# More Reading...
|
14
|
+
# http://martinfowler.com/eaaCatalog/identityMap.html
|
15
|
+
# http://en.wikipedia.org/wiki/Identity_map
|
16
|
+
|
17
|
+
class User
|
18
|
+
include Toy::Store
|
19
|
+
store :memory, {}
|
20
|
+
|
21
|
+
# identity_map_off # if uncommented, User would not use identity map
|
22
|
+
|
23
|
+
attribute :name, String
|
24
|
+
end
|
25
|
+
|
26
|
+
user = User.create(:name => 'John')
|
27
|
+
# User is added to identity map
|
28
|
+
# ToyStore SET #<User:0x10220b4c8> :memory "08a1c54c-3393-11e0-8b37-89da41d2f675"
|
29
|
+
# {"name"=>"John"}
|
30
|
+
# ToyStore IMS User "08a1c54c-3393-11e0-8b37-89da41d2f675"
|
31
|
+
|
32
|
+
User.get(user.id)
|
33
|
+
# User is retrieved from identity map instead of querying
|
34
|
+
# ToyStore IMG User "08a1c54c-3393-11e0-8b37-89da41d2f675"
|
35
|
+
|
36
|
+
User.without_identity_map do
|
37
|
+
User.get(user.id)
|
38
|
+
# User is queried from database
|
39
|
+
# ToyStore GET User :memory "08a1c54c-3393-11e0-8b37-89da41d2f675"
|
40
|
+
# {"name"=>"John"}
|
41
|
+
end
|
42
|
+
|
43
|
+
User.get(user.id)
|
44
|
+
# User is retrieved from identity map instead of querying
|
45
|
+
# ToyStore IMG User "08a1c54c-3393-11e0-8b37-89da41d2f675"
|
data/examples/mongo.rb
CHANGED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'pathname'
|
3
|
+
require 'rubygems'
|
4
|
+
require 'adapter/memcached'
|
5
|
+
require 'adapter/riak'
|
6
|
+
|
7
|
+
root_path = Pathname(__FILE__).dirname.join('..').expand_path
|
8
|
+
lib_path = root_path.join('lib')
|
9
|
+
$:.unshift(lib_path)
|
10
|
+
require 'toystore'
|
11
|
+
|
12
|
+
class User
|
13
|
+
include Toy::Store
|
14
|
+
identity_map_off # turning off so we can better illustrate read/write through caching stuff
|
15
|
+
|
16
|
+
store :riak, Riak::Client.new['users']
|
17
|
+
cache :memcached, Memcached.new
|
18
|
+
|
19
|
+
attribute :email, String
|
20
|
+
end
|
21
|
+
|
22
|
+
user = User.create(:email => 'nunemaker@gmail.com')
|
23
|
+
# ToyStore WTS #<User:0x102810e18> :memcached "6c39dd2a-3392-11e0-9fbf-040220ce8970"
|
24
|
+
# {"email"=>"nunemaker@gmail.com"}
|
25
|
+
# ToyStore SET #<User:0x102810e18> :riak "6c39dd2a-3392-11e0-9fbf-040220ce8970"
|
26
|
+
# {"email"=>"nunemaker@gmail.com"}
|
27
|
+
|
28
|
+
user = User.get(user.id)
|
29
|
+
# Get hits memcache instead of riak since it is cached
|
30
|
+
# ToyStore RTG User :memcached "6c39dd2a-3392-11e0-9fbf-040220ce8970"
|
31
|
+
# {"email"=>"nunemaker@gmail.com"}
|
32
|
+
|
33
|
+
# delete from cache to demonstrate population on cache miss
|
34
|
+
user.cache.delete(user.id)
|
35
|
+
|
36
|
+
user = User.get(user.id)
|
37
|
+
# Attempt read again, misses memcache, hits riak, caches in memcache
|
38
|
+
# ToyStore RTG User :memcached "6c39dd2a-3392-11e0-9fbf-040220ce8970"
|
39
|
+
# nil
|
40
|
+
# ToyStore GET User :riak "6c39dd2a-3392-11e0-9fbf-040220ce8970"
|
41
|
+
# {"email"=>"nunemaker@gmail.com"}
|
42
|
+
# ToyStore RTS User :memcached "6c39dd2a-3392-11e0-9fbf-040220ce8970"
|
43
|
+
# {"email"=>"nunemaker@gmail.com"}
|
44
|
+
|
45
|
+
user.update_attributes(:email => 'john@orderedlist.com')
|
46
|
+
# updated in memcache, then riak
|
47
|
+
# ToyStore WTS #<User:0x10266f0a0> :memcached "6c39dd2a-3392-11e0-9fbf-040220ce8970"
|
48
|
+
# {"email"=>"john@orderedlist.com"}
|
49
|
+
# ToyStore SET #<User:0x10266f0a0> :riak "6c39dd2a-3392-11e0-9fbf-040220ce8970"
|
50
|
+
# {"email"=>"john@orderedlist.com"}
|
data/lib/toy/attributes.rb
CHANGED
@@ -29,7 +29,7 @@ module Toy
|
|
29
29
|
def initialize(attrs={})
|
30
30
|
@_new_record = true unless defined?(@_new_record)
|
31
31
|
initialize_attributes_with_defaults
|
32
|
-
|
32
|
+
send(:attributes=, attrs, @_new_record)
|
33
33
|
write_attribute :id, self.class.next_key(self) unless id?
|
34
34
|
end
|
35
35
|
|
@@ -44,7 +44,7 @@ module Toy
|
|
44
44
|
attrs['id'] = store_key
|
45
45
|
instance_variables.each { |ivar| instance_variable_set(ivar, nil) }
|
46
46
|
initialize_attributes_with_defaults
|
47
|
-
|
47
|
+
send(:attributes=, attrs, new_record?)
|
48
48
|
self.class.lists.each_key { |name| send(name).reset }
|
49
49
|
self.class.references.each_key { |name| send(name).reset }
|
50
50
|
else
|
@@ -78,7 +78,7 @@ module Toy
|
|
78
78
|
end
|
79
79
|
end
|
80
80
|
|
81
|
-
def attributes=(attrs)
|
81
|
+
def attributes=(attrs, *)
|
82
82
|
return if attrs.nil?
|
83
83
|
attrs.each do |key, value|
|
84
84
|
if attribute_method?(key)
|
data/lib/toy/exceptions.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Toy
|
2
2
|
class Error < StandardError; end
|
3
3
|
|
4
|
-
class
|
4
|
+
class RecordInvalid < Error
|
5
5
|
attr_reader :record
|
6
6
|
def initialize(record)
|
7
7
|
@record = record
|
@@ -15,18 +15,6 @@ module Toy
|
|
15
15
|
end
|
16
16
|
end
|
17
17
|
|
18
|
-
class UndefinedLock < Error
|
19
|
-
def initialize(klass, name)
|
20
|
-
super("Undefined lock :#{name} for class #{klass.name}")
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class AdapterNoLocky < Error
|
25
|
-
def initialize(adapter)
|
26
|
-
super("#{adapter.name.to_s.capitalize} adapter does not support locking")
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
18
|
class InvalidKeyFactory < Error
|
31
19
|
def initialize(name_or_factory)
|
32
20
|
super("#{name_or_factory.inspect} is not a valid name and did not respond to next_key and key_type")
|
data/lib/toy/identity.rb
CHANGED
@@ -4,12 +4,9 @@ module Toy
|
|
4
4
|
include ActiveModel::MassAssignmentSecurity
|
5
5
|
|
6
6
|
module InstanceMethods
|
7
|
-
def
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def update_attributes(attrs={})
|
12
|
-
super(sanitize_for_mass_assignment(attrs || {}))
|
7
|
+
def attributes=(attrs, guard_protected_attributes=true)
|
8
|
+
attrs = sanitize_for_mass_assignment(attrs || {}) if guard_protected_attributes
|
9
|
+
super(attrs)
|
13
10
|
end
|
14
11
|
end
|
15
12
|
end
|
data/lib/toy/querying.rb
CHANGED
data/lib/toy/reference.rb
CHANGED
data/lib/toy/validations.rb
CHANGED
data/lib/toy/version.rb
CHANGED
data/spec/toy/attributes_spec.rb
CHANGED
@@ -130,6 +130,13 @@ describe Toy::Attributes do
|
|
130
130
|
it "does not fail with nil" do
|
131
131
|
User.new(nil).should be_instance_of(User)
|
132
132
|
end
|
133
|
+
|
134
|
+
it "does guard attributes=" do
|
135
|
+
attrs = {'age' => 21}
|
136
|
+
user = User.allocate
|
137
|
+
user.should_receive(:attributes=).with(attrs, true)
|
138
|
+
user.send(:initialize, attrs)
|
139
|
+
end
|
133
140
|
end
|
134
141
|
|
135
142
|
describe "#initialize_from_database" do
|
@@ -159,6 +166,12 @@ describe Toy::Attributes do
|
|
159
166
|
it "returns self" do
|
160
167
|
@user.initialize_from_database.should == @user
|
161
168
|
end
|
169
|
+
|
170
|
+
it "does not guard attributes=" do
|
171
|
+
attrs = {'age' => 21}
|
172
|
+
@user.should_receive(:attributes=).with(attrs, false)
|
173
|
+
@user.initialize_from_database(attrs)
|
174
|
+
end
|
162
175
|
end
|
163
176
|
|
164
177
|
describe "#attributes" do
|
@@ -390,6 +403,15 @@ describe Toy::Attributes do
|
|
390
403
|
@user.reload
|
391
404
|
@user.skills.should == []
|
392
405
|
end
|
406
|
+
|
407
|
+
it "reloads attributes protected from mass assignment" do
|
408
|
+
User.attribute(:admin, Boolean)
|
409
|
+
User.attr_accessible(:name)
|
410
|
+
user = User.new(:name => 'John')
|
411
|
+
user.admin = true
|
412
|
+
user.save
|
413
|
+
user.reload.admin.should be_true
|
414
|
+
end
|
393
415
|
end
|
394
416
|
|
395
417
|
describe "Initialization of array attributes" do
|
data/spec/toy/exceptions_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
|
-
describe Toy::
|
3
|
+
describe Toy::RecordInvalid do
|
4
4
|
uses_constants('User')
|
5
5
|
|
6
6
|
before do
|
@@ -13,6 +13,6 @@ describe Toy::RecordInvalidError do
|
|
13
13
|
it "should include a message of the errors" do
|
14
14
|
user = User.new
|
15
15
|
user.should_not be_valid
|
16
|
-
Toy::
|
16
|
+
Toy::RecordInvalid.new(user).message.should == "Invalid record: Name can't be blank and Age can't be blank"
|
17
17
|
end
|
18
18
|
end
|
@@ -4,7 +4,7 @@ require 'toy/identity/object_id_key_factory'
|
|
4
4
|
describe Toy::Identity::ObjectIdKeyFactory do
|
5
5
|
uses_constants('User')
|
6
6
|
|
7
|
-
it "should use
|
7
|
+
it "should use BSON::ObjectId as key_type" do
|
8
8
|
Toy::Identity::ObjectIdKeyFactory.new.key_type.should be(BSON::ObjectId)
|
9
9
|
end
|
10
10
|
|
@@ -19,6 +19,10 @@ describe Toy::Identity::ObjectIdKeyFactory do
|
|
19
19
|
User.attribute(:name, String)
|
20
20
|
end
|
21
21
|
|
22
|
+
it "returns BSON::ObjectId as .key_type" do
|
23
|
+
User.key_type.should be(BSON::ObjectId)
|
24
|
+
end
|
25
|
+
|
22
26
|
it "sets id attribute to BSON::ObjectId type" do
|
23
27
|
User.attributes['id'].type.should be(BSON::ObjectId)
|
24
28
|
end
|
data/spec/toy/identity_spec.rb
CHANGED
@@ -45,6 +45,13 @@ describe Toy::Identity do
|
|
45
45
|
end
|
46
46
|
end
|
47
47
|
|
48
|
+
describe ".key_type" do
|
49
|
+
it "returns the type based on the key factory" do
|
50
|
+
User.key(Toy::Identity::UUIDKeyFactory.new)
|
51
|
+
User.key_type.should be(String)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
48
55
|
describe "initializing the id" do
|
49
56
|
it "should pass use pass the new object" do
|
50
57
|
Piece.attribute(:name, String)
|
@@ -31,14 +31,30 @@ describe Toy::MassAssignmentSecurity do
|
|
31
31
|
user.name.should == 'John'
|
32
32
|
end
|
33
33
|
|
34
|
+
it "should ignore inaccessible attribute on #attributes=" do
|
35
|
+
user = User.new
|
36
|
+
user.attributes = {:name => 'John', :admin => true}
|
37
|
+
user.admin.should be_false
|
38
|
+
user.name.should == 'John'
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should not ignore inaccessible attribute on #attributes= with no guard" do
|
42
|
+
user = User.new
|
43
|
+
user.send(:attributes=, {:name => 'John', :admin => true}, false)
|
44
|
+
user.admin.should be_true
|
45
|
+
user.name.should == 'John'
|
46
|
+
end
|
47
|
+
|
34
48
|
it "should not ignore inaccessible attributes on #initialize from the database" do
|
35
49
|
user = User.new(:name => 'John')
|
36
50
|
user.admin = true
|
37
51
|
user.save!
|
38
52
|
|
39
|
-
|
40
|
-
|
41
|
-
|
53
|
+
User.without_identity_map do
|
54
|
+
user = User.get(user.id)
|
55
|
+
user.admin.should be_true
|
56
|
+
user.name.should == 'John'
|
57
|
+
end
|
42
58
|
end
|
43
59
|
|
44
60
|
it "should ignore inaccessible attribute on #update_attributes" do
|
data/spec/toy/reference_spec.rb
CHANGED
@@ -37,6 +37,10 @@ describe Toy::Reference do
|
|
37
37
|
Game.attributes.keys.should include('user_id')
|
38
38
|
end
|
39
39
|
|
40
|
+
it "sets attribute type to .key_type" do
|
41
|
+
Game.attributes['user_id'].type.should be(String)
|
42
|
+
end
|
43
|
+
|
40
44
|
it "adds reader method" do
|
41
45
|
Game.new.should respond_to(:user)
|
42
46
|
end
|
@@ -45,6 +49,17 @@ describe Toy::Reference do
|
|
45
49
|
Game.new.should respond_to(:user=)
|
46
50
|
end
|
47
51
|
|
52
|
+
describe "with object_id key" do
|
53
|
+
before(:each) do
|
54
|
+
User.key(:object_id)
|
55
|
+
@reference = Game.reference(:user)
|
56
|
+
end
|
57
|
+
|
58
|
+
it "sets type to BSON::ObjectId" do
|
59
|
+
Game.attributes['user_id'].type.should be(BSON::ObjectId)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
48
63
|
describe "#eql?" do
|
49
64
|
it "returns true if same class, model, and name" do
|
50
65
|
reference.should eql(reference)
|
@@ -127,8 +127,8 @@ describe Toy::Validations do
|
|
127
127
|
@doc = User.new
|
128
128
|
end
|
129
129
|
|
130
|
-
it "raises an
|
131
|
-
lambda { @doc.save! }.should raise_error(Toy::
|
130
|
+
it "raises an RecordInvalid" do
|
131
|
+
lambda { @doc.save! }.should raise_error(Toy::RecordInvalid)
|
132
132
|
end
|
133
133
|
end
|
134
134
|
end
|
@@ -142,8 +142,8 @@ describe Toy::Validations do
|
|
142
142
|
end
|
143
143
|
|
144
144
|
context "with invalid" do
|
145
|
-
it "raises an
|
146
|
-
lambda { User.create! }.should raise_error(Toy::
|
145
|
+
it "raises an RecordInvalid" do
|
146
|
+
lambda { User.create! }.should raise_error(Toy::RecordInvalid)
|
147
147
|
end
|
148
148
|
end
|
149
149
|
end
|
data/specs.watchr
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: toystore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 6
|
9
|
-
-
|
10
|
-
version: 0.6.
|
9
|
+
- 4
|
10
|
+
version: 0.6.4
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Geoffrey Dagley
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date: 2011-02-
|
19
|
+
date: 2011-02-13 00:00:00 -05:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|
@@ -95,7 +95,6 @@ extra_rdoc_files: []
|
|
95
95
|
|
96
96
|
files:
|
97
97
|
- .autotest
|
98
|
-
- .bundle/config
|
99
98
|
- .gitignore
|
100
99
|
- Gemfile
|
101
100
|
- Gemfile.lock
|
@@ -103,12 +102,16 @@ files:
|
|
103
102
|
- LOGGING.rdoc
|
104
103
|
- README.rdoc
|
105
104
|
- Rakefile
|
105
|
+
- examples/attributes_abbreviation.rb
|
106
|
+
- examples/attributes_virtual.rb
|
106
107
|
- examples/changing_key_factory.rb
|
108
|
+
- examples/identity_map.rb
|
107
109
|
- examples/memcached.rb
|
108
110
|
- examples/memory.rb
|
109
111
|
- examples/models.rb
|
110
112
|
- examples/mongo.rb
|
111
113
|
- examples/namespacing_keys.rb
|
114
|
+
- examples/read_write_caching.rb
|
112
115
|
- examples/redis.rb
|
113
116
|
- examples/riak.rb
|
114
117
|
- lib/toy.rb
|
data/.bundle/config
DELETED