sporkd-couchrest 0.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +176 -0
  3. data/Rakefile +74 -0
  4. data/THANKS.md +18 -0
  5. data/examples/model/example.rb +144 -0
  6. data/examples/word_count/markov +38 -0
  7. data/examples/word_count/views/books/chunked-map.js +3 -0
  8. data/examples/word_count/views/books/united-map.js +1 -0
  9. data/examples/word_count/views/markov/chain-map.js +6 -0
  10. data/examples/word_count/views/markov/chain-reduce.js +7 -0
  11. data/examples/word_count/views/word_count/count-map.js +6 -0
  12. data/examples/word_count/views/word_count/count-reduce.js +3 -0
  13. data/examples/word_count/word_count.rb +46 -0
  14. data/examples/word_count/word_count_query.rb +40 -0
  15. data/examples/word_count/word_count_views.rb +26 -0
  16. data/history.txt +33 -0
  17. data/lib/couchrest/commands/generate.rb +71 -0
  18. data/lib/couchrest/commands/push.rb +103 -0
  19. data/lib/couchrest/core/database.rb +317 -0
  20. data/lib/couchrest/core/design.rb +79 -0
  21. data/lib/couchrest/core/document.rb +84 -0
  22. data/lib/couchrest/core/response.rb +16 -0
  23. data/lib/couchrest/core/server.rb +88 -0
  24. data/lib/couchrest/core/view.rb +4 -0
  25. data/lib/couchrest/helper/pager.rb +103 -0
  26. data/lib/couchrest/helper/streamer.rb +44 -0
  27. data/lib/couchrest/helper/upgrade.rb +51 -0
  28. data/lib/couchrest/mixins/attachments.rb +31 -0
  29. data/lib/couchrest/mixins/callbacks.rb +532 -0
  30. data/lib/couchrest/mixins/class_proxy.rb +112 -0
  31. data/lib/couchrest/mixins/collection.rb +222 -0
  32. data/lib/couchrest/mixins/design_doc.rb +98 -0
  33. data/lib/couchrest/mixins/document_queries.rb +51 -0
  34. data/lib/couchrest/mixins/extended_attachments.rb +74 -0
  35. data/lib/couchrest/mixins/extended_document_mixins.rb +8 -0
  36. data/lib/couchrest/mixins/properties.rb +181 -0
  37. data/lib/couchrest/mixins/validation.rb +246 -0
  38. data/lib/couchrest/mixins/views.rb +173 -0
  39. data/lib/couchrest/mixins.rb +4 -0
  40. data/lib/couchrest/monkeypatches.rb +113 -0
  41. data/lib/couchrest/more/casted_model.rb +57 -0
  42. data/lib/couchrest/more/extended_document.rb +283 -0
  43. data/lib/couchrest/more/property.rb +59 -0
  44. data/lib/couchrest/support/blank.rb +42 -0
  45. data/lib/couchrest/support/class.rb +190 -0
  46. data/lib/couchrest/support/rails.rb +47 -0
  47. data/lib/couchrest/validation/auto_validate.rb +161 -0
  48. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  49. data/lib/couchrest/validation/validation_errors.rb +118 -0
  50. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  51. data/lib/couchrest/validation/validators/confirmation_validator.rb +99 -0
  52. data/lib/couchrest/validation/validators/format_validator.rb +117 -0
  53. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  54. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  55. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  56. data/lib/couchrest/validation/validators/length_validator.rb +134 -0
  57. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  58. data/lib/couchrest/validation/validators/numeric_validator.rb +104 -0
  59. data/lib/couchrest/validation/validators/required_field_validator.rb +109 -0
  60. data/lib/couchrest.rb +200 -0
  61. data/spec/couchrest/core/couchrest_spec.rb +201 -0
  62. data/spec/couchrest/core/database_spec.rb +700 -0
  63. data/spec/couchrest/core/design_spec.rb +138 -0
  64. data/spec/couchrest/core/document_spec.rb +267 -0
  65. data/spec/couchrest/core/server_spec.rb +35 -0
  66. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  67. data/spec/couchrest/helpers/streamer_spec.rb +23 -0
  68. data/spec/couchrest/more/casted_extended_doc_spec.rb +73 -0
  69. data/spec/couchrest/more/casted_model_spec.rb +406 -0
  70. data/spec/couchrest/more/extended_doc_attachment_spec.rb +135 -0
  71. data/spec/couchrest/more/extended_doc_spec.rb +712 -0
  72. data/spec/couchrest/more/extended_doc_subclass_spec.rb +98 -0
  73. data/spec/couchrest/more/extended_doc_view_spec.rb +415 -0
  74. data/spec/couchrest/more/property_spec.rb +233 -0
  75. data/spec/fixtures/attachments/README +3 -0
  76. data/spec/fixtures/attachments/couchdb.png +0 -0
  77. data/spec/fixtures/attachments/test.html +11 -0
  78. data/spec/fixtures/more/article.rb +34 -0
  79. data/spec/fixtures/more/card.rb +22 -0
  80. data/spec/fixtures/more/cat.rb +19 -0
  81. data/spec/fixtures/more/course.rb +14 -0
  82. data/spec/fixtures/more/event.rb +6 -0
  83. data/spec/fixtures/more/invoice.rb +17 -0
  84. data/spec/fixtures/more/person.rb +9 -0
  85. data/spec/fixtures/more/question.rb +6 -0
  86. data/spec/fixtures/more/service.rb +12 -0
  87. data/spec/fixtures/views/lib.js +3 -0
  88. data/spec/fixtures/views/test_view/lib.js +3 -0
  89. data/spec/fixtures/views/test_view/only-map.js +4 -0
  90. data/spec/fixtures/views/test_view/test-map.js +3 -0
  91. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  92. data/spec/spec.opts +6 -0
  93. data/spec/spec_helper.rb +37 -0
  94. data/utils/remap.rb +27 -0
  95. data/utils/subset.rb +30 -0
  96. metadata +194 -0
