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.
Files changed (71) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +250 -0
  3. data/Rakefile +69 -0
  4. data/THANKS.md +22 -0
  5. data/examples/model/example.rb +144 -0
  6. data/history.txt +165 -0
  7. data/lib/couchrest/casted_array.rb +39 -0
  8. data/lib/couchrest/casted_model.rb +53 -0
  9. data/lib/couchrest/extended_document.rb +262 -0
  10. data/lib/couchrest/mixins.rb +12 -0
  11. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  12. data/lib/couchrest/mixins/attributes.rb +75 -0
  13. data/lib/couchrest/mixins/callbacks.rb +534 -0
  14. data/lib/couchrest/mixins/class_proxy.rb +120 -0
  15. data/lib/couchrest/mixins/collection.rb +260 -0
  16. data/lib/couchrest/mixins/design_doc.rb +159 -0
  17. data/lib/couchrest/mixins/document_queries.rb +82 -0
  18. data/lib/couchrest/mixins/extended_attachments.rb +73 -0
  19. data/lib/couchrest/mixins/properties.rb +130 -0
  20. data/lib/couchrest/mixins/typecast.rb +174 -0
  21. data/lib/couchrest/mixins/views.rb +148 -0
  22. data/lib/couchrest/property.rb +96 -0
  23. data/lib/couchrest/support/couchrest.rb +19 -0
  24. data/lib/couchrest/support/rails.rb +42 -0
  25. data/lib/couchrest/validation.rb +246 -0
  26. data/lib/couchrest/validation/auto_validate.rb +156 -0
  27. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  28. data/lib/couchrest/validation/validation_errors.rb +125 -0
  29. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  30. data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
  31. data/lib/couchrest/validation/validators/format_validator.rb +122 -0
  32. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  33. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  34. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  35. data/lib/couchrest/validation/validators/length_validator.rb +139 -0
  36. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  37. data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
  38. data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
  39. data/lib/couchrest_extended_document.rb +23 -0
  40. data/spec/couchrest/attribute_protection_spec.rb +150 -0
  41. data/spec/couchrest/casted_extended_doc_spec.rb +79 -0
  42. data/spec/couchrest/casted_model_spec.rb +424 -0
  43. data/spec/couchrest/extended_doc_attachment_spec.rb +148 -0
  44. data/spec/couchrest/extended_doc_inherited_spec.rb +40 -0
  45. data/spec/couchrest/extended_doc_spec.rb +869 -0
  46. data/spec/couchrest/extended_doc_subclass_spec.rb +101 -0
  47. data/spec/couchrest/extended_doc_view_spec.rb +529 -0
  48. data/spec/couchrest/property_spec.rb +790 -0
  49. data/spec/fixtures/attachments/README +3 -0
  50. data/spec/fixtures/attachments/couchdb.png +0 -0
  51. data/spec/fixtures/attachments/test.html +11 -0
  52. data/spec/fixtures/more/article.rb +35 -0
  53. data/spec/fixtures/more/card.rb +22 -0
  54. data/spec/fixtures/more/cat.rb +22 -0
  55. data/spec/fixtures/more/course.rb +25 -0
  56. data/spec/fixtures/more/event.rb +8 -0
  57. data/spec/fixtures/more/invoice.rb +17 -0
  58. data/spec/fixtures/more/person.rb +9 -0
  59. data/spec/fixtures/more/question.rb +7 -0
  60. data/spec/fixtures/more/service.rb +12 -0
  61. data/spec/fixtures/more/user.rb +22 -0
  62. data/spec/fixtures/views/lib.js +3 -0
  63. data/spec/fixtures/views/test_view/lib.js +3 -0
  64. data/spec/fixtures/views/test_view/only-map.js +4 -0
  65. data/spec/fixtures/views/test_view/test-map.js +3 -0
  66. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  67. data/spec/spec.opts +5 -0
  68. data/spec/spec_helper.rb +49 -0
  69. data/utils/remap.rb +27 -0
  70. data/utils/subset.rb +30 -0
  71. 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