jchris-couchrest 0.12.6 → 0.16

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 (59) hide show
  1. data/README.md +33 -8
  2. data/Rakefile +1 -1
  3. data/examples/model/example.rb +19 -13
  4. data/lib/couchrest.rb +27 -2
  5. data/lib/couchrest/core/database.rb +113 -41
  6. data/lib/couchrest/core/document.rb +48 -27
  7. data/lib/couchrest/core/response.rb +15 -0
  8. data/lib/couchrest/core/server.rb +47 -10
  9. data/lib/couchrest/mixins.rb +4 -0
  10. data/lib/couchrest/mixins/attachments.rb +31 -0
  11. data/lib/couchrest/mixins/callbacks.rb +442 -0
  12. data/lib/couchrest/mixins/design_doc.rb +63 -0
  13. data/lib/couchrest/mixins/document_queries.rb +48 -0
  14. data/lib/couchrest/mixins/extended_attachments.rb +68 -0
  15. data/lib/couchrest/mixins/extended_document_mixins.rb +6 -0
  16. data/lib/couchrest/mixins/properties.rb +120 -0
  17. data/lib/couchrest/mixins/validation.rb +234 -0
  18. data/lib/couchrest/mixins/views.rb +168 -0
  19. data/lib/couchrest/monkeypatches.rb +75 -0
  20. data/lib/couchrest/more/casted_model.rb +28 -0
  21. data/lib/couchrest/more/extended_document.rb +215 -0
  22. data/lib/couchrest/more/property.rb +40 -0
  23. data/lib/couchrest/support/blank.rb +42 -0
  24. data/lib/couchrest/support/class.rb +175 -0
  25. data/lib/couchrest/validation/auto_validate.rb +163 -0
  26. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  27. data/lib/couchrest/validation/validation_errors.rb +118 -0
  28. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  29. data/lib/couchrest/validation/validators/confirmation_validator.rb +99 -0
  30. data/lib/couchrest/validation/validators/format_validator.rb +117 -0
  31. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  32. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  33. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  34. data/lib/couchrest/validation/validators/length_validator.rb +134 -0
  35. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  36. data/lib/couchrest/validation/validators/numeric_validator.rb +104 -0
  37. data/lib/couchrest/validation/validators/required_field_validator.rb +109 -0
  38. data/spec/couchrest/core/database_spec.rb +183 -67
  39. data/spec/couchrest/core/design_spec.rb +1 -1
  40. data/spec/couchrest/core/document_spec.rb +271 -173
  41. data/spec/couchrest/core/server_spec.rb +35 -0
  42. data/spec/couchrest/helpers/pager_spec.rb +1 -1
  43. data/spec/couchrest/more/casted_model_spec.rb +97 -0
  44. data/spec/couchrest/more/extended_doc_attachment_spec.rb +129 -0
  45. data/spec/couchrest/more/extended_doc_spec.rb +509 -0
  46. data/spec/couchrest/more/extended_doc_view_spec.rb +204 -0
  47. data/spec/couchrest/more/property_spec.rb +129 -0
  48. data/spec/fixtures/more/article.rb +34 -0
  49. data/spec/fixtures/more/card.rb +20 -0
  50. data/spec/fixtures/more/course.rb +14 -0
  51. data/spec/fixtures/more/event.rb +6 -0
  52. data/spec/fixtures/more/invoice.rb +17 -0
  53. data/spec/fixtures/more/person.rb +8 -0
  54. data/spec/fixtures/more/question.rb +6 -0
  55. data/spec/fixtures/more/service.rb +12 -0
  56. data/spec/spec_helper.rb +13 -7
  57. metadata +76 -3
  58. data/lib/couchrest/core/model.rb +0 -613
  59. data/spec/couchrest/core/model_spec.rb +0 -855
