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 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
@@ -1,3 +1,4 @@
1
+ require 'validatable'
1
2
  require 'couch_potato'
2
3
 
3
4
  require File.expand_path(File.dirname(__FILE__) + '/../simply_stored')
@@ -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.12
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-01-20 00:00:00 +01:00
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