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 CHANGED
@@ -3,4 +3,5 @@ testing/
3
3
  .rvmrc
4
4
  log
5
5
  tmp
6
- pkg
6
+ pkg
7
+ .bundle
@@ -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"
@@ -7,6 +7,7 @@ root_path = Pathname(__FILE__).dirname.join('..').expand_path
7
7
  lib_path = root_path.join('lib')
8
8
  $:.unshift(lib_path)
9
9
  require 'toystore'
10
+
10
11
  class User
11
12
  include Toy::Store
12
13
  store :mongo, Mongo::Connection.new.db('adapter')['testing']
@@ -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"}
@@ -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
- self.attributes = attrs
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
- self.attributes = attrs
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)
@@ -1,7 +1,7 @@
1
1
  module Toy
2
2
  class Error < StandardError; end
3
3
 
4
- class RecordInvalidError < Error
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")
@@ -26,6 +26,10 @@ module Toy
26
26
  @key_factory
27
27
  end
28
28
 
29
+ def key_type
30
+ @key_factory.key_type
31
+ end
32
+
29
33
  def next_key(object = nil)
30
34
  @key_factory.next_key(object).tap do |key|
31
35
  raise InvalidKey.new if key.nil?
@@ -20,7 +20,7 @@ class BSON::ObjectId
20
20
  BSON::ObjectId.from_string(value.to_s)
21
21
  end
22
22
 
23
- def self.from_store(value, *)
24
- value
23
+ def self.from_store(value, *args)
24
+ to_store(value, *args)
25
25
  end
26
26
  end
@@ -4,12 +4,9 @@ module Toy
4
4
  include ActiveModel::MassAssignmentSecurity
5
5
 
6
6
  module InstanceMethods
7
- def initialize(attrs={})
8
- super(sanitize_for_mass_assignment(attrs || {}))
9
- end
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
@@ -49,9 +49,7 @@ module Toy
49
49
  alias :has_key? :key?
50
50
 
51
51
  def load(key, attrs)
52
- return nil if attrs.nil?
53
- attrs['id'] = key
54
- allocate.initialize_from_database(attrs)
52
+ attrs && allocate.initialize_from_database(attrs.update('id' => key))
55
53
  end
56
54
  end
57
55
  end
@@ -9,7 +9,7 @@ module Toy
9
9
  @type = args.shift
10
10
 
11
11
  model.references[name] = self
12
- model.attribute(key, String)
12
+ model.attribute(key, type.key_type)
13
13
  create_accessors
14
14
  end
15
15
 
@@ -38,7 +38,7 @@ module Toy
38
38
  end
39
39
 
40
40
  def save!
41
- save || raise(RecordInvalidError.new(self))
41
+ save || raise(RecordInvalid.new(self))
42
42
  end
43
43
  end
44
44
  end
@@ -1,3 +1,3 @@
1
1
  module Toy
2
- VERSION = "0.6.3"
2
+ VERSION = "0.6.4"
3
3
  end
@@ -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
@@ -1,6 +1,6 @@
1
1
  require 'helper'
2
2
 
3
- describe Toy::RecordInvalidError do
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::RecordInvalidError.new(user).message.should == "Invalid record: Name can't be blank and Age can't be blank"
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 String as store_type" do
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
@@ -16,6 +16,10 @@ describe Toy::Identity::UUIDKeyFactory do
16
16
  User.key(:uuid)
17
17
  end
18
18
 
19
+ it "returns String as .key_type" do
20
+ User.key_type.should be(String)
21
+ end
22
+
19
23
  it "sets id attribute to String type" do
20
24
  User.attributes['id'].type.should be(String)
21
25
  end
@@ -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
- user = User.get(user.id)
40
- user.admin.should be_true
41
- user.name.should == 'John'
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
@@ -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 RecordInvalidError" do
131
- lambda { @doc.save! }.should raise_error(Toy::RecordInvalidError)
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 RecordInvalidError" do
146
- lambda { User.create! }.should raise_error(Toy::RecordInvalidError)
145
+ it "raises an RecordInvalid" do
146
+ lambda { User.create! }.should raise_error(Toy::RecordInvalid)
147
147
  end
148
148
  end
149
149
  end
@@ -34,7 +34,7 @@ def run_spec(path)
34
34
  path.gsub!(file_name, file_name + "_spec")
35
35
  if File.exists?(path)
36
36
  system('clear')
37
- run(%Q(bundle exec spec #{path}))
37
+ run(%Q(bundle exec rspec #{path}))
38
38
  end
39
39
  end
40
40
 
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: 1
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 6
9
- - 3
10
- version: 0.6.3
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-05 00:00:00 -05:00
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
@@ -1,2 +0,0 @@
1
- ---
2
- BUNDLE_DISABLE_SHARED_GEMS: "1"