toystore-mongo 0.5.0

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
+ log
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in toystore-mongo.gemspec
4
+ gem 'toystore', :path => '/Users/jnunemaker/projects/toystore'
5
+ gemspec
6
+
7
+ group(:development) do
8
+ gem 'bson_ext'
9
+ gem 'SystemTimer'
10
+ gem 'rake', '~> 0.8.7'
11
+ gem 'rspec', '~> 2.3'
12
+ gem 'log_buddy', '~> 0.5.0'
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 John Nunemaker
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,19 @@
1
+ = Toystore Mongo
2
+
3
+ Mongo integration for Toystore.
4
+
5
+ == Install
6
+
7
+ $ gem install toystore-mongo
8
+
9
+ == Note on Patches/Pull Requests
10
+
11
+ * Fork the project.
12
+ * Make your feature addition or bug fix.
13
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
14
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
15
+ * Send me a pull request. Bonus points for topic branches.
16
+
17
+ == Copyright
18
+
19
+ See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new
6
+
7
+ task :default => :spec
@@ -0,0 +1,80 @@
1
+ module Toy
2
+ module Mongo
3
+ module Querying
4
+ extend ActiveSupport::Concern
5
+
6
+ PluckyMethods = [
7
+ :where, :filter, :limit, :skip, :offset, :sort, :order,
8
+ :fields, :ignore, :only,
9
+ :each, :find_each,
10
+ :count, :size, :distinct,
11
+ :last, :first, :all, :paginate,
12
+ :exists?, :exist?, :empty?,
13
+ :to_a, :remove,
14
+ ]
15
+
16
+ module ClassMethods
17
+ def transformer
18
+ @transformer ||= lambda do |doc|
19
+ load(doc.delete('_id'), doc)
20
+ end
21
+ end
22
+
23
+ def object_id_attributes
24
+ attributes.values.select do |attribute|
25
+ attribute.type == BSON::ObjectId
26
+ end.map do |attribute|
27
+ sym = attribute.name.to_sym
28
+ sym == :id ? :_id : sym
29
+ end
30
+ end
31
+
32
+ def query
33
+ # TODO: add object id keys to convert
34
+ Plucky::Query.new(store.client, :transformer => transformer).object_ids(object_id_attributes)
35
+ end
36
+
37
+ PluckyMethods.each do |name|
38
+ define_method(name) do |*args|
39
+ query.send(name, *args)
40
+ end
41
+ end
42
+ end
43
+
44
+ # Very basic method for determining what has changed locally
45
+ # so we can just update changes instead of entire document
46
+ #
47
+ # Does not work with complex objects (array, hash, set, etc.)
48
+ # as it does not attempt to determine what has changed in them,
49
+ # just whether or not they have changed at all.
50
+ def persistable_changes
51
+ attrs = {}
52
+ pattrs = persisted_attributes
53
+ changed.each do |key|
54
+ attribute = self.class.attributes[key.to_s]
55
+ next if attribute.virtual?
56
+ attrs[attribute.store_key] = pattrs[attribute.store_key]
57
+ end
58
+ attrs
59
+ end
60
+
61
+ def atomic_update_attributes(attrs={})
62
+ self.attributes = attrs
63
+ if valid?
64
+ atomic_update('$set' => persistable_changes)
65
+ true
66
+ else
67
+ false
68
+ end
69
+ end
70
+
71
+ def atomic_update(update, opts={})
72
+ options = {}
73
+ criteria = {'_id' => id}
74
+ criteria.update(opts[:criteria]) if opts[:criteria]
75
+ options[:safe] = opts.key?(:safe) ? opts[:safe] : store.options[:safe]
76
+ store.client.update(criteria, update, options)
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,5 @@
1
+ module Toy
2
+ module Mongo
3
+ VERSION = '0.5.0'
4
+ end
5
+ end
data/lib/toy/mongo.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'plucky'
2
+ require 'toy'
3
+ require 'toy/identity/object_id_key_factory'
4
+ require 'toy/mongo/querying'
5
+ require 'adapter/mongo'
6
+
7
+
8
+ module Toy
9
+ module Mongo
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ include Querying
14
+ end
15
+ end
16
+ end
17
+
18
+ Toy.key_factory = Toy::Identity::ObjectIdKeyFactory.new
19
+ Toy.plugin(Toy::Mongo)
@@ -0,0 +1 @@
1
+ require 'toy/mongo'
data/spec/helper.rb ADDED
@@ -0,0 +1,32 @@
1
+ $:.unshift(File.expand_path('../../lib', __FILE__))
2
+
3
+ require 'pathname'
4
+ require 'logger'
5
+
6
+ root_path = Pathname(__FILE__).dirname.join('..').expand_path
7
+ lib_path = root_path.join('lib')
8
+ log_path = root_path.join('log')
9
+ log_path.mkpath
10
+
11
+ require 'rubygems'
12
+ require 'bundler'
13
+
14
+ Bundler.require(:development)
15
+
16
+ require 'toy/mongo'
17
+ require 'support/constants'
18
+
19
+ STORE = Mongo::Connection.new.db('testing')['toystore-mongo']
20
+
21
+ Logger.new(log_path.join('test.log')).tap do |log|
22
+ LogBuddy.init(:logger => log)
23
+ Toy.logger = log
24
+ end
25
+
26
+ Rspec.configure do |c|
27
+ c.include(Support::Constants)
28
+
29
+ c.before(:each) do
30
+ STORE.remove
31
+ end
32
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,3 @@
1
+ --colour
2
+ --timeout
3
+ 20
@@ -0,0 +1,41 @@
1
+ module Support
2
+ module Constants
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def uses_constants(*constants)
9
+ before { create_constants(*constants) }
10
+ end
11
+ end
12
+
13
+ def create_constants(*constants)
14
+ constants.each { |constant| create_constant(constant) }
15
+ end
16
+
17
+ def remove_constants(*constants)
18
+ constants.each { |constant| remove_constant(constant) }
19
+ end
20
+
21
+ def create_constant(constant)
22
+ remove_constant(constant)
23
+ Kernel.const_set(constant, Model(constant))
24
+ end
25
+
26
+ def remove_constant(constant)
27
+ Kernel.send(:remove_const, constant) if Kernel.const_defined?(constant)
28
+ end
29
+
30
+ def Model(name=nil)
31
+ Class.new.tap do |model|
32
+ model.class_eval """
33
+ def self.name; '#{name}' end
34
+ def self.to_s; '#{name}' end
35
+ """ if name
36
+ model.send(:include, Toy::Store)
37
+ model.store(:mongo, STORE)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,135 @@
1
+ require 'helper'
2
+
3
+ describe Toy::Mongo::Querying do
4
+ uses_constants('User')
5
+
6
+ before(:each) do
7
+ User.identity_map_off
8
+ User.attribute(:name, String)
9
+ User.attribute(:bio, String)
10
+ end
11
+
12
+ describe "#query" do
13
+ it "returns a plucky query instance" do
14
+ User.query.should be_instance_of(Plucky::Query)
15
+ end
16
+ end
17
+
18
+ Toy::Mongo::Querying::PluckyMethods.each do |name|
19
+ it "delegates ##{name} to #query" do
20
+ query = User.query
21
+ query.should_receive(name)
22
+ User.should_receive(:query).and_return(query)
23
+ User.send(name)
24
+ end
25
+ end
26
+
27
+ describe "#atomic_update" do
28
+ before(:each) do
29
+ @user = User.create(:name => 'John')
30
+ end
31
+
32
+ it "performs update" do
33
+ @user.atomic_update('$set' => {'name' => 'Frank'})
34
+ @user.reload
35
+ @user.name.should == 'Frank'
36
+ end
37
+
38
+ it "defaults to store's :safe option" do
39
+ @user.store.client.should_receive(:update).with(kind_of(Hash), kind_of(Hash), :safe => nil)
40
+ @user.atomic_update('$set' => {'name' => 'Frank'})
41
+
42
+ User.store(:mongo, STORE, :safe => false)
43
+ @user.store.client.should_receive(:update).with(kind_of(Hash), kind_of(Hash), :safe => false)
44
+ @user.atomic_update('$set' => {'name' => 'Frank'})
45
+
46
+ User.store(:mongo, STORE, :safe => true)
47
+ @user.store.client.should_receive(:update).with(kind_of(Hash), kind_of(Hash), :safe => true)
48
+ @user.atomic_update('$set' => {'name' => 'Frank'})
49
+ end
50
+
51
+ context "with :safe option" do
52
+ it "overrides store's :safe option" do
53
+ User.store(:mongo, STORE, :safe => false)
54
+ @user.store.client.should_receive(:update).with(kind_of(Hash), kind_of(Hash), :safe => true)
55
+ @user.atomic_update({'$set' => {'name' => 'Frank'}}, :safe => true)
56
+
57
+ User.store(:mongo, STORE, :safe => true)
58
+ @user.store.client.should_receive(:update).with(kind_of(Hash), kind_of(Hash), :safe => false)
59
+ @user.atomic_update({'$set' => {'name' => 'Frank'}}, :safe => false)
60
+ end
61
+ end
62
+
63
+ context "with :criteria option" do
64
+ uses_constants('Site')
65
+
66
+ it "allows updating embedded documents using $ positional operator" do
67
+ User.attribute(:sites, Array)
68
+ site1 = Site.create
69
+ site2 = Site.create
70
+ @user.update_attributes(:sites => [{'id' => site1.id, 'ui' => 1}, {'id' => site2.id, 'ui' => 2}])
71
+
72
+ @user.atomic_update(
73
+ {'$set' => {'sites.$.ui' => 2}},
74
+ {:criteria => {'sites.id' => site1.id}}
75
+ )
76
+ @user.reload
77
+ @user.sites.map { |s| s['ui'] }.should == [2, 2]
78
+ end
79
+ end
80
+ end
81
+
82
+ describe "#persistable_changes" do
83
+ before(:each) do
84
+ User.attribute(:password, String, :virtual => true)
85
+ User.attribute(:email, String, :abbr => :e)
86
+ @user = User.create(:name => 'John', :password => 'secret', :email => 'nunemaker@gmail.com')
87
+ end
88
+
89
+ it "returns only changed attributes" do
90
+ @user.name = 'Frank'
91
+ @user.persistable_changes.should == {'name' => 'Frank'}
92
+ end
93
+
94
+ it "returns typecast values" do
95
+ @user.name = 1234
96
+ @user.persistable_changes.should == {'name' => '1234'}
97
+ end
98
+
99
+ it "ignores virtual attributes" do
100
+ @user.password = 'ignore me'
101
+ @user.persistable_changes.should be_empty
102
+ end
103
+
104
+ it "uses abbreviated key" do
105
+ @user.email = 'john@orderedlist.com'
106
+ @user.persistable_changes.should == {'e' => 'john@orderedlist.com'}
107
+ end
108
+ end
109
+
110
+ describe "#atomic_update_attributes" do
111
+ before(:each) do
112
+ @user = User.create(:name => 'John', :bio => 'I make things simple.')
113
+ end
114
+
115
+ it "updates document" do
116
+ @user.atomic_update_attributes(:name => 'Frank')
117
+ @user.reload
118
+ @user.name.should == 'Frank'
119
+ end
120
+
121
+ it "returns true if valid" do
122
+ @user.atomic_update_attributes(:name => 'Frank').should be_true
123
+ end
124
+
125
+ it "returns false if invalid" do
126
+ User.validates_presence_of(:name)
127
+ @user.atomic_update_attributes(:name => '').should be_false
128
+ end
129
+
130
+ it "only persists changes" do
131
+ @user.should_receive(:atomic_update).with('$set' => {'name' => 'Frank'})
132
+ @user.atomic_update_attributes(:name => 'Frank')
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,10 @@
1
+ require 'helper'
2
+
3
+ describe Toy::Mongo do
4
+ it "defaults key factory to object_id" do
5
+ klass = Class.new do
6
+ include Toy::Store
7
+ end
8
+ klass.key_factory.should be_instance_of(Toy::Identity::ObjectIdKeyFactory)
9
+ end
10
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "toy/mongo/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "toystore-mongo"
7
+ s.version = Toy::Mongo::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['John Nunemaker']
10
+ s.email = ['nunemaker@gmail.com']
11
+ s.homepage = ''
12
+ s.summary = %q{Mongo integration for Toystore}
13
+ s.description = %q{Mongo integration for Toystore}
14
+
15
+ s.add_dependency('plucky', '~> 0.4.0')
16
+ s.add_dependency('toystore', '~> 0.6.5')
17
+ s.add_dependency('adapter-mongo', '~> 0.5.2')
18
+
19
+ s.files = `git ls-files`.split("\n") - ['specs.watchr']
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+ end
metadata ADDED
@@ -0,0 +1,132 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: toystore-mongo
3
+ version: !ruby/object:Gem::Version
4
+ hash: 11
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 5
9
+ - 0
10
+ version: 0.5.0
11
+ platform: ruby
12
+ authors:
13
+ - John Nunemaker
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-05-08 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: plucky
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 15
29
+ segments:
30
+ - 0
31
+ - 4
32
+ - 0
33
+ version: 0.4.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: toystore
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 13
45
+ segments:
46
+ - 0
47
+ - 6
48
+ - 5
49
+ version: 0.6.5
50
+ type: :runtime
51
+ version_requirements: *id002
52
+ - !ruby/object:Gem::Dependency
53
+ name: adapter-mongo
54
+ prerelease: false
55
+ requirement: &id003 !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ hash: 15
61
+ segments:
62
+ - 0
63
+ - 5
64
+ - 2
65
+ version: 0.5.2
66
+ type: :runtime
67
+ version_requirements: *id003
68
+ description: Mongo integration for Toystore
69
+ email:
70
+ - nunemaker@gmail.com
71
+ executables: []
72
+
73
+ extensions: []
74
+
75
+ extra_rdoc_files: []
76
+
77
+ files:
78
+ - .gitignore
79
+ - Gemfile
80
+ - LICENSE
81
+ - README.rdoc
82
+ - Rakefile
83
+ - lib/toy/mongo.rb
84
+ - lib/toy/mongo/querying.rb
85
+ - lib/toy/mongo/version.rb
86
+ - lib/toystore-mongo.rb
87
+ - spec/helper.rb
88
+ - spec/spec.opts
89
+ - spec/support/constants.rb
90
+ - spec/toy/mongo/querying_spec.rb
91
+ - spec/toy/mongo_spec.rb
92
+ - toystore-mongo.gemspec
93
+ homepage: ""
94
+ licenses: []
95
+
96
+ post_install_message:
97
+ rdoc_options: []
98
+
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ none: false
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ hash: 3
107
+ segments:
108
+ - 0
109
+ version: "0"
110
+ required_rubygems_version: !ruby/object:Gem::Requirement
111
+ none: false
112
+ requirements:
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ hash: 3
116
+ segments:
117
+ - 0
118
+ version: "0"
119
+ requirements: []
120
+
121
+ rubyforge_project:
122
+ rubygems_version: 1.7.2
123
+ signing_key:
124
+ specification_version: 3
125
+ summary: Mongo integration for Toystore
126
+ test_files:
127
+ - spec/helper.rb
128
+ - spec/spec.opts
129
+ - spec/support/constants.rb
130
+ - spec/toy/mongo/querying_spec.rb
131
+ - spec/toy/mongo_spec.rb
132
+ has_rdoc: