simply_stored 0.1.12 → 0.1.13
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +26 -0
- data/LICENSE.txt +15 -0
- data/README.md +221 -0
- data/lib/simply_stored/couch/belongs_to.rb +8 -0
- data/lib/simply_stored/couch/has_many.rb +22 -0
- data/lib/simply_stored/couch.rb +1 -0
- data/lib/simply_stored/instance_methods.rb +14 -2
- data/test/simply_stored_couch_test.rb +63 -0
- metadata +18 -4
data/CHANGELOG.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Changelog
|
2
|
+
=============
|
3
|
+
|
4
|
+
0.1.13
|
5
|
+
=============
|
6
|
+
|
7
|
+
- Please delete all your design documents!
|
8
|
+
- Auto-generate count-methods for has_many associations, e.g
|
9
|
+
|
10
|
+
class Blog
|
11
|
+
include SimplyStored::Couch
|
12
|
+
has_many :posts
|
13
|
+
end
|
14
|
+
|
15
|
+
class Post
|
16
|
+
include SimplyStored::Couch
|
17
|
+
belongs_to :blog
|
18
|
+
end
|
19
|
+
|
20
|
+
blog = Blog.create
|
21
|
+
blog.post_count
|
22
|
+
# => 0
|
23
|
+
|
24
|
+
Post.create(:blog => blog)
|
25
|
+
blog.post_count(:force_reload => true)
|
26
|
+
# => 1
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* Copyright (c) 2010 Peritor GmbH <info@peritor.com>
|
3
|
+
*
|
4
|
+
* Permission to use, copy, modify, and distribute this software for any
|
5
|
+
* purpose with or without fee is hereby granted, provided that the above
|
6
|
+
* copyright notice and this permission notice appear in all copies.
|
7
|
+
*
|
8
|
+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
9
|
+
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
10
|
+
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
11
|
+
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
12
|
+
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
13
|
+
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
14
|
+
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
15
|
+
*/
|
data/README.md
ADDED
@@ -0,0 +1,221 @@
|
|
1
|
+
Convenience layer for CouchDB and SimpleDB. Requires CouchPotato and RightAWS library respectively.
|
2
|
+
|
3
|
+
SimplyStored allows you to persist your objects to CouchDB (or SimpleDB) using an ActiveRecord-like syntax.
|
4
|
+
|
5
|
+
In contrast to [CouchPotato](http://github.com/langalex/couch_potato) (on top of it is build)
|
6
|
+
it supports associations and other syntactic sugar that makes ActiveRecord so appealing.
|
7
|
+
|
8
|
+
Both backends have also support for S3 attachments.
|
9
|
+
|
10
|
+
See also [RockingChair](http://github.com/jweiss/rocking_chair) on how to speed-up your unit tests
|
11
|
+
by using an in-memory CouchDB backend.
|
12
|
+
|
13
|
+
Installation
|
14
|
+
============
|
15
|
+
|
16
|
+
gem install simply_stored
|
17
|
+
|
18
|
+
Usage
|
19
|
+
=============
|
20
|
+
|
21
|
+
Require the SimplyStored and decide with backend you want (CouchDB or SimpleDB).
|
22
|
+
|
23
|
+
require 'simply_stored/couch'
|
24
|
+
CouchPotato::Config.database_name = "http://example.com:5984/name_of_the_db"
|
25
|
+
|
26
|
+
or
|
27
|
+
|
28
|
+
require 'simply_stored/simpledb'
|
29
|
+
SimplyStored::Simple.aws_access_key = 'foo'
|
30
|
+
SimplyStored::Simple.aws_secret_access_key = 'bar'
|
31
|
+
RightAws::ActiveSdb.establish_connection(SimplyStored::Simple.aws_access_key, SimplyStored::Simple.aws_secret_access_key, :protocol => 'https')
|
32
|
+
|
33
|
+
From now on you can define classes that use SimplyStored.
|
34
|
+
|
35
|
+
CouchDB - Intro
|
36
|
+
=============
|
37
|
+
|
38
|
+
The CouchDB backend is better supported and has more features. It auto-generates views for you and handles
|
39
|
+
all the serialization and de-serialization stuff.
|
40
|
+
|
41
|
+
class User
|
42
|
+
include SimplyStored::Couch
|
43
|
+
|
44
|
+
property :login
|
45
|
+
property :age
|
46
|
+
property :accepted_terms_of_service, :type => :boolean
|
47
|
+
property :last_login, :type => Time
|
48
|
+
end
|
49
|
+
|
50
|
+
user = User.new(:login => 'Bert', :age => 12, :accepted_terms_of_service => true, :last_login = Time.now)
|
51
|
+
user.save
|
52
|
+
|
53
|
+
User.find_by_age(12).login
|
54
|
+
# => 'Bert'
|
55
|
+
|
56
|
+
User.all
|
57
|
+
# => [user]
|
58
|
+
|
59
|
+
class Post
|
60
|
+
include SimplyStored::Couch
|
61
|
+
|
62
|
+
property :title
|
63
|
+
property :body
|
64
|
+
|
65
|
+
belongs_to :user
|
66
|
+
end
|
67
|
+
|
68
|
+
class User
|
69
|
+
has_many :posts
|
70
|
+
end
|
71
|
+
|
72
|
+
post = Post.create(:title => 'My first post', :body => 'SimplyStored is so nice!', :user => user)
|
73
|
+
|
74
|
+
user.posts
|
75
|
+
# => [post]
|
76
|
+
|
77
|
+
Post.find_all_by_title_and_user_id('My first post', user.id).first.body
|
78
|
+
# => 'SimplyStored is so nice!'
|
79
|
+
|
80
|
+
post.destroy
|
81
|
+
|
82
|
+
user.posts(:force_reload => true)
|
83
|
+
# => []
|
84
|
+
|
85
|
+
|
86
|
+
CouchDB - Associations
|
87
|
+
=============
|
88
|
+
|
89
|
+
The supported associations are: belongs_to, has_one, has_many, and has_many :through
|
90
|
+
|
91
|
+
class Post
|
92
|
+
include SimplyStored::Couch
|
93
|
+
|
94
|
+
property :title
|
95
|
+
property :body
|
96
|
+
|
97
|
+
has_many :posts, :dependent => :destroy
|
98
|
+
has_many :users, :through => :posts
|
99
|
+
belongs_to :user
|
100
|
+
end
|
101
|
+
|
102
|
+
class Comment
|
103
|
+
include SimplyStored::Couch
|
104
|
+
|
105
|
+
property :body
|
106
|
+
|
107
|
+
belongs_to :post
|
108
|
+
belongs_to :user
|
109
|
+
end
|
110
|
+
|
111
|
+
post = Post.create(:title => 'Look ma!', :body => 'I can have comments')
|
112
|
+
|
113
|
+
mike = User.create(:login => 'mike')
|
114
|
+
mikes_comment = Comment.create(:user => mike, :post => post, :body => 'Wow, comments are nice')
|
115
|
+
|
116
|
+
john = User.create(:login => 'john')
|
117
|
+
johns_comment = Comment.create(:user => john, :post => post, :body => 'They are indeed')
|
118
|
+
|
119
|
+
post.comments
|
120
|
+
# => [mikes_comment, johns_comment]
|
121
|
+
|
122
|
+
post.comment_count
|
123
|
+
# => 2
|
124
|
+
|
125
|
+
post.users
|
126
|
+
# => [mike, john]
|
127
|
+
|
128
|
+
post.user_count
|
129
|
+
# => 2
|
130
|
+
|
131
|
+
|
132
|
+
CouchDB - Validations
|
133
|
+
=============
|
134
|
+
|
135
|
+
Further, you can have validations (using the validatable gem)
|
136
|
+
|
137
|
+
class Project
|
138
|
+
include SimplyStored::Couch
|
139
|
+
|
140
|
+
property :title
|
141
|
+
property :budget
|
142
|
+
property :deadline, :type => Time
|
143
|
+
property :priority
|
144
|
+
|
145
|
+
validates_presence_of :budget
|
146
|
+
validates_uniqueness_of :priority
|
147
|
+
validates_format_of :title, :with => /\A[a-z0-9\-']+\Z/, :allow_blank => true
|
148
|
+
validates_inclusion_of :priority, :in => [0,1,2,3,4,5]
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
project = Project.new
|
153
|
+
project.save
|
154
|
+
# => false
|
155
|
+
|
156
|
+
project.errors
|
157
|
+
# => #<Validatable::Errors:0x102592740 @errors={:budget=>["can't be empty"], :priority=>["must be one or more of 0, 1, 2, 3, 4, 5"]}
|
158
|
+
|
159
|
+
project.save!
|
160
|
+
# => raises CouchPotato::Database::ValidationsFailedError: #<CouchPotato::Database::ValidationsFailedError:0x102571130>
|
161
|
+
|
162
|
+
|
163
|
+
CouchDB - S3 Attachments
|
164
|
+
=============
|
165
|
+
|
166
|
+
Both the CouchDB backend and the SimpleDB backend have support for S3 attachments:
|
167
|
+
|
168
|
+
class Log
|
169
|
+
include SimplyStored::Couch
|
170
|
+
has_s3_attachment :data, :bucket => 'the-bucket-name',
|
171
|
+
:access_key => 'my-AWS-key-id',
|
172
|
+
:secret_access_key => 'psst!-secret',
|
173
|
+
:location => :eu,
|
174
|
+
:after_delete => :delete
|
175
|
+
|
176
|
+
end
|
177
|
+
|
178
|
+
log = Log.new
|
179
|
+
log.data = File.read('/var/log/messages')
|
180
|
+
log.save
|
181
|
+
# => true
|
182
|
+
|
183
|
+
This will create an item on S3 in the specified bucket. The item will use the ID of the log object as the key and the body will be the data attribute. This way you can store big files outside of CouchDB or SimpleDB.
|
184
|
+
|
185
|
+
|
186
|
+
CouchDB - Soft delete
|
187
|
+
=============
|
188
|
+
|
189
|
+
SimplyStored also has support for "soft deleting" - much like acts_as_paranoid. Items will then not be deleted but only marked as deleted. This way you can recover them later.
|
190
|
+
|
191
|
+
class Document
|
192
|
+
include SimplyStored::Couch
|
193
|
+
|
194
|
+
property :title
|
195
|
+
enable_soft_delete # will use :deleted_at attribute by default
|
196
|
+
end
|
197
|
+
|
198
|
+
doc = Document.create(:title => 'secret project info')
|
199
|
+
Document.find_all_by_title('secret project info')
|
200
|
+
# => [doc]
|
201
|
+
|
202
|
+
doc.destroy
|
203
|
+
|
204
|
+
Document.find_all_by_title('secret project info')
|
205
|
+
# => []
|
206
|
+
|
207
|
+
Document.find_all_by_title('secret project info', :with_deleted => true)
|
208
|
+
# => [doc]
|
209
|
+
|
210
|
+
|
211
|
+
License
|
212
|
+
=============
|
213
|
+
|
214
|
+
SimplyStored is licensed under the OpenBSD / two-clause BSD license, modeled after the ISC license. See LICENSE.txt
|
215
|
+
|
216
|
+
About
|
217
|
+
=============
|
218
|
+
|
219
|
+
SimplyStored was written by [Mathias Meyer](http://twitter.com/roidrage) and [Jonathan Weiss](http://twitter.com/jweiss) for [Peritor](http://www.peritor.com).
|
220
|
+
|
221
|
+
|
@@ -15,9 +15,16 @@ module SimplyStored
|
|
15
15
|
}
|
16
16
|
}
|
17
17
|
eos
|
18
|
+
|
19
|
+
reduce_definition = <<-eos
|
20
|
+
function(key, values) {
|
21
|
+
return values.length;
|
22
|
+
}
|
23
|
+
eos
|
18
24
|
|
19
25
|
view "association_#{self.name.underscore}_belongs_to_#{name}",
|
20
26
|
:map => map_definition_without_deleted,
|
27
|
+
:reduce => reduce_definition,
|
21
28
|
:type => "custom",
|
22
29
|
:include_docs => true
|
23
30
|
|
@@ -31,6 +38,7 @@ module SimplyStored
|
|
31
38
|
|
32
39
|
view "association_#{self.name.underscore}_belongs_to_#{name}_with_deleted",
|
33
40
|
:map => map_definition_with_deleted,
|
41
|
+
:reduce => reduce_definition,
|
34
42
|
:type => "custom",
|
35
43
|
:include_docs => true
|
36
44
|
|
@@ -93,6 +93,26 @@ module SimplyStored
|
|
93
93
|
end
|
94
94
|
end
|
95
95
|
|
96
|
+
def define_has_many_count(name, through = nil)
|
97
|
+
method_name = name.to_s.singularize.underscore + "_count"
|
98
|
+
define_method(method_name) do |*args|
|
99
|
+
options = args.first && args.first.is_a?(Hash) && args.first
|
100
|
+
if options
|
101
|
+
options.assert_valid_keys(:force_reload, :with_deleted)
|
102
|
+
forced_reload = options[:force_reload]
|
103
|
+
with_deleted = options[:with_deleted]
|
104
|
+
else
|
105
|
+
forced_reload = false
|
106
|
+
with_deleted = false
|
107
|
+
end
|
108
|
+
|
109
|
+
if forced_reload || instance_variable_get("@#{method_name}").nil?
|
110
|
+
instance_variable_set("@#{method_name}", count_associated(through || name, self.class, :with_deleted => with_deleted))
|
111
|
+
end
|
112
|
+
instance_variable_get("@#{method_name}")
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
96
116
|
class Property
|
97
117
|
attr_reader :name, :options
|
98
118
|
|
@@ -108,6 +128,7 @@ module SimplyStored
|
|
108
128
|
if options[:through]
|
109
129
|
owner_clazz.class_eval do
|
110
130
|
define_has_many_through_getter(name, options[:through])
|
131
|
+
define_has_many_count(name, options[:through])
|
111
132
|
end
|
112
133
|
else
|
113
134
|
owner_clazz.class_eval do
|
@@ -115,6 +136,7 @@ module SimplyStored
|
|
115
136
|
define_has_many_setter_add(name)
|
116
137
|
define_has_many_setter_remove(name)
|
117
138
|
define_has_many_setter_remove_all(name)
|
139
|
+
define_has_many_count(name)
|
118
140
|
end
|
119
141
|
end
|
120
142
|
end
|
data/lib/simply_stored/couch.rb
CHANGED
@@ -124,11 +124,23 @@ module SimplyStored
|
|
124
124
|
if options[:with_deleted]
|
125
125
|
CouchPotato.database.view(
|
126
126
|
self.class.get_class_from_name(from).send(
|
127
|
-
"association_#{from.to_s.singularize.underscore}_belongs_to_#{to.name.singularize.underscore}_with_deleted", :key => id))
|
127
|
+
"association_#{from.to_s.singularize.underscore}_belongs_to_#{to.name.singularize.underscore}_with_deleted", :reduce => false, :key => id))
|
128
128
|
else
|
129
129
|
CouchPotato.database.view(
|
130
130
|
self.class.get_class_from_name(from).send(
|
131
|
-
"association_#{from.to_s.singularize.underscore}_belongs_to_#{to.name.singularize.underscore}", :key => id))
|
131
|
+
"association_#{from.to_s.singularize.underscore}_belongs_to_#{to.name.singularize.underscore}", :reduce => false, :key => id))
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def count_associated(from, to, options = {})
|
136
|
+
if options[:with_deleted]
|
137
|
+
CouchPotato.database.view(
|
138
|
+
self.class.get_class_from_name(from).send(
|
139
|
+
"association_#{from.to_s.singularize.underscore}_belongs_to_#{to.name.singularize.underscore}_with_deleted", :key => id, :reduce => true, :include_docs => false))
|
140
|
+
else
|
141
|
+
CouchPotato.database.view(
|
142
|
+
self.class.get_class_from_name(from).send(
|
143
|
+
"association_#{from.to_s.singularize.underscore}_belongs_to_#{to.name.singularize.underscore}", :key => id, :reduce => true, :include_docs => false))
|
132
144
|
end
|
133
145
|
end
|
134
146
|
|
@@ -627,6 +627,69 @@ class CouchTest < Test::Unit::TestCase
|
|
627
627
|
assert_equal [], user.posts
|
628
628
|
assert_equal [], user.instance_variable_get("@posts")
|
629
629
|
end
|
630
|
+
|
631
|
+
context "when counting" do
|
632
|
+
setup do
|
633
|
+
@user = User.create(:title => "Mr.")
|
634
|
+
end
|
635
|
+
|
636
|
+
should "define a count method" do
|
637
|
+
assert @user.respond_to?(:post_count)
|
638
|
+
end
|
639
|
+
|
640
|
+
should "cache the result" do
|
641
|
+
assert_equal 0, @user.post_count
|
642
|
+
Post.create(:user => @user)
|
643
|
+
assert_equal 0, @user.post_count
|
644
|
+
assert_equal 0, @user.instance_variable_get("@post_count")
|
645
|
+
@user.instance_variable_set("@post_count", nil)
|
646
|
+
assert_equal 1, @user.post_count
|
647
|
+
end
|
648
|
+
|
649
|
+
should "force reload even if cached" do
|
650
|
+
assert_equal 0, @user.post_count
|
651
|
+
Post.create(:user => @user)
|
652
|
+
assert_equal 0, @user.post_count
|
653
|
+
assert_equal 1, @user.post_count(:force_reload => true)
|
654
|
+
end
|
655
|
+
|
656
|
+
should "count the number of belongs_to objects" do
|
657
|
+
assert_equal 0, @user.post_count(:force_reload => true)
|
658
|
+
Post.create(:user => @user)
|
659
|
+
assert_equal 1, @user.post_count(:force_reload => true)
|
660
|
+
Post.create(:user => @user)
|
661
|
+
assert_equal 2, @user.post_count(:force_reload => true)
|
662
|
+
end
|
663
|
+
|
664
|
+
should "not count foreign objects" do
|
665
|
+
assert_equal 0, @user.post_count
|
666
|
+
Post.create(:user => nil)
|
667
|
+
Post.create(:user => User.create(:title => 'Doc'))
|
668
|
+
assert_equal 0, @user.post_count
|
669
|
+
assert_equal 2, Post.count
|
670
|
+
end
|
671
|
+
|
672
|
+
should "not count delete objects" do
|
673
|
+
hemorrhoid = Hemorrhoid.create(:user => @user)
|
674
|
+
assert_equal 1, @user.hemorrhoid_count
|
675
|
+
hemorrhoid.delete
|
676
|
+
assert_equal 0, @user.hemorrhoid_count(:force_reload => true)
|
677
|
+
assert_equal 1, @user.hemorrhoid_count(:force_reload => true, :with_deleted => true)
|
678
|
+
end
|
679
|
+
|
680
|
+
should "work with has_many :through" do
|
681
|
+
assert_equal 0, @user.pain_count
|
682
|
+
first_pain = Pain.create
|
683
|
+
frist_hemorrhoid = Hemorrhoid.create(:user => @user, :pain => first_pain)
|
684
|
+
assert_equal [first_pain], @user.pains
|
685
|
+
assert_equal 1, @user.pain_count(:force_reload => true)
|
686
|
+
|
687
|
+
second_pain = Pain.create
|
688
|
+
second_hemorrhoid = Hemorrhoid.create(:user => @user, :pain => second_pain)
|
689
|
+
assert_equal 2, @user.pain_count(:force_reload => true)
|
690
|
+
end
|
691
|
+
|
692
|
+
end
|
630
693
|
end
|
631
694
|
|
632
695
|
context 'when destroying the parent objects' do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simply_stored
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Mathias Meyer, Jonathan Weiss
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2010-
|
12
|
+
date: 2010-02-12 00:00:00 +01:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,15 +22,29 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: 0.2.15
|
24
24
|
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: validatable
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: "0"
|
34
|
+
version:
|
25
35
|
description: Convenience layer for CouchDB and SimpleDB. Requires CouchPotato and RightAWS library respectively.
|
26
36
|
email: info@peritor.com
|
27
37
|
executables: []
|
28
38
|
|
29
39
|
extensions: []
|
30
40
|
|
31
|
-
extra_rdoc_files:
|
32
|
-
|
41
|
+
extra_rdoc_files:
|
42
|
+
- LICENSE.txt
|
43
|
+
- README.md
|
33
44
|
files:
|
45
|
+
- CHANGELOG.md
|
46
|
+
- LICENSE.txt
|
47
|
+
- README.md
|
34
48
|
- lib/simply_stored.rb
|
35
49
|
- lib/simply_stored/class_methods_base.rb
|
36
50
|
- lib/simply_stored/couch.rb
|