toystore-couch 0.1.0

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
+ log
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+ gemspec
3
+
4
+ group(:development) do
5
+ gem 'rake', '~> 0.8.7'
6
+ gem 'rspec', '~> 2.3'
7
+ gem 'log_buddy', '~> 0.5.0'
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Pelle Braendgaard, 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,51 @@
1
+ = Toystore Couch
2
+
3
+ CouchDB integration for Toystore. Much of this is stolen whole sale from CouchRest Model https://github.com/couchrest/couchrest_model
4
+
5
+ Currently it adds a few Couch necessities such as support for views (map reduce queries):
6
+
7
+ ==== Example views:
8
+
9
+ class Post
10
+ include Toy::Store
11
+ store :couch, CouchRest.database!("http://127.0.0.1:5984/agree2-development")
12
+ # view with default options
13
+ # query with Post.by_date
14
+ view_by :date, :descending => true
15
+
16
+ # view with compound sort-keys
17
+ # query with Post.by_user_id_and_date
18
+ view_by :user_id, :date
19
+
20
+ # view with custom map/reduce functions
21
+ # query with Post.by_tags :reduce => true
22
+ view_by :tags,
23
+ :map =>
24
+ "function(doc) {
25
+ if (doc['model'] == 'Post' && doc.tags) {
26
+ doc.tags.forEach(function(tag){
27
+ emit(doc.tag, 1);
28
+ });
29
+ }
30
+ }",
31
+ :reduce =>
32
+ "function(keys, values, rereduce) {
33
+ return sum(values);
34
+ }"
35
+ end
36
+
37
+ == Install
38
+
39
+ $ gem install toystore-couch
40
+
41
+ == Note on Patches/Pull Requests
42
+
43
+ * Fork the project.
44
+ * Make your feature addition or bug fix.
45
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
46
+ * 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)
47
+ * Send me a pull request. Bonus points for topic branches.
48
+
49
+ == Copyright
50
+
51
+ 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
data/lib/toy/couch.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'toy'
2
+ require 'toy/attributes'
3
+ require 'toy/couch/type'
4
+ require 'toy/couch/design_doc'
5
+ require 'toy/couch/views'
6
+ require 'adapter/couch'
7
+
8
+
9
+ module Toy
10
+ module Couch
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ include DesignDoc
15
+ include Views
16
+ end
17
+ end
18
+ end
19
+
20
+ Toy.plugin(Toy::Couch)
@@ -0,0 +1,96 @@
1
+ # encoding: utf-8
2
+ require 'couchrest'
3
+ require 'couchrest/design'
4
+
5
+ module Toy
6
+ module Couch
7
+ module DesignDoc
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+
12
+ def design_doc
13
+ @design_doc ||= ::CouchRest::Design.new(default_design_doc)
14
+ end
15
+
16
+ def design_doc_id
17
+ "_design/#{design_doc_slug}"
18
+ end
19
+
20
+ def design_doc_slug
21
+ self.to_s
22
+ end
23
+
24
+ def design_doc_uri
25
+ "#{store.client.root}/#{design_doc_id}"
26
+ end
27
+
28
+ # Retreive the latest version of the design document directly
29
+ # from the database. This is never cached and will return nil if
30
+ # the design is not present.
31
+ #
32
+ # Use this method if you'd like to compare revisions [_rev] which
33
+ # is not stored in the normal design doc.
34
+ def stored_design_doc
35
+ store.client.get(design_doc_id)
36
+ rescue RestClient::ResourceNotFound
37
+ nil
38
+ end
39
+
40
+ # Save the design doc onto a target database in a thread-safe way,
41
+ # not modifying the model's design_doc
42
+ #
43
+ # See also save_design_doc! to always save the design doc even if there
44
+ # are no changes.
45
+ def save_design_doc(force = false)
46
+ update_design_doc(force)
47
+ end
48
+
49
+ # Force the update of the model's design_doc even if it hasn't changed.
50
+ def save_design_doc!
51
+ save_design_doc(true)
52
+ end
53
+
54
+ private
55
+
56
+ # Writes out a design_doc to a given database if forced.
57
+ #
58
+ # Returns the original design_doc provided, but does
59
+ # not update it with the revision.
60
+ def update_design_doc(force = false)
61
+ return design_doc unless force
62
+
63
+ # Load up the stored doc (if present), update, and save
64
+ saved = stored_design_doc
65
+ if saved
66
+ saved.merge!(design_doc)
67
+ store.client.save_doc(saved)
68
+ else
69
+ store.client.save_doc(design_doc)
70
+ design_doc.delete('_rev') # Prevent conflicts, never store rev as db specific
71
+ end
72
+
73
+ design_doc
74
+ end
75
+
76
+ def default_design_doc
77
+ {
78
+ "_id" => design_doc_id,
79
+ "language" => "javascript",
80
+ "views" => {
81
+ 'all' => {
82
+ 'map' => "function(doc) {
83
+ if (doc['type'] == '#{self.to_s}') {
84
+ emit(doc['_id'],1);
85
+ }
86
+ }"
87
+ }
88
+ }
89
+ }
90
+ end
91
+
92
+ end # module ClassMethods
93
+
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,13 @@
1
+ module Toy
2
+ module Couch
3
+ module Type
4
+ extend ActiveSupport::Concern
5
+
6
+ module InstanceMethods
7
+ def persisted_attributes
8
+ super.merge("type"=>self.class.name)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module Toy
2
+ module Couch
3
+ VERSION = '0.1.0'
4
+ end
5
+ end
@@ -0,0 +1,146 @@
1
+ require 'couchrest/design'
2
+ module Toy
3
+ module Couch
4
+ module Views
5
+ extend ActiveSupport::Concern
6
+
7
+ module ClassMethods
8
+ # This was pretty much stolen whole sale from CouchRest Model
9
+ # https://github.com/couchrest/couchrest_model/blob/master/lib/couchrest/model/views.rb
10
+ #
11
+ # Define a Couchstore.client view. The name of the view will be the concatenation
12
+ # of <tt>by</tt> and the keys joined by <tt>_and_</tt>
13
+ #
14
+ # ==== Example views:
15
+ #
16
+ # class Post
17
+ # include Toy::Store
18
+ # store :couch, CouchRest.database!("http://127.0.0.1:5984/agree2-development")
19
+ # # view with default options
20
+ # # query with Post.by_date
21
+ # view_by :date, :descending => true
22
+ #
23
+ # # view with compound sort-keys
24
+ # # query with Post.by_user_id_and_date
25
+ # view_by :user_id, :date
26
+ #
27
+ # # view with custom map/reduce functions
28
+ # # query with Post.by_tags :reduce => true
29
+ # view_by :tags,
30
+ # :map =>
31
+ # "function(doc) {
32
+ # if (doc['model'] == 'Post' && doc.tags) {
33
+ # doc.tags.forEach(function(tag){
34
+ # emit(doc.tag, 1);
35
+ # });
36
+ # }
37
+ # }",
38
+ # :reduce =>
39
+ # "function(keys, values, rereduce) {
40
+ # return sum(values);
41
+ # }"
42
+ # end
43
+ #
44
+ # <tt>view_by :date</tt> will create a view defined by this Javascript
45
+ # function:
46
+ #
47
+ # function(doc) {
48
+ # if (doc['model'] == 'Post' && doc.date) {
49
+ # emit(doc.date, null);
50
+ # }
51
+ # }
52
+ #
53
+ # It can be queried by calling <tt>Post.by_date</tt> which accepts all
54
+ # valid options for CouchRest::Database#view. In addition, calling with
55
+ # the <tt>:raw => true</tt> option will return the view rows
56
+ # themselves. By default <tt>Post.by_date</tt> will return the
57
+ # documents included in the generated view.
58
+ #
59
+ # Toy::Stor#view options can be applied at view definition
60
+ # time as defaults, and they will be curried and used at view query
61
+ # time. Or they can be overridden at query time.
62
+ #
63
+ # Custom views can be queried with <tt>:reduce => true</tt> to return
64
+ # reduce results. The default for custom views is to query with
65
+ # <tt>:reduce => false</tt>.
66
+ #
67
+ # Views are generated (on a per-model basis) lazily on first-access.
68
+ # This means that if you are deploying changes to a view, the views for
69
+ # that model won't be available until generation is complete. This can
70
+ # take some time with large databases. Strategies are in the works.
71
+ #
72
+
73
+ def view_by(*keys)
74
+ include Type
75
+ opts = keys.pop if keys.last.is_a?(Hash)
76
+ opts ||= {}
77
+ ducktype = opts.delete(:ducktype)
78
+ unless ducktype || opts[:map]
79
+ opts[:guards] ||= []
80
+ opts[:guards].push "(doc['type'] == '#{self.to_s}')"
81
+ end
82
+ keys.push opts
83
+ design_doc.view_by(*keys)
84
+ end
85
+
86
+ # returns stored defaults if there is a view named this in the design doc
87
+ def has_view?(name)
88
+ design_doc && design_doc.has_view?(name)
89
+ end
90
+
91
+ # Check if the view can be reduced by checking to see if it has a
92
+ # reduce function.
93
+ def can_reduce_view?(name)
94
+ design_doc && design_doc.can_reduce_view?(name)
95
+ end
96
+
97
+ # Dispatches to any named view.
98
+ def view(name, query={}, &block)
99
+ query = query.dup # Modifications made on copy!
100
+ query[:raw] = true if query[:reduce]
101
+ raw = query.delete(:raw)
102
+ save_design_doc(store.client)
103
+ fetch_view_with_docs( name, query, raw, &block)
104
+ end
105
+
106
+ # Find the first entry in the view. If the second parameter is a string
107
+ # it will be used as the key for the request, for example:
108
+ #
109
+ # Course.first_from_view('by_teacher', 'Fred')
110
+ #
111
+ # More advanced requests can be performed by providing a hash:
112
+ #
113
+ # Course.first_from_view('by_teacher', :startkey => 'bbb', :endkey => 'eee')
114
+ #
115
+ def first_from_view(name, *args)
116
+ query = {:limit => 1}
117
+ case args.first
118
+ when String, Array
119
+ query.update(args[1]) unless args[1].nil?
120
+ query[:key] = args.first
121
+ when Hash
122
+ query.update(args.first)
123
+ end
124
+ view(name, query).first
125
+ end
126
+
127
+ private
128
+
129
+ def fetch_view_with_docs( name, opts, raw=false, &block)
130
+ if raw || (opts.has_key?(:include_docs) && opts[:include_docs] == false)
131
+ fetch_view( name, opts, &block)
132
+ else
133
+ opts = opts.merge(:include_docs => true)
134
+ view = fetch_view name, opts, &block
135
+ view['rows'].collect{|r| load(r['doc']['_id'], r['doc'])} if view['rows']
136
+ end
137
+ end
138
+
139
+ def fetch_view(view_name, opts, &block)
140
+ raise "A view needs a database to operate on (specify :database option, or use_database in the #{self.class} class)" unless store.client
141
+ design_doc.view_on(store.client, view_name, opts, &block)
142
+ end
143
+ end # module ClassMethods
144
+ end
145
+ end
146
+ end
@@ -0,0 +1 @@
1
+ require 'toy/couch'
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/couch'
17
+ require 'support/constants'
18
+
19
+ STORE = CouchRest.database!("http://127.0.0.1:5984/toystore-couch-test")
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.recreate!
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(:couch, STORE)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,21 @@
1
+ require 'helper'
2
+ describe Toy::Couch::Type do
3
+ uses_constants('User')
4
+
5
+ before(:each) do
6
+ User.identity_map_off
7
+ User.send :include, Toy::Couch::Type
8
+ User.attribute(:name, String)
9
+ User.attribute(:state, String)
10
+ end
11
+
12
+ describe "revisioning" do
13
+ before(:each) do
14
+ @john = User.create(:name => 'John', :state=>"CA")
15
+ end
16
+
17
+ it "performs view" do
18
+ @john.persisted_attributes["type"].should == "User"
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,36 @@
1
+ require 'helper'
2
+ describe Toy::Couch::Views do
3
+ uses_constants('User')
4
+
5
+ before(:each) do
6
+ User.identity_map_off
7
+ User.attribute(:name, String)
8
+ User.attribute(:state, String)
9
+ User.view_by :state
10
+ User.view_by :name
11
+ end
12
+
13
+ describe "revisioning" do
14
+ before(:each) do
15
+ @john = User.create(:name => 'John', :state=>"CA")
16
+ @bill = User.create(:name => 'Bill', :state=>"FL")
17
+ @miguel = User.create(:name => 'Miguel', :state=>"FL")
18
+ end
19
+
20
+ it "performs view" do
21
+ all = User.view(:all)
22
+ all.should include(@john)
23
+ all.should include(@bill)
24
+ all.should include(@miguel)
25
+
26
+ User.view(:by_state, :key=>"FL").should include(@miguel)
27
+ User.view(:by_state, :key=>"FL").should include(@bill)
28
+
29
+ User.view(:by_state, :key=>"CA").should include(@john)
30
+
31
+ User.view(:by_name, :key=>"John").should include(@john)
32
+
33
+ User.first_from_view(:by_name, "John").should == @john
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "toy/couch/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "toystore-couch"
7
+ s.version = Toy::Couch::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Pelle Braendgaard']
10
+ s.email = ['pelle@stakeventures.com']
11
+ s.homepage = ''
12
+ s.summary = %q{CouchDB integration for Toystore}
13
+ s.description = %q{CouchDB integration for Toystore}
14
+
15
+ s.add_dependency('toystore', '~> 0.7.0')
16
+ s.add_dependency('adapter-couch', '~> 0.1.2')
17
+
18
+ s.files = `git ls-files`.split("\n") - ['specs.watchr']
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: toystore-couch
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Pelle Braendgaard
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-05-22 00:00:00 -04:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: toystore
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: 0.7.0
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: adapter-couch
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: 0.1.2
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ description: CouchDB integration for Toystore
39
+ email:
40
+ - pelle@stakeventures.com
41
+ executables: []
42
+
43
+ extensions: []
44
+
45
+ extra_rdoc_files: []
46
+
47
+ files:
48
+ - .gitignore
49
+ - Gemfile
50
+ - LICENSE
51
+ - README.rdoc
52
+ - Rakefile
53
+ - lib/toy/couch.rb
54
+ - lib/toy/couch/design_doc.rb
55
+ - lib/toy/couch/type.rb
56
+ - lib/toy/couch/version.rb
57
+ - lib/toy/couch/views.rb
58
+ - lib/toystore-couch.rb
59
+ - spec/helper.rb
60
+ - spec/spec.opts
61
+ - spec/support/constants.rb
62
+ - spec/toy/couch/type_spec.rb
63
+ - spec/toy/couch/views_spec.rb
64
+ - toystore-couch.gemspec
65
+ has_rdoc: true
66
+ homepage: ""
67
+ licenses: []
68
+
69
+ post_install_message:
70
+ rdoc_options: []
71
+
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: "0"
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: "0"
86
+ requirements: []
87
+
88
+ rubyforge_project:
89
+ rubygems_version: 1.6.2
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: CouchDB integration for Toystore
93
+ test_files:
94
+ - spec/helper.rb
95
+ - spec/spec.opts
96
+ - spec/support/constants.rb
97
+ - spec/toy/couch/type_spec.rb
98
+ - spec/toy/couch/views_spec.rb