ShyCouch 0.3.5 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,7 +10,7 @@ GEM
10
10
  bundler (~> 1.0)
11
11
  git (>= 1.2.5)
12
12
  rake
13
- rake (0.8.7)
13
+ rake (0.9.2)
14
14
  rcov (0.9.10)
15
15
  ruby2ruby (1.2.5)
16
16
  ruby_parser (~> 2.0)
data/README CHANGED
@@ -1,15 +1,29 @@
1
1
  = ShyCouch
2
2
 
3
- ShyCouch is a Ruby library for CouchDB. It's a data persistence layer that uses native objects and a simple query language, designed primarily to replace ActiveRecord in Camping. A Python version is forthcoming.
3
+ ShyCouch is a Ruby library for CouchDB. It's a data persistence layer that uses native objects and lets you write native Ruby blocks that'll be parsed into MapReduce JavaScript functions.
4
4
 
5
- I'm mainly putting this up for feedback at the moment. It's a complete mess.
5
+ The structure is a bit of a mess, but essentially it provides:
6
6
 
7
- Eventually I'm gonna re-release it with all of Camping's code, with AR and stuff removed and this stuff put in.
7
+ - a database object
8
+ - a CouchDocument object, which is a glorified hash
9
+ - a Design object representing the Design document for an app in CouchDB
8
10
 
9
- ShyCouch does not come with an elaborate query language. It'll automatically create an 'all' query for all your models (don't use it unless you have a really good excuse not to write a custom query, doing the filtering at the application layer is hell slow), but aside from that you have to write your own map and reduce functions.
11
+ At the moment, all views have to be written manually. I'm currently evaluating the wisdom of building a query language to dynamically write views and cache them in Couch.
10
12
 
11
13
  = Usage
12
14
 
15
+ require 'shycouch'
16
+ settings = {
17
+ "db"=> {
18
+ "host" => "ramponeau.local",
19
+ "port" => 5984,
20
+ "name" => "food",
21
+ "user" => "cerales",
22
+ "password" => "password"
23
+ },
24
+ }
25
+ db = ShyCouch::CouchDatabase()
26
+
13
27
  == Models
14
28
 
15
29
  You don't need to define entity relationships or anything. Your models can just look like this:
data/Rakefile CHANGED
@@ -44,7 +44,8 @@ end
44
44
 
45
45
  task :default => :test
46
46
 
47
- require 'rake/rdoctask'
47
+ # require 'rake/rdoctask'
48
+ require 'rdoc/task'
48
49
  Rake::RDocTask.new do |rdoc|
49
50
  version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
51
 
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{ShyCouch}
8
- s.version = "0.3.5"
8
+ s.version = "0.4.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = [%q{Shy Inc.}, %q{Daniel Bryan}, %q{Cerales}]
12
- s.date = %q{2011-08-20}
12
+ s.date = %q{2011-08-28}
13
13
  s.description = %q{Ruby API for CouchDB, designed to work with the Camping micro-framework.}
14
14
  s.email = %q{danbryan@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -37,7 +37,8 @@ Gem::Specification.new do |s|
37
37
  "test/test_couchdb_api.rb",
38
38
  "test/test_couchdb_factory.rb",
39
39
  "test/test_design_documents.rb",
40
- "test/test_fields.rb"
40
+ "test/test_fields.rb",
41
+ "test/test_views.rb"
41
42
  ]
