couchmodel 0.1.0.beta2

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 (45) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +156 -0
  3. data/Rakefile +20 -0
  4. data/lib/core_extension/array.rb +14 -0
  5. data/lib/core_extension/string.rb +12 -0
  6. data/lib/couch_model/active_model.rb +86 -0
  7. data/lib/couch_model/base/accessor.rb +39 -0
  8. data/lib/couch_model/base/association.rb +63 -0
  9. data/lib/couch_model/base/finder.rb +28 -0
  10. data/lib/couch_model/base/setup.rb +88 -0
  11. data/lib/couch_model/base.rb +117 -0
  12. data/lib/couch_model/collection.rb +84 -0
  13. data/lib/couch_model/configuration.rb +68 -0
  14. data/lib/couch_model/database.rb +64 -0
  15. data/lib/couch_model/design.rb +92 -0
  16. data/lib/couch_model/server.rb +44 -0
  17. data/lib/couch_model/transport.rb +68 -0
  18. data/lib/couch_model/view.rb +52 -0
  19. data/lib/couch_model.rb +15 -0
  20. data/spec/fake_transport.yml +202 -0
  21. data/spec/fake_transport_helper.rb +27 -0
  22. data/spec/integration/basic_spec.rb +125 -0
  23. data/spec/integration/design/membership.design +5 -0
  24. data/spec/integration/design/user.design +2 -0
  25. data/spec/lib/core_extension/array_spec.rb +24 -0
  26. data/spec/lib/core_extension/string_spec.rb +22 -0
  27. data/spec/lib/couch_model/active_model_spec.rb +228 -0
  28. data/spec/lib/couch_model/base_spec.rb +169 -0
  29. data/spec/lib/couch_model/collection_spec.rb +100 -0
  30. data/spec/lib/couch_model/configuration_spec.rb +117 -0
  31. data/spec/lib/couch_model/core/accessor_spec.rb +59 -0
  32. data/spec/lib/couch_model/core/association_spec.rb +114 -0
  33. data/spec/lib/couch_model/core/finder_spec.rb +24 -0
  34. data/spec/lib/couch_model/core/setup_spec.rb +88 -0
  35. data/spec/lib/couch_model/database_spec.rb +165 -0
  36. data/spec/lib/couch_model/design/association_test_model_one.design +5 -0
  37. data/spec/lib/couch_model/design/base_test_model.design +10 -0
  38. data/spec/lib/couch_model/design/setup_test_model.design +10 -0
  39. data/spec/lib/couch_model/design_spec.rb +144 -0
  40. data/spec/lib/couch_model/server_spec.rb +64 -0
  41. data/spec/lib/couch_model/transport_spec.rb +44 -0
  42. data/spec/lib/couch_model/view_spec.rb +166 -0
  43. data/spec/lib/couch_model_spec.rb +3 -0
  44. data/spec/spec_helper.rb +27 -0
  45. metadata +128 -0
