boombera 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -3,9 +3,9 @@ source "http://rubygems.org"
3
3
  gem 'couchrest', '~> 1.0.2'
4
4
 
5
5
  group :development do
6
- gem "rspec", "~> 2.3.0"
6
+ gem "rspec", "~> 2.6.0"
7
7
  gem "bundler", "~> 1.0.0"
8
- gem "jeweler", :git => "git://github.com/jwilger/jeweler.git"
8
+ gem "jeweler", "~> 1.6.1"
9
9
  gem "rcov", ">= 0"
10
10
  gem "reek", "~> 1.2.8"
11
11
  gem "rdoc", "~> 3.6.1"
@@ -1,12 +1,3 @@
1
- GIT
2
- remote: git://github.com/jwilger/jeweler.git
3
- revision: d7461cba474852ab16d31618e6465b56b00db49e
4
- specs:
5
- jeweler (1.6.0)
6
- bundler (~> 1.0.0)
7
- git (>= 1.2.5)
8
- rake
9
-
10
1
  GEM
11
2
  remote: http://rubygems.org/
12
3
  specs:
@@ -22,6 +13,10 @@ GEM
22
13
  rest-client (~> 1.6.1)
23
14
  diff-lcs (1.1.2)
24
15
  git (1.2.5)
16
+ jeweler (1.6.1)
17
+ bundler (~> 1.0.0)
18
+ git (>= 1.2.5)
19
+ rake
25
20
  json (1.5.1)
26
21
  mime-types (1.16)
27
22
  rake (0.9.0)
@@ -33,14 +28,14 @@ GEM
33
28
  sexp_processor (~> 3.0)
34
29
  rest-client (1.6.1)
35
30
  mime-types (>= 1.16)
36
- rspec (2.3.0)
37
- rspec-core (~> 2.3.0)
38
- rspec-expectations (~> 2.3.0)
39
- rspec-mocks (~> 2.3.0)
40
- rspec-core (2.3.1)
41
- rspec-expectations (2.3.0)
31
+ rspec (2.6.0)
32
+ rspec-core (~> 2.6.0)
33
+ rspec-expectations (~> 2.6.0)
34
+ rspec-mocks (~> 2.6.0)
35
+ rspec-core (2.6.3)
36
+ rspec-expectations (2.6.0)
42
37
  diff-lcs (~> 1.1.2)
43
- rspec-mocks (2.3.0)
38
+ rspec-mocks (2.6.0)
44
39
  ruby2ruby (1.2.5)
45
40
  ruby_parser (~> 2.0)
46
41
  sexp_processor (~> 3.0)
@@ -58,8 +53,8 @@ DEPENDENCIES
58
53
  autotest-growl (~> 0.2.9)
59
54
  bundler (~> 1.0.0)
60
55
  couchrest (~> 1.0.2)
61
- jeweler!
56
+ jeweler (~> 1.6.1)
62
57
  rcov
63
58
  rdoc (~> 3.6.1)
64
59
  reek (~> 1.2.8)
65
- rspec (~> 2.3.0)
60
+ rspec (~> 2.6.0)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{boombera}
8
- s.version = "0.1.2"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["John Wilger"]
@@ -47,9 +47,9 @@ Gem::Specification.new do |s|
47
47
 
48
48
  if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
49
49
  s.add_runtime_dependency(%q<couchrest>, ["~> 1.0.2"])
50
- s.add_development_dependency(%q<rspec>, ["~> 2.3.0"])
50
+ s.add_development_dependency(%q<rspec>, ["~> 2.6.0"])
51
51
  s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
52
- s.add_development_dependency(%q<jeweler>, [">= 0"])
52
+ s.add_development_dependency(%q<jeweler>, ["~> 1.6.1"])
53
53
  s.add_development_dependency(%q<rcov>, [">= 0"])
54
54
  s.add_development_dependency(%q<reek>, ["~> 1.2.8"])
55
55
  s.add_development_dependency(%q<rdoc>, ["~> 3.6.1"])
@@ -58,9 +58,9 @@ Gem::Specification.new do |s|
58
58
  s.add_development_dependency(%q<autotest-fsevent>, ["~> 0.2.5"])
59
59
  else
60
60
  s.add_dependency(%q<couchrest>, ["~> 1.0.2"])
61
- s.add_dependency(%q<rspec>, ["~> 2.3.0"])
61
+ s.add_dependency(%q<rspec>, ["~> 2.6.0"])
62
62
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
63
- s.add_dependency(%q<jeweler>, [">= 0"])
63
+ s.add_dependency(%q<jeweler>, ["~> 1.6.1"])
64
64
  s.add_dependency(%q<rcov>, [">= 0"])
65
65
  s.add_dependency(%q<reek>, ["~> 1.2.8"])
66
66
  s.add_dependency(%q<rdoc>, ["~> 3.6.1"])
@@ -70,9 +70,9 @@ Gem::Specification.new do |s|
70
70
  end
71
71
  else
72
72
  s.add_dependency(%q<couchrest>, ["~> 1.0.2"])
73
- s.add_dependency(%q<rspec>, ["~> 2.3.0"])
73
+ s.add_dependency(%q<rspec>, ["~> 2.6.0"])
74
74
  s.add_dependency(%q<bundler>, ["~> 1.0.0"])
75
- s.add_dependency(%q<jeweler>, [">= 0"])
75
+ s.add_dependency(%q<jeweler>, ["~> 1.6.1"])
76
76
  s.add_dependency(%q<rcov>, [">= 0"])
77
77
  s.add_dependency(%q<reek>, ["~> 1.2.8"])
78
78
  s.add_dependency(%q<rdoc>, ["~> 3.6.1"])
@@ -11,6 +11,7 @@ require 'boombera/information'
11
11
 
12
12
  class Boombera
13
13
  VersionMismatch = Class.new(StandardError)
14
+ InvalidMapping = Class.new(RuntimeError)
14
15
 
15
16
  extend Boombera::Information
16
17
 
@@ -22,13 +23,19 @@ class Boombera
22
23
  end
23
24
 
24
25
  def put(path, body)
25
- content_item = get(path) and content_item.body = body
26
+ content_item = get(path, :resolve_map => false) and content_item.body = body
26
27
  content_item ||= ContentItem.new(path, body, db)
27
28
  content_item.save
28
29
  end
29
30
 
30
- def get(path)
31
- ContentItem.get(path, db)
31
+ def get(path, options = {})
32
+ ContentItem.get(path, db, options)
33
+ end
34
+
35
+ def map(path, source_path)
36
+ content_map = get(path, :resolve_map => false) || ContentItem.new(path, nil, db)
37
+ content_map.map_to source_path
38
+ content_map.save
32
39
  end
33
40
 
34
41
  private
@@ -1,15 +1,20 @@
1
1
  class Boombera::ContentItem < CouchRest::Document
2
2
  class << self
3
- def get(path, database)
3
+ def get(path, database, options = {})
4
4
  rows = database.view('boombera/content_map', :key => path)['rows']
5
5
  return nil if rows.empty?
6
- id = rows.first['id']
7
- new(database.get(id))
6
+ match = rows.first
7
+ maps_to = match['value']
8
+ if maps_to == path || options[:resolve_map] == false
9
+ new(database.get(match['id']))
10
+ else
11
+ get(maps_to, database)
12
+ end
8
13
  end
9
14
  end
10
15
 
11
16
  attr_accessor :body
12
- attr_reader :path
17
+ attr_reader :path, :maps_to
13
18
 
14
19
  def initialize(doc_or_path, body = nil, database = nil)
15
20
  case doc_or_path
@@ -24,6 +29,22 @@ class Boombera::ContentItem < CouchRest::Document
24
29
  end
25
30
  end
26
31
 
