couchmodel 0.1.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
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