@@ -0,0 +1,27 @@
1
+ require 'yaml'
2
+
3
+ module CouchModel
4
+
5
+ module Transport
6
+
7
+ def self.fake!
8
+ @@fake ||= YAML::load_file File.join(File.dirname(__FILE__), "fake_transport.yml")
9
+ self.stub!(:request).and_return do |http_method, url, options|
10
+ options ||= { }
11
+ parameters = options[:parameters]
12
+ expected_status_code = options[:expected_status_code]
13
+
14
+ request = @@fake.detect do |hash|
15
+ hash[:http_method].to_s == http_method.to_s &&
16
+ hash[:url].to_s == url.to_s &&
17
+ hash[:parameters] == parameters
18
+ end
19
+ raise StandardError, "no fake request found for [#{http_method} #{url} #{parameters.inspect}]" unless request
20
+ raise UnexpectedStatusCodeError, request[:response][:code].to_i if expected_status_code && expected_status_code.to_s != request[:response][:code]
21
+ request[:response][:json].dup
22
+ end
23
+ end
24
+
25
+ end
26
+
27
+ end
@@ -0,0 +1,125 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "lib", "couch_model"))
3
+
4
+ CouchModel::Configuration.design_directory = File.join File.dirname(__FILE__), "design"
5
+
6
+ class User < CouchModel::Base
7
+
8
+ setup_database :url => "http://localhost:5984/test", :setup_on_initialization => true, :delete_if_exists => true
9
+
10
+ key_accessor :username
11
+ key_accessor :email
12
+
13
+ has_many :memberships,
14
+ :class_name => "Membership",
15
+ :view_name => :by_user_id_and_created_at,
16
+ :query => lambda { |created_at| { :startkey => [ self.id, (created_at || nil) ], :endkey => [ self.id, (created_at || { }) ] } }
17
+
18
+ end
19
+
20
+ class Membership < CouchModel::Base
21
+
22
+ setup_database :url => "http://localhost:5984/test", :setup_on_initialization => true, :delete_if_exists => true
23
+
24
+ key_accessor :created_at
25
+
26
+ belongs_to :user, :class_name => "User"
27
+
28
+ end
29
+
30
+ describe "integration" do
31
+
32
+ use_real_transport!
33
+
34
+ context "on new models" do
35
+
36
+ before :each do
37
+ @user = User.new :username => "user", :email => "email"
38
+ end
39
+
40
+ describe "setup" do
41
+
42
+ it "should have been created the database" do
43
+ User.database.exists?.should be_true
44
+ end
45
+
46
+ it "should have been created the design" do
47
+ User.design.exists?.should be_true
48
+ end
49
+
50
+ it "should setup unique databases" do
51
+ User.database.should === Membership.database
52
+ end
53
+
54
+ it "should setup designs for each model" do
55
+ User.design.should_not == Membership.design
56
+ end
57
+
58
+ end
59
+
60
+ describe "save" do
61
+
62
+ it "should create the model" do
63
+ @user.save
64
+ @user.should_not be_new
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+
71
+ context "on saved models" do
72
+
73
+ before :each do
74
+ @user_one = User.new :username => "user one", :email => "email one"
75
+ @user_one.save
76
+ @user_two = User.new :username => "user two", :email => "email two"
77
+ @user_two.save
78
+ @membership_one = Membership.new :created_at => "yesterday"
79
+ @membership_one.user = @user_one
80
+ @membership_one.save
81
+ @membership_two = Membership.new :created_at => "yesterday"
82
+ @membership_two.user = @user_two
83
+ @membership_two.save
84
+ end
85
+
86
+ describe "all" do
87
+
88
+ it "should include the saved user" do
89
+ User.all.should include(@user_one)
90
+ User.all.should include(@user_two)
91
+ end
92
+
93
+ end
94
+
95
+ describe "belongs_to" do
96
+
97
+ it "should return the related model" do
98
+ @membership_one.user.should == @user_one
99
+ @membership_two.user.should == @user_two
100
+ end
101
+
102
+ end
103
+
104
+ describe "has_many" do
105
+
106
+ it "should include the related model" do
107
+ @user_one.memberships.should include(@membership_one)
108
+ @user_two.memberships.should include(@membership_two)
109
+ end
110
+
111
+ it "should not include the not-related model" do
112
+ @user_one.memberships.should_not include(@membership_two)
113
+ @user_two.memberships.should_not include(@membership_one)
114
+ end
115
+
116
+ it "should use the selector" do
117
+ @user_one.memberships("yesterday").should include(@membership_one)
118
+ @user_one.memberships("today").should_not include(@membership_one)
119
+ end
120
+
121
+ end
122
+
123
+ end
124
+
125
+ end
@@ -0,0 +1,5 @@
1
+ :id: "membership"
2
+ :language: "javascript"
3
+ :views:
4
+ "by_user_id_and_created_at":
5
+ :keys: [ "user_id", "created_at" ]
@@ -0,0 +1,2 @@
1
+ :id: "user"
2
+ :language: "javascript"
@@ -0,0 +1,24 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "core_extension", "array"))
3
+
4
+ describe Array do
5
+
6
+ describe "wrap" do
7
+
8
+ it "should wrap an object into an array" do
9
+ Array.wrap("test").should == [ "test" ]
10
+ end
11
+
12
+ it "should keep an array as it is" do
13
+ Array.wrap([ "test" ]).should == [ "test" ]
14
+ end
15
+
16
+ it "should use to_ary to convert the object" do
17
+ object = Object.new
18
+ object.stub!(:to_ary).and_return([ "test" ])
19
+ Array.wrap(object).should == [ "test" ]
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,22 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "core_extension", "string"))
3
+
4
+ describe String do
5
+
6
+ describe "underscore" do
7
+
8
+ it "should convert camelcase to underscore" do
9
+ "TestModel".underscore.should == "test_model"
10
+ end
11
+
12
+ end
13
+
14
+ describe "camelize" do
15
+
16
+ it "should convert underscore to camelcase" do
17
+ "test_model".camelize.should == "TestModel"
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,228 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "couch_model", "active_model"))
3
+
4
+ class ActiveTestModel < CouchModel::Base
5
+
6
+ setup_database :url => "http://localhost:5984/test"
7
+
8
+ key_accessor :name
9
+ key_accessor :email
10
+
11
+ validates_presence_of :name
12
+
13
+ before_initialize :initialize_callback
14
+ before_save :save_callback
15
+ before_create :create_callback
16
+ before_update :update_callback
17
+ before_destroy :destroy_callback
18
+
19
+ attr_reader :initialized
20
+
21
+ def initialize_callback
22
+ @initialized = true
23
+ end
24
+
25
+ def save_callback; end
26
+ def create_callback; end
27
+ def update_callback; end
28
+ def destroy_callback; end
29
+
30
+ end
31
+
32
+ describe ActiveTestModel do
33
+ include ActiveModel::Lint::Tests
34
+
35
+ before :each do
36
+ @model = ActiveTestModel.new :id => "test_model_1"
37
+ end
38
+
39
+ describe "initialize" do
40
+
41
+ it "should call the initialize callback" do
42
+ @model.initialized.should be_true
43
+ end
44
+
45
+ end
46
+
47
+ describe "new_record?" do
48
+
49
+ it "should fullfill the lint test" do
50
+ test_new_record?
51
+ end
52
+
53
+ end
54
+
55
+ describe "destroyed?" do
56
+
57
+ it "should fullfill the lint test" do
58
+ test_destroyed?
59
+ end
60
+
61
+ it "should return true if model is new" do
62
+ @model.stub!(:new?).and_return(true)
63
+ @model.should be_destroyed
64
+ end
65
+
66
+ end
67
+
68
+ describe "naming" do
69
+
70
+ it "should fullfill the lint test" do
71
+ test_model_naming
72
+ end
73
+
74
+ end
75
+
76
+ describe "valid?" do
77
+
78
+ it "should be true with a given name" do
79
+ @model.name = "test"
80
+ @model.should be_valid
81
+ end
82
+
83
+ it "should be false without a given name" do
84
+ @model.name = ""
85
+ @model.should_not be_valid
86
+ end
87
+
88
+ end
89
+
90
+ describe "changed?" do
91
+
92
+ it "should be true if a attribute has changed" do
93
+ @model.name = "test"
94
+ @model.should be_changed
95
+ end
96
+
97
+ end
98
+
99
+ describe "name_changed?" do
100
+
101
+ it "should be true if the attribute has changed" do
102
+ @model.name = "test"
103
+ @model.should be_name_changed
104
+ end
105
+
106
+ it "should be false if another attribute has changed" do
107
+ @model.email = "test"
108
+ @model.should_not be_name_changed
109
+ end
110
+
111
+ end
112
+
113
+ describe "reset_name!" do
114
+
115
+ before :each do
116
+ @model.name = "test"
117
+ end
118
+
119
+ it "should reset the attributes" do
120
+ @model.reset_name!
121
+ @model.name.should be_nil
122
+ end
123
+
124
+ end
125
+
126
+ describe "save" do
127
+
128
+ before :each do
129
+ @model.name = "test"
130
+ end
131
+
132
+ it "should commit the changes" do
133
+ @model.save
134
+ @model.should_not be_changed
135
+ end
136
+
137
+ it "should call the save callback" do
138
+ @model.should_receive(:save_callback)
139
+ @model.save
140
+ end
141
+
142
+ describe "on a new model" do
143
+
144
+ before :each do
145
+ @model.stub!(:new?).and_return(true)
146
+ end
147
+
148
+ it "should call the create callback" do
149
+ @model.should_receive(:create_callback)
150
+ @model.save
151
+ end
152
+
153
+ end
154
+
155
+ describe "on an existing model" do
156
+
157
+ before :each do
158
+ @model.stub!(:new?).and_return(false)
159
+ end
160
+
161
+ it "should call the update callback" do
162
+ @model.should_receive(:update_callback)
163
+ @model.save
164
+ end
165
+
166
+ end
167
+
168
+ end
169
+
170
+ describe "destroy" do
171
+
172
+ def do_destroy
173
+ @model.destroy
174
+ end
175
+
176
+ it "should call the destroy callback" do
177
+ @model.should_receive(:destroy_callback)
178
+ do_destroy
179
+ end
180
+
181
+ end
182
+
183
+ describe "to_json" do
184
+
185
+ before :each do
186
+ @model.name = "test"
187
+ @model.email = "test"
188
+ end
189
+
190
+ it "should return all attributes as json" do
191
+ @model.to_json.should == "{\"_id\":\"test_model_1\",\"email\":\"test\",\"model_class\":\"ActiveTestModel\",\"name\":\"test\"}"
192
+ end
193
+
194
+ end
195
+
196
+ describe "to_xml" do
197
+
198
+ before :each do
199
+ @model.name = "test"
200
+ @model.email = "test"
201
+ end
202
+
203
+ it "should return all attributes as xml" do
204
+ @model.to_xml.should == "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<active-test-model>\n <-id>test_model_1</-id>\n <email>test</email>\n <model-class>ActiveTestModel</model-class>\n <name>test</name>\n</active-test-model>\n"
205
+ end
206
+
207
+ end
208
+
209
+ describe "human_attribute_name" do
210
+
211
+ it "should return a human readable attribute name" do
212
+ ActiveTestModel.human_attribute_name("name").should == "Name"
213
+ end
214
+
215
+ end
216
+
217
+ private
218
+
219
+ def assert(condition, message = nil)
220
+ puts message unless condition
221
+ condition.should be_true
222
+ end
223
+
224
+ def assert_kind_of(klass, value)
225
+ value.should be_kind_of(klass)
226
+ end
227
+
228
+ end
@@ -0,0 +1,169 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "couch_model", "base"))
3
+
4
+ CouchModel::Configuration.design_directory = File.join File.dirname(__FILE__), "design"
5
+
6
+ class BaseTestModel < CouchModel::Base
7
+
8
+ setup_database :url => "http://localhost:5984/test"
9
+
10
+ key_accessor :name
11
+
12
+ end
13
+
14
+ describe BaseTestModel do
15
+
16
+ before :each do
17
+ @model = BaseTestModel.new :id => "test_model_1"
18
+ end
19
+
20
+ describe "attributes=" do
21
+
22
+ it "should convert an :id or 'id' key to '_id'" do
23
+ @model.attributes = { :id => "test" }
24
+ @model.attributes.should == { "_id" => "test", CouchModel::Configuration::CLASS_KEY => "BaseTestModel" }
25
+ end
26
+
27
+ end
28
+
29
+ describe "==" do
30
+
31
+ before :each do
32
+ @other = BaseTestModel.new
33
+ @other.id = "test_model_1"
34
+ end
35
+
36
+ it "should be true if the id's of the models are equal" do
37
+ @model.should == @other
38
+ end
39
+
40
+ it "should be false if the id's of the models are not equal" do
41
+ @other.id = "invalid"
42
+ @model.should_not == @other
43
+ end
44
+
45
+ end
46
+
47
+ describe "new?" do
48
+
49
+ it "should be true on new model" do
50
+ BaseTestModel.new.should be_new
51
+ end
52
+
53
+ it "should be false on existing model" do
54
+ BaseTestModel.find("test_model_1").should_not be_new
55
+ end
56
+
57
+ end
58
+
59
+ describe "load" do
60
+
61
+ before :each do
62
+ @model = BaseTestModel.new :id => "test_model_1"
63
+ end
64
+
65
+ it "should load the model" do
66
+ @model.load
67
+ @model.attributes["name"].should == "phil"
68
+ end
69
+
70
+ it "should raise an NotFoundError if the model id is not existing" do
71
+ @model.id = "invalid"
72
+ lambda do
73
+ @model.load
74
+ end.should raise_error(CouchModel::Base::NotFoundError)
75
+ end
76
+
77
+ end
78
+
79
+ describe "save" do
80
+
81
+ def do_save
82
+ @model.save
83
+ end
84
+
85
+ describe "a new model" do
86
+
87
+ before :each do
88
+ @model = BaseTestModel.new
89
+ end
90
+
91
+ it "should return true if the model has been saved" do
92
+ do_save.should be_true
93
+ end
94
+
95
+ it "should return false on wrong status code" do
96
+ CouchModel::Transport.stub!(:request).and_raise(CouchModel::Transport::UnexpectedStatusCodeError.new(404))
97
+ do_save.should be_false
98
+ end
99
+
100
+ end
101
+
102
+ describe "an existing model" do
103
+
104
+ before :each do
105
+ @model = BaseTestModel.find "test_model_1"
106
+ end
107
+
108
+ it "should return true if the model has been updated" do
109
+ do_save.should be_true
110
+ end
111
+
112
+ it "should return false on wrong status code" do
113
+ CouchModel::Transport.stub!(:request).and_raise(CouchModel::Transport::UnexpectedStatusCodeError.new(404))
114
+ do_save.should be_false
115
+ end
116
+
117
+ end
118
+
119
+ end
120
+
121
+ describe "destroy" do
122
+
123
+ def do_destroy
124
+ @model.destroy
125
+ end
126
+
127
+ describe "on a new model" do
128
+
129
+ it "should return false" do
130
+ do_destroy.should be_false
131
+ end
132
+
133
+ end
134
+
135
+ describe "on an existing model" do
136
+
137
+ before :each do
138
+ @model.load
139
+ end
140
+
141
+ it "should return true if the model has been destroyed" do
142
+ do_destroy.should be_true
143
+ end
144
+
145
+ it "should raise NotFoundError on wrong status code" do
146
+ CouchModel::Transport.stub!(:request).and_raise(CouchModel::Transport::UnexpectedStatusCodeError.new(404))
147
+ lambda do
148
+ do_destroy
149
+ end.should raise_error(CouchModel::Base::NotFoundError)
150
+ end
151
+
152
+ it "should be new afterwards" do
153
+ do_destroy
154
+ @model.should be_new
155
+ end
156
+
157
+ end
158
+
159
+ end
160
+
161
+ describe "all" do
162
+
163
+ it "should return a collection for the class view" do
164
+ BaseTestModel.all.should be_instance_of(CouchModel::Collection)
165
+ end
166
+
167
+ end
168
+
169
+ end
@@ -0,0 +1,100 @@
1
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper"))
2
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "couch_model", "base"))
3
+ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "lib", "couch_model", "collection"))
4
+
5
+ CouchModel::Configuration.design_directory = File.join File.dirname(__FILE__), "design"
6
+
7
+ class CollectionTestModel < CouchModel::Base
8
+
9
+ setup_database :url => "http://localhost:5984/test"
10
+
11
+ key_accessor :name
12
+
13
+ end
14
+
15
+ describe CouchModel::Collection do
16
+
17
+ before :each do
18
+ @database = CouchModel::Database.new :name => "test"
19
+ @collection = @database.documents :limit => 1
20
+ end
21
+
22
+ describe "initialize" do
23
+
24
+ before :each do
25
+ @collection = CouchModel::Collection.new @database.url + "/_all_docs", :option => "test"
26
+ end
27
+
28
+ it "should set the url" do
29
+ @collection.url.should == @database.url + "/_all_docs"
30
+ end
31
+
32
+ it "should set the options" do
33
+ @collection.options.should == { :option => "test" }
34
+ end
35
+
36
+ end
37
+
38
+ describe "total_count" do
39
+
40
+ describe "without a previously performed fetch" do
41
+
42
+ it "should perform a meta fetch (with a limit of zero)" do
43
+ CouchModel::Transport.should_receive(:request).with(anything, anything,
44
+ hash_including(:parameters => { "include_docs" => "true", "limit" => "0" }))
45
+ @collection.total_count
46
+ end
47
+
48
+ it "should return the total count" do
49
+ @collection.total_count.should == 1
50
+ end
51
+
52
+ end
53
+
54
+ describe "with a previously performed fetch" do
55
+
56
+ before :each do
57
+ @collection.first # perform the fetch
58
+ end
59
+
60
+ it "should not perform another fetch" do
61
+ CouchModel::Transport.should_not_receive(:request)
62
+ @collection.total_count
63
+ end
64
+
65
+ it "should return the total count" do
66
+ @collection.total_count.should == 1
67
+ end
68
+
69
+ end
70
+
71
+ end
72
+
73
+ describe "fetch" do
74
+
75
+ def do_fetch
76
+ @collection.send :fetch
77
+ end
78
+
79
+ it "should return true" do
80
+ do_fetch.should be_true
81
+ end
82
+
83
+ it "should fetch the model" do
84
+ do_fetch
85
+ @collection.first.should be_instance_of(CollectionTestModel)
86
+ @collection.first.name.should == "phil"
87
+ end
88
+
89
+ end
90
+
91
+ describe "request_parameters" do
92
+
93
+ it "should convert options to request parameters" do
94
+ parameters = @collection.send :request_parameters
95
+ parameters.should == { "include_docs" => "true", "limit" => "1" }
96
+ end
97
+
98
+ end
99
+
100
+ end