couchbase-orm 0.0.1
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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/.travis.yml +34 -0
- data/Gemfile +4 -0
- data/LICENSE +24 -0
- data/README.md +131 -0
- data/Rakefile +75 -0
- data/couchbase-orm.gemspec +27 -0
- data/lib/couchbase-orm.rb +43 -0
- data/lib/couchbase-orm/associations.rb +87 -0
- data/lib/couchbase-orm/base.rb +254 -0
- data/lib/couchbase-orm/connection.rb +22 -0
- data/lib/couchbase-orm/error.rb +15 -0
- data/lib/couchbase-orm/id_generator.rb +30 -0
- data/lib/couchbase-orm/persistence.rb +240 -0
- data/lib/couchbase-orm/railtie.rb +96 -0
- data/lib/couchbase-orm/utilities/ensure_unique.rb +18 -0
- data/lib/couchbase-orm/utilities/enum.rb +49 -0
- data/lib/couchbase-orm/utilities/has_many.rb +73 -0
- data/lib/couchbase-orm/utilities/index.rb +117 -0
- data/lib/couchbase-orm/utilities/join.rb +68 -0
- data/lib/couchbase-orm/version.rb +5 -0
- data/lib/couchbase-orm/views.rb +148 -0
- data/lib/rails/generators/couchbase_orm/config/config_generator.rb +42 -0
- data/lib/rails/generators/couchbase_orm/config/templates/couchbase.yml +18 -0
- data/lib/rails/generators/couchbase_orm_generator.rb +42 -0
- data/spec/associations_spec.rb +80 -0
- data/spec/base_spec.rb +72 -0
- data/spec/has_many_spec.rb +82 -0
- data/spec/id_generator_spec.rb +48 -0
- data/spec/index_spec.rb +73 -0
- data/spec/persistence_spec.rb +237 -0
- data/spec/support.rb +28 -0
- data/spec/views_spec.rb +73 -0
- metadata +184 -0
@@ -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/couchbase_orm_generator'
|
21
|
+
|
22
|
+
module CouchbaseOrm
|
23
|
+
module Generators
|
24
|
+
class ConfigGenerator < Rails::Generators::Base
|
25
|
+
desc 'Creates a Couchbase configuration file at config/couchbase.yml'
|
26
|
+
argument :database_name, :type => :string, :optional => true
|
27
|
+
|
28
|
+
def self.source_root
|
29
|
+
@_couchbase_source_root ||= File.expand_path('../templates', __FILE__)
|
30
|
+
end
|
31
|
+
|
32
|
+
def app_name
|
33
|
+
Rails::Application.subclasses.first.parent.to_s.underscore
|
34
|
+
end
|
35
|
+
|
36
|
+
def create_config_file
|
37
|
+
template 'couchbase.yml', File.join('config', 'couchbase.yml')
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
common: &common
|
2
|
+
hosts: localhost
|
3
|
+
password:
|
4
|
+
|
5
|
+
development:
|
6
|
+
<<: *common
|
7
|
+
bucket: <%= database_name || 'default' %>
|
8
|
+
|
9
|
+
test:
|
10
|
+
<<: *common
|
11
|
+
bucket: <%= database_name || app_name %>_test
|
12
|
+
password: for_test_bucket
|
13
|
+
|
14
|
+
# set these environment variables on your production server
|
15
|
+
production:
|
16
|
+
hosts: <%%= ENV['COUCHBASE_HOST'] || ENV['COUCHBASE_HOSTS'] %>
|
17
|
+
bucket: <%%= ENV['COUCHBASE_BUCKET'] %>
|
18
|
+
password: <%%= ENV['COUCHBASE_PASSWORD'] %>
|
@@ -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 CouchbaseOrm #: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,80 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require File.expand_path("../support", __FILE__)
|
4
|
+
|
5
|
+
|
6
|
+
class Parent < CouchbaseOrm::Base
|
7
|
+
attribute :name
|
8
|
+
end
|
9
|
+
|
10
|
+
class Child < CouchbaseOrm::Base
|
11
|
+
attribute :name
|
12
|
+
|
13
|
+
belongs_to :parent, dependent: :destroy
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
describe CouchbaseOrm::Associations do
|
18
|
+
it "should work with dependent associations" do
|
19
|
+
parent = Parent.create!(name: 'joe')
|
20
|
+
child = Child.create!(name: 'bob', parent_id: parent.id)
|
21
|
+
|
22
|
+
expect(parent.persisted?).to be(true)
|
23
|
+
expect(child.persisted?).to be(true)
|
24
|
+
id = parent.id
|
25
|
+
|
26
|
+
child.destroy
|
27
|
+
expect(child.destroyed?).to be(true)
|
28
|
+
expect(parent.destroyed?).to be(false)
|
29
|
+
|
30
|
+
# Ensure that parent has been destroyed
|
31
|
+
expect { Parent.find(id) }.to raise_error(Libcouchbase::Error::KeyNotFound)
|
32
|
+
expect(Parent.find_by_id(id)).to be(nil)
|
33
|
+
|
34
|
+
expect { parent.reload }.to raise_error(Libcouchbase::Error::KeyNotFound)
|
35
|
+
|
36
|
+
# Save will always return true unless the model is changed (won't touch the database)
|
37
|
+
parent.name = 'should fail'
|
38
|
+
expect { parent.save }.to raise_error(Libcouchbase::Error::KeyNotFound)
|
39
|
+
expect { parent.save! }.to raise_error(Libcouchbase::Error::KeyNotFound)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should cache associations" do
|
43
|
+
parent = Parent.create!(name: 'joe')
|
44
|
+
child = Child.create!(name: 'bob', parent_id: parent.id)
|
45
|
+
|
46
|
+
id = child.parent.__id__
|
47
|
+
expect(parent.__id__).not_to eq(child.parent.__id__)
|
48
|
+
expect(parent).to eq(child.parent)
|
49
|
+
expect(child.parent.__id__).to eq(id)
|
50
|
+
|
51
|
+
child.reload
|
52
|
+
expect(parent).to eq(child.parent)
|
53
|
+
expect(child.parent.__id__).not_to eq(id)
|
54
|
+
|
55
|
+
child.destroy
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should ignore associations when delete is used" do
|
59
|
+
parent = Parent.create!(name: 'joe')
|
60
|
+
child = Child.create!(name: 'bob', parent_id: parent.id)
|
61
|
+
|
62
|
+
id = child.id
|
63
|
+
child.delete
|
64
|
+
|
65
|
+
expect(Child.exists?(id)).to be(false)
|
66
|
+
expect(Parent.exists?(parent.id)).to be(true)
|
67
|
+
|
68
|
+
id = parent.id
|
69
|
+
parent.delete
|
70
|
+
expect(Parent.exists?(id)).to be(false)
|
71
|
+
end
|
72
|
+
|
73
|
+
describe Parent do
|
74
|
+
it_behaves_like "ActiveModel"
|
75
|
+
end
|
76
|
+
|
77
|
+
describe Child do
|
78
|
+
it_behaves_like "ActiveModel"
|
79
|
+
end
|
80
|
+
end
|
data/spec/base_spec.rb
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require File.expand_path("../support", __FILE__)
|
4
|
+
|
5
|
+
|
6
|
+
class BaseTest < CouchbaseOrm::Base
|
7
|
+
attribute :name, :job
|
8
|
+
end
|
9
|
+
|
10
|
+
class CompareTest < CouchbaseOrm::Base
|
11
|
+
attribute :age
|
12
|
+
end
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
describe CouchbaseOrm::Base do
|
17
|
+
it "should be comparable to other objects" do
|
18
|
+
base = BaseTest.create!(name: 'joe')
|
19
|
+
base2 = BaseTest.create!(name: 'joe')
|
20
|
+
|
21
|
+
expect(base).to eq(base)
|
22
|
+
expect(base).to be(base)
|
23
|
+
expect(base).not_to eq(base2)
|
24
|
+
|
25
|
+
same_base = BaseTest.find(base.id)
|
26
|
+
expect(base).to eq(same_base)
|
27
|
+
expect(base).not_to be(same_base)
|
28
|
+
expect(base2).not_to eq(same_base)
|
29
|
+
|
30
|
+
base.delete
|
31
|
+
base2.delete
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should load database responses" do
|
35
|
+
base = BaseTest.create!(name: 'joe')
|
36
|
+
resp = BaseTest.bucket.get(base.id, extended: true)
|
37
|
+
|
38
|
+
expect(resp.key).to eq(base.id)
|
39
|
+
|
40
|
+
base_loaded = BaseTest.new(resp)
|
41
|
+
expect(base_loaded).to eq(base)
|
42
|
+
expect(base_loaded).not_to be(base)
|
43
|
+
|
44
|
+
base.destroy
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should not load objects if there is a type mismatch" do
|
48
|
+
base = BaseTest.create!(name: 'joe')
|
49
|
+
|
50
|
+
expect { CompareTest.find_by_id(base.id) }.to raise_error(RuntimeError)
|
51
|
+
|
52
|
+
base.destroy
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should support serialisation" do
|
56
|
+
base = BaseTest.create!(name: 'joe')
|
57
|
+
|
58
|
+
base_id = base.id
|
59
|
+
expect(base.to_json).to eq({name: 'joe', job: nil, id: base_id}.to_json)
|
60
|
+
expect(base.to_json(only: :name)).to eq({name: 'joe'}.to_json)
|
61
|
+
|
62
|
+
base.destroy
|
63
|
+
end
|
64
|
+
|
65
|
+
describe BaseTest do
|
66
|
+
it_behaves_like "ActiveModel"
|
67
|
+
end
|
68
|
+
|
69
|
+
describe CompareTest do
|
70
|
+
it_behaves_like "ActiveModel"
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require File.expand_path("../support", __FILE__)
|
4
|
+
|
5
|
+
|
6
|
+
class ObjectRatingTest < CouchbaseOrm::Base
|
7
|
+
join :object_test, :rating_test
|
8
|
+
view :all
|
9
|
+
end
|
10
|
+
|
11
|
+
class RatingTest < CouchbaseOrm::Base
|
12
|
+
enum rating: [:awesome, :good, :okay, :bad], default: :okay
|
13
|
+
belongs_to :object_test
|
14
|
+
|
15
|
+
has_many :object_tests, through: :object_rating_test
|
16
|
+
view :all
|
17
|
+
end
|
18
|
+
|
19
|
+
class ObjectTest < CouchbaseOrm::Base
|
20
|
+
attribute :name, type: String
|
21
|
+
has_many :rating_tests, dependent: :destroy
|
22
|
+
|
23
|
+
view :all
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
describe CouchbaseOrm::HasMany do
|
28
|
+
before :all do
|
29
|
+
RatingTest.ensure_design_document!
|
30
|
+
ObjectTest.ensure_design_document!
|
31
|
+
ObjectRatingTest.ensure_design_document!
|
32
|
+
end
|
33
|
+
|
34
|
+
after :each do
|
35
|
+
ObjectTest.all.stream { |ob| ob.delete }
|
36
|
+
RatingTest.all.stream { |ob| ob.delete }
|
37
|
+
ObjectRatingTest.all.stream { |ob| ob.delete }
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should return matching results" do
|
41
|
+
first = ObjectTest.create! name: :bob
|
42
|
+
second = ObjectTest.create! name: :jane
|
43
|
+
|
44
|
+
rate = RatingTest.create! rating: :awesome, object_test: first
|
45
|
+
RatingTest.create! rating: :bad, object_test: second
|
46
|
+
RatingTest.create! rating: :good, object_test: first
|
47
|
+
|
48
|
+
expect(rate.object_test_id).to eq(first.id)
|
49
|
+
expect(RatingTest.respond_to?(:find_by_object_test_id)).to be(true)
|
50
|
+
expect(first.respond_to?(:rating_tests)).to be(true)
|
51
|
+
|
52
|
+
docs = first.rating_tests.collect { |ob|
|
53
|
+
ob.rating
|
54
|
+
}
|
55
|
+
|
56
|
+
expect(docs).to eq([1, 2])
|
57
|
+
|
58
|
+
first.destroy
|
59
|
+
expect { RatingTest.find rate.id }.to raise_error(::Libcouchbase::Error::KeyNotFound)
|
60
|
+
expect(RatingTest.all.count).to be(1)
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should work through a join model" do
|
64
|
+
first = ObjectTest.create! name: :bob
|
65
|
+
second = ObjectTest.create! name: :jane
|
66
|
+
|
67
|
+
rate1 = RatingTest.create! rating: :awesome, object_test: first
|
68
|
+
rate2 = RatingTest.create! rating: :bad, object_test: second
|
69
|
+
rate3 = RatingTest.create! rating: :good, object_test: first
|
70
|
+
|
71
|
+
ort = ObjectRatingTest.create! object_test: first, rating_test: rate1
|
72
|
+
ObjectRatingTest.create! object_test: second, rating_test: rate1
|
73
|
+
|
74
|
+
expect(ort.rating_test_id).to eq(rate1.id)
|
75
|
+
expect(rate1.respond_to?(:object_tests)).to be(true)
|
76
|
+
docs = rate1.object_tests.collect { |ob|
|
77
|
+
ob.name
|
78
|
+
}
|
79
|
+
|
80
|
+
expect(docs).to eq(['bob', 'jane'])
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require 'couchbase-orm'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
class IdTestModel < CouchbaseOrm::Base; end
|
7
|
+
|
8
|
+
describe CouchbaseOrm::IdGenerator do
|
9
|
+
it "should not generate ID clashes" do
|
10
|
+
model = IdTestModel.new
|
11
|
+
|
12
|
+
ids1 = []
|
13
|
+
thread1 = Thread.new do
|
14
|
+
(1..10000).each {
|
15
|
+
ids1 << CouchbaseOrm::IdGenerator.next(model)
|
16
|
+
}
|
17
|
+
end
|
18
|
+
|
19
|
+
ids2 = []
|
20
|
+
thread2 = Thread.new do
|
21
|
+
(1..10000).each {
|
22
|
+
ids2 << CouchbaseOrm::IdGenerator.next(model)
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
ids3 = []
|
27
|
+
thread3 = Thread.new do
|
28
|
+
(1..10000).each {
|
29
|
+
ids3 << CouchbaseOrm::IdGenerator.next(model)
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
ids4 = []
|
34
|
+
thread4 = Thread.new do
|
35
|
+
(1..10000).each {
|
36
|
+
ids4 << CouchbaseOrm::IdGenerator.next(model)
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
thread1.join
|
41
|
+
thread2.join
|
42
|
+
thread3.join
|
43
|
+
thread4.join
|
44
|
+
|
45
|
+
results = [ids1, ids2, ids3, ids4].flatten
|
46
|
+
expect(results.uniq).to eq(results)
|
47
|
+
end
|
48
|
+
end
|
data/spec/index_spec.rb
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true, encoding: ASCII-8BIT
|
2
|
+
|
3
|
+
require File.expand_path("../support", __FILE__)
|
4
|
+
|
5
|
+
|
6
|
+
class IndexTest < CouchbaseOrm::Base
|
7
|
+
attribute :email
|
8
|
+
ensure_unique :email
|
9
|
+
end
|
10
|
+
|
11
|
+
class EnumTest < CouchbaseOrm::Base
|
12
|
+
enum visibility: [:group, :authority, :public], default: :authority
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
describe CouchbaseOrm::Index do
|
17
|
+
it "should prevent models being created if they should have unique keys" do
|
18
|
+
joe = IndexTest.create!(email: 'joe@aca.com')
|
19
|
+
expect { IndexTest.create!(email: 'joe@aca.com') }.to raise_error(CouchbaseOrm::Error::RecordInvalid)
|
20
|
+
|
21
|
+
joe.email = 'other@aca.com'
|
22
|
+
joe.save
|
23
|
+
other = IndexTest.new(email: 'joe@aca.com')
|
24
|
+
expect(other.save).to be(true)
|
25
|
+
|
26
|
+
expect { IndexTest.create!(email: 'joe@aca.com') }.to raise_error(CouchbaseOrm::Error::RecordInvalid)
|
27
|
+
expect { IndexTest.create!(email: 'other@aca.com') }.to raise_error(CouchbaseOrm::Error::RecordInvalid)
|
28
|
+
|
29
|
+
joe.destroy
|
30
|
+
other.destroy
|
31
|
+
|
32
|
+
again = IndexTest.new(email: 'joe@aca.com')
|
33
|
+
expect(again.save).to be(true)
|
34
|
+
|
35
|
+
again.destroy
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should provide helper methods for looking up the model" do
|
39
|
+
joe = IndexTest.create!(email: 'joe@aca.com')
|
40
|
+
|
41
|
+
joe_again = IndexTest.find_by_email('joe@aca.com')
|
42
|
+
expect(joe).to eq(joe_again)
|
43
|
+
|
44
|
+
joe.destroy
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should clean up itself if dangling keys are left" do
|
48
|
+
joe = IndexTest.create!(email: 'joe@aca.com')
|
49
|
+
joe.delete # no callbacks are executed
|
50
|
+
|
51
|
+
again = IndexTest.new(email: 'joe@aca.com')
|
52
|
+
expect(again.save).to be(true)
|
53
|
+
|
54
|
+
again.destroy
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should work with enumerators" do
|
58
|
+
# Test symbol
|
59
|
+
enum = EnumTest.create!(visibility: :public)
|
60
|
+
expect(enum.visibility).to eq(3)
|
61
|
+
enum.destroy
|
62
|
+
|
63
|
+
# Test number
|
64
|
+
enum = EnumTest.create!(visibility: 2)
|
65
|
+
expect(enum.visibility).to eq(2)
|
66
|
+
enum.destroy
|
67
|
+
|
68
|
+
# Test default
|
69
|
+
enum = EnumTest.create!
|
70
|
+
expect(enum.visibility).to eq(2)
|
71
|
+
enum.destroy
|
72
|
+
end
|
73
|
+
end
|