@@ -0,0 +1,233 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
2
+ require File.join(FIXTURE_PATH, 'more', 'person')
3
+ require File.join(FIXTURE_PATH, 'more', 'card')
4
+ require File.join(FIXTURE_PATH, 'more', 'invoice')
5
+ require File.join(FIXTURE_PATH, 'more', 'service')
6
+ require File.join(FIXTURE_PATH, 'more', 'event')
7
+ require File.join(FIXTURE_PATH, 'more', 'cat')
8
+
9
+
10
+ describe "ExtendedDocument properties" do
11
+
12
+ before(:each) do
13
+ reset_test_db!
14
+ @card = Card.new(:first_name => "matt")
15
+ end
16
+
17
+ it "should be accessible from the object" do
18
+ @card.properties.should be_an_instance_of(Array)
19
+ @card.properties.map{|p| p.name}.should include("first_name")
20
+ end
21
+
22
+ it "should let you access a property value (getter)" do
23
+ @card.first_name.should == "matt"
24
+ end
25
+
26
+ it "should let you set a property value (setter)" do
27
+ @card.last_name = "Aimonetti"
28
+ @card.last_name.should == "Aimonetti"
29
+ end
30
+
31
+ it "should not let you set a property value if it's read only" do
32
+ lambda{@card.read_only_value = "test"}.should raise_error
33
+ end
34
+
35
+ it "should let you use an alias for an attribute" do
36
+ @card.last_name = "Aimonetti"
37
+ @card.family_name.should == "Aimonetti"
38
+ @card.family_name.should == @card.last_name
39
+ end
40
+
41
+ it "should let you use an alias for a casted attribute" do
42
+ @card.cast_alias = Person.new(:name => "Aimonetti")
43
+ @card.cast_alias.name.should == "Aimonetti"
44
+ @card.calias.name.should == "Aimonetti"
45
+ card = Card.new(:first_name => "matt", :cast_alias => {:name => "Aimonetti"})
46
+ card.cast_alias.name.should == "Aimonetti"
47
+ card.calias.name.should == "Aimonetti"
48
+ end
49
+
50
+ it "should be auto timestamped" do
51
+ @card.created_at.should be_nil
52
+ @card.updated_at.should be_nil
53
+ @card.save.should be_true
54
+ @card.created_at.should_not be_nil
55
+ @card.updated_at.should_not be_nil
56
+ end
57
+
58
+ describe "validation" do
59
+ before(:each) do
60
+ @invoice = Invoice.new(:client_name => "matt", :employee_name => "Chris", :location => "San Diego, CA")
61
+ end
62
+
63
+ it "should be able to be validated" do
64
+ @card.valid?.should == true
65
+ end
66
+
67
+ it "should let you validate the presence of an attribute" do
68
+ @card.first_name = nil
69
+ @card.should_not be_valid
70
+ @card.errors.should_not be_empty
71
+ @card.errors.on(:first_name).should == ["First name must not be blank"]
72
+ end
73
+
74
+ it "should let you look up errors for a field by a string name" do
75
+ @card.first_name = nil
76
+ @card.should_not be_valid
77
+ @card.errors.on('first_name').should == ["First name must not be blank"]
78
+ end
79
+
80
+ it "should validate the presence of 2 attributes" do
81
+ @invoice.clear
82
+ @invoice.should_not be_valid
83
+ @invoice.errors.should_not be_empty
84
+ @invoice.errors.on(:client_name).first.should == "Client name must not be blank"
85
+ @invoice.errors.on(:employee_name).should_not be_empty
86
+ end
87
+
88
+ it "should let you set an error message" do
89
+ @invoice.location = nil
90
+ @invoice.valid?
91
+ @invoice.errors.on(:location).should == ["Hey stupid!, you forgot the location"]
92
+ end
93
+
94
+ it "should validate before saving" do
95
+ @invoice.location = nil
96
+ @invoice.should_not be_valid
97
+ @invoice.save.should be_false
98
+ @invoice.should be_new
99
+ end
100
+ end
101
+
102
+ describe "autovalidation" do
103
+ before(:each) do
104
+ @service = Service.new(:name => "Coumpound analysis", :price => 3_000)
105
+ end
106
+
107
+ it "should be valid" do
108
+ @service.should be_valid
109
+ end
110
+
111
+ it "should not respond to properties not setup" do
112
+ @service.respond_to?(:client_name).should be_false
113
+ end
114
+
115
+ describe "property :name, :length => 4...20" do
116
+
117
+ it "should autovalidate the presence when length is set" do
118
+ @service.name = nil
119
+ @service.should_not be_valid
120
+ @service.errors.should_not be_nil
121
+ @service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
122
+ end
123
+
124
+ it "should autovalidate the correct length" do
125
+ @service.name = "a"
126
+ @service.should_not be_valid
127
+ @service.errors.should_not be_nil
128
+ @service.errors.on(:name).first.should == "Name must be between 4 and 19 characters long"
129
+ end
130
+ end
131
+ end
132
+
133
+ describe "casting" do
134
+ describe "cast keys to any type" do
135
+ before(:all) do
136
+ event_doc = { :subject => "Some event", :occurs_at => Time.now }
137
+ e = Event.database.save_doc event_doc
138
+
139
+ @event = Event.get e['id']
140
+ end
141
+ it "should cast created_at to Time" do
142
+ @event['occurs_at'].should be_an_instance_of(Time)
143
+ end
144
+ end
145
+
146
+ describe "casting to Float object" do
147
+ class RootBeerFloat < CouchRest::ExtendedDocument
148
+ use_database DB
149
+ property :price, :cast_as => 'Float'
150
+ end
151
+
152
+ it "should convert a string into a float if casted as so" do
153
+ RootBeerFloat.new(:price => '12.50').price.should == 12.50
154
+ RootBeerFloat.new(:price => '9').price.should == 9.0
155
+ RootBeerFloat.new(:price => '-9').price.should == -9.0
156
+ end
157
+
158
+ it "should not convert a string if it's not a string that can be cast as a float" do
159
+ RootBeerFloat.new(:price => 'test').price.should == 'test'
160
+ end
161
+
162
+ it "should work fine when a float is being passed" do
163
+ RootBeerFloat.new(:price => 9.99).price.should == 9.99
164
+ end
165
+ end
166
+
167
+ end
168
+ end
169
+
170
+ describe "a newly created casted model" do
171
+ before(:each) do
172
+ reset_test_db!
173
+ @cat = Cat.new(:name => 'Toonces')
174
+ @squeaky_mouse = CatToy.new(:name => 'Squeaky')
175
+ end
176
+
177
+ describe "assigned assigned to a casted property" do
178
+ it "should have casted_by set to its parent" do
179
+ @squeaky_mouse.casted_by.should be_nil
180
+ @cat.favorite_toy = @squeaky_mouse
181
+ @squeaky_mouse.casted_by.should === @cat
182
+ end
183
+ end
184
+
185
+ describe "appended to a casted collection" do
186
+ it "should have casted_by set to its parent" do
187
+ @squeaky_mouse.casted_by.should be_nil
188
+ @cat.toys << @squeaky_mouse
189
+ @squeaky_mouse.casted_by.should === @cat
190
+ @cat.save
191
+ @cat.toys.first.casted_by.should === @cat
192
+ end
193
+ end
194
+
195
+ describe "list assigned to a casted collection" do
196
+ it "should have casted_by set on all elements" do
197
+ toy1 = CatToy.new(:name => 'Feather')
198
+ toy2 = CatToy.new(:name => 'Mouse')
199
+ @cat.toys = [toy1, toy2]
200
+ toy1.casted_by.should === @cat
201
+ toy2.casted_by.should === @cat
202
+ @cat.save
203
+ @cat = Cat.get(@cat.id)
204
+ @cat.toys[0].casted_by.should === @cat
205
+ @cat.toys[1].casted_by.should === @cat
206
+ end
207
+ end
208
+ end
209
+
210
+ describe "a casted model retrieved from the database" do
211
+ before(:each) do
212
+ reset_test_db!
213
+ @cat = Cat.new(:name => 'Stimpy')
214
+ @cat.favorite_toy = CatToy.new(:name => 'Stinky')
215
+ @cat.toys << CatToy.new(:name => 'Feather')
216
+ @cat.toys << CatToy.new(:name => 'Mouse')
217
+ @cat.save
218
+ @cat = Cat.get(@cat.id)
219
+ end
220
+
221
+ describe "as a casted property" do
222
+ it "should already be casted_by its parent" do
223
+ @cat.favorite_toy.casted_by.should === @cat
224
+ end
225
+ end
226
+
227
+ describe "from a casted collection" do
228
+ it "should already be casted_by its parent" do
229
+ @cat.toys[0].casted_by.should === @cat
230
+ @cat.toys[1].casted_by.should === @cat
231
+ end
232
+ end
233
+ end
@@ -0,0 +1,3 @@
1
+ This is an example README file.
2
+
3
+ More of the README, whee.
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Test</title>
5
+ </head>
6
+ <body>
7
+ <p>
8
+ Test
9
+ </p>
10
+ </body>
11
+ </html>
@@ -0,0 +1,34 @@
1
+ class Article < CouchRest::ExtendedDocument
2
+ use_database DB
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
+ before_save :generate_slug_from_title
30
+
31
+ def generate_slug_from_title
32
+ self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new?
33
+ end
34
+ end
@@ -0,0 +1,22 @@
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 DB
9
+
10
+ # Official Schema
11
+ property :first_name
12
+ property :last_name, :alias => :family_name
13
+ property :read_only_value, :read_only => true
14
+ property :cast_alias, :cast_as => 'Person', :alias => :calias
15
+
16
+
17
+ timestamps!
18
+
19
+ # Validation
20
+ validates_present :first_name
21
+
22
+ end
@@ -0,0 +1,19 @@
1
+ class Cat < CouchRest::ExtendedDocument
2
+ include ::CouchRest::Validation
3
+
4
+ # Set the default database to use
5
+ use_database DB
6
+
7
+ property :name
8
+ property :toys, :cast_as => ['CatToy'], :default => []
9
+ property :favorite_toy, :cast_as => 'CatToy'
10
+ end
11
+
12
+ class CatToy < Hash
13
+ include ::CouchRest::CastedModel
14
+ include ::CouchRest::Validation
15
+
16
+ property :name
17
+
18
+ validates_present :name
19
+ 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 DB
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 DB
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,9 @@
1
+ class Person < Hash
2
+ include ::CouchRest::CastedModel
3
+ property :name
4
+ property :pet, :cast_as => 'Cat'
5
+
6
+ def last_name
7
+ name.last
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ class Question < Hash
2
+ include ::CouchRest::CastedModel
3
+
4
+ property :q
5
+ property :a
6
+ end
@@ -0,0 +1,12 @@
1
+ class Service < CouchRest::ExtendedDocument
2
+ # Include the validation module to get access to the validation methods
3
+ include CouchRest::Validation
4
+ auto_validate!
5
+ # Set the default database to use
6
+ use_database DB
7
+
8
+ # Official Schema
9
+ property :name, :length => 4...20
10
+ property :price, :type => Integer
11
+
12
+ end
@@ -0,0 +1,3 @@
1
+ function globalLib() {
2
+ return "fixture";
3
+ };
@@ -0,0 +1,3 @@
1
+ function justThisView() {
2
+ return "fixture";
3
+ };
@@ -0,0 +1,4 @@
1
+ function(doc) {
2
+ //include-lib
3
+ emit(null, null);
4
+ };
@@ -0,0 +1,3 @@
1
+ function(doc) {
2
+ emit(null, null);
3
+ };
@@ -0,0 +1,3 @@
1
+ function(ks,vs,co) {
2
+ return vs.length;
3
+ };
data/spec/spec.opts ADDED
@@ -0,0 +1,6 @@
1
+ --colour
2
+ --format
3
+ progress
4
+ --loadby
5
+ mtime
6
+ --reverse
@@ -0,0 +1,37 @@
1
+ require "rubygems"
2
+ require "spec" # Satisfies Autotest and anyone else not using the Rake tasks
3
+
4
+ require File.join(File.dirname(__FILE__), '..','lib','couchrest')
5
+ # check the following file to see how to use the spec'd features.
6
+
7
+ unless defined?(FIXTURE_PATH)
8
+ FIXTURE_PATH = File.join(File.dirname(__FILE__), '/fixtures')
9
+ SCRATCH_PATH = File.join(File.dirname(__FILE__), '/tmp')
10
+
11
+ COUCHHOST = "http://127.0.0.1:5984"
12
+ TESTDB = 'couchrest-test'
13
+ TEST_SERVER = CouchRest.new
14
+ TEST_SERVER.default_database = TESTDB
15
+ DB = TEST_SERVER.database(TESTDB)
16
+ end
17
+
18
+ class Basic < CouchRest::ExtendedDocument
19
+ use_database TEST_SERVER.default_database
20
+ end
21
+
22
+ def reset_test_db!
23
+ DB.recreate! rescue nil
24
+ DB
25
+ end
26
+
27
+ Spec::Runner.configure do |config|
28
+ config.before(:all) { reset_test_db! }
29
+
30
+ config.after(:all) do
31
+ cr = TEST_SERVER
32
+ test_dbs = cr.databases.select { |db| db =~ /^#{TESTDB}/ }
33
+ test_dbs.each do |db|
34
+ cr.database(db).delete! rescue nil
35
+ end
36
+ end
37
+ end
data/utils/remap.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'couchrest'
3
+
4
+ # set the source db and map view
5
+ source = CouchRest.new("http://127.0.0.1:5984").database('source-db')
6
+ source_view = 'mydesign/view-map'
7
+
8
+ # set the target db
9
+ target = CouchRest.new("http://127.0.0.1:5984").database('target-db')
10
+
11
+
12
+ pager = CouchRest::Pager.new(source)
13
+
14
+ # pager will yield once per uniq key in the source view
15
+
16
+ pager.key_reduce(source_view, 10000) do |key, values|
17
+ # create a doc from the key and the values
18
+ example_doc = {
19
+ :key => key,
20
+ :values => values.uniq
21
+ }
22
+
23
+ target.save(example_doc)
24
+
25
+ # keep us up to date with progress
26
+ puts k if (rand > 0.9)
27
+ end
data/utils/subset.rb ADDED
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'couchrest'
3
+
4
+ # subset.rb replicates a percentage of a database to a fresh database.
5
+ # use it to create a smaller dataset on which to prototype views.
6
+
7
+ # specify the source database
8
+ source = CouchRest.new("http://127.0.0.1:5984").database('source-db')
9
+
10
+ # specify the target database
11
+ target = CouchRest.new("http://127.0.0.1:5984").database('target-db')
12
+
13
+ # pager efficiently yields all view rows
14
+ pager = CouchRest::Pager.new(source)
15
+
16
+ pager.all_docs(1000) do |rows|
17
+ docs = rows.collect do |r|
18
+ # the percentage of docs to clone
19
+ next if rand > 0.1
20
+ doc = source.get(r['id'])
21
+ doc.delete('_rev')
22
+ doc
23
+ end.compact
24
+ puts docs.length
25
+ next if docs.empty?
26
+
27
+ puts docs.first['_id']
28
+ target.bulk_save(docs)
29
+ end
30
+