couchpillow 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/README.markdown +106 -0
- data/Rakefile +13 -0
- data/couchpillow.gemspec +21 -0
- data/lib/couchpillow.rb +17 -0
- data/lib/couchpillow/document.rb +153 -0
- data/lib/couchpillow/version.rb +5 -0
- data/test/document.rb +93 -0
- metadata +80 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aebdc9cea445fcc41867e0b89beda54aa8f4e317
|
4
|
+
data.tar.gz: ea3f214cabe96aac8e3001c1c48633e7cc9f4fc9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 102f839f848e55a30e02e9e1e7e5cad6c3d8cef6d641ba5bfa0aaef39d7c18129793dd4e380af6e98c27e03a1dd14b7f1a40e2f1ed47240f4f4f0f9137830b97
|
7
|
+
data.tar.gz: 10c976296156db15b542456e44bfed6d8c8e38d6247ceb62a8674606f109e51427a4810040933267cc13c16db966c6ba3ea134b75d902ea518d05298db973354
|
data/.gitignore
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# CouchPillow
|
2
|
+
|
3
|
+
Light and comfortable Document wrapper for Couchbase Server.
|
4
|
+
|
5
|
+
|
6
|
+
## Why CouchPillow?
|
7
|
+
|
8
|
+
Yet another ORM? Not quite. CouchPillow separates itself from the database
|
9
|
+
drivers, making it light and independent from the implementation.
|
10
|
+
|
11
|
+
Although it is initially designed to work with Couchbase Server, one can
|
12
|
+
easily extend this to other NoSQL drivers as long as they `respond_to?`
|
13
|
+
the `set`, `delete`, `replace`, and `get` methods.
|
14
|
+
|
15
|
+
|
16
|
+
## Features
|
17
|
+
|
18
|
+
- Automatic timestamp
|
19
|
+
- Validation
|
20
|
+
|
21
|
+
|
22
|
+
## How To Use
|
23
|
+
|
24
|
+
CouchPillow.db = Couchbase.connect(
|
25
|
+
bucket: 'default',
|
26
|
+
host: 'localhost' )
|
27
|
+
doc = CouchPillow::Document.new( { :stuff => 'hello' }, '123' )
|
28
|
+
doc.save!
|
29
|
+
|
30
|
+
# {
|
31
|
+
# '_type': 'default',
|
32
|
+
# 'stuff': 'hello',
|
33
|
+
# 'created_at': '2014-07-04 00:00:00 UTC'
|
34
|
+
# 'updated_at': '2014-07-04 00:00:00 UTC'
|
35
|
+
# }
|
36
|
+
|
37
|
+
|
38
|
+
Retrieving Documents
|
39
|
+
|
40
|
+
doc = CouchPillow::Document.get('123')
|
41
|
+
doc.stuff # '123'
|
42
|
+
|
43
|
+
|
44
|
+
Overriding `CouchPillow::Document`
|
45
|
+
|
46
|
+
class User < CouchPillow::Document
|
47
|
+
type :user
|
48
|
+
end
|
49
|
+
|
50
|
+
CouchPillow.db = Couchbase.connect(
|
51
|
+
bucket: 'default',
|
52
|
+
host: 'localhost' )
|
53
|
+
doc = User.new( { :email => 'john@email.com' } )
|
54
|
+
doc.email # 'john@email.com'
|
55
|
+
doc.save!
|
56
|
+
|
57
|
+
# {
|
58
|
+
# '_id': 'fb579b265cc005c47ff420a5c2a15d2b',
|
59
|
+
# '_type': 'user',
|
60
|
+
# 'email': 'john@email.com',
|
61
|
+
# 'created_at': '2014-07-04 00:00:00 UTC'
|
62
|
+
# 'updated_at': '2014-07-04 00:00:00 UTC'
|
63
|
+
# }
|
64
|
+
|
65
|
+
Using validation
|
66
|
+
|
67
|
+
class User < CouchPillow::Document
|
68
|
+
type :user
|
69
|
+
validate_presence :email
|
70
|
+
end
|
71
|
+
|
72
|
+
CouchPillow.db = Couchbase.connect(
|
73
|
+
bucket: 'default',
|
74
|
+
host: 'localhost' )
|
75
|
+
doc = User.new( { :first_name => 'John' } )
|
76
|
+
doc.save! # raises ValidationError('email is missing')
|
77
|
+
doc.email = 'john@email.com'
|
78
|
+
doc.save! # Success!
|
79
|
+
|
80
|
+
Using custom validation blocks
|
81
|
+
|
82
|
+
class User < CouchPillow::Document
|
83
|
+
type :user
|
84
|
+
validate_presence :email
|
85
|
+
validate :phone, 'is not a number', lambda { |v| v.is_a? Numeric }
|
86
|
+
end
|
87
|
+
|
88
|
+
CouchPillow.db = Couchbase.connect(
|
89
|
+
bucket: 'default',
|
90
|
+
host: 'localhost' )
|
91
|
+
doc = User.new
|
92
|
+
doc.email = 'john@email.com'
|
93
|
+
doc.first_name = 'john'
|
94
|
+
doc.phone = '123'
|
95
|
+
doc.save! # raises ValidationError('phone is not a number')
|
96
|
+
doc.phone = 123
|
97
|
+
doc.save! # Success!
|
98
|
+
|
99
|
+
What about Design Docs and Views? They are outside the scope of CouchPillow.
|
100
|
+
However, given a design doc named `my_design_doc` and a View named `by_email`,
|
101
|
+
that returns documents as values, you can easily use it like this:
|
102
|
+
|
103
|
+
CouchPillow.db.design_docs['my_design_doc'].
|
104
|
+
by_email(:body => { :key => 'john@email.com' }).map do |v|
|
105
|
+
new User(v.value, v.id)
|
106
|
+
end
|
data/Rakefile
ADDED
data/couchpillow.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require 'couchpillow/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "#{CouchPillow::GEM_NAME}"
|
6
|
+
s.version = CouchPillow::VERSION
|
7
|
+
s.authors = ["Albert Tedja"]
|
8
|
+
s.email = "nicho_tedja@yahoo.com"
|
9
|
+
s.homepage = "https://github.com/atedja/pillow"
|
10
|
+
s.summary = "Document wrapper for Couchbase"
|
11
|
+
s.description = "#{CouchPillow::NAME} is a light and comfortable Document wrapper for Couchbase Server."
|
12
|
+
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.test_files = `git ls-files -- {test}/*`.split("\n")
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
|
17
|
+
s.add_development_dependency 'minitest', '~> 5.3'
|
18
|
+
s.add_development_dependency 'mocha', '~> 1.1'
|
19
|
+
|
20
|
+
s.license = "BSD"
|
21
|
+
end
|
data/lib/couchpillow.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
module CouchPillow
|
2
|
+
|
3
|
+
class Document
|
4
|
+
|
5
|
+
class ValidationError < StandardError; end
|
6
|
+
|
7
|
+
|
8
|
+
attr_reader :id
|
9
|
+
|
10
|
+
|
11
|
+
@type = "default"
|
12
|
+
|
13
|
+
|
14
|
+
PRESENCE_LAMBDA = lambda do |value| !value.nil? end
|
15
|
+
|
16
|
+
|
17
|
+
def initialize hash = {}, id = SecureRandom.hex
|
18
|
+
@data = self.class.symbolize(hash)
|
19
|
+
@id = id
|
20
|
+
|
21
|
+
@data[:created_at] and
|
22
|
+
@data[:created_at] = Time.parse(@data[:created_at]) or
|
23
|
+
@data[:created_at] = Time.now.utc
|
24
|
+
@data[:updated_at] = Time.parse(@data[:updated_at]) if @data[:updated_at]
|
25
|
+
|
26
|
+
raise TypeError if @data[:_type] && @data[:_type] != self.class._type
|
27
|
+
@data[:_type] = self.class._type
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Map hash keys to methods
|
32
|
+
#
|
33
|
+
def method_missing m, *args, &block
|
34
|
+
ms = m.to_s
|
35
|
+
if ms.end_with?("=")
|
36
|
+
ms.gsub!('=', '')
|
37
|
+
@data[ms.to_sym] = args[0]
|
38
|
+
else
|
39
|
+
@data.has_key?(m) and
|
40
|
+
@data[m] or
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
def respond_to? m
|
47
|
+
ms = m.to_s
|
48
|
+
return true if ms.end_with?("=")
|
49
|
+
@data.has_key?(m) or
|
50
|
+
super
|
51
|
+
end
|
52
|
+
|
53
|
+
def timestamp!
|
54
|
+
@data[:updated_at] = Time.now.utc
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def save!
|
59
|
+
validate
|
60
|
+
sort!
|
61
|
+
timestamp!
|
62
|
+
CouchPillow.db.set @id, @data
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def delete!
|
67
|
+
CouchPillow.db.delete @id
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def sort!
|
72
|
+
@data = @data.sort.to_h
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# Attempt to update this Document. Fails if this Document does not yet
|
77
|
+
# exist in the database.
|
78
|
+
#
|
79
|
+
def update!
|
80
|
+
validate
|
81
|
+
sort!
|
82
|
+
timestamp!
|
83
|
+
CouchPillow.db.replace @id, @data
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def to_json *a
|
88
|
+
h = { :_id => @id }.merge!(@data)
|
89
|
+
h.to_json(*a)
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
def validate
|
94
|
+
self.class.validate_keys.each do |k, msg, method|
|
95
|
+
raise ValidationError, "#{k} #{msg}" unless
|
96
|
+
@data.has_key?(k) && method.call(@data[k])
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
def self.get id
|
102
|
+
new(CouchPillow.db.get(id), id)
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
def self.type value
|
107
|
+
@type = value.to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
def self._type
|
112
|
+
@type
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def self.design_doc value
|
117
|
+
@ddoc = value
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
# Validate the presence of a particular key, and the value of that key
|
122
|
+
# cannot be nil.
|
123
|
+
#
|
124
|
+
def self.validate_presence key
|
125
|
+
validate key, "is missing", PRESENCE_LAMBDA
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
# Validate the presence of a particular key using a custom validation method.
|
130
|
+
# Implies the Document must contain the key.
|
131
|
+
#
|
132
|
+
def self.validate key, message, block
|
133
|
+
raise ValidationError, "Provide validation method for key #{key}" unless block
|
134
|
+
validate_keys << [key, message, block]
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
|
141
|
+
def self.validate_keys
|
142
|
+
@validate_key ||= []
|
143
|
+
end
|
144
|
+
|
145
|
+
|
146
|
+
def self.symbolize hash
|
147
|
+
hash.inject({}) do |memo,(k,v)|
|
148
|
+
memo[k.to_sym] = v
|
149
|
+
memo
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
data/test/document.rb
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
require 'minitest/unit'
|
3
|
+
require 'mocha/mini_test'
|
4
|
+
require './lib/couchpillow.rb'
|
5
|
+
|
6
|
+
class TestDocument < Minitest::Test
|
7
|
+
|
8
|
+
class TestDoc < CouchPillow::Document
|
9
|
+
type :test
|
10
|
+
validate_presence :xyz
|
11
|
+
validate :xyz, "must be Numeric", lambda { |v| v.is_a? Numeric }
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
def mock_time
|
16
|
+
return @time if @time
|
17
|
+
@time = Time.now
|
18
|
+
Time.stubs(:now).returns(@time)
|
19
|
+
@time
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
def setup
|
24
|
+
@db = mock('couchbaseserver')
|
25
|
+
@db.stubs(:set)
|
26
|
+
@db.stubs(:delete)
|
27
|
+
@db.stubs(:replace)
|
28
|
+
@db.stubs(:get)
|
29
|
+
CouchPillow.db = @db
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def test_create
|
34
|
+
d = CouchPillow::Document.new
|
35
|
+
d.save!
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def test_timestamp
|
40
|
+
d = CouchPillow::Document.new({}, "1")
|
41
|
+
d.save!
|
42
|
+
assert d.created_at
|
43
|
+
assert d.updated_at
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def test_type
|
48
|
+
d = CouchPillow::Document.new({}, "1")
|
49
|
+
assert_equal "default", d._type
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def test_type_subclasses
|
54
|
+
d = TestDoc.new({}, "1")
|
55
|
+
assert_equal "test", d._type
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def test_validate_presence
|
60
|
+
d = TestDoc.new
|
61
|
+
assert_raises CouchPillow::Document::ValidationError do
|
62
|
+
d.save!
|
63
|
+
end
|
64
|
+
|
65
|
+
d.xyz = 10
|
66
|
+
d.save!
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def test_validate_custom
|
71
|
+
d = TestDoc.new
|
72
|
+
d.xyz = "string"
|
73
|
+
assert_raises CouchPillow::Document::ValidationError do
|
74
|
+
d.save!
|
75
|
+
end
|
76
|
+
|
77
|
+
d.xyz = {}
|
78
|
+
assert_raises CouchPillow::Document::ValidationError do
|
79
|
+
d.save!
|
80
|
+
end
|
81
|
+
|
82
|
+
d.xyz = 123
|
83
|
+
d.save!
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def test_to_json
|
88
|
+
mock_time
|
89
|
+
d = CouchPillow::Document.new({}, "1")
|
90
|
+
assert_equal "{\"_id\":\"1\",\"created_at\":\"#{mock_time.to_s}\",\"_type\":\"default\"}", d.to_json
|
91
|
+
end
|
92
|
+
|
93
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: couchpillow
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Albert Tedja
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: minitest
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: mocha
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.1'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.1'
|
41
|
+
description: CouchPillow is a light and comfortable Document wrapper for Couchbase
|
42
|
+
Server.
|
43
|
+
email: nicho_tedja@yahoo.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- README.markdown
|
50
|
+
- Rakefile
|
51
|
+
- couchpillow.gemspec
|
52
|
+
- lib/couchpillow.rb
|
53
|
+
- lib/couchpillow/document.rb
|
54
|
+
- lib/couchpillow/version.rb
|
55
|
+
- test/document.rb
|
56
|
+
homepage: https://github.com/atedja/pillow
|
57
|
+
licenses:
|
58
|
+
- BSD
|
59
|
+
metadata: {}
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
requirements: []
|
75
|
+
rubyforge_project:
|
76
|
+
rubygems_version: 2.2.2
|
77
|
+
signing_key:
|
78
|
+
specification_version: 4
|
79
|
+
summary: Document wrapper for Couchbase
|
80
|
+
test_files: []
|