davber_couchrest_extended_document 1.0.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/LICENSE +176 -0
- data/README.md +250 -0
- data/Rakefile +69 -0
- data/THANKS.md +22 -0
- data/examples/model/example.rb +144 -0
- data/history.txt +165 -0
- data/lib/couchrest/casted_array.rb +39 -0
- data/lib/couchrest/casted_model.rb +53 -0
- data/lib/couchrest/extended_document.rb +262 -0
- data/lib/couchrest/mixins.rb +12 -0
- data/lib/couchrest/mixins/attribute_protection.rb +74 -0
- data/lib/couchrest/mixins/attributes.rb +75 -0
- data/lib/couchrest/mixins/callbacks.rb +534 -0
- data/lib/couchrest/mixins/class_proxy.rb +120 -0
- data/lib/couchrest/mixins/collection.rb +260 -0
- data/lib/couchrest/mixins/design_doc.rb +159 -0
- data/lib/couchrest/mixins/document_queries.rb +82 -0
- data/lib/couchrest/mixins/extended_attachments.rb +73 -0
- data/lib/couchrest/mixins/properties.rb +130 -0
- data/lib/couchrest/mixins/typecast.rb +174 -0
- data/lib/couchrest/mixins/views.rb +148 -0
- data/lib/couchrest/property.rb +96 -0
- data/lib/couchrest/support/couchrest.rb +19 -0
- data/lib/couchrest/support/rails.rb +42 -0
- data/lib/couchrest/validation.rb +246 -0
- data/lib/couchrest/validation/auto_validate.rb +156 -0
- data/lib/couchrest/validation/contextual_validators.rb +78 -0
- data/lib/couchrest/validation/validation_errors.rb +125 -0
- data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
- data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
- data/lib/couchrest/validation/validators/format_validator.rb +122 -0
- data/lib/couchrest/validation/validators/formats/email.rb +66 -0
- data/lib/couchrest/validation/validators/formats/url.rb +43 -0
- data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
- data/lib/couchrest/validation/validators/length_validator.rb +139 -0
- data/lib/couchrest/validation/validators/method_validator.rb +89 -0
- data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
- data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
- data/lib/couchrest_extended_document.rb +23 -0
- data/spec/couchrest/attribute_protection_spec.rb +150 -0
- data/spec/couchrest/casted_extended_doc_spec.rb +79 -0
- data/spec/couchrest/casted_model_spec.rb +424 -0
- data/spec/couchrest/extended_doc_attachment_spec.rb +148 -0
- data/spec/couchrest/extended_doc_inherited_spec.rb +40 -0
- data/spec/couchrest/extended_doc_spec.rb +869 -0
- data/spec/couchrest/extended_doc_subclass_spec.rb +101 -0
- data/spec/couchrest/extended_doc_view_spec.rb +529 -0
- data/spec/couchrest/property_spec.rb +790 -0
- data/spec/fixtures/attachments/README +3 -0
- data/spec/fixtures/attachments/couchdb.png +0 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/more/article.rb +35 -0
- data/spec/fixtures/more/card.rb +22 -0
- data/spec/fixtures/more/cat.rb +22 -0
- data/spec/fixtures/more/course.rb +25 -0
- data/spec/fixtures/more/event.rb +8 -0
- data/spec/fixtures/more/invoice.rb +17 -0
- data/spec/fixtures/more/person.rb +9 -0
- data/spec/fixtures/more/question.rb +7 -0
- data/spec/fixtures/more/service.rb +12 -0
- data/spec/fixtures/more/user.rb +22 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +49 -0
- data/utils/remap.rb +27 -0
- data/utils/subset.rb +30 -0
- metadata +225 -0
data/THANKS.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
CouchRest THANKS
|
2
|
+
=====================
|
3
|
+
|
4
|
+
CouchRest was originally developed by J. Chris Anderson <jchris@grabb.it>
|
5
|
+
and a number of other contributors. Many people further contributed to
|
6
|
+
CouchRest by reporting problems, suggesting various improvements or submitting
|
7
|
+
changes. A list of these people is included below.
|
8
|
+
|
9
|
+
* [Matt Aimonetti](http://merbist.com/about/)
|
10
|
+
* [Greg Borenstein](http://ideasfordozens.com)
|
11
|
+
* [Geoffrey Grosenbach](http://nubyonrails.com/)
|
12
|
+
* [Jonathan S. Katz](http://github.com/jkatz)
|
13
|
+
* [Matt Lyon](http://mattly.tumblr.com/)
|
14
|
+
* Simon Rozet (simon /at/ rozet /dot/ name)
|
15
|
+
* [Marcos Tapajós](http://tapajos.me)
|
16
|
+
* [Sam Lown](http://github.com/samlown)
|
17
|
+
* [Will Leinweber](http://github.com/will)
|
18
|
+
|
19
|
+
|
20
|
+
Patches are welcome. The primary source for this software project is [on Github](http://github.com/couchrest/couchrest_extended_document)
|
21
|
+
|
22
|
+
A lot of people have active forks - thank you all - even the patches I don't end up using are helpful.
|
@@ -0,0 +1,144 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'couchrest')
|
2
|
+
|
3
|
+
def show obj
|
4
|
+
puts obj.inspect
|
5
|
+
puts
|
6
|
+
end
|
7
|
+
|
8
|
+
SERVER = CouchRest.new
|
9
|
+
SERVER.default_database = 'couchrest-extendeddoc-example'
|
10
|
+
|
11
|
+
class Author < CouchRest::ExtendedDocument
|
12
|
+
use_database SERVER.default_database
|
13
|
+
property :name
|
14
|
+
|
15
|
+
def drink_scotch
|
16
|
+
puts "... glug type glug ... I'm #{name} ... type glug glug ..."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Post < CouchRest::ExtendedDocument
|
21
|
+
use_database SERVER.default_database
|
22
|
+
|
23
|
+
property :title
|
24
|
+
property :body
|
25
|
+
property :author, :cast_as => 'Author'
|
26
|
+
|
27
|
+
timestamps!
|
28
|
+
end
|
29
|
+
|
30
|
+
class Comment < CouchRest::ExtendedDocument
|
31
|
+
use_database SERVER.default_database
|
32
|
+
|
33
|
+
property :commenter, :cast_as => 'Author'
|
34
|
+
timestamps!
|
35
|
+
|
36
|
+
def post= post
|
37
|
+
self["post_id"] = post.id
|
38
|
+
end
|
39
|
+
def post
|
40
|
+
Post.get(self['post_id']) if self['post_id']
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
puts "Act I: CRUD"
|
46
|
+
puts
|
47
|
+
puts "(pause for dramatic effect)"
|
48
|
+
puts
|
49
|
+
sleep 2
|
50
|
+
|
51
|
+
puts "Create an author."
|
52
|
+
quentin = Author.new("name" => "Quentin Hazel")
|
53
|
+
show quentin
|
54
|
+
|
55
|
+
puts "Create a new post."
|
56
|
+
post = Post.new(:title => "First Post", :body => "Lorem ipsum dolor sit amet, consectetur adipisicing elit...")
|
57
|
+
show post
|
58
|
+
|
59
|
+
puts "Add the author to the post."
|
60
|
+
post.author = quentin
|
61
|
+
show post
|
62
|
+
|
63
|
+
puts "Save the post."
|
64
|
+
post.save
|
65
|
+
show post
|
66
|
+
|
67
|
+
puts "Load the post."
|
68
|
+
reloaded = Post.get(post.id)
|
69
|
+
show reloaded
|
70
|
+
|
71
|
+
puts "The author of the post is an instance of Author."
|
72
|
+
reloaded.author.drink_scotch
|
73
|
+
|
74
|
+
puts "\nAdd some comments to the post."
|
75
|
+
comment_one = Comment.new :text => "Blah blah blah", :commenter => {:name => "Joe Sixpack"}
|
76
|
+
comment_two = Comment.new :text => "Yeah yeah yeah", :commenter => {:name => "Jane Doe"}
|
77
|
+
comment_three = Comment.new :text => "Whatever...", :commenter => {:name => "John Stewart"}
|
78
|
+
|
79
|
+
# TODO - maybe add some magic here?
|
80
|
+
comment_one.post = post
|
81
|
+
comment_two.post = post
|
82
|
+
comment_three.post = post
|
83
|
+
comment_one.save
|
84
|
+
comment_two.save
|
85
|
+
comment_three.save
|
86
|
+
|
87
|
+
show comment_one
|
88
|
+
show comment_two
|
89
|
+
show comment_three
|
90
|
+
|
91
|
+
puts "We can load a post through its comment (no magic here)."
|
92
|
+
show post = comment_one.post
|
93
|
+
|
94
|
+
puts "Commenters are also authors."
|
95
|
+
comment_two['commenter'].drink_scotch
|
96
|
+
comment_one['commenter'].drink_scotch
|
97
|
+
comment_three['commenter'].drink_scotch
|
98
|
+
|
99
|
+
puts "\nLet's save an author to her own document."
|
100
|
+
jane = comment_two['commenter']
|
101
|
+
jane.save
|
102
|
+
show jane
|
103
|
+
|
104
|
+
puts "Oh, that's neat! Because Ruby passes hash valuee by reference, Jane's new id has been added to the comment she left."
|
105
|
+
show comment_two
|
106
|
+
|
107
|
+
puts "Of course, we'd better remember to save it."
|
108
|
+
comment_two.save
|
109
|
+
show comment_two
|
110
|
+
|
111
|
+
puts "Oooh, denormalized... feel the burn!"
|
112
|
+
puts
|
113
|
+
puts
|
114
|
+
puts
|
115
|
+
puts "Act II: Views"
|
116
|
+
puts
|
117
|
+
puts
|
118
|
+
sleep 2
|
119
|
+
|
120
|
+
puts "Let's find all the comments that go with our post."
|
121
|
+
puts "Our post has id #{post.id}, so lets find all the comments with that post_id."
|
122
|
+
puts
|
123
|
+
|
124
|
+
class Comment
|
125
|
+
view_by :post_id
|
126
|
+
end
|
127
|
+
|
128
|
+
comments = Comment.by_post_id :key => post.id
|
129
|
+
show comments
|
130
|
+
|
131
|
+
puts "That was too easy."
|
132
|
+
puts "We can even wrap it up in a finder on the Post class."
|
133
|
+
puts
|
134
|
+
|
135
|
+
class Post
|
136
|
+
def comments
|
137
|
+
Comment.by_post_id :key => id
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
show post.comments
|
142
|
+
puts "Gimme 5 minutes and I'll roll this into the framework. ;)"
|
143
|
+
puts
|
144
|
+
puts "There is a lot more that can be done with views, but a lot of the interesting stuff is joins, which of course range across types. We'll pick up where we left off, next time."
|
data/history.txt
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
== Next Version
|
2
|
+
|
3
|
+
* Major enhancements
|
4
|
+
|
5
|
+
* Minor enhancements
|
6
|
+
|
7
|
+
== 1.0.0.beta6
|
8
|
+
|
9
|
+
* Minor enhancements
|
10
|
+
* Added 'find_by_*' alias for finding first item in view with matching key.
|
11
|
+
* Fixed issue with active_support in Rails3 and text in README for JSON.
|
12
|
+
* Refactoring of properties, added read_attribute and write_attribute methods.
|
13
|
+
* Now possible to send anything to update_attribtues method. Invalid or readonly attributes will be ignored.
|
14
|
+
* Updating to use couchrest_inheritable_attributes to avoid possible Rails conflicts
|
15
|
+
|
16
|
+
* Major enhancements
|
17
|
+
* Added support for anonymous CastedModels defined in Documents
|
18
|
+
|
19
|
+
== 1.0.0.beta5
|
20
|
+
|
21
|
+
* Minor enhancements
|
22
|
+
* Added 'find' alias for 'get' for easier rails transition
|
23
|
+
|
24
|
+
== 1.0.0.beta3
|
25
|
+
|
26
|
+
* Minor enhancements
|
27
|
+
* Removed Validation by default, requires too many structure changes (FAIL)
|
28
|
+
* Added support for instantiation of documents read from database as couchrest-type provided (Sam Lown)
|
29
|
+
* Improved attachment handling for detecting file type (Sam Lown)
|
30
|
+
* Removing some monkey patches and relying on active_support for constantize and humanize (Sam Lown)
|
31
|
+
* Added support for setting type directly on property (Sam Lown)
|
32
|
+
|
33
|
+
|
34
|
+
== 1.0.0.beta2
|
35
|
+
|
36
|
+
* Minor enhancements
|
37
|
+
* Enable Validation by default and refactored location (Sam Lown)
|
38
|
+
|
39
|
+
== 1.0.0.beta
|
40
|
+
|
41
|
+
* Major enhancements
|
42
|
+
* Separated ExtendedDocument from main CouchRest gem (Sam Lown)
|
43
|
+
|
44
|
+
* Minor enhancements
|
45
|
+
* active_support included by default
|
46
|
+
|
47
|
+
== 0.37
|
48
|
+
|
49
|
+
* Minor enhancements
|
50
|
+
* Added gemspec (needed for Bundler install) (Tapajós)
|
51
|
+
|
52
|
+
== 0.36
|
53
|
+
|
54
|
+
* Major enhancements
|
55
|
+
* Adds support for continuous replication (sauy7)
|
56
|
+
* Automatic Type Casting (Alexander Uvarov, Sam Lown, Tim Heighes, Will Leinweber)
|
57
|
+
* Added a search method to CouchRest:Database to search the documents in a given database. (Dave Farkas, Arnaud Berthomier, John Wood)
|
58
|
+
|
59
|
+
* Minor enhancements
|
60
|
+
* Provide a description of the timeout error (John Wood)
|
61
|
+
|
62
|
+
== 0.35
|
63
|
+
|
64
|
+
* Major enhancements
|
65
|
+
* CouchRest::ExtendedDocument allow chaining the inherit class callback (Kenneth Kalmer) - http://github.com/couchrest/couchrest/issues#issue/8
|
66
|
+
|
67
|
+
* Minor enhancements
|
68
|
+
* Fix attachment bug (Johannes Jörg Schmidt)
|
69
|
+
* Fix create database exception bug (Damien Mathieu)
|
70
|
+
* Compatible with restclient >= 1.4.0 new responses (Julien Kirch)
|
71
|
+
* Bug fix: Attribute protection no longer strips attributes coming from the database (Will Leinweber)
|
72
|
+
* Bug fix: Remove double CGI escape when PUTting an attachment (nzoschke)
|
73
|
+
* Bug fix: Changing Class proxy to set database on result sets (Peter Gumeson)
|
74
|
+
* Bug fix: Updated time regexp (Nolan Darilek)
|
75
|
+
* Added an update_doc method to database to handle conflicts during atomic updates. (Pierre Larochelle)
|
76
|
+
* Bug fix: http://github.com/couchrest/couchrest/issues/#issue/2 (Luke Burton)
|
77
|
+
|
78
|
+
== 0.34
|
79
|
+
|
80
|
+
* Major enhancements
|
81
|
+
|
82
|
+
* Added support for https database URIs. (Mathias Meyer)
|
83
|
+
* Changing some validations to be compatible with activemodel. (Marcos Tapajós)
|
84
|
+
* Adds attribute protection to properties. (Will Leinweber)
|
85
|
+
* Improved CouchRest::Database#save_doc, added "batch" mode to significantly speed up saves at cost of lower durability gurantees. (Igal Koshevoy)
|
86
|
+
* Added CouchRest::Database#bulk_save_doc and #batch_save_doc as human-friendlier wrappers around #save_doc. (Igal Koshevoy)
|
87
|
+
|
88
|
+
* Minor enhancements
|
89
|
+
|
90
|
+
* Fix content_type handling for attachments
|
91
|
+
* Fixed a bug in the pagination code that caused it to paginate over records outside of the scope of the view parameters.(John Wood)
|
92
|
+
* Removed amount_pages calculation for the pagination collection, since it cannot be reliably calculated without a view.(John Wood)
|
93
|
+
* Bug fix: http://github.com/couchrest/couchrest/issues/#issue/2 (Luke Burton)
|
94
|
+
* Bug fix: http://github.com/couchrest/couchrest/issues/#issue/1 (Marcos Tapajós)
|
95
|
+
* Removed the Database class deprecation notices (Matt Aimonetti)
|
96
|
+
* Adding support to :cast_as => 'Date'. (Marcos Tapajós)
|
97
|
+
* Improve documentation (Marcos Tapajós)
|
98
|
+
* Streamer fixes (Julien Sanchez)
|
99
|
+
* Fix Save on Document & ExtendedDocument crashed if bulk (Julien Sanchez)
|
100
|
+
* Fix Initialization of ExtendentDocument model shouldn't failed on a nil value in argument (deepj)
|
101
|
+
* Change to use Jeweler and Gemcutter (Marcos Tapajós)
|
102
|
+
|
103
|
+
== 0.33
|
104
|
+
|
105
|
+
* Major enhancements
|
106
|
+
|
107
|
+
* Added a new Rack logger middleware letting you log/save requests/queries (Matt Aimonetti)
|
108
|
+
|
109
|
+
* Minor enhancements
|
110
|
+
|
111
|
+
* Added #amount_pages to a paginated result array (Matt Aimonetti)
|
112
|
+
* Ruby 1.9.2 compatible (Matt Aimonetti)
|
113
|
+
* Added a property? method for property cast as :boolean (John Wood)
|
114
|
+
* Added an option to force the deletion of a attachments (bypass 409s) (Matt Aimonetti)
|
115
|
+
* Created a new abstraction layer for the REST API (Matt Aimonetti)
|
116
|
+
* Bug fix: made ExtendedDocument#all compatible with Couch 0.10 (tc)
|
117
|
+
|
118
|
+
== 0.32
|
119
|
+
|
120
|
+
* Major enhancements
|
121
|
+
|
122
|
+
* ExtendedDocument.get doesn't raise an exception anymore. If no documents are found nil is returned.
|
123
|
+
* ExtendedDocument.get! works the say #get used to work and will raise an exception if a document isn't found.
|
124
|
+
|
125
|
+
* Minor enhancements
|
126
|
+
|
127
|
+
* Bug fix: Model.all(:keys => [1,2]) was not working (Matt Aimonetti)
|
128
|
+
* Added ValidationErrors#count in order to play nicely with Rails (Peter Wagenet)
|
129
|
+
* Bug fix: class proxy design doc refresh (Daniel Kirsh)
|
130
|
+
* Bug fix: the count method on the proxy collection was missing (Daniel Kirsch)
|
131
|
+
* Added #amount_pages to a paginated collection. (Matt Aimonetti)
|
132
|
+
|
133
|
+
== 0.31
|
134
|
+
|
135
|
+
* Major enhancements
|
136
|
+
|
137
|
+
* Created an abstraction HTTP layer to support different http adapters (Matt Aimonetti)
|
138
|
+
* Added ExtendedDocument.create({}) and #create!({}) so you don't have to do Model.new.create (Matt Aimonetti)
|
139
|
+
|
140
|
+
* Minor enhancements
|
141
|
+
|
142
|
+
* Added an init.rb file for easy usage as a Rails plugin (Aaron Quint)
|
143
|
+
* Bug fix: pagination shouldn't die on empty results (Arnaud Berthomier)
|
144
|
+
* Optimized ExtendedDocument.count to run about 3x faster (Matt Aimonetti)
|
145
|
+
* Added Float casting (Ryan Felton & Matt Aimonetti)
|
146
|
+
|
147
|
+
== 0.30
|
148
|
+
|
149
|
+
* Major enhancements
|
150
|
+
|
151
|
+
* Added support for pagination (John Wood)
|
152
|
+
* Improved performance when initializing documents with timestamps (Matt Aimonetti)
|
153
|
+
|
154
|
+
* Minor enhancements
|
155
|
+
|
156
|
+
* Extended the API to retrieve an attachment URI (Matt Aimonetti)
|
157
|
+
* Bug fix: default value should be able to be set as false (Alexander Uvarov)
|
158
|
+
* Bug fix: validates_is_numeric should be able to properly validate a Float instance (Rob Kaufman)
|
159
|
+
* Bug fix: fixed the Timeout implementation (Seth Falcon)
|
160
|
+
|
161
|
+
|
162
|
+
---
|
163
|
+
|
164
|
+
Unfortunately, before 0.30 we did not keep a track of the modifications made to CouchRest.
|
165
|
+
You can see the full commit history on GitHub: http://github.com/couchrest/couchrest/commits/master/
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#
|
2
|
+
# Wrapper around Array so that the casted_by attribute is set in all
|
3
|
+
# elements of the array.
|
4
|
+
#
|
5
|
+
|
6
|
+
module CouchRest
|
7
|
+
class CastedArray < Array
|
8
|
+
attr_accessor :casted_by
|
9
|
+
attr_accessor :property
|
10
|
+
|
11
|
+
def initialize(array, property)
|
12
|
+
self.property = property
|
13
|
+
super(array)
|
14
|
+
end
|
15
|
+
|
16
|
+
def << obj
|
17
|
+
super(instantiate_and_cast(obj))
|
18
|
+
end
|
19
|
+
|
20
|
+
def push(obj)
|
21
|
+
super(instantiate_and_cast(obj))
|
22
|
+
end
|
23
|
+
|
24
|
+
def []= index, obj
|
25
|
+
super(index, instantiate_and_cast(obj))
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def instantiate_and_cast(obj)
|
31
|
+
if self.casted_by && self.property && obj.class != self.property.type_class
|
32
|
+
self.property.cast_value(self.casted_by, obj)
|
33
|
+
else
|
34
|
+
obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
|
35
|
+
obj
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module CastedModel
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send(:include, ::CouchRest::Mixins::Callbacks)
|
6
|
+
base.send(:include, ::CouchRest::Mixins::Properties)
|
7
|
+
base.send(:attr_accessor, :casted_by)
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(keys={})
|
11
|
+
raise StandardError unless self.is_a? Hash
|
12
|
+
apply_all_property_defaults # defined in CouchRest::Mixins::Properties
|
13
|
+
super()
|
14
|
+
keys.each do |k,v|
|
15
|
+
write_attribute(k.to_s, v)
|
16
|
+
end if keys
|
17
|
+
end
|
18
|
+
|
19
|
+
def []= key, value
|
20
|
+
super(key.to_s, value)
|
21
|
+
end
|
22
|
+
|
23
|
+
def [] key
|
24
|
+
super(key.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Gets a reference to the top level extended
|
28
|
+
# document that a model is saved inside of
|
29
|
+
def base_doc
|
30
|
+
return nil unless @casted_by
|
31
|
+
@casted_by.base_doc
|
32
|
+
end
|
33
|
+
|
34
|
+
# False if the casted model has already
|
35
|
+
# been saved in the containing document
|
36
|
+
def new?
|
37
|
+
@casted_by.nil? ? true : @casted_by.new?
|
38
|
+
end
|
39
|
+
alias :new_record? :new?
|
40
|
+
|
41
|
+
# Sets the attributes from a hash
|
42
|
+
def update_attributes_without_saving(hash)
|
43
|
+
hash.each do |k, v|
|
44
|
+
raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
|
45
|
+
end
|
46
|
+
hash.each do |k, v|
|
47
|
+
self.send("#{k}=",v)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
alias :attributes= :update_attributes_without_saving
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,262 @@
|
|
1
|
+
|
2
|
+
require File.join(File.dirname(__FILE__), "property")
|
3
|
+
require File.join(File.dirname(__FILE__), "validation")
|
4
|
+
require File.join(File.dirname(__FILE__), 'mixins')
|
5
|
+
|
6
|
+
module CouchRest
|
7
|
+
|
8
|
+
# Same as CouchRest::Document but with properties and validations
|
9
|
+
class ExtendedDocument < Document
|
10
|
+
|
11
|
+
VERSION = "1.0.0"
|
12
|
+
|
13
|
+
include CouchRest::Mixins::Callbacks
|
14
|
+
include CouchRest::Mixins::DocumentQueries
|
15
|
+
include CouchRest::Mixins::Views
|
16
|
+
include CouchRest::Mixins::DesignDoc
|
17
|
+
include CouchRest::Mixins::ExtendedAttachments
|
18
|
+
include CouchRest::Mixins::ClassProxy
|
19
|
+
include CouchRest::Mixins::Collection
|
20
|
+
include CouchRest::Mixins::AttributeProtection
|
21
|
+
include CouchRest::Mixins::Attributes
|
22
|
+
|
23
|
+
# Including validation here does not work due to the way inheritance is handled.
|
24
|
+
#include CouchRest::Validation
|
25
|
+
|
26
|
+
def self.subclasses
|
27
|
+
@subclasses ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.inherited(subklass)
|
31
|
+
super
|
32
|
+
subklass.send(:include, CouchRest::Mixins::Properties)
|
33
|
+
subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
34
|
+
def self.inherited(subklass)
|
35
|
+
super
|
36
|
+
subklass.properties = self.properties.dup
|
37
|
+
end
|
38
|
+
EOS
|
39
|
+
subclasses << subklass
|
40
|
+
end
|
41
|
+
|
42
|
+
# Accessors
|
43
|
+
attr_accessor :casted_by
|
44
|
+
|
45
|
+
# Callbacks
|
46
|
+
define_callbacks :create, "result == :halt"
|
47
|
+
define_callbacks :save, "result == :halt"
|
48
|
+
define_callbacks :update, "result == :halt"
|
49
|
+
define_callbacks :destroy, "result == :halt"
|
50
|
+
|
51
|
+
# Creates a new instance, bypassing attribute protection
|
52
|
+
#
|
53
|
+
#
|
54
|
+
# ==== Returns
|
55
|
+
# a document instance
|
56
|
+
def self.create_from_database(doc = {})
|
57
|
+
type_field = CouchRest.type_field
|
58
|
+
base = (doc[type_field].blank? || doc[type_field] == self.to_s) ? self : doc[type_field].constantize
|
59
|
+
base.new(doc, :directly_set_attributes => true)
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
# Instantiate a new ExtendedDocument by preparing all properties
|
64
|
+
# using the provided document hash.
|
65
|
+
#
|
66
|
+
# Options supported:
|
67
|
+
#
|
68
|
+
# * :directly_set_attributes: true when data comes directly from database
|
69
|
+
#
|
70
|
+
def initialize(doc = {}, options = {})
|
71
|
+
prepare_all_attributes(doc, options) # defined in CouchRest::Mixins::Attributes
|
72
|
+
super(doc)
|
73
|
+
unless self['_id'] && self['_rev']
|
74
|
+
self[CouchRest.type_field] = self.class.to_s
|
75
|
+
end
|
76
|
+
after_initialize if respond_to?(:after_initialize)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Defines an instance and save it directly to the database
|
80
|
+
#
|
81
|
+
# ==== Returns
|
82
|
+
# returns the reloaded document
|
83
|
+
def self.create(options)
|
84
|
+
instance = new(options)
|
85
|
+
instance.create
|
86
|
+
instance
|
87
|
+
end
|
88
|
+
|
89
|
+
# Defines an instance and save it directly to the database
|
90
|
+
#
|
91
|
+
# ==== Returns
|
92
|
+
# returns the reloaded document or raises an exception
|
93
|
+
def self.create!(options)
|
94
|
+
instance = new(options)
|
95
|
+
instance.create!
|
96
|
+
instance
|
97
|
+
end
|
98
|
+
|
99
|
+
# Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
|
100
|
+
# on the document whenever saving occurs. CouchRest uses a pretty
|
101
|
+
# decent time format by default. See Time#to_json
|
102
|
+
def self.timestamps!
|
103
|
+
class_eval <<-EOS, __FILE__, __LINE__
|
104
|
+
property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
|
105
|
+
property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false)
|
106
|
+
|
107
|
+
set_callback :save, :before do |object|
|
108
|
+
write_attribute('updated_at', Time.now)
|
109
|
+
write_attribute('created_at', Time.now) if object.new?
|
110
|
+
end
|
111
|
+
EOS
|
112
|
+
end
|
113
|
+
|
114
|
+
# Name a method that will be called before the document is first saved,
|
115
|
+
# which returns a string to be used for the document's <tt>_id</tt>.
|
116
|
+
# Because CouchDB enforces a constraint that each id must be unique,
|
117
|
+
# this can be used to enforce eg: uniq usernames. Note that this id
|
118
|
+
# must be globally unique across all document types which share a
|
119
|
+
# database, so if you'd like to scope uniqueness to this class, you
|
120
|
+
# should use the class name as part of the unique id.
|
121
|
+
def self.unique_id method = nil, &block
|
122
|
+
if method
|
123
|
+
define_method :set_unique_id do
|
124
|
+
self['_id'] ||= self.send(method)
|
125
|
+
end
|
126
|
+
elsif block
|
127
|
+
define_method :set_unique_id do
|
128
|
+
uniqid = block.call(self)
|
129
|
+
raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
|
130
|
+
self['_id'] ||= uniqid
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Temp solution to make the view_by methods available
|
136
|
+
def self.method_missing(m, *args, &block)
|
137
|
+
if has_view?(m)
|
138
|
+
query = args.shift || {}
|
139
|
+
return view(m, query, *args, &block)
|
140
|
+
elsif m.to_s =~ /^find_(by_.+)/
|
141
|
+
view_name = $1
|
142
|
+
if has_view?(view_name)
|
143
|
+
query = {:key => args.first, :limit => 1}
|
144
|
+
return view(view_name, query).first
|
145
|
+
end
|
146
|
+
end
|
147
|
+
super
|
148
|
+
end
|
149
|
+
|
150
|
+
### instance methods
|
151
|
+
|
152
|
+
# Gets a reference to the actual document in the DB
|
153
|
+
# Calls up to the next document if there is one,
|
154
|
+
# Otherwise we're at the top and we return self
|
155
|
+
def base_doc
|
156
|
+
return self if base_doc?
|
157
|
+
@casted_by.base_doc
|
158
|
+
end
|
159
|
+
|
160
|
+
# Checks if we're the top document
|
161
|
+
def base_doc?
|
162
|
+
!@casted_by
|
163
|
+
end
|
164
|
+
|
165
|
+
# for compatibility with old-school frameworks
|
166
|
+
alias :new_record? :new?
|
167
|
+
alias :new_document? :new?
|
168
|
+
|
169
|
+
# Trigger the callbacks (before, after, around)
|
170
|
+
# and create the document
|
171
|
+
# It's important to have a create callback since you can't check if a document
|
172
|
+
# was new after you saved it
|
173
|
+
#
|
174
|
+
# When creating a document, both the create and the save callbacks will be triggered.
|
175
|
+
def create(bulk = false)
|
176
|
+
caught = catch(:halt) do
|
177
|
+
_run_create_callbacks do
|
178
|
+
_run_save_callbacks do
|
179
|
+
create_without_callbacks(bulk)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# unlike save, create returns the newly created document
|
186
|
+
def create_without_callbacks(bulk =false)
|
187
|
+
raise ArgumentError, "a document requires a database to be created to (The document or the #{self.class} default database were not set)" unless database
|
188
|
+
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
189
|
+
result = database.save_doc(self, bulk)
|
190
|
+
(result["ok"] == true) ? self : false
|
191
|
+
end
|
192
|
+
|
193
|
+
# Creates the document in the db. Raises an exception
|
194
|
+
# if the document is not created properly.
|
195
|
+
def create!
|
196
|
+
raise "#{self.inspect} failed to save" unless self.create
|
197
|
+
end
|
198
|
+
|
199
|
+
# Trigger the callbacks (before, after, around)
|
200
|
+
# only if the document isn't new
|
201
|
+
def update(bulk = false)
|
202
|
+
caught = catch(:halt) do
|
203
|
+
if self.new?
|
204
|
+
save(bulk)
|
205
|
+
else
|
206
|
+
_run_update_callbacks do
|
207
|
+
_run_save_callbacks do
|
208
|
+
save_without_callbacks(bulk)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Trigger the callbacks (before, after, around)
|
216
|
+
# and save the document
|
217
|
+
def save(bulk = false)
|
218
|
+
caught = catch(:halt) do
|
219
|
+
if self.new?
|
220
|
+
_run_save_callbacks do
|
221
|
+
save_without_callbacks(bulk)
|
222
|
+
end
|
223
|
+
else
|
224
|
+
update(bulk)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
# Overridden to set the unique ID.
|
230
|
+
# Returns a boolean value
|
231
|
+
def save_without_callbacks(bulk = false)
|
232
|
+
raise ArgumentError, "a document requires a database to be saved to (The document or the #{self.class} default database were not set)" unless database
|
233
|
+
set_unique_id if new? && self.respond_to?(:set_unique_id)
|
234
|
+
result = database.save_doc(self, bulk)
|
235
|
+
result["ok"] == true
|
236
|
+
end
|
237
|
+
|
238
|
+
# Saves the document to the db using save. Raises an exception
|
239
|
+
# if the document is not saved properly.
|
240
|
+
def save!
|
241
|
+
raise "#{self.inspect} failed to save" unless self.save
|
242
|
+
true
|
243
|
+
end
|
244
|
+
|
245
|
+
# Deletes the document from the database. Runs the :destroy callbacks.
|
246
|
+
# Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
|
247
|
+
# document to be saved to a new <tt>_id</tt>.
|
248
|
+
def destroy(bulk=false)
|
249
|
+
caught = catch(:halt) do
|
250
|
+
_run_destroy_callbacks do
|
251
|
+
result = database.delete_doc(self, bulk)
|
252
|
+
if result['ok']
|
253
|
+
self.delete('_rev')
|
254
|
+
self.delete('_id')
|
255
|
+
end
|
256
|
+
result['ok']
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
end
|