jmoses-couchbase-model 0.5.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,40 @@
1
+ // The map function is the most critical part of any view as it provides the
2
+ // logical mapping between the input fields of the individual objects stored
3
+ // within Couchbase to the information output when the view is accessed.
4
+ //
5
+ // Read more about how to write map functions at:
6
+ // http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views-writing-map.html
7
+
8
+ function(doc, meta) {
9
+ emit(meta.id, null);
10
+ }
11
+
12
+ // You can also check out following examples
13
+ //
14
+ // The simplest example of a map function:
15
+ //
16
+ // function(doc, meta) {
17
+ // emit(meta.id, doc);
18
+ // }
19
+ //
20
+ // Slightly more complex example of a function that defines a view on values
21
+ // computed from customer documents:
22
+ //
23
+ // function(doc, meta) {
24
+ // if (doc.type == "customer") {
25
+ // emit(meta.id, {last_name: doc.last_name, first_name: doc.first_name});
26
+ // }
27
+ // }
28
+ //
29
+ // To be able to filter or sort the view by some document property, you
30
+ // would use that property for the key. For example, the following view
31
+ // would allow you to lookup customer documents by the last_name or
32
+ // first_name fields (your keys could be compound, e.g. arrays):
33
+ //
34
+ // function(doc, meta) {
35
+ // if (doc.type == "customer") {
36
+ // emit(doc.last_name, {first_name: doc.first_name});
37
+ // emit(doc.first_name, {last_name: doc.last_name});
38
+ // }
39
+ // }
40
+ //
@@ -0,0 +1,61 @@
1
+ // If a view has a reduce function, it is used to produce aggregate results
2
+ // for that view. A reduce function is passed a set of intermediate values
3
+ // and combines them to a single value. Reduce functions must accept, as
4
+ // input, results emitted by its corresponding map function as well as
5
+ // results returned by the reduce function itself. The latter case is
6
+ // referred to as a rereduce.
7
+ //
8
+ // function (key, values, rereduce) {
9
+ // return sum(values);
10
+ // }
11
+ //
12
+ // Reduce functions must handle two cases:
13
+ //
14
+ // 1. When rereduce is false:
15
+ //
16
+ // reduce([ [key1,id1], [key2,id2], [key3,id3] ], [value1,value2,value3], false)
17
+ //
18
+ // * key will be an array whose elements are arrays of the form [key,id],
19
+ // where key is a key emitted by the map function and id is that of the
20
+ // document from which the key was generated.
21
+ // * values will be an array of the values emitted for the respective
22
+ // elements in keys
23
+ //
24
+ // 2. When rereduce is true:
25
+ //
26
+ // reduce(null, [intermediate1,intermediate2,intermediate3], true)
27
+ //
28
+ // * key will be null
29
+ // * values will be an array of values returned by previous calls to the
30
+ // reduce function
31
+ //
32
+ // Reduce functions should return a single value, suitable for both the
33
+ // value field of the final view and as a member of the values array passed
34
+ // to the reduce function.
35
+ //
36
+ // NOTE: If this file is empty, reduce part will be skipped in design document
37
+ //
38
+ // There is number of built-in functions, which could be used instead of
39
+ // javascript implementation of reduce function.
40
+ //
41
+ // The _count function provides a simple count of the input rows from the
42
+ // map function, using the keys and group level to provide to provide a
43
+ // count of the correlated items. The values generated during the map()
44
+ // stage are ignored.
45
+ //
46
+ // _count
47
+ //
48
+ // The built-in _sum function collates the output from the map function
49
+ // call. The information can either be a single number or an array of numbers.
50
+ //
51
+ // _sum
52
+ //
53
+ // The _stats built-in produces statistical calculations for the input data.
54
+ // Like the _sum call the source information should be a number. The
55
+ // generated statistics include the sum, count, minimum (min), maximum (max)
56
+ // and sum squared (sumsqr) of the input rows.
57
+ //
58
+ // _stats
59
+ //
60
+ // Read more about how to write reduce functions at:
61
+ // http://www.couchbase.com/docs/couchbase-manual-2.0/couchbase-views-writing-reduce.html
@@ -0,0 +1,43 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Couchbase <info@couchbase.com>
4
+ # Copyright:: 2012 Couchbase, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'rails/generators/couchbase_generator'
21
+
22
+ module Couchbase
23
+ module Generators
24
+ class ViewGenerator < Rails::Generators::Base
25
+ desc 'Creates a Couchbase views skeletons for map/reduce functions'
26
+
27
+ argument :model_name, :type => :string
28
+ argument :view_name, :type => :string
29
+
30
+ source_root File.expand_path('../templates', __FILE__)
31
+
32
+ def app_name
33
+ Rails::Application.subclasses.first.parent.to_s.underscore
34
+ end
35
+
36
+ def create_map_reduce_files
37
+ template 'map.js', File.join('app', 'models', model_name, view_name, 'map.js')
38
+ template 'reduce.js', File.join('app', 'models', model_name, view_name, 'reduce.js')
39
+ end
40
+
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+ #
3
+ # Author:: Couchbase <info@couchbase.com>
4
+ # Copyright:: 2012 Couchbase, Inc.
5
+ # License:: Apache License, Version 2.0
6
+ #
7
+ # Licensed under the Apache License, Version 2.0 (the "License");
8
+ # you may not use this file except in compliance with the License.
9
+ # You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing, software
14
+ # distributed under the License is distributed on an "AS IS" BASIS,
15
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ # See the License for the specific language governing permissions and
17
+ # limitations under the License.
18
+ #
19
+
20
+ require 'rails/generators/named_base'
21
+ require 'rails/generators/active_model'
22
+
23
+ module Couchbase #:nodoc:
24
+ module Generators #:nodoc:
25
+
26
+ class Base < ::Rails::Generators::NamedBase #:nodoc:
27
+
28
+ def self.source_root
29
+ @_couchbase_source_root ||=
30
+ File.expand_path("../#{base_name}/#{generator_name}/templates", __FILE__)
31
+ end
32
+
33
+ unless methods.include?(:module_namespacing)
34
+ def module_namespacing(&block)
35
+ yield if block
36
+ end
37
+ end
38
+
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2012 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'rubygems/package_task'
19
+
20
+ def gemspec
21
+ @clean_gemspec ||= eval(File.read(File.expand_path('../../couchbase-model.gemspec', __FILE__)))
22
+ end
23
+
24
+ Gem::PackageTask.new(gemspec) do |pkg|
25
+ pkg.need_tar = true
26
+ end
27
+
@@ -0,0 +1,34 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2012 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'rake/testtask'
19
+ require 'rake/clean'
20
+
21
+ rule 'test/CouchbaseMock.jar' do |task|
22
+ jar_path = "0.5-SNAPSHOT/CouchbaseMock-0.5-20120726.220757-19.jar"
23
+ sh %{wget -q -O test/CouchbaseMock.jar http://files.couchbase.com/maven2/org/couchbase/mock/CouchbaseMock/#{jar_path}}
24
+ end
25
+
26
+ CLOBBER << 'test/CouchbaseMock.jar'
27
+
28
+ Rake::TestTask.new do |test|
29
+ test.libs << "test" << "."
30
+ test.pattern = 'test/test_*.rb'
31
+ test.options = '--verbose'
32
+ end
33
+
34
+ Rake::Task['test'].prerequisites.unshift('test/CouchbaseMock.jar')
@@ -0,0 +1,21 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2012 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ desc 'Start an irb session and load the library.'
19
+ task :console do
20
+ exec "irb -I lib -rcouchbase-model"
21
+ end
@@ -0,0 +1,168 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2012 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require 'minitest/autorun'
19
+ require 'couchbase'
20
+ require 'couchbase/model'
21
+
22
+ require 'socket'
23
+ require 'open-uri'
24
+
25
+ class CouchbaseServer
26
+ attr_accessor :host, :port, :num_nodes, :buckets_spec
27
+
28
+ def real?
29
+ true
30
+ end
31
+
32
+ def initialize(params = {})
33
+ @host, @port = ENV['COUCHBASE_SERVER'].split(':')
34
+ @port = @port.to_i
35
+
36
+ if @host.nil? || @host.empty? || @port == 0
37
+ raise ArgumentError, 'Check COUCHBASE_SERVER variable. It should be hostname:port'
38
+ end
39
+
40
+ @config = Yajl::Parser.parse(open("http://#{@host}:#{@port}/pools/default"))
41
+ @num_nodes = @config['nodes'].size
42
+ @buckets_spec = params[:buckets_spec] || 'default:' # "default:,protected:secret,cache::memcache"
43
+ end
44
+
45
+ def start
46
+ # flush all buckets
47
+ @buckets_spec.split(',') do |bucket|
48
+ name, password, _ = bucket.split(':')
49
+ connection = Couchbase.new(:hostname => @host,
50
+ :port => @port,
51
+ :username => name,
52
+ :bucket => name,
53
+ :password => password)
54
+ connection.flush
55
+ end
56
+ end
57
+ def stop; end
58
+ end
59
+
60
+ class CouchbaseMock
61
+ Monitor = Struct.new(:pid, :client, :socket, :port)
62
+
63
+ attr_accessor :host, :port, :buckets_spec, :num_nodes, :num_vbuckets
64
+
65
+ def real?
66
+ false
67
+ end
68
+
69
+ def initialize(params = {})
70
+ @host = '127.0.0.1'
71
+ @port = 0
72
+ @num_nodes = 10
73
+ @num_vbuckets = 4096
74
+ @buckets_spec = 'default:' # "default:,protected:secret,cache::memcache"
75
+ params.each do |key, value|
76
+ send("#{key}=", value)
77
+ end
78
+ yield self if block_given?
79
+ if @num_vbuckets < 1 || (@num_vbuckets & (@num_vbuckets - 1) != 0)
80
+ raise ArgumentError, 'Number of vbuckets should be a power of two and greater than zero'
81
+ end
82
+ end
83
+
84
+ def start
85
+ @monitor = Monitor.new
86
+ @monitor.socket = TCPServer.new(nil, 0)
87
+ @monitor.socket.listen(10)
88
+ _, @monitor.port, _, _ = @monitor.socket.addr
89
+ trap('CLD') do
90
+ puts 'CouchbaseMock.jar died unexpectedly during startup'
91
+ exit(1)
92
+ end
93
+ @monitor.pid = fork
94
+ if @monitor.pid.nil?
95
+ rc = exec(command_line("--harakiri-monitor=:#{@monitor.port}"))
96
+ else
97
+ trap('CLD', 'SIG_DFL')
98
+ @monitor.client, _ = @monitor.socket.accept
99
+ @port = @monitor.client.recv(100).to_i
100
+ end
101
+ end
102
+
103
+ def stop
104
+ @monitor.client.close
105
+ @monitor.socket.close
106
+ Process.kill('TERM', @monitor.pid)
107
+ Process.wait(@monitor.pid)
108
+ end
109
+
110
+ def failover_node(index, bucket = 'default')
111
+ @monitor.client.send("failover,#{index},#{bucket}", 0)
112
+ end
113
+
114
+ def respawn_node(index, bucket = 'default')
115
+ @monitor.client.send("respawn,#{index},#{bucket}", 0)
116
+ end
117
+
118
+ protected
119
+
120
+ def command_line(extra = nil)
121
+ cmd = "java -jar #{File.dirname(__FILE__)}/CouchbaseMock.jar"
122
+ cmd << " --host #{@host}" if @host
123
+ cmd << " --port #{@port}" if @port
124
+ cmd << " --nodes #{@num_nodes}" if @num_nodes
125
+ cmd << " --vbuckets #{@num_vbuckets}" if @num_vbuckets
126
+ cmd << " --buckets #{@buckets_spec}" if @buckets_spec
127
+ cmd << " #{extra}"
128
+ cmd
129
+ end
130
+ end
131
+
132
+ class MiniTest::Unit::TestCase
133
+
134
+ def start_mock(params = {})
135
+ mock = nil
136
+ if ENV['COUCHBASE_SERVER']
137
+ mock = CouchbaseServer.new(params)
138
+ if (params[:port] && mock.port != params[:port]) ||
139
+ (params[:host] && mock.host != params[:host]) ||
140
+ mock.buckets_spec != 'default:'
141
+ skip("Unable to configure real cluster. Requested config is: #{params.inspect}")
142
+ end
143
+ else
144
+ mock = CouchbaseMock.new(params)
145
+ end
146
+ mock.start
147
+ mock
148
+ end
149
+
150
+ def stop_mock(mock)
151
+ assert(mock)
152
+ mock.stop
153
+ end
154
+
155
+ def with_mock(params = {})
156
+ mock = nil
157
+ if block_given?
158
+ mock = start_mock(params)
159
+ yield mock
160
+ end
161
+ ensure
162
+ stop_mock(mock) if mock
163
+ end
164
+
165
+ def uniq_id(*suffixes)
166
+ [caller.first[/.*[` ](.*)'/, 1], suffixes].join('_')
167
+ end
168
+ end
@@ -0,0 +1,302 @@
1
+ # Author:: Couchbase <info@couchbase.com>
2
+ # Copyright:: 2011, 2012 Couchbase, Inc.
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+
18
+ require File.join(File.dirname(__FILE__), 'setup')
19
+
20
+ class Post < Couchbase::Model
21
+ attribute :title
22
+ attribute :body
23
+ attribute :author, :default => 'Anonymous'
24
+ attribute :created_at, :default => lambda { Time.utc('2010-01-01') }
25
+ end
26
+
27
+ class ValidPost < Couchbase::Model
28
+ attribute :title
29
+
30
+ def valid?
31
+ title && !title.empty?
32
+ end
33
+ end
34
+
35
+ class Brewery < Couchbase::Model
36
+ attribute :name
37
+ end
38
+
39
+ class Beer < Couchbase::Model
40
+ attribute :name
41
+ belongs_to :brewery
42
+ end
43
+
44
+ class Attachment < Couchbase::Model
45
+ defaults :format => :plain
46
+ end
47
+
48
+ class Comments < Couchbase::Model
49
+ include Enumerable
50
+ attribute :comments, :default => []
51
+ end
52
+
53
+ class User < Couchbase::Model
54
+ design_document :people
55
+ end
56
+
57
+ class TestModel < MiniTest::Unit::TestCase
58
+
59
+ def setup
60
+ @mock = start_mock
61
+ bucket = Couchbase.connect(:hostname => @mock.host, :port => @mock.port)
62
+ [Post, ValidPost, Brewery, Beer, Attachment].each do |model|
63
+ model.bucket = bucket
64
+ end
65
+ end
66
+
67
+ def teardown
68
+ stop_mock(@mock)
69
+ end
70
+
71
+ def test_design_document
72
+ assert_equal 'people', User.design_document
73
+ assert_equal 'new_people', User.design_document('new_people')
74
+ assert_equal 'post', Post.design_document
75
+ end
76
+
77
+ def test_it_supports_value_property
78
+ doc = {
79
+ 'id' => 'x',
80
+ 'key' => 'x',
81
+ 'value' => 'x',
82
+ 'doc' => {
83
+ 'value' => {'title' => 'foo'}
84
+ }
85
+ }
86
+ post = Post.wrap(Post.bucket, doc)
87
+ assert_equal 'foo', post.title
88
+ end
89
+
90
+ def test_it_supports_json_property
91
+ doc = {
92
+ 'id' => 'x',
93
+ 'key' => 'x',
94
+ 'value' => 'x',
95
+ 'doc' => {
96
+ 'json' => {'title' => 'foo'}
97
+ }
98
+ }
99
+ post = Post.wrap(Post.bucket, doc)
100
+ assert_equal 'foo', post.title
101
+ end
102
+
103
+ def test_assigns_attributes_from_the_hash
104
+ post = Post.new(:title => 'Hello, world')
105
+ assert_equal 'Hello, world', post.title
106
+ refute post.body
107
+ refute post.id
108
+ end
109
+
110
+ def test_uses_default_value_or_nil
111
+ post = Post.new(:title => 'Hello, world')
112
+ refute post.body
113
+ assert_equal 'Anonymous', post.author
114
+ assert_equal 'Anonymous', post.attributes[:author]
115
+ end
116
+
117
+ def test_allows_lambda_as_default_value
118
+ post = Post.new(:title => 'Hello, world')
119
+ expected = Time.utc('2010-01-01')
120
+ assert_equal expected, post.created_at
121
+ assert_equal expected, post.attributes[:created_at]
122
+ end
123
+
124
+ def test_assings_id_and_saves_the_object
125
+ post = Post.create(:title => 'Hello, world')
126
+ assert post.id
127
+ end
128
+
129
+ def test_updates_attributes
130
+ post = Post.create(:title => 'Hello, world')
131
+ post.update(:body => 'This is my first example')
132
+ assert_equal 'This is my first example', post.body
133
+ end
134
+
135
+ def test_refreshes_the_attributes_with_reload_method
136
+ orig = Post.create(:title => 'Hello, world')
137
+ double = Post.find(orig.id)
138
+ double.update(:title => 'Good bye, world')
139
+ orig.reload
140
+ assert_equal 'Good bye, world', orig.title
141
+ end
142
+
143
+ def test_it_raises_not_found_exception
144
+ assert_raises Couchbase::Error::NotFound do
145
+ Post.find('missing_key')
146
+ end
147
+ end
148
+
149
+ def test_it_returns_nil_when_key_not_found
150
+ refute Post.find_by_id('missing_key')
151
+ end
152
+
153
+ def test_doesnt_raise_if_the_attribute_redefined
154
+ eval <<-EOC
155
+ class RefinedPost < Couchbase::Model
156
+ attribute :title
157
+ attribute :title
158
+ end
159
+ EOC
160
+ end
161
+
162
+ def test_allows_to_define_several_attributes_at_once
163
+ eval <<-EOC
164
+ class Comment < Couchbase::Model
165
+ attribute :name, :email, :body
166
+ end
167
+ EOC
168
+
169
+ comment = Comment.new
170
+ assert_respond_to comment, :name
171
+ assert_respond_to comment, :email
172
+ assert_respond_to comment, :body
173
+ end
174
+
175
+ def test_allows_arbitrary_ids
176
+ Post.create(:id => uniq_id, :title => 'Foo')
177
+ assert_equal 'Foo', Post.find(uniq_id).title
178
+ end
179
+
180
+ def test_returns_an_instance_of_post
181
+ Post.bucket.set(uniq_id, {:title => 'foo'})
182
+ assert Post.find(uniq_id).kind_of?(Post)
183
+ assert_equal uniq_id, Post.find(uniq_id).id
184
+ assert_equal 'foo', Post.find(uniq_id).title
185
+ end
186
+
187
+ def test_changes_its_attributes
188
+ post = Post.create(:title => 'Hello, world')
189
+ post.title = 'Good bye, world'
190
+ post.save.reload
191
+ assert_equal 'Good bye, world', post.title
192
+ end
193
+
194
+ def test_assings_a_new_id_to_each_record
195
+ post1 = Post.create
196
+ post2 = Post.create
197
+
198
+ refute post1.new?
199
+ refute post2.new?
200
+ refute_equal post1.id, post2.id
201
+ end
202
+
203
+ def test_deletes_an_existent_model
204
+ post = Post.create(:id => uniq_id)
205
+ assert post.delete
206
+ assert_raises Couchbase::Error::NotFound do
207
+ Post.bucket.get(uniq_id)
208
+ end
209
+ end
210
+
211
+ def test_fails_to_delete_model_without_id
212
+ post = Post.new(:title => 'Hello')
213
+ refute post.id
214
+ assert_raises Couchbase::Error::MissingId do
215
+ post.delete
216
+ end
217
+ end
218
+
219
+ def test_belongs_to_assoc
220
+ brewery = Brewery.create(:name => 'Anheuser-Busch')
221
+ assert_includes Beer.attributes.keys, :brewery_id
222
+ beer = Beer.create(:name => 'Budweiser', :brewery_id => brewery.id)
223
+ assert_respond_to beer, :brewery
224
+ assoc = beer.brewery
225
+ assert_instance_of Brewery, assoc
226
+ assert_equal 'Anheuser-Busch', assoc.name
227
+ end
228
+
229
+ def test_to_key
230
+ assert_equal ['the-id'], Post.new(:id => 'the-id').to_key
231
+ assert_equal ['the-key'], Post.new(:key => 'the-key').to_key
232
+ end
233
+
234
+ def test_to_param
235
+ assert_equal 'the-id', Post.new(:id => 'the-id').to_param
236
+ assert_equal 'the-key', Post.new(:key => ['the', 'key']).to_param
237
+ end
238
+
239
+ def test_as_json
240
+ require 'active_support/json/encoding'
241
+
242
+ response = {'id' => 'the-id'}
243
+ assert_equal response, Post.new(:id => 'the-id').as_json
244
+
245
+ response = {}
246
+ assert_equal response, Post.new(:id => 'the-id').as_json(:except => :id)
247
+ end
248
+
249
+ def test_validation
250
+ post = ValidPost.create(:title => 'Hello, World!')
251
+ assert post.valid?, 'post with title should be valid'
252
+ post.title = nil
253
+ refute post.save
254
+ assert_raises(Couchbase::Error::RecordInvalid) do
255
+ post.save!
256
+ end
257
+ refute ValidPost.create(:title => nil)
258
+ assert_raises(Couchbase::Error::RecordInvalid) do
259
+ ValidPost.create!(:title => nil)
260
+ end
261
+ end
262
+
263
+ def test_blob_documents
264
+ contents = File.read(__FILE__)
265
+ id = Attachment.create(:raw => contents).id
266
+ blob = Attachment.find(id)
267
+ assert_equal contents, blob.raw
268
+ end
269
+
270
+ def test_couchbase_ancestor
271
+ assert_equal Couchbase::Model, Comments.couchbase_ancestor
272
+ end
273
+
274
+ def test_returns_multiple_instances_of_post
275
+ Post.create(:id => uniq_id('first'), :title => 'foo')
276
+ Post.create(:id => uniq_id('second'), :title => 'bar')
277
+
278
+ results = Post.find([uniq_id('first'), uniq_id('second')])
279
+ assert results.kind_of?(Array)
280
+ assert results.size == 2
281
+ assert results.detect { |post| post.id == uniq_id('first') }.title == 'foo'
282
+ assert results.detect { |post| post.id == uniq_id('second') }.title == 'bar'
283
+ end
284
+
285
+ def test_returns_array_for_array_of_ids
286
+ Post.create(:id => uniq_id('first'), :title => 'foo')
287
+
288
+ results = Post.find([uniq_id('first')])
289
+ assert results.kind_of?(Array)
290
+ assert results.size == 1
291
+ assert results[0].title == 'foo'
292
+ end
293
+
294
+ def test_returns_array_for_array_of_ids_using_find_by_id
295
+ Post.create(:id => uniq_id('first'), :title => 'foo')
296
+
297
+ results = Post.find_by_id([uniq_id('first')])
298
+ assert results.kind_of?(Array)
299
+ assert results.size == 1
300
+ assert results[0].title == 'foo'
301
+ end
302
+ end