42
43
  s.homepage = %q{http://github.com/Cerales/ShyCouch}
43
44
  s.licenses = [%q{MIT}]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.5
1
+ 0.4.0
@@ -11,63 +11,37 @@ $:.unshift(File.dirname(__FILE__)) unless
11
11
  require 'net/http'
12
12
  require 'json'
13
13
  require 'resolv'
14
- require 'shyrubyjs'
14
+ # require 'shyrubyjs'
15
+ require '~/dev/gems/ShyRubyJS/lib/ShyRubyJS'
15
16
  # require everything from the 'ShyCouch' subdirectory
16
17
  Dir.new(File.dirname(__FILE__)+'/ShyCouch').each { |f| require 'shycouch/' + f.split('.')[0] unless f == '.' or f == '..' }
17
18
 
18
19
 
19
20
  module ShyCouch
20
21
  class << self
21
- def goes(m)
22
- Camping.goes m
23
- c = %{
24
- #{m.to_s}::Models::CouchDocument = ShyCouch::Data::CouchDocument
25
- }
26
- eval(c)
27
- ShyCouch.create
28
- end
29
22
 
30
- def create(settings=nil)
23
+ def create(settings=nil) #TODO - change this
31
24
  $couchdb = ShyCouch.getDB(settings)
32
25
  end
33
26
 
34
27
  def getDB(settings=nil)
35
- settings = $settings unless settings
36
- database = CouchDBAPI.new(settings["db"]["host"], settings["db"]["port"], settings["db"]["name"], settings["db"]["user"], settings["db"]["password"])
37
- puts database.connect unless database.connect["ok"]
28
+ settings = $couch_settings unless settings
29
+ database = CouchDatabase.new(settings)
30
+ puts database.connect unless database.connect["ok"] #TODO - hm
38
31
  database.create unless database.on_server?
39
32
  return database
40
33
  end
41
34
 
42
35
  end
43
36
  attr_accessor :database
44
-
45
- class Connection
46
- # Test that the database is accessible and give back a CouchDBAPI object if so.
47
- # Doesn't actually gets instantiated - is just here to allow nice ShyCouch::Connection.Create syntax
48
- # def self.Create(settings=nil)
49
- # settings = $settings unless settings
50
- # database = CouchDBAPI.new(settings["db"]["host"], settings["db"]["port"], settings["db"]["name"], settings["db"]["user"], settings["db"]["password"])
51
- # puts database.connect unless database.connect["ok"]
52
- # database.create unless database.on_server?
53
- # return database
54
- # end
55
-
56
- def push_generic_views
57
- #TODO
58
- end
59
- end
60
37
 
61
- class CouchDBAPI
62
- def initialize(host, port, name, user, password)
63
- @host, @port, @name, @user, @password = host, port, name, user, password
64
- @views = []
65
- @server = CouchServerConnection.allocate
38
+ class ShyCouchError < StandardError; end
39
+
40
+ class CouchDatabase
41
+ def initialize(settings)
42
+ # args = explode_settings(args) if args.size == 1
43
+ init(settings)
66
44
  end
67
- #
68
- # def initialize(*settings)
69
- # @host, @port, @name, @user, @password = settings["db"]["host"], settings["db"]["port"], settings["db"]["name"], settings["db"]["user"], settings["db"]["password"]
70
- # end
71
45
 
72
46
  attr_accessor :server, :name, :host, :port, :views
73
47
 
@@ -134,6 +108,14 @@ module ShyCouch
134
108
  end
135
109
 
136
110
  private
111
+
112
+ def init(settings)
113
+ db_settings = settings["db"]
114
+ @host, @port, @name, @user, @password = db_settings["host"],db_settings["port"], db_settings["name"],db_settings["user"], db_settings["password"]
115
+ @views = []
116
+ @server = CouchServerConnection.allocate
117
+ end
118
+
137
119
  class CouchServerConnection
138
120
  def initialize(args, options=nil)#host, port, user, password, options = nil)
139
121
  @host = args["host"]
@@ -237,9 +219,13 @@ module ShyCouch
237
219
 
238
220
  private
239
221
 
240
- def handle_error(req, res)
222
+ def handle_failure(req, res)
241
223
  raise RuntimeError.new("#{res.code}:#{res.message}\nMETHOD:#{req.method}\nURI:#{req.path}\n#{res.body}")
242
224
  end
225
+
226
+ def handle_error(e)
227
+ raise RuntimeError.new("#{e.inspect}\n Maybe be due to illegal rev or id change")
228
+ end
243
229
 
244
230
  def request(req)
245
231
  res = Net::HTTP.start(@host, @port) { |http|
@@ -247,11 +233,13 @@ module ShyCouch
247
233
  http.request(req)
248
234
  }
249
235
  unless res.kind_of?(Net::HTTPSuccess)
250
- handle_error(req, res)
236
+ handle_failure(req, res)
251
237
  end
252
238
  res
239
+ rescue Errno::ECONNRESET => e
240
+ handle_error(e)
253
241
  end
254
242
  end
255
-
243
+
256
244
  end
257
245
  end
@@ -5,9 +5,9 @@ module ShyCouch
5
5
  class CouchDocument < Hash
6
6
  class << self
7
7
  # allows instance.class.requirements to be called
8
- attr_accessor :requirements
9
8
  end
10
-
9
+ @@needs, @@suggests = [], []
10
+
11
11
  def initialize(hash={})
12
12
  # Assumes that the "kind" is the class name unless explicitly stated otherwise
13
13
  # TODO - maybe just force it to be the class name no matter what tbh
@@ -17,27 +17,30 @@ module ShyCouch
17
17
  # super(hash)
18
18
  end
19
19
 
20
- # def initialize(hash=nil, requirements)
21
- # @requirements = requirements
22
- # merge!(hash) if hash
23
- # raise TypeError unless valid? #TODO - should raise a more specific and useful error
24
- # end
25
-
26
20
  def self.all
27
- database = CouchDatabase.new($settings)
28
- database.get()
29
21
  end
30
-
31
- def self.requires(*requirements)
32
- @requirements = requirements
22
+
23
+ def self.needs(*requirements)
24
+ requirements.map { |requirement| @@needs << requirement } unless requirements.empty?
25
+ return @@needs
33
26
  end
34
-
35
- def add_key(key, value=nil)
36
- # The attr value assignment operator has been overriden, but it checks for the existence of a key.
37
- # And therefore the user has to explicitly call this method first.
38
- self[key] = value
27
+
28
+ def self.suggests(*suggestions)
29
+ suggestions.map { |suggestion| @@suggests << suggestion } unless suggestions.empty?
30
+ return @@suggests
39
31
  end
40
-
32
+
33
+ def needs;self.class.needs; end
34
+ def suggests; self.class.suggests; end
35
+
36
+ def needs?(requirement)
37
+ @@needs.include?(requirement) ? true : false
38
+ end
39
+
40
+ def suggests?(requirement)
41
+ @@suggests.include?(requirement) ? true : false
42
+ end
43
+
41
44
  def attr_keys
42
45
  # returns the keys for all the attrs that aren't the id or rev
43
46
  attr_keys = []
@@ -90,15 +93,54 @@ module ShyCouch
90
93
  end
91
94
 
92
95
  end
96
+
97
+ class View
98
+ attr_accessor :map, :reduce, :name
99
+
100
+ def initialize(view_name, &block)
101
+ @parser = ShyRubyJS::ShySexpParser.new
102
+ sexp_check = block.to_sexp
103
+ sexp = block.to_sexp(:strip_enclosure=>true)
104
+
105
+ # make sure the two blocks inside are calls to "map" and "reduce"
106
+
107
+ @name = view_name.to_s
108
+ if sexp[0] == :block
109
+ unless sexp_check[3][1][1][2] == :map and sexp_check[3][2][1][2] == :reduce
110
+ raise ShyCouchError, "view must be called with map block and optional reduce block"
111
+ end
112
+ [1,2].each { |num|
113
+ 2.times { sexp[num].delete_at(1) }
114
+ }
115
+ @map = @parser.parse(sexp[1])[0]
116
+ @reduce = @parser.parse(sexp[2]) if sexp[2].length > 1
117
+ elsif sexp[0] == :iter
118
+ raise ShyCouchError, "view must be called with map block and optional reduce block" unless sexp[1][2] == :map
119
+ @map = @parser.parse(sexp[3])
120
+ end
121
+ end
122
+
123
+ def as_hash
124
+ h = {}
125
+ h[@name] = {"map" => @map}
126
+ h.merge!({"reduce" => @reduce}) if @reduce
127
+ return h
128
+ end
129
+ end
93
130
 
94
131
  class Design < CouchDocument
95
132
  # this is used to manage design documents
96
133
  # In practise, the Controllers should be a list of classes corresponding to design documents
97
-
98
- def map(&block);end
99
-
100
- def reduce(&block);end
101
-
134
+
135
+ def initialize(name)
136
+ merge! "_id" => "_design/#{name.to_s}"
137
+ @parser = ShyRubyJS::ShySexpParser.new
138
+ end
139
+
140
+ def self.setup
141
+ # setup_all_view
142
+ end
143
+
102
144
  def push;end #must override push in order to set the ID
103
145
  end
104
146
 
@@ -7,7 +7,8 @@ require_relative '../lib/ShyCouch'
7
7
  # Settings for a database that is set up and working, with an admin user
8
8
  $settings = {
9
9
  "db"=> {
10
- "host" => "localhost",
10
+ "host" => "ramponeau.local",
11
+ # "host" => "localhost",
11
12
  "port" => 5984,
12
13
  "name" => "test",
13
14
  "user" => "cerales",
@@ -29,4 +30,6 @@ require_relative 'test_camping_integration'
29
30
 
30
31
  require_relative 'test_couchdb_factory'
31
32
 
32
- require_relative 'test_design_documents'
33
+ require_relative 'test_design_documents'
34
+
35
+ require_relative 'test_views'
@@ -38,7 +38,7 @@ class CouchDocumentTests# < Test::Unit::TestCase
38
38
  def test_create_from_fixnum
39
39
  # Ensure no doc creation with fixnum argument
40
40
  fixnum = 1
41
- assert_raises TypeError do
41
+ assert_raise TypeError do
42
42
  doc = ShyCouch::Data::CouchDocument.new(fixnum)
43
43
  end
44
44
  end
@@ -110,9 +110,9 @@ class CouchDocumentTests# < Test::Unit::TestCase
110
110
  @existing_valid_documents.each { |doc|
111
111
  # add some more attributes
112
112
  assert(doc._rev)
113
- doc.add_key("owner", "the guvvmint")
114
- doc.add_key("buttonCount")
115
- doc.add_key("friends")
113
+ doc["owner"] = "the guvvmint"
114
+ doc["buttonCount"] = nil
115
+ doc["friends"] = nil
116
116
  doc.buttonCount = 5
117
117
  doc.friends = ["alan", "alex", "all me other mates"]
118
118
 
@@ -132,7 +132,7 @@ class CouchDocumentTests# < Test::Unit::TestCase
132
132
  def test_illegal_change_to_rev
133
133
  @existing_valid_documents.each { |doc|
134
134
  doc._rev = "hurr"
135
- assert_raises RuntimeError do
135
+ assert_raise RuntimeError do
136
136
  res = doc.push
137
137
  end
138
138
  }
@@ -0,0 +1,51 @@
1
+ require 'test/unit'
2
+ require_relative '../lib/ShyCouch.rb'
3
+
4
+ class CouchViewTests < Test::Unit::TestCase
5
+ JS_MAP_FUNCTION_HEADER = "function ( doc ) { \n "
6
+ JS_REDUCE_FUNCTION_HEADER = "function(key, values, rereduce)"
7
+ JS_FUNCTION_FOOTER = "}"
8
+ def setup
9
+ @couch_views = []
10
+ end
11
+
12
+ def view(view_name, &block)
13
+ @couch_views << ShyCouch::Data::View.new(view_name, &block)
14
+ end
15
+ def teardown; end
16
+
17
+ def test_define_map_view
18
+ view :five_star_butts do
19
+ map do
20
+ def function(doc)
21
+ emit(doc) if doc.kind == "butt" and doc.star_rating == 5
22
+ end
23
+ end
24
+ end
25
+ expected_js = JS_MAP_FUNCTION_HEADER + %{if( doc.kind == "butt" && doc.star_rating == 5 ) {\n emit(doc)\n} \n } + JS_FUNCTION_FOOTER
26
+ assert_equal(expected_js, @couch_views[0].map)
27
+ # puts @couch_views[0].map
28
+ end
29
+
30
+ def test_define_map_and_reduce_view
31
+ view :beggar_count do
32
+ map do
33
+ def function(doc)
34
+ emit(doc) if doc.kind == "beggar"
35
+ end
36
+ end
37
+ reduce do
38
+ def function(key, values, rereduce)
39
+ return sum(values)
40
+ end
41
+ end
42
+ end
43
+ puts @couch_views[0].map
44
+ puts @couch_views[0].reduce
45
+ expected_map = JS_MAP_FUNCTION_HEADER + %{if( doc.kind == "beggar" ) {\n emit(doc)\n} \n } + JS_FUNCTION_FOOTER
46
+ expected_rejuce = JS_REDUCE_FUNCTION_HEADER
47
+ assert_equal(expected_map, @couch_views[0].map)
48
+
49
+ end
50
+
51
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ShyCouch
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.5
4
+ version: 0.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,11 +11,11 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2011-08-20 00:00:00.000000000Z
14
+ date: 2011-08-28 00:00:00.000000000Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: bundler
18
- requirement: &70214897246860 !ruby/object:Gem::Requirement
18
+ requirement: &70288155812040 !ruby/object:Gem::Requirement
19
19
  none: false
20
20
  requirements:
21
21
  - - ~>
@@ -23,10 +23,10 @@ dependencies:
23
23
  version: 1.0.0
24
24
  type: :development
25
25
  prerelease: false
26
- version_requirements: *70214897246860
26
+ version_requirements: *70288155812040
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: jeweler
29
- requirement: &70214897243320 !ruby/object:Gem::Requirement
29
+ requirement: &70288155810560 !ruby/object:Gem::Requirement
30
30
  none: false
31
31
  requirements:
32
32
  - - ~>
@@ -34,10 +34,10 @@ dependencies:
34
34
  version: 1.6.4
35
35
  type: :development
36
36
  prerelease: false
37
- version_requirements: *70214897243320
37
+ version_requirements: *70288155810560
38
38
  - !ruby/object:Gem::Dependency
39
39
  name: rcov
40
- requirement: &70214897240520 !ruby/object:Gem::Requirement
40
+ requirement: &70288155807920 !ruby/object:Gem::Requirement
41
41
  none: false
42
42
  requirements:
43
43
  - - ! '>='
@@ -45,10 +45,10 @@ dependencies:
45
45
  version: '0'
46
46
  type: :development
47
47
  prerelease: false
48
- version_requirements: *70214897240520
48
+ version_requirements: *70288155807920
49
49
  - !ruby/object:Gem::Dependency
50
50
  name: sourcify
51
- requirement: &70214897238760 !ruby/object:Gem::Requirement
51
+ requirement: &70288155806340 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
54
  - - ~>
@@ -56,10 +56,10 @@ dependencies:
56
56
  version: 0.5.0
57
57
  type: :development
58
58
  prerelease: false
59
- version_requirements: *70214897238760
59
+ version_requirements: *70288155806340
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: ShyRubyJS
62
- requirement: &70214897237620 !ruby/object:Gem::Requirement
62
+ requirement: &70288155792060 !ruby/object:Gem::Requirement
63
63
  none: false
64
64
  requirements:
65
65
  - - ! '>='
@@ -67,10 +67,10 @@ dependencies:
67
67
  version: '0'
68
68
  type: :development
69
69
  prerelease: false
70
- version_requirements: *70214897237620
70
+ version_requirements: *70288155792060
71
71
  - !ruby/object:Gem::Dependency
72
72
  name: ShyRubyJS
73
- requirement: &70214897236100 !ruby/object:Gem::Requirement
73
+ requirement: &70288155790660 !ruby/object:Gem::Requirement
74
74
  none: false
75
75
  requirements:
76
76
  - - ! '>='
@@ -78,10 +78,10 @@ dependencies:
78
78
  version: '0'
79
79
  type: :runtime
80
80
  prerelease: false
81
- version_requirements: *70214897236100
81
+ version_requirements: *70288155790660
82
82
  - !ruby/object:Gem::Dependency
83
83
  name: sourcify
84
- requirement: &70214897235140 !ruby/object:Gem::Requirement
84
+ requirement: &70288155789540 !ruby/object:Gem::Requirement
85
85
  none: false
86
86
  requirements:
87
87
  - - ! '>='
@@ -89,7 +89,7 @@ dependencies:
89
89
  version: '0'
90
90
  type: :runtime
91
91
  prerelease: false
92
- version_requirements: *70214897235140
92
+ version_requirements: *70288155789540
93
93
  description: Ruby API for CouchDB, designed to work with the Camping micro-framework.
94
94
  email: danbryan@gmail.com
95
95
  executables: []
@@ -119,6 +119,7 @@ files:
119
119
  - test/test_couchdb_factory.rb
120
120
  - test/test_design_documents.rb
121
121
  - test/test_fields.rb
122
+ - test/test_views.rb
122
123
  homepage: http://github.com/Cerales/ShyCouch
123
124
  licenses:
124
125
  - MIT
@@ -134,7 +135,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
134
135
  version: '0'
135
136
  segments:
136
137
  - 0
137
- hash: 3382610228249582822
138
+ hash: 1563418179645536452
138
139
  required_rubygems_version: !ruby/object:Gem::Requirement
139
140
  none: false
140
141
  requirements: