jmoses-couchbase-model 0.5.3

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.
@@ -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