32
+ def map_to(source_path)
33
+ rows = @database.view('boombera/content_map', :key => source_path)['rows']
34
+ if rows.empty?
35
+ raise Boombera::InvalidMapping,
36
+ "Tried to map #{path} to #{source_path}, but #{source_path} doesn't exist."
37
+ else
38
+ self.body = nil
39
+ self[:maps_to] = source_path
40
+ end
41
+ end
42
+
43
+ def referenced_by
44
+ rows = @database.view('boombera/map_references', :key => path)['rows']
45
+ rows.map{ |r| r['value'] }.sort
46
+ end
47
+
27
48
  # :nodoc:
28
49
  def path
29
50
  self[:path]
@@ -37,5 +58,11 @@ class Boombera::ContentItem < CouchRest::Document
37
58
  # :nodoc:
38
59
  def body=(new_body)
39
60
  self[:body] = new_body
61
+ self[:maps_to] = path unless new_body.nil?
62
+ end
63
+
64
+ # :nodoc:
65
+ def maps_to
66
+ self[:maps_to]
40
67
  end
41
68
  end
@@ -26,7 +26,20 @@ module Boombera::Information
26
26
  'map' => <<-EOF
27
27
  function(doc) {
28
28
  if (doc['path']) {
29
- emit(doc.path, doc.path);
29
+ if (doc['maps_to']) {
30
+ emit(doc.path, doc.maps_to);
31
+ } else {
32
+ emit(doc.path, doc.path);
33
+ }
34
+ }
35
+ }
36
+ EOF
37
+ },
38
+ 'map_references' => {
39
+ 'map' => <<-EOF
40
+ function(doc) {
41
+ if(doc['maps_to'] && doc.maps_to != doc.path) {
42
+ emit(doc.maps_to, doc.path);
30
43
  }
31
44
  }
32
45
  EOF
@@ -81,6 +81,52 @@ describe 'The Boombera library:' do
81
81
  document['path'].should == '/foo'
82
82
  document['body'].should == 'the new content'
83
83
  end
84
+
85
+ it 'turns a pointer into a content item' do
86
+ boombera.put('/foo', 'foo bar baz')
87
+ boombera.map('/bar', '/foo')
88
+ boombera.put('/bar', 'the new content')
89
+ results = db.view('boombera/content_map', :key => '/bar')['rows']
90
+ results.length.should == 1
91
+ document = db.get(results.first['id'])
92
+ document['path'].should == '/bar'
93
+ document['body'].should == 'the new content'
94
+ document['points_to'].should be_nil
95
+ end
96
+ end
97
+
98
+ describe 'mapping content aliases' do
99
+ it 'creates a pointer from a path to another path' do
100
+ boombera.put('/foo', 'foo bar baz')
101
+ boombera.map('/bar', '/foo')
102
+ results = db.view('boombera/content_map', :key => '/bar')['rows']
103
+ results.length.should == 1
104
+ map_item = results.first
105
+ map_item['value'].should == '/foo'
106
+ end
107
+
108
+ it 'updates a pointer from a path to another path' do
109
+ boombera.put('/foo', 'foo bar baz')
110
+ boombera.put('/spam', 'ham spam can')
111
+ boombera.map('/bar', '/foo')
112
+ boombera.map('/bar', '/spam')
113
+ results = db.view('boombera/content_map', :key => '/bar')['rows']
114
+ results.length.should == 1
115
+ map_item = results.first
116
+ map_item['value'].should == '/spam'
117
+ end
118
+
119
+ it 'turns a content item into a pointer' do
120
+ boombera.put('/foo', 'foo bar baz')
121
+ boombera.put('/bar', 'some old bar content')
122
+ boombera.map('/bar', '/foo')
123
+ results = db.view('boombera/content_map', :key => '/bar')['rows']
124
+ results.length.should == 1
125
+ map_item = results.first
126
+ map_item['value'].should == '/foo'
127
+ doc = db.get(map_item['id'])
128
+ doc['body'].should be_nil
129
+ end
84
130
  end
85
131
 
86
132
  describe 'getting content from the database' do
@@ -94,6 +140,14 @@ describe 'The Boombera library:' do
94
140
  result.path.should == '/index'
95
141
  result.body.should == 'Hello, World!'
96
142
  end
143
+
144
+ it 'gives you the resulting ContentItem when a pointer is requested' do
145
+ boombera.put('/foo', 'some content')
146
+ boombera.map('/bar', '/foo')
147
+ result = boombera.get('/bar')
148
+ result.path.should == '/foo'
149
+ result.body.should == 'some content'
150
+ end
97
151
  end
98
152
 
99
153
  describe 'working with ContentItem' do
@@ -104,5 +158,16 @@ describe 'The Boombera library:' do
104
158
  content.save
105
159
  boombera.get('/foo').body.should == 'new content'
106
160
  end
161
+
162
+ it 'knows which pointers reference it' do
163
+ boombera.put('/foo', 'some content')
164
+ boombera.map('/zurg', '/foo')
165
+ boombera.map('/bar', '/foo')
166
+ boombera.map('/bar/baz', '/foo')
167
+ boombera.put('/spam', 'spam content')
168
+ boombera.map('/spam2', '/spam')
169
+ content = boombera.get('/foo')
170
+ content.referenced_by.should == ['/bar', '/bar/baz', '/zurg']
171
+ end
107
172
  end
108
173
  end
@@ -4,7 +4,7 @@ describe Boombera::ContentItem do
4
4
  describe '.get' do
5
5
  context 'with an existing content item' do
6
6
  it 'returns a ContentItem instance for the found document' do
7
- view_result = {'rows' => [{'id' => '123'}]}
7
+ view_result = {'rows' => [{'id' => '123', 'value' => '/foo'}]}
8
8
  db = mock(CouchRest::Database)
9
9
  db.should_receive(:view) \
10
10
  .with('boombera/content_map', :key => '/foo') \
@@ -17,6 +17,51 @@ describe Boombera::ContentItem do
17
17
  result.body.should == 'bar'
18
18
  end
19
19
  end
20
+
21
+ context 'with a non-existant content item' do
22
+ it 'returns nil' do
23
+ view_result = {'rows' => []}
24
+ db = mock(CouchRest::Database)
25
+ db.should_receive(:view) \
26
+ .with('boombera/content_map', :key => '/foo') \
27
+ .and_return(view_result)
28
+ Boombera::ContentItem.get('/foo', db).should == nil
29
+ end
30
+ end
31
+
32
+ context 'with a path that maps to another content item' do
33
+ it 'returns the mapped content item' do
34
+ map_view_result = {'rows' => [{'id' => '123', 'value' => '/bar'}]}
35
+ content_view_result = {'rows' => [{'id' => '456', 'value' => '/bar'}]}
36
+ db = mock(CouchRest::Database)
37
+ db.should_receive(:view) \
38
+ .with('boombera/content_map', :key => '/foo') \
39
+ .and_return(map_view_result)
40
+ db.should_receive(:view) \
41
+ .with('boombera/content_map', :key => '/bar') \
42
+ .and_return(content_view_result)
43
+ db.should_receive(:get) \
44
+ .with('456') \
45
+ .and_return(CouchRest::Document.new('path' => '/bar', 'body' => 'bar'))
46
+ result = Boombera::ContentItem.get('/foo', db)
47
+ result.path.should == '/bar'
48
+ result.body.should == 'bar'
49
+ end
50
+
51
+ it 'returns the pointer content item when passed the :resolve_map option as false' do
52
+ map_view_result = {'rows' => [{'id' => '123', 'value' => '/bar'}]}
53
+ db = mock(CouchRest::Database)
54
+ db.should_receive(:view) \
55
+ .with('boombera/content_map', :key => '/foo') \
56
+ .and_return(map_view_result)
57
+ db.should_receive(:get) \
58
+ .with('123') \
59
+ .and_return(CouchRest::Document.new('path' => '/foo', 'maps_to' => '/bar'))
60
+ result = Boombera::ContentItem.get('/foo', db, :resolve_map => false)
61
+ result.path.should == '/foo'
62
+ result.maps_to.should == '/bar'
63
+ end
64
+ end
20
65
  end
21
66
 
22
67
  describe '.new' do
@@ -52,10 +97,58 @@ describe Boombera::ContentItem do
52
97
  end
53
98
 
54
99
  describe '#body=' do
100
+ let(:db) { stub(CouchRest::Database) }
101
+ let(:content) { Boombera::ContentItem.new('/foo', 'not bar', db) }
102
+
55
103
  it 'overwrites the current contents of the document body' do
56
- content = Boombera::ContentItem.new('/foo', 'not bar')
57
104
  content.body = 'bar'
58
105
  content.body.should == 'bar'
59
106
  end
107
+
108
+ it 'sets the maps_to attribute equal to the path attribute if argument is not nil' do
109
+ db.stub!(:view => {'rows' => [{'value' => '/foo'}]})
110
+ content.map_to '/bar'
111
+ content.body = 'something'
112
+ content.maps_to.should == '/foo'
113
+ end
114
+
115
+ it 'does not change the maps_to attribute if the argument is nil' do
116
+ db.stub!(:view => {'rows' => [{'value' => '/foo'}]})
117
+ content.map_to '/bar'
118
+ content.body = nil
119
+ content.maps_to.should == '/bar'
120
+ end
121
+ end
122
+
123
+ describe '#map_to' do
124
+ context 'the source content item does not exist' do
125
+ it 'raises an InvalidMapping exception' do
126
+ db = stub(CouchRest::Database)
127
+ db.stub!(:view => {'rows' => []})
128
+ content = Boombera::ContentItem.new('/bar', nil, db)
129
+ lambda { content.map_to('/foo') }.should \
130
+ raise_error(Boombera::InvalidMapping, "Tried to map /bar to /foo, but /foo doesn't exist.")
131
+ end
132
+ end
133
+
134
+ context 'the source content item is a normal content item' do
135
+ let(:db) { stub(CouchRest::Database) }
136
+ let(:content) { Boombera::ContentItem.new('/bar', 'foo bar', db) }
137
+
138
+ before(:each) do
139
+ db.should_receive(:view) \
140
+ .with('boombera/content_map', :key => '/foo') \
141
+ .and_return({'rows' => [{'value' => '/foo'}]})
142
+ content.map_to '/foo'
143
+ end
144
+
145
+ it 'sets the maps_to attribute to the path of the source content' do
146
+ content.maps_to.should == '/foo'
147
+ end
148
+
149
+ it 'sets the body attribute to nil' do
150
+ content.body.should be_nil
151
+ end
152
+ end
60
153
  end
61
154
  end
@@ -7,6 +7,9 @@ describe Boombera do
7
7
  db
8
8
  end
9
9
 
10
+ let(:content_item) { stub(Boombera::ContentItem) }
11
+ let(:boombera) { Boombera.new('boombera_test') }
12
+
10
13
  before(:each) do
11
14
  Boombera.stub!(:version => '1.2.3')
12
15
  Boombera.stub!(:database_version => '1.2.3')
@@ -35,30 +38,25 @@ describe Boombera do
35
38
  end
36
39
 
37
40
  describe '#put' do
38
- let(:content_item) { mock(Boombera::ContentItem) }
39
- let(:content_item_save_expectations) do
40
- lambda {
41
- content_item.should_receive(:save).and_return(true)
42
- boombera = Boombera.new('boombera_test')
43
- boombera.put('/foo', 'bar').should == true
44
- }
45
- end
46
-
47
41
  context "to an existing path" do
48
42
  it 'updates and saves the existing content item' do
49
- Boombera::ContentItem.should_receive(:get).with('/foo', db).and_return(content_item)
43
+ Boombera::ContentItem.should_receive(:get) \
44
+ .with('/foo', db, :resolve_map => false) \
45
+ .and_return(content_item)
50
46
  content_item.should_receive(:body=).with('bar')
51
- content_item_save_expectations.call
47
+ content_item.should_receive(:save).and_return(true)
48
+ boombera.put('/foo', 'bar').should == true
52
49
  end
53
50
  end
54
51
 
55
52
  context "to a new path" do
56
- it 'creates and saves the existing content item' do
53
+ it 'creates and saves the content item' do
57
54
  Boombera::ContentItem.stub!(:get => nil)
58
55
  Boombera::ContentItem.should_receive(:new) \
59
56
  .with('/foo', 'bar', db) \
60
57
  .and_return(content_item)
61
- content_item_save_expectations.call
58
+ content_item.should_receive(:save).and_return(true)
59
+ boombera.put('/foo', 'bar').should == true
62
60
  end
63
61
  end
64
62
  end
@@ -66,12 +64,52 @@ describe Boombera do
66
64
  describe '#get' do
67
65
  it 'gets the content item at the specified path from the current database' do
68
66
  db.as_null_object
69
- Boombera::ContentItem.should_receive(:get).with('/foo', db)
70
- boombera = Boombera.new('boombera_test')
67
+ Boombera::ContentItem.should_receive(:get).with('/foo', db, {})
71
68
  boombera.get('/foo')
72
69
  end
73
70
  end
74
71
 
72
+ describe '#map' do
73
+ context 'to a new path' do
74
+ before(:each) do
75
+ Boombera::ContentItem.stub!(:get)
76
+ Boombera::ContentItem.should_receive(:new).with('/bar', nil, db).and_return(content_item)
77
+ end
78
+
79
+ it 'creates and saves ContentItem as pointer' do
80
+ content_item.should_receive(:map_to).with('/foo')
81
+ content_item.should_receive(:save).and_return(true)
82
+ boombera.map('/bar', '/foo').should == true
83
+ end
84
+
85
+ it 'raises an InvalidMapping exception if the source document does not exist' do
86
+ content_item.stub!(:map_to).and_raise(Boombera::InvalidMapping)
87
+ lambda { boombera.map('/bar', '/foo') }.should \
88
+ raise_error(Boombera::InvalidMapping)
89
+ end
90
+ end
91
+
92
+ context 'to an existing path' do
93
+ before(:each) do
94
+ Boombera::ContentItem.should_receive(:get) \
95
+ .with('/bar', db, :resolve_map => false) \
96
+ .and_return(content_item)
97
+ end
98
+
99
+ it 'updates ContentItem as pointer' do
100
+ content_item.should_receive(:map_to).with('/foo')
101
+ content_item.should_receive(:save).and_return(true)
102
+ boombera.map('/bar', '/foo').should == true
103
+ end
104
+
105
+ it 'raises an InvalidMapping exception if the source document does not exist' do
106
+ content_item.stub!(:map_to).and_raise(Boombera::InvalidMapping)
107
+ lambda { boombera.map('/bar', '/foo') }.should \
108
+ raise_error(Boombera::InvalidMapping)
109
+ end
110
+ end
111
+ end
112
+
75
113
  describe '.install_design_doc!' do
76
114
  context 'when the design doc does not yet exist' do
77
115
  it 'creates the design doc on the specified database' do
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: boombera
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.1.2
5
+ version: 0.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - John Wilger
@@ -31,7 +31,7 @@ dependencies:
31
31
  requirements:
32
32
  - - ~>
33
33
  - !ruby/object:Gem::Version
34
- version: 2.3.0
34
+ version: 2.6.0
35
35
  type: :development
36
36
  prerelease: false
37
37
  version_requirements: *id002
@@ -51,9 +51,9 @@ dependencies:
51
51
  requirement: &id004 !ruby/object:Gem::Requirement
52
52
  none: false
53
53
  requirements:
54
- - - ">="
54
+ - - ~>
55
55
  - !ruby/object:Gem::Version
56
- version: "0"
56
+ version: 1.6.1
57
57
  type: :development
58
58
  prerelease: false
59
59
  version_requirements: *id004
@@ -165,7 +165,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
165
165
  requirements:
166
166
  - - ">="
167
167
  - !ruby/object:Gem::Version
168
- hash: 2330631431022327628
168
+ hash: -941585902333991149
169
169
  segments:
170
170
  - 0
171
171
  version: "0"