@@ -0,0 +1,204 @@
1
+ require File.dirname(__FILE__) + '/../../spec_helper'
2
+ require File.join(FIXTURE_PATH, 'more', 'article')
3
+ require File.join(FIXTURE_PATH, 'more', 'course')
4
+
5
+ describe "ExtendedDocument views" do
6
+
7
+ describe "a model with simple views and a default param" do
8
+ before(:all) do
9
+ Article.all.map{|a| a.destroy(true)}
10
+ Article.database.bulk_delete
11
+ written_at = Time.now - 24 * 3600 * 7
12
+ @titles = ["this and that", "also interesting", "more fun", "some junk"]
13
+ @titles.each do |title|
14
+ a = Article.new(:title => title)
15
+ a.date = written_at
16
+ a.save
17
+ written_at += 24 * 3600
18
+ end
19
+ end
20
+
21
+ it "should have a design doc" do
22
+ Article.design_doc["views"]["by_date"].should_not be_nil
23
+ end
24
+
25
+ it "should save the design doc" do
26
+ Article.by_date #rescue nil
27
+ doc = Article.database.get Article.design_doc.id
28
+ doc['views']['by_date'].should_not be_nil
29
+ end
30
+
31
+ it "should return the matching raw view result" do
32
+ view = Article.by_date :raw => true
33
+ view['rows'].length.should == 4
34
+ end
35
+
36
+ it "should not include non-Articles" do
37
+ Article.database.save_doc({"date" => 1})
38
+ view = Article.by_date :raw => true
39
+ view['rows'].length.should == 4
40
+ end
41
+
42
+ it "should return the matching objects (with default argument :descending => true)" do
43
+ articles = Article.by_date
44
+ articles.collect{|a|a.title}.should == @titles.reverse
45
+ end
46
+
47
+ it "should allow you to override default args" do
48
+ articles = Article.by_date :descending => false
49
+ articles.collect{|a|a.title}.should == @titles
50
+ end
51
+ end
52
+
53
+ describe "another model with a simple view" do
54
+ before(:all) do
55
+ reset_test_db!
56
+ %w{aaa bbb ddd eee}.each do |title|
57
+ Course.new(:title => title).save
58
+ end
59
+ end
60
+ it "should make the design doc upon first query" do
61
+ Course.by_title
62
+ doc = Course.design_doc
63
+ doc['views']['all']['map'].should include('Course')
64
+ end
65
+ it "should can query via view" do
66
+ # register methods with method-missing, for local dispatch. method
67
+ # missing lookup table, no heuristics.
68
+ view = Course.view :by_title
69
+ designed = Course.by_title
70
+ view.should == designed
71
+ end
72
+ it "should get them" do
73
+ rs = Course.by_title
74
+ rs.length.should == 4
75
+ end
76
+ it "should yield" do
77
+ courses = []
78
+ rs = Course.by_title # remove me
79
+ Course.view(:by_title) do |course|
80
+ courses << course
81
+ end
82
+ courses[0]["doc"]["title"].should =='aaa'
83
+ end
84
+ end
85
+
86
+
87
+ describe "a ducktype view" do
88
+ before(:all) do
89
+ @id = TEST_SERVER.default_database.save_doc({:dept => true})['id']
90
+ end
91
+ it "should setup" do
92
+ duck = Course.get(@id) # from a different db
93
+ duck["dept"].should == true
94
+ end
95
+ it "should make the design doc" do
96
+ @as = Course.by_dept
97
+ @doc = Course.design_doc
98
+ @doc["views"]["by_dept"]["map"].should_not include("couchrest")
99
+ end
100
+ it "should not look for class" do |variable|
101
+ @as = Course.by_dept
102
+ @as[0]['_id'].should == @id
103
+ end
104
+ end
105
+
106
+ describe "a model with a compound key view" do
107
+ before(:all) do
108
+ written_at = Time.now - 24 * 3600 * 7
109
+ @titles = ["uniq one", "even more interesting", "less fun", "not junk"]
110
+ @user_ids = ["quentin", "aaron"]
111
+ @titles.each_with_index do |title,i|
112
+ u = i % 2
113
+ a = Article.new(:title => title, :user_id => @user_ids[u])
114
+ a.date = written_at
115
+ a.save
116
+ written_at += 24 * 3600
117
+ end
118
+ end
119
+ it "should create the design doc" do
120
+ Article.by_user_id_and_date rescue nil
121
+ doc = Article.design_doc
122
+ doc['views']['by_date'].should_not be_nil
123
+ end
124
+ it "should sort correctly" do
125
+ articles = Article.by_user_id_and_date
126
+ articles.collect{|a|a['user_id']}.should == ['aaron', 'aaron', 'quentin',
127
+ 'quentin']
128
+ articles[1].title.should == 'not junk'
129
+ end
130
+ it "should be queryable with couchrest options" do
131
+ articles = Article.by_user_id_and_date :limit => 1, :startkey => 'quentin'
132
+ articles.length.should == 1
133
+ articles[0].title.should == "even more interesting"
134
+ end
135
+ end
136
+
137
+ describe "with a custom view" do
138
+ before(:all) do
139
+ @titles = ["very uniq one", "even less interesting", "some fun",
140
+ "really junk", "crazy bob"]
141
+ @tags = ["cool", "lame"]
142
+ @titles.each_with_index do |title,i|
143
+ u = i % 2
144
+ a = Article.new(:title => title, :tags => [@tags[u]])
145
+ a.save
146
+ end
147
+ end
148
+ it "should be available raw" do
149
+ view = Article.by_tags :raw => true
150
+ view['rows'].length.should == 5
151
+ end
152
+
153
+ it "should be default to :reduce => false" do
154
+ ars = Article.by_tags
155
+ ars.first.tags.first.should == 'cool'
156
+ end
157
+
158
+ it "should be raw when reduce is true" do
159
+ view = Article.by_tags :reduce => true, :group => true
160
+ view['rows'].find{|r|r['key'] == 'cool'}['value'].should == 3
161
+ end
162
+ end
163
+
164
+ # TODO: moved to Design, delete
165
+ describe "adding a view" do
166
+ before(:each) do
167
+ Article.by_date
168
+ @design_docs = Article.database.documents :startkey => "_design/",
169
+ :endkey => "_design/\u9999"
170
+ end
171
+ it "should not create a design doc on view definition" do
172
+ Article.view_by :created_at
173
+ newdocs = Article.database.documents :startkey => "_design/",
174
+ :endkey => "_design/\u9999"
175
+ newdocs["rows"].length.should == @design_docs["rows"].length
176
+ end
177
+ it "should create a new design document on view access" do
178
+ Article.view_by :updated_at
179
+ Article.by_updated_at
180
+ newdocs = Article.database.documents :startkey => "_design/",
181
+ :endkey => "_design/\u9999"
182
+ # puts @design_docs.inspect
183
+ # puts newdocs.inspect
184
+ newdocs["rows"].length.should == @design_docs["rows"].length + 1
185
+ end
186
+ end
187
+
188
+ describe "with a lot of designs left around" do
189
+ before(:each) do
190
+ Article.by_date
191
+ Article.view_by :field
192
+ Article.by_field
193
+ end
194
+ it "should clean them up" do
195
+ Article.view_by :stream
196
+ Article.by_stream
197
+ ddocs = Article.all_design_doc_versions
198
+ ddocs["rows"].length.should > 1
199
+ Article.cleanup_design_docs!
200
+ ddocs = Article.all_design_doc_versions
201
+ ddocs["rows"].length.should == 1
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,129 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+ require File.join(FIXTURE_PATH, 'more', 'card')
3
+ require File.join(FIXTURE_PATH, 'more', 'invoice')
4
+ require File.join(FIXTURE_PATH, 'more', 'service')
5
+ require File.join(FIXTURE_PATH, 'more', 'event')
6
+
7
+
8
+ describe "ExtendedDocument properties" do
9
+
10
+ before(:each) do
11
+ @card = Card.new(:first_name => "matt")
12
+ end
13
+
14
+ it "should be accessible from the object" do
15
+ @card.properties.should be_an_instance_of(Array)
16
+ @card.properties.map{|p| p.name}.should include("first_name")
17
+ end
18
+
19
+ it "should let you access a property value (getter)" do
20
+ @card.first_name.should == "matt"
21
+ end
22
+
23
+ it "should let you set a property value (setter)" do
24
+ @card.last_name = "Aimonetti"
25
+ @card.last_name.should == "Aimonetti"
26
+ end
27
+
28
+ it "should not let you set a property value if it's read only" do
29
+ lambda{@card.read_only_value = "test"}.should raise_error
30
+ end
31
+
32
+ it "should let you use an alias for an attribute" do
33
+ @card.last_name = "Aimonetti"
34
+ @card.family_name.should == "Aimonetti"
35
+ @card.family_name.should == @card.last_name
36
+ end
37
+
38
+ it "should be auto timestamped" do
39
+ @card.created_at.should be_nil
40
+ @card.updated_at.should be_nil
41
+ @card.save.should be_true
42
+ @card.created_at.should_not be_nil
43
+ @card.updated_at.should_not be_nil
44
+ end
45
+
46
+ describe "validation" do
47
+ before(:each) do
48
+ @invoice = Invoice.new(:client_name => "matt", :employee_name => "Chris", :location => "San Diego, CA")
49
+ end
50
+
51
+ it "should be able to be validated" do
52
+ @card.valid?.should == true
53
+ end
54
+
55
+ it "should let you validate the presence of an attribute" do
56
+ @card.first_name = nil
57
+ @card.should_not be_valid
58
+ @card.errors.should_not be_empty
59
+ @card.errors.on(:first_name).should == ["First name must not be blank"]
60
+ end
61
+
62
+ it "should validate the presence of 2 attributes" do
63
+ @invoice.clear
64
+ @invoice.should_not be_valid
65
+ @invoice.errors.should_not be_empty
66
+ @invoice.errors.on(:client_name).first.should == "Client name must not be blank"
67
+ @invoice.errors.on(:employee_name).should_not be_empty
68
+ end
69
+
70
+ it "should let you set an error message" do
71
+ @invoice.location = nil
72
+ @invoice.valid?
73
+ @invoice.errors.on(:location).should == ["Hey stupid!, you forgot the location"]
74
+ end
75
+
76
+ it "should validate before saving" do
77
+ @invoice.location = nil
78
+ @invoice.should_not be_valid
79
+ @invoice.save.should be_false
80
+ @invoice.should be_new_document
81
+ end
82
+ end
83
+
84
+ describe "autovalidation" do
85
+ before(:each) do
86
+ @service = Service.new(:name => "Coumpound analysis", :price => 3_000)
87
+ end
88
+
89
+ it "should be valid" do
90
+ @service.should be_valid
91
+ end
92
+
93
+ it "should not respond to properties not setup" do
94
+ @service.respond_to?(:client_name).should be_false
95
+ end
96
+
97
+ describe "property :name, :length => 4...20" do
98
+
99
+ it "should autovalidate the presence when length is set" do
100
+ @service.name = nil
101
+ @service.should_not be_valid
102
+ @service.errors.should_not be_nil
103
+ @service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
104
+ end
105
+
106
+ it "should autovalidate the correct length" do
107
+ @service.name = "a"
108
+ @service.should_not be_valid
109
+ @service.errors.should_not be_nil
110
+ @service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
111
+ end
112
+ end
113
+ end
114
+
115
+ describe "casting" do
116
+ describe "cast keys to any type" do
117
+ before(:all) do
118
+ event_doc = { :subject => "Some event", :occurs_at => Time.now }
119
+ e = Event.database.save_doc event_doc
120
+
121
+ @event = Event.get e['id']
122
+ end
123
+ it "should cast created_at to Time" do
124
+ @event['occurs_at'].should be_an_instance_of(Time)
125
+ end
126
+ end
127
+ end
128
+
129
+ end
@@ -0,0 +1,34 @@
1
+ class Article < CouchRest::ExtendedDocument
2
+ use_database TEST_SERVER.default_database
3
+ unique_id :slug
4
+
5
+ view_by :date, :descending => true
6
+ view_by :user_id, :date
7
+
8
+ view_by :tags,
9
+ :map =>
10
+ "function(doc) {
11
+ if (doc['couchrest-type'] == 'Article' && doc.tags) {
12
+ doc.tags.forEach(function(tag){
13
+ emit(tag, 1);
14
+ });
15
+ }
16
+ }",
17
+ :reduce =>
18
+ "function(keys, values, rereduce) {
19
+ return sum(values);
20
+ }"
21
+
22
+ property :date
23
+ property :slug, :read_only => true
24
+ property :title
25
+ property :tags
26
+
27
+ timestamps!
28
+
29
+ save_callback :before, :generate_slug_from_title
30
+
31
+ def generate_slug_from_title
32
+ self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new_document?
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ class Card < CouchRest::ExtendedDocument
2
+ # Include the validation module to get access to the validation methods
3
+ include CouchRest::Validation
4
+ # set the auto_validation before defining the properties
5
+ auto_validate!
6
+
7
+ # Set the default database to use
8
+ use_database TEST_SERVER.default_database
9
+
10
+ # Official Schema
11
+ property :first_name
12
+ property :last_name, :alias => :family_name
13
+ property :read_only_value, :read_only => true
14
+
15
+ timestamps!
16
+
17
+ # Validation
18
+ validates_present :first_name
19
+
20
+ end
@@ -0,0 +1,14 @@
1
+ require File.join(FIXTURE_PATH, 'more', 'question')
2
+ require File.join(FIXTURE_PATH, 'more', 'person')
3
+
4
+ class Course < CouchRest::ExtendedDocument
5
+ use_database TEST_SERVER.default_database
6
+
7
+ property :title
8
+ property :questions, :cast_as => ['Question']
9
+ property :professor, :cast_as => 'Person'
10
+ property :final_test_at, :cast_as => 'Time'
11
+
12
+ view_by :title
13
+ view_by :dept, :ducktype => true
14
+ end
@@ -0,0 +1,6 @@
1
+ class Event < CouchRest::ExtendedDocument
2
+ use_database TEST_SERVER.default_database
3
+
4
+ property :subject
5
+ property :occurs_at, :cast_as => 'Time', :send => 'parse'
6
+ end
@@ -0,0 +1,17 @@
1
+ class Invoice < CouchRest::ExtendedDocument
2
+ # Include the validation module to get access to the validation methods
3
+ include CouchRest::Validation
4
+
5
+ # Set the default database to use
6
+ use_database TEST_SERVER.default_database
7
+
8
+ # Official Schema
9
+ property :client_name
10
+ property :employee_name
11
+ property :location
12
+
13
+ # Validation
14
+ validates_present :client_name, :employee_name
15
+ validates_present :location, :message => "Hey stupid!, you forgot the location"
16
+
17
+ end
@@ -0,0 +1,8 @@
1
+ class Person < Hash
2
+ include ::CouchRest::CastedModel
3
+ property :name
4
+
5
+ def last_name
6
+ name.last
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ class Question < Hash
2
+ include ::CouchRest::CastedModel
3
+
4
+ property :q
5
+ property :a
6
+ end