guacamole 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +18 -0
- data/CONTRIBUTING.md +26 -0
- data/Gemfile +16 -0
- data/Gemfile.devtools +55 -0
- data/Guardfile +13 -0
- data/LICENSE +202 -0
- data/README.md +130 -0
- data/Rakefile +7 -0
- data/config/devtools.yml +4 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +104 -0
- data/config/rubocop.yml +68 -0
- data/config/yardstick.yml +2 -0
- data/guacamole.gemspec +28 -0
- data/lib/guacamole.rb +19 -0
- data/lib/guacamole/collection.rb +246 -0
- data/lib/guacamole/configuration.rb +123 -0
- data/lib/guacamole/document_model_mapper.rb +95 -0
- data/lib/guacamole/model.rb +210 -0
- data/lib/guacamole/query.rb +72 -0
- data/lib/guacamole/railtie.rb +27 -0
- data/lib/guacamole/railtie/database.rake +5 -0
- data/lib/guacamole/version.rb +5 -0
- data/log/.gitkeep +0 -0
- data/spec/acceptance/.gitkeep +0 -0
- data/spec/acceptance/basic_spec.rb +112 -0
- data/spec/acceptance/config/guacamole.yml +11 -0
- data/spec/acceptance/spec_helper.rb +61 -0
- data/spec/fabricators/article_fabricator.rb +10 -0
- data/spec/fabricators/comment_fabricator.rb +5 -0
- data/spec/setup/arangodb.sh +65 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/unit/.gitkeep +0 -0
- data/spec/unit/collection_spec.rb +380 -0
- data/spec/unit/configuration_spec.rb +119 -0
- data/spec/unit/document_model_mapper_spec.rb +120 -0
- data/spec/unit/example_spec.rb +8 -0
- data/spec/unit/model_spec.rb +116 -0
- data/spec/unit/query_spec.rb +140 -0
- data/tasks/adjustments.rake +35 -0
- metadata +191 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'guacamole'
|
4
|
+
require 'guacamole/configuration'
|
5
|
+
|
6
|
+
require 'rails'
|
7
|
+
|
8
|
+
module Guacamole
|
9
|
+
# Class to hook into Rails configuration and initializer
|
10
|
+
# @api private
|
11
|
+
class Railtie < Rails::Railtie
|
12
|
+
|
13
|
+
rake_tasks do
|
14
|
+
load 'guacamole/railtie/database.rake'
|
15
|
+
end
|
16
|
+
|
17
|
+
config.guacamole = ::Guacamole::Configuration
|
18
|
+
|
19
|
+
initializer 'guacamole.load-config' do
|
20
|
+
config_file = Rails.root.join('config', 'guacamole.yml')
|
21
|
+
if config_file.file?
|
22
|
+
Guacamole::Configuration.load config_file
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
data/log/.gitkeep
ADDED
File without changes
|
File without changes
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'guacamole'
|
3
|
+
require 'acceptance/spec_helper'
|
4
|
+
|
5
|
+
class Comment
|
6
|
+
include Guacamole::Model
|
7
|
+
|
8
|
+
attribute :text, String
|
9
|
+
end
|
10
|
+
|
11
|
+
class Article
|
12
|
+
include Guacamole::Model
|
13
|
+
|
14
|
+
attribute :title, String
|
15
|
+
attribute :comments, Array[Comment]
|
16
|
+
|
17
|
+
validates :title, presence: true
|
18
|
+
end
|
19
|
+
|
20
|
+
class ArticlesCollection
|
21
|
+
include Guacamole::Collection
|
22
|
+
|
23
|
+
map do
|
24
|
+
embeds :comments
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe 'ModelBasics' do
|
29
|
+
|
30
|
+
describe Article do
|
31
|
+
it 'should allow setting its title' do
|
32
|
+
subject.title = 'This is my fancy article'
|
33
|
+
|
34
|
+
expect(subject.title).to eq('This is my fancy article')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'should have key and rev attributes' do
|
38
|
+
expect(subject.key).to be_nil
|
39
|
+
expect(subject.rev).to be_nil
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should have timestamp attributes which are empty' do
|
43
|
+
expect(subject.created_at).to be_nil
|
44
|
+
expect(subject.updated_at).to be_nil
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should validate its attributes' do
|
48
|
+
expect(subject.valid?).to be_false
|
49
|
+
subject.title = 'The Legend of Zelda'
|
50
|
+
expect(subject.valid?).to be_true
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'should know its model name' do
|
54
|
+
# This test passes when you only require ActiveModel::Validations
|
55
|
+
expect(subject.class.model_name).to eq 'Article'
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should convert itself to params' do
|
59
|
+
subject.key = 'random_number'
|
60
|
+
expect(subject.to_param).to eq 'random_number'
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
describe 'CollectionBasics' do
|
67
|
+
|
68
|
+
describe ArticlesCollection do
|
69
|
+
subject { ArticlesCollection }
|
70
|
+
|
71
|
+
let(:some_article) { Fabricate(:article) }
|
72
|
+
|
73
|
+
it 'should provide a method to find documents by key and return the appropriate model' do
|
74
|
+
found_model = subject.by_key some_article.key
|
75
|
+
expect(found_model).to eq some_article
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'should save models to the database' do
|
79
|
+
new_article = Fabricate.build(:article)
|
80
|
+
subject.save new_article
|
81
|
+
|
82
|
+
expect(subject.by_key(new_article.key)).to eq new_article
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'should update models in the database' do
|
86
|
+
some_article.title = 'Has been updated'
|
87
|
+
subject.replace some_article
|
88
|
+
|
89
|
+
updated_article = subject.by_key(some_article.key)
|
90
|
+
|
91
|
+
expect(updated_article.title).to eq 'Has been updated'
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should receive all documents by title' do
|
95
|
+
subject.save Fabricate.build(:article, title: 'Disturbed')
|
96
|
+
subject.save Fabricate.build(:article, title: 'Not so Disturbed')
|
97
|
+
|
98
|
+
result = subject.by_example(title: 'Disturbed').first
|
99
|
+
|
100
|
+
expect(result.title).to eq 'Disturbed'
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'should allow to nest models' do
|
104
|
+
article_with_comments = Fabricate(:article_with_two_comments)
|
105
|
+
found_article = subject.by_key(article_with_comments.key)
|
106
|
+
|
107
|
+
expect(found_article.comments.first).to be_a Comment
|
108
|
+
expect(found_article.comments).to eq article_with_comments.comments
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# FIXME: This is copied from Ashikawa::Core for now but is not recommended. This
|
2
|
+
# setup uses the default database instead of a custom DB. Due to this we're deleting
|
3
|
+
# all collections in the default database each time we run the specs.
|
4
|
+
# => This is not good!
|
5
|
+
test:
|
6
|
+
protocol: 'http'
|
7
|
+
host: 'localhost'
|
8
|
+
port: 8529
|
9
|
+
password: ''
|
10
|
+
username: ''
|
11
|
+
database: '_system'
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
4
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
5
|
+
|
6
|
+
require 'fabrication'
|
7
|
+
require 'logging'
|
8
|
+
require 'ashikawa-core'
|
9
|
+
|
10
|
+
begin
|
11
|
+
require 'debugger'
|
12
|
+
rescue LoadError
|
13
|
+
puts "Debugger is not available. Maybe you're Travis."
|
14
|
+
end
|
15
|
+
|
16
|
+
class Fabrication::Generator::Guacamole < Fabrication::Generator::Base
|
17
|
+
|
18
|
+
def self.supports?(klass)
|
19
|
+
defined?(Guacamole) && klass.ancestors.include?(Guacamole::Model)
|
20
|
+
end
|
21
|
+
|
22
|
+
def persist
|
23
|
+
collection = [_instance.class.name.pluralize, 'Collection'].join.constantize
|
24
|
+
collection.save _instance
|
25
|
+
end
|
26
|
+
|
27
|
+
def validate_instance
|
28
|
+
_instance.valid?
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
Fabrication::Schematic::Definition::GENERATORS.unshift Fabrication::Generator::Guacamole
|
34
|
+
|
35
|
+
ENV['GUACAMOLE_ENV'] = 'test'
|
36
|
+
|
37
|
+
Guacamole.configure do |config|
|
38
|
+
logger = Logging.logger['guacamole_logger']
|
39
|
+
logger.add_appenders(
|
40
|
+
Logging.appenders.file('log/acceptance.log')
|
41
|
+
)
|
42
|
+
logger.level = :info
|
43
|
+
|
44
|
+
config.logger = logger
|
45
|
+
|
46
|
+
config.load File.join(File.dirname(__FILE__), 'config', 'guacamole.yml')
|
47
|
+
end
|
48
|
+
|
49
|
+
RSpec.configure do |config|
|
50
|
+
config.expect_with :rspec do |c|
|
51
|
+
c.syntax = :expect
|
52
|
+
end
|
53
|
+
|
54
|
+
config.mock_with :rspec do |c|
|
55
|
+
c.syntax = :expect
|
56
|
+
end
|
57
|
+
|
58
|
+
config.before(:each) do
|
59
|
+
Guacamole.configuration.database.collections.each { |collection| collection.truncate! }
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Fabricator(:article) do
|
4
|
+
title 'And then there was silence'
|
5
|
+
end
|
6
|
+
|
7
|
+
Fabricator(:article_with_two_comments, from: Article) do
|
8
|
+
title 'I have two comments'
|
9
|
+
comments(count: 2) { |attrs, i| Fabricate.build(:comment, text: "I'm comment number #{i}") }
|
10
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
4
|
+
cd $DIR
|
5
|
+
|
6
|
+
# VERSION=1.4.devel
|
7
|
+
NAME=ArangoDB-$VERSION
|
8
|
+
|
9
|
+
if [ ! -d "$DIR/$NAME" ]; then
|
10
|
+
# download ArangoDB
|
11
|
+
echo "wget http://www.arangodb.org/travisCI/$NAME.tar.gz"
|
12
|
+
wget http://www.arangodb.org/travisCI/$NAME.tar.gz
|
13
|
+
echo "tar zxf $NAME.tar.gz"
|
14
|
+
tar zvxf $NAME.tar.gz
|
15
|
+
fi
|
16
|
+
|
17
|
+
ARCH=$(arch)
|
18
|
+
PID=$(echo $PPID)
|
19
|
+
TMP_DIR="/tmp/arangodb.$PID"
|
20
|
+
PID_FILE="/tmp/arangodb.$PID.pid"
|
21
|
+
ARANGODB_DIR="$DIR/$NAME"
|
22
|
+
|
23
|
+
ARANGOD="${ARANGODB_DIR}/bin/arangod"
|
24
|
+
if [ "$ARCH" == "x86_64" ]; then
|
25
|
+
ARANGOD="${ARANGOD}_x86_64"
|
26
|
+
fi
|
27
|
+
|
28
|
+
# create database directory
|
29
|
+
mkdir ${TMP_DIR}
|
30
|
+
|
31
|
+
echo "Starting arangodb '${ARANGOD}'"
|
32
|
+
|
33
|
+
${ARANGOD} \
|
34
|
+
--database.directory ${TMP_DIR} \
|
35
|
+
--configuration none \
|
36
|
+
--server.endpoint tcp://127.0.0.1:8529 \
|
37
|
+
--javascript.startup-directory ${ARANGODB_DIR}/js \
|
38
|
+
--server.admin-directory ${ARANGODB_DIR}/html/admin \
|
39
|
+
--javascript.modules-path ${ARANGODB_DIR}/js/server/modules:${ARANGODB_DIR}/js/common/modules:${ARANGODB_DIR}/js/node \
|
40
|
+
--javascript.package-path ${ARANGODB_DIR}/js/npm:${ARANGODB_DIR}/js/common/test-data/modules \
|
41
|
+
--javascript.action-directory ${ARANGODB_DIR}/js/actions \
|
42
|
+
--javascript.app-path ${ARANGODB_DIR}/js/apps \
|
43
|
+
--database.maximal-journal-size 1048576 \
|
44
|
+
--server.disable-admin-interface ${ARANGODB_DISABLE_AUTHENTIFICATION} \
|
45
|
+
--server.disable-authentication true \
|
46
|
+
--javascript.gc-interval 1 &
|
47
|
+
|
48
|
+
sleep 2
|
49
|
+
|
50
|
+
echo "Check for arangod process"
|
51
|
+
process=$(ps auxww | grep "bin/arangod" | grep -v grep)
|
52
|
+
|
53
|
+
if [ "x$process" == "x" ]; then
|
54
|
+
echo "no 'arangod' process found"
|
55
|
+
echo "ARCH = $ARCH"
|
56
|
+
exit 1
|
57
|
+
fi
|
58
|
+
|
59
|
+
echo "Waiting until ArangoDB is ready on port 8529"
|
60
|
+
while [[ -z `curl -s 'http://127.0.0.1:8529/_api/version' ` ]] ; do
|
61
|
+
echo -n "."
|
62
|
+
sleep 2s
|
63
|
+
done
|
64
|
+
|
65
|
+
echo "ArangoDB is up"
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'debugger'
|
7
|
+
rescue LoadError
|
8
|
+
puts "Debugger is not available. Maybe you're Travis."
|
9
|
+
end
|
10
|
+
|
11
|
+
RSpec.configure do |config|
|
12
|
+
config.expect_with :rspec do |c|
|
13
|
+
c.syntax = :expect
|
14
|
+
end
|
15
|
+
|
16
|
+
config.mock_with :rspec do |c|
|
17
|
+
c.syntax = :expect
|
18
|
+
end
|
19
|
+
end
|
data/spec/unit/.gitkeep
ADDED
File without changes
|
@@ -0,0 +1,380 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'guacamole/collection'
|
5
|
+
|
6
|
+
class Test
|
7
|
+
end
|
8
|
+
|
9
|
+
class TestCollection
|
10
|
+
include Guacamole::Collection
|
11
|
+
end
|
12
|
+
|
13
|
+
describe Guacamole::Collection do
|
14
|
+
subject { TestCollection }
|
15
|
+
|
16
|
+
describe 'Configuration' do
|
17
|
+
it 'should set the connection to the ArangoDB collection' do
|
18
|
+
mock_collection_connection = double('ConnectionToCollection')
|
19
|
+
subject.connection = mock_collection_connection
|
20
|
+
|
21
|
+
expect(subject.connection).to eq mock_collection_connection
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'should set the Mapper instance to map documents to models and vice versa' do
|
25
|
+
mock_mapper = double('Mapper')
|
26
|
+
subject.mapper = mock_mapper
|
27
|
+
|
28
|
+
expect(subject.mapper).to eq mock_mapper
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'should set the connection to ArangoDB' do
|
32
|
+
mock_db = double('Ashikawa::Core::Database')
|
33
|
+
subject.database = mock_db
|
34
|
+
|
35
|
+
expect(subject.database).to eq mock_db
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should know the name of the collection in ArangoDB' do
|
39
|
+
expect(subject.collection_name).to eq 'test'
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'should know the class of the model to manage' do
|
43
|
+
expect(subject.model_class).to eq Test
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe 'database' do
|
48
|
+
before do
|
49
|
+
subject.database = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should default to Guacamole.configuration.database' do
|
53
|
+
default_database = double('Database')
|
54
|
+
configuration = double('Configuration', database: default_database)
|
55
|
+
allow(Guacamole).to receive(:configuration).and_return(configuration)
|
56
|
+
|
57
|
+
expect(subject.database).to eq default_database
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'connection' do
|
62
|
+
before do
|
63
|
+
subject.connection = nil
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should default to the collection "collection_name" in the database' do
|
67
|
+
database = double('Database')
|
68
|
+
allow(subject).to receive(:database).and_return(database)
|
69
|
+
|
70
|
+
expect(database).to receive(:[]).with(subject.collection_name)
|
71
|
+
|
72
|
+
subject.connection
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe 'mapper' do
|
77
|
+
before do
|
78
|
+
subject.mapper = nil
|
79
|
+
end
|
80
|
+
|
81
|
+
it 'should default to Guacamole.configuration.default_mapper' do
|
82
|
+
default_mapper = double('Mapper')
|
83
|
+
mapper_instance = double('MapperInstance')
|
84
|
+
configuration = double('Configuration', default_mapper: default_mapper)
|
85
|
+
allow(Guacamole).to receive(:configuration).and_return(configuration)
|
86
|
+
allow(default_mapper).to receive(:new).with(subject.model_class).and_return(mapper_instance)
|
87
|
+
|
88
|
+
expect(subject.mapper).to eq mapper_instance
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
let(:connection) { double('Connection') }
|
93
|
+
let(:mapper) { double('Mapper') }
|
94
|
+
|
95
|
+
before do
|
96
|
+
subject.connection = connection
|
97
|
+
subject.mapper = mapper
|
98
|
+
end
|
99
|
+
|
100
|
+
describe 'by_key' do
|
101
|
+
it 'should get mapped documents by key from the database' do
|
102
|
+
document = { data: 'foo' }
|
103
|
+
model = double('Model')
|
104
|
+
|
105
|
+
expect(connection).to receive(:fetch).with('some_key').and_return(document)
|
106
|
+
expect(mapper).to receive(:document_to_model).with(document).and_return(model)
|
107
|
+
|
108
|
+
expect(subject.by_key('some_key')).to eq model
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'should raise a Ashikawa::Core::DocumentNotFoundException exception for nil' do
|
112
|
+
expect { subject.by_key(nil) }.to raise_error(Ashikawa::Core::DocumentNotFoundException)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
describe 'save' do
|
117
|
+
|
118
|
+
before do
|
119
|
+
allow(connection).to receive(:create_document).with(document).and_return(document)
|
120
|
+
allow(mapper).to receive(:model_to_document).with(model).and_return(document)
|
121
|
+
end
|
122
|
+
|
123
|
+
let(:key) { double('Key') }
|
124
|
+
let(:rev) { double('Rev') }
|
125
|
+
let(:document) { double('Document', key: key, revision: rev).as_null_object }
|
126
|
+
let(:model) { double('Model').as_null_object }
|
127
|
+
|
128
|
+
context 'a valid model' do
|
129
|
+
before do
|
130
|
+
allow(model).to receive(:valid?).and_return(true)
|
131
|
+
end
|
132
|
+
|
133
|
+
it 'should create a document' do
|
134
|
+
expect(connection).to receive(:create_document).with(document).and_return(document)
|
135
|
+
expect(mapper).to receive(:model_to_document).with(model).and_return(document)
|
136
|
+
|
137
|
+
subject.save model
|
138
|
+
end
|
139
|
+
|
140
|
+
it 'should return the model after calling save' do
|
141
|
+
expect(subject.save(model)).to eq model
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'should set timestamps before creating the document' do
|
145
|
+
now = double('Time.now')
|
146
|
+
|
147
|
+
allow(Time).to receive(:now).once.and_return(now)
|
148
|
+
|
149
|
+
expect(model).to receive(:created_at=).with(now).ordered
|
150
|
+
expect(model).to receive(:updated_at=).with(now).ordered
|
151
|
+
|
152
|
+
allow(connection).to receive(:create_document).with(document).and_return(document).ordered
|
153
|
+
|
154
|
+
subject.save model
|
155
|
+
end
|
156
|
+
|
157
|
+
it 'should add key to model' do
|
158
|
+
expect(model).to receive(:key=).with(key)
|
159
|
+
|
160
|
+
subject.save model
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'should add rev to model' do
|
164
|
+
expect(model).to receive(:rev=).with(rev)
|
165
|
+
|
166
|
+
subject.save model
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
context 'an invalid model' do
|
171
|
+
|
172
|
+
before do
|
173
|
+
expect(model).to receive(:valid?).and_return(false)
|
174
|
+
end
|
175
|
+
|
176
|
+
it 'should not be used to create the document' do
|
177
|
+
expect(connection).to receive(:create_document).never
|
178
|
+
|
179
|
+
subject.save model
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'should not be changed' do
|
183
|
+
expect(model).to receive(:created_at=).never
|
184
|
+
expect(model).to receive(:updated_at=).never
|
185
|
+
expect(model).to receive(:key=).never
|
186
|
+
expect(model).to receive(:rev=).never
|
187
|
+
|
188
|
+
subject.save model
|
189
|
+
end
|
190
|
+
|
191
|
+
it 'should return false' do
|
192
|
+
expect(subject.save(model)).to be false
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
describe 'delete' do
|
198
|
+
let(:document) { double('Document') }
|
199
|
+
let(:key) { double('Key') }
|
200
|
+
let(:model) { double('Model', key: key) }
|
201
|
+
|
202
|
+
before do
|
203
|
+
allow(connection).to receive(:fetch).with(key).and_return(document)
|
204
|
+
allow(document).to receive(:delete)
|
205
|
+
end
|
206
|
+
|
207
|
+
context 'a key was provided' do
|
208
|
+
it 'should delete the according document' do
|
209
|
+
expect(document).to receive(:delete)
|
210
|
+
|
211
|
+
subject.delete key
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'should return the according key' do
|
215
|
+
expect(subject.delete(key)).to eq key
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
context 'a model was provided' do
|
220
|
+
it 'should delete the according document' do
|
221
|
+
expect(document).to receive(:delete)
|
222
|
+
|
223
|
+
subject.delete model
|
224
|
+
end
|
225
|
+
|
226
|
+
it 'should return the according key' do
|
227
|
+
expect(subject.delete(model)).to eq key
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe 'replace' do
|
233
|
+
let(:key) { double('Key') }
|
234
|
+
let(:rev) { double('Rev') }
|
235
|
+
let(:model) { double('Model', key: key).as_null_object }
|
236
|
+
let(:document) { double('Document').as_null_object }
|
237
|
+
let(:response) { double('Hash') }
|
238
|
+
|
239
|
+
before do
|
240
|
+
allow(mapper).to receive(:model_to_document).with(model).and_return(document)
|
241
|
+
allow(connection).to receive(:replace).and_return(response)
|
242
|
+
allow(response).to receive(:[]).with('_rev').and_return(rev)
|
243
|
+
end
|
244
|
+
|
245
|
+
context 'a valid model' do
|
246
|
+
before do
|
247
|
+
allow(model).to receive(:valid?).and_return(true)
|
248
|
+
end
|
249
|
+
|
250
|
+
it 'should set the updated_at timestamp before replacing the document' do
|
251
|
+
now = double('Time.now')
|
252
|
+
|
253
|
+
allow(Time).to receive(:now).once.and_return(now)
|
254
|
+
expect(model).to receive(:updated_at=).with(now)
|
255
|
+
|
256
|
+
subject.replace model
|
257
|
+
end
|
258
|
+
|
259
|
+
it 'should replace the document by key via the connection' do
|
260
|
+
expect(connection).to receive(:replace).with(key, document)
|
261
|
+
|
262
|
+
subject.replace model
|
263
|
+
end
|
264
|
+
|
265
|
+
it 'should update the revision after replacing the document' do
|
266
|
+
allow(connection).to receive(:replace).and_return(response).ordered
|
267
|
+
expect(model).to receive(:rev=).with(rev).ordered
|
268
|
+
|
269
|
+
subject.replace model
|
270
|
+
end
|
271
|
+
|
272
|
+
it 'should return the model' do
|
273
|
+
expect(subject.replace(model)).to eq model
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'should not update created_at' do
|
277
|
+
expect(model).to receive(:created_at=).never
|
278
|
+
|
279
|
+
subject.replace model
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
context 'an invalid model' do
|
284
|
+
before do
|
285
|
+
allow(model).to receive(:valid?).and_return(false)
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'should not be used to replace the document' do
|
289
|
+
expect(connection).to receive(:replace).never
|
290
|
+
|
291
|
+
subject.replace model
|
292
|
+
end
|
293
|
+
|
294
|
+
it 'should not be changed' do
|
295
|
+
expect(model).to receive(:rev=).never
|
296
|
+
expect(model).to receive(:updated_at=).never
|
297
|
+
|
298
|
+
subject.replace model
|
299
|
+
end
|
300
|
+
|
301
|
+
it 'should return false' do
|
302
|
+
expect(subject.replace(model)).to be false
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
describe 'by_example' do
|
308
|
+
let(:example) { double }
|
309
|
+
let(:query_connection) { double }
|
310
|
+
let(:query) { double }
|
311
|
+
|
312
|
+
before do
|
313
|
+
allow(connection).to receive(:query)
|
314
|
+
.and_return(query_connection)
|
315
|
+
|
316
|
+
allow(Guacamole::Query).to receive(:new)
|
317
|
+
.and_return(query)
|
318
|
+
|
319
|
+
allow(query).to receive(:example=)
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'should create a new query with the query connection and mapper' do
|
323
|
+
expect(Guacamole::Query).to receive(:new)
|
324
|
+
.with(query_connection, mapper)
|
325
|
+
|
326
|
+
subject.by_example(example)
|
327
|
+
end
|
328
|
+
|
329
|
+
it 'should set the example for the query' do
|
330
|
+
expect(query).to receive(:example=)
|
331
|
+
.with(example)
|
332
|
+
|
333
|
+
subject.by_example(example)
|
334
|
+
end
|
335
|
+
|
336
|
+
it 'should return the query' do
|
337
|
+
expect(subject.by_example(example)).to be query
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
describe 'all' do
|
342
|
+
let(:query_connection) { double }
|
343
|
+
let(:query) { double }
|
344
|
+
|
345
|
+
before do
|
346
|
+
allow(connection).to receive(:query)
|
347
|
+
.and_return(query_connection)
|
348
|
+
|
349
|
+
allow(Guacamole::Query).to receive(:new)
|
350
|
+
.and_return(query)
|
351
|
+
end
|
352
|
+
|
353
|
+
it 'should create a new query with the query connection and mapper' do
|
354
|
+
expect(Guacamole::Query).to receive(:new)
|
355
|
+
.with(query_connection, mapper)
|
356
|
+
|
357
|
+
subject.all
|
358
|
+
end
|
359
|
+
|
360
|
+
it 'should return the query' do
|
361
|
+
expect(subject.all).to be query
|
362
|
+
end
|
363
|
+
end
|
364
|
+
|
365
|
+
describe 'map' do
|
366
|
+
let(:mapper) { double('Mapper') }
|
367
|
+
|
368
|
+
before do
|
369
|
+
subject.mapper = mapper
|
370
|
+
end
|
371
|
+
|
372
|
+
it 'should evaluate the block on the mapper instance' do
|
373
|
+
expect(mapper).to receive(:method_to_call_on_mapper)
|
374
|
+
|
375
|
+
subject.map do
|
376
|
+
method_to_call_on_mapper
|
377
|
+
end
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|