Skip to content
This repository
Browse code

Continuing modifications to get new design docs working

  • Loading branch information...
commit 88def18b484c05a5da9a1b22ed29816f610edde3 1 parent fa5ef9e
Sam Lown samlown authored
7 lib/couchrest/model/designs.rb
@@ -48,6 +48,10 @@ def default_per_page
48 48 @_default_per_page || 25
49 49 end
50 50
  51 + def design_docs
  52 + @_design_docs ||= []
  53 + end
  54 +
51 55 end
52 56
53 57
@@ -68,6 +72,9 @@ def initialize(model, prefix = nil)
68 72 create_design_doc_method
69 73 self.design_doc = model.send(method)
70 74
  75 + # Ensure model has up to date list of design docs
  76 + model.design_docs << design_doc unless model.design_docs.include?(design_doc)
  77 +
71 78 # Some defaults
72 79 design_doc.auto_update = model.auto_update_design_doc
73 80 end
22 lib/couchrest/model/designs/design.rb
@@ -37,7 +37,7 @@ def sync(db = nil)
37 37 # Load up the last copy. We never overwrite the remote copy
38 38 # as it may contain views that are not used or known about by
39 39 # our model.
40   - doc = load_from_database(database)
  40 + doc = load_from_database(db)
41 41
42 42 if !doc || doc['couchrest-hash'] != checksum
43 43 # We need to save something
@@ -56,6 +56,7 @@ def sync(db = nil)
56 56 self
57 57 end
58 58
  59 +
59 60 def checksum
60 61 sum = self['couchrest-hash']
61 62 if sum && (@_original_hash == to_hash)
@@ -69,6 +70,18 @@ def database
69 70 model.database
70 71 end
71 72
  73 + # Override the default #uri method for one that accepts
  74 + # the current database.
  75 + # This is used by the caching code.
  76 + def uri(db = database)
  77 + "#{db.root}/#{self['_id']}"
  78 + end
  79 +
  80 + # Helper method to provide a list of all the views
  81 + def view_names
  82 + self['views'].keys
  83 + end
  84 +
72 85 protected
73 86
74 87 def load_from_database(db = database)
@@ -108,13 +121,6 @@ def checksum!
108 121 self['couchrest-hash'] = Digest::MD5.hexdigest(flatten.call(base).sort.join(''))
109 122 end
110 123
111   - # Override the default #uri method for one that accepts
112   - # the current database.
113   - # This is used by the caching code.
114   - def uri(db)
115   - "#{db.root}/#{self['_id']}"
116   - end
117   -
118 124 def cache
119 125 Thread.current[:couchrest_design_cache] ||= {}
120 126 end
4 lib/couchrest/model/designs/view.rb
@@ -310,8 +310,8 @@ def database(value)
310 310
311 311 # Set the view's proxy that will be used instead of the model
312 312 # for any future searches. As soon as this enters the
313   - # new object's initializer it will be removed and replace
314   - # the model object.
  313 + # new view's initializer it will be removed and set as the model
  314 + # object.
315 315 #
316 316 # See the Proxyable mixin for more details.
317 317 #
74 lib/couchrest/model/proxyable.rb
@@ -70,6 +70,8 @@ def initialize(model, owner, owner_name, database)
70 70 @owner = owner
71 71 @owner_name = owner_name
72 72 @database = database
  73 +
  74 + create_view_methods
73 75 end
74 76
75 77 # Base
@@ -81,39 +83,18 @@ def build_from_database(attrs = {}, options = {}, &block)
81 83 proxy_block_update(:build_from_database, attrs, options, &block)
82 84 end
83 85
84   - def method_missing(m, *args, &block)
85   - if has_view?(m)
86   - if model.respond_to?(m)
87   - return model.send(m, *args).proxy(self)
88   - else
89   - query = args.shift || {}
90   - return view(m, query, *args, &block)
91   - end
92   - elsif m.to_s =~ /^find_(by_.+)/
93   - view_name = $1
94   - if has_view?(view_name)
95   - return first_from_view(view_name, *args)
96   - end
97   - end
98   - super
99   - end
100   -
101   - # DocumentQueries
102   -
103   - def all(opts = {}, &block)
104   - proxy_update_all(@model.all({:database => @database}.merge(opts), &block))
105   - end
  86 + # From DocumentQueries (The old fashioned way)
106 87
107 88 def count(opts = {})
108   - @model.count({:database => @database}.merge(opts))
  89 + all(opts).count
109 90 end
110 91
111 92 def first(opts = {})
112   - proxy_update(@model.first({:database => @database}.merge(opts)))
  93 + all(opts).first
113 94 end
114 95
115 96 def last(opts = {})
116   - proxy_update(@model.last({:database => @database}.merge(opts)))
  97 + all(opts).last
117 98 end
118 99
119 100 def get(id)
@@ -121,38 +102,23 @@ def get(id)
121 102 end
122 103 alias :find :get
123 104
124   - # Views
125   -
126   - def has_view?(view)
127   - @model.has_view?(view)
128   - end
129   -
130   - def view_by(*args)
131   - @model.view_by(*args)
132   - end
133   -
134   - def view(name, query={}, &block)
135   - proxy_update_all(@model.view(name, {:database => @database}.merge(query), &block))
136   - end
137   -
138   - def first_from_view(name, *args)
139   - # add to first hash available, or add to end
140   - (args.last.is_a?(Hash) ? args.last : (args << {}).last)[:database] = @database
141   - proxy_update(@model.first_from_view(name, *args))
142   - end
143   -
144   - # DesignDoc
145   - def design_doc
146   - @model.design_doc
147   - end
  105 + protected
148 106
149   - def save_design_doc(db = nil)
150   - @model.save_design_doc(db || @database)
  107 + def create_view_methods
  108 + model.design_docs.each do |doc|
  109 + doc.view_names.each do |name|
  110 + class_eval <<-EOS, __FILE__, __LINE__ + 1
  111 + def self.#{name}(opts = {})
  112 + #{name}({:proxy => self}.merge(opts))
  113 + end
  114 + def self.find_#{name}(*key)
  115 + #{name}.key(*key).first()
  116 + end
  117 + EOS
  118 + end
  119 + end
151 120 end
152 121
153   -
154   - protected
155   -
156 122 # Update the document's proxy details, specifically, the fields that
157 123 # link back to the original document.
158 124 def proxy_update(doc)
4 spec/unit/class_proxy_spec.rb
@@ -5,7 +5,9 @@ class UnattachedDoc < CouchRest::Model::Base
5 5 property :title
6 6 property :questions
7 7 property :professor
8   - view_by :title
  8 + design do
  9 + view :title
  10 + end
9 11 end
10 12
11 13
227 spec/unit/design_doc_spec.rb
... ... @@ -1,227 +0,0 @@
1   -# encoding: utf-8
2   -require 'spec_helper'
3   -
4   -describe CouchRest::Model::DesignDoc do
5   -
6   - before :all do
7   - reset_test_db!
8   - end
9   -
10   - describe "CouchRest Extension" do
11   -
12   - it "should have created a checksum! method" do
13   - ::CouchRest::Design.new.should respond_to(:checksum!)
14   - end
15   -
16   - it "should calculate a consistent checksum for model" do
17   - WithTemplateAndUniqueID.design_doc.checksum!.should eql('caa2b4c27abb82b4e37421de76d96ffc')
18   - end
19   -
20   - it "should calculate checksum for complex model" do
21   - Article.design_doc.checksum!.should eql('70dff8caea143bf40fad09adf0701104')
22   - end
23   -
24   - it "should cache the generated checksum value" do
25   - Article.design_doc.checksum!
26   - Article.design_doc['couchrest-hash'].should_not be_blank
27   - end
28   - end
29   -
30   - describe "class methods" do
31   -
32   - describe ".design_doc" do
33   - it "should provide Design document" do
34   - Article.design_doc.should be_a(::CouchRest::Design)
35   - end
36   - end
37   -
38   - describe ".design_doc_id" do
39   - it "should provide a reasonable id" do
40   - Article.design_doc_id.should eql("_design/Article")
41   - end
42   - end
43   -
44   - describe ".design_doc_slug" do
45   - it "should provide slug part of design doc" do
46   - Article.design_doc_slug.should eql('Article')
47   - end
48   - end
49   -
50   - describe ".design_doc_uri" do
51   - it "should provide complete url" do
52   - Article.design_doc_uri.should eql("#{COUCHHOST}/#{TESTDB}/_design/Article")
53   - end
54   - it "should provide complete url for new DB" do
55   - db = mock("Database")
56   - db.should_receive(:root).and_return('db')
57   - Article.design_doc_uri(db).should eql("db/_design/Article")
58   - end
59   - end
60   -
61   - describe ".stored_design_doc" do
62   - it "should load a stored design from the database" do
63   - Article.by_date
64   - Article.stored_design_doc['_rev'].should_not be_blank
65   - end
66   - it "should return nil if not already stored" do
67   - WithDefaultValues.stored_design_doc.should be_nil
68   - end
69   - end
70   -
71   - describe ".save_design_doc" do
72   - it "should call up the design updater" do
73   - Article.should_receive(:update_design_doc).with('db', false)
74   - Article.save_design_doc('db')
75   - end
76   - end
77   -
78   - describe ".save_design_doc!" do
79   - it "should call save_design_doc with force" do
80   - Article.should_receive(:save_design_doc).with('db', true)
81   - Article.save_design_doc!('db')
82   - end
83   - end
84   -
85   - end
86   -
87   - describe "basics" do
88   -
89   - before :all do
90   - reset_test_db!
91   - end
92   -
93   - it "should have been instantiated with views" do
94   - d = Article.design_doc
95   - d['views']['all']['map'].should include('Article')
96   - end
97   -
98   - it "should not have been saved yet" do
99   - lambda { Article.database.get(Article.design_doc.id) }.should raise_error(RestClient::ResourceNotFound)
100   - end
101   -
102   - describe "after requesting a view" do
103   - before :each do
104   - Article.all
105   - end
106   - it "should have saved the design doc after view request" do
107   - Article.database.get(Article.design_doc.id).should_not be_nil
108   - end
109   - end
110   -
111   - describe "model with simple views" do
112   - before(:all) do
113   - Article.all.map{|a| a.destroy(true)}
114   - Article.database.bulk_delete
115   - written_at = Time.now - 24 * 3600 * 7
116   - @titles = ["this and that", "also interesting", "more fun", "some junk"]
117   - @titles.each do |title|
118   - a = Article.new(:title => title)
119   - a.date = written_at
120   - a.save
121   - written_at += 24 * 3600
122   - end
123   - end
124   -
125   - it "will send request for the saved design doc on view request" do
126   - reset_test_db!
127   - Article.should_receive(:stored_design_doc).and_return(nil)
128   - Article.by_date
129   - end
130   -
131   - it "should have generated a design doc" do
132   - Article.design_doc["views"]["by_date"].should_not be_nil
133   - end
134   - it "should save the design doc when view requested" do
135   - Article.by_date
136   - doc = Article.database.get Article.design_doc.id
137   - doc['views']['by_date'].should_not be_nil
138   - end
139   - it "should save design doc if a view changed" do
140   - Article.by_date
141   - orig = Article.stored_design_doc
142   - design = Article.design_doc
143   - view = design['views']['by_date']['map']
144   - design['views']['by_date']['map'] = view + ' ' # little bit of white space
145   - Article.by_date
146   - Article.stored_design_doc['_rev'].should_not eql(orig['_rev'])
147   - orig['views']['by_date']['map'].should_not eql(Article.design_doc['views']['by_date']['map'])
148   - end
149   - it "should not save design doc if not changed" do
150   - Article.by_date
151   - orig = Article.stored_design_doc['_rev']
152   - Article.by_date
153   - Article.stored_design_doc['_rev'].should eql(orig)
154   - end
155   - it "should recreate the design doc if database deleted" do
156   - Article.database.recreate!
157   - lambda { Article.by_date }.should_not raise_error(RestClient::ResourceNotFound)
158   - end
159   - end
160   -
161   - describe "when auto_update_design_doc false" do
162   - # We really do need a new class for each of example. If we try
163   - # to use the same class the examples interact with each in ways
164   - # that can hide failures because the design document gets cached
165   - # at the class level.
166   - let(:model_class) {
167   - model_class = Article.dup
168   - model_class.class_eval do
169   - self.auto_update_design_doc = false
170   - design do
171   - view :by_title
172   - end
173   - end
174   - doc = DB.get("_design/Article")
175   - DB.delete_doc doc if doc
176   - doc = CouchRest::Document.new("_id" => "_design/Article")
177   - doc["language"] = "javascript"
178   - doc["views"] = {"all" => {"map" => "function(doc) { if (doc['type'] == 'Article') { emit(doc['_id'],1); } }"},
179   - "by_title" => {"map" =>
180   - "function(doc) {
181   - if ((doc['type'] == 'Article') && (doc['title'] != null)) {
182   - emit(doc['title'], null);
183   - }", "reduce" => "function(k,v,r) { return sum(v); }"}}
184   - DB.save_doc doc
185   - model_class
186   - }
187   -
188   - it "will not update stored design doc if view changed" do
189   - model_class.by_title # Perform the view
190   - orig = model_class.stored_design_doc
191   - design = model_class.design_doc
192   - view = design['views']['by_title']['map']
193   - design['views']['by_title']['map'] = view + ' '
194   - model_class.by_name
195   - model_class.stored_design_doc['_rev'].should eql(orig['_rev'])
196   - end
197   -
198   - it "will update stored design if forced" do
199   - model_class.by_title
200   - orig = model_class.stored_design_doc
201   - design = model_class.design_doc
202   - view = design['views']['by_title']['map']
203   - design['views']['by_title']['map'] = view + ' '
204   - model_class.save_design_doc!
205   - model_class.stored_design_doc['_rev'].should_not eql(orig['_rev'])
206   - end
207   -
208   - it "is able to use predefined views" do
209   - lambda { model_class.by_title.key("special").all }.should_not raise_error
210   - end
211   - end
212   - end
213   -
214   - describe "lazily refreshing the design document" do
215   - before(:all) do
216   - @db = reset_test_db!
217   - WithTemplateAndUniqueID.new('slug' => '1').save
218   - end
219   - it "should not save the design doc twice" do
220   - WithTemplateAndUniqueID.all
221   - rev = WithTemplateAndUniqueID.design_doc['_rev']
222   - WithTemplateAndUniqueID.design_doc['_rev'].should eql(rev)
223   - end
224   - end
225   -
226   -
227   -end
28 spec/unit/designs/design_spec.rb
@@ -9,6 +9,7 @@
9 9 end
10 10
11 11 class DesignSampleModel < CouchRest::Model::Base
  12 + use_database DB
12 13 property :name
13 14 property :surname
14 15 design do
@@ -77,11 +78,16 @@ class DesignSampleModel < CouchRest::Model::Base
77 78 describe "with real model" do
78 79
79 80 before :all do
  81 + reset_test_db!
80 82 @mod = DesignSampleModel
81 83 @doc = @mod.design_doc
82 84 @db = @mod.database
83 85 end
84 86
  87 + it "should not have been saved up until sync called" do
  88 + lambda { @mod.database.get(@doc['_id']) }.should raise_error(RestClient::ResourceNotFound)
  89 + end
  90 +
85 91 it "should save a non existant design" do
86 92 begin
87 93 doc = @db.get(@doc['_id'])
@@ -129,6 +135,13 @@ class DesignSampleModel < CouchRest::Model::Base
129 135 @doc['views'].should_not have_key('test')
130 136 end
131 137
  138 + it "should be re-created if database destroyed" do
  139 + @doc.sync # saved
  140 + reset_test_db!
  141 + @db.should_receive(:save_doc).with(@doc)
  142 + @doc.sync
  143 + end
  144 +
132 145 end
133 146
134 147 end
@@ -167,6 +180,21 @@ class DesignSampleModel < CouchRest::Model::Base
167 180 end
168 181 end
169 182
  183 +
  184 + describe "#uri" do
  185 + it "should provide complete url" do
  186 + @doc = DesignSampleModel.design_doc
  187 + @doc.uri.should eql("#{DesignSampleModel.database.root}/_design/DesignSampleModel")
  188 + end
  189 + end
  190 +
  191 + describe "#view_names" do
  192 + it "should provide a list of all the views available" do
  193 + @doc = DesignSampleModel.design_doc
  194 + @doc.view_names.should eql(['by_name', 'all'])
  195 + end
  196 + end
  197 +
170 198 end
171 199
172 200
214 spec/unit/designs_spec.rb
... ... @@ -1,6 +1,7 @@
1 1 require "spec_helper"
2 2
3 3 class DesignModel < CouchRest::Model::Base
  4 + use_database DB
4 5 end
5 6
6 7 describe CouchRest::Model::Designs do
@@ -62,7 +63,7 @@ class DesignModel < CouchRest::Model::Base
62 63 @klass = CouchRest::Model::Designs::DesignMapper
63 64 end
64 65
65   - describe 'initialize with prefix' do
  66 + describe 'initialize without prefix' do
66 67
67 68 before :all do
68 69 @object = @klass.new(DesignModel)
@@ -74,8 +75,8 @@ class DesignModel < CouchRest::Model::Base
74 75 @object.send(:method).should eql('design_doc')
75 76 end
76 77
77   - it "should create an all method" do
78   - @object.model.should respond_to('all')
  78 + it "should add design doc to list" do
  79 + @object.model.design_docs.should include(@object.model.design_doc)
79 80 end
80 81
81 82 it "should create a design doc method" do
@@ -100,8 +101,12 @@ class DesignModel < CouchRest::Model::Base
100 101 @object.send(:method).should eql('stats_design_doc')
101 102 end
102 103
  104 + it "should add design doc to list" do
  105 + @object.model.design_docs.should include(@object.model.stats_design_doc)
  106 + end
  107 +
103 108 it "should not create an all method" do
104   - @object.model.should respond_to('all')
  109 + @object.model.should_not respond_to('all')
105 110 end
106 111
107 112 it "should create a design doc method" do
@@ -141,18 +146,18 @@ class DesignModel < CouchRest::Model::Base
141 146 end
142 147
143 148 it "should call create method on view" do
144   - CouchRest::Model::Designs::View.should_receive(:create).with(DesignModel, @object.design_doc, 'test', {})
  149 + CouchRest::Model::Designs::View.should_receive(:define).with(DesignModel, @object.design_doc, 'test', {})
145 150 @object.view('test')
146 151 end
147 152
148 153 it "should create a method on parent model" do
149   - CouchRest::Model::Designs::View.stub!(:create)
  154 + CouchRest::Model::Designs::View.stub!(:define)
150 155 @object.view('test_view')
151 156 DesignModel.should respond_to(:test_view)
152 157 end
153 158
154 159 it "should create a method for view instance" do
155   - CouchRest::Model::Designs::View.stub!(:create)
  160 + CouchRest::Model::Designs::View.stub!(:define)
156 161 @object.should_receive(:create_view_method).with('test')
157 162 @object.view('test')
158 163 end
@@ -178,7 +183,7 @@ class DesignModel < CouchRest::Model::Base
178 183 end
179 184
180 185 it "should create a method that returns view instance" do
181   - @object.design_doc.should_receive(:view).with({}, 'test_view').and_return(nil)
  186 + @object.design_doc.should_receive(:view).with('test_view', {}).and_return(nil)
182 187 @object.send(:create_view_method, 'test_view')
183 188 DesignModel.test_view
184 189 end
@@ -187,7 +192,7 @@ class DesignModel < CouchRest::Model::Base
187 192 view = mock("View")
188 193 view.stub(:key).and_return(view)
189 194 view.stub(:first).and_return(true)
190   - @object.design_doc.should_receive(:view).with({}, 'by_test_view').and_return(view)
  195 + @object.design_doc.should_receive(:view).with('by_test_view', {}).and_return(view)
191 196 @object.send(:create_view_method, 'by_test_view')
192 197 lambda { DesignModel.find_by_test_view('test').should be_true }.should_not raise_error
193 198 end
@@ -196,4 +201,195 @@ class DesignModel < CouchRest::Model::Base
196 201
197 202 end
198 203
  204 +
  205 + class DesignsNoAutoUpdate < CouchRest::Model::Base
  206 + use_database DB
  207 + property :title, String
  208 + design do
  209 + disable_auto_update
  210 + view :by_title_fail, :by => ['title']
  211 + view :by_title, :reduce => true
  212 + end
  213 + end
  214 +
  215 + describe "Scenario testing" do
  216 +
  217 + describe "with auto update disabled" do
  218 +
  219 + before :all do
  220 + reset_test_db!
  221 + @mod = DesignsNoAutoUpdate
  222 + end
  223 +
  224 + before(:all) do
  225 + id = @mod.to_s
  226 + doc = CouchRest::Document.new("_id" => "_design/#{id}")
  227 + doc["language"] = "javascript"
  228 + doc["views"] = {"all" => {"map" => "function(doc) { if (doc['type'] == '#{id}') { emit(doc['_id'],1); } }"},
  229 + "by_title" => {"map" =>
  230 + "function(doc) {
  231 + if ((doc['type'] == '#{id}') && (doc['title'] != null)) {
  232 + emit(doc['title'], 1);
  233 + }
  234 + }", "reduce" => "function(k,v,r) { return sum(v); }"}}
  235 + DB.save_doc doc
  236 + end
  237 +
  238 + it "will fail if reduce is not specific in view" do
  239 + @mod.create(:title => 'This is a test')
  240 + lambda { @mod.by_title_fail.first }.should raise_error(RestClient::ResourceNotFound)
  241 + end
  242 +
  243 + it "will perform view request" do
  244 + @mod.create(:title => 'This is a test')
  245 + @mod.by_title.first.title.should eql("This is a test")
  246 + end
  247 +
  248 + end
  249 +
  250 + describe "using views" do
  251 +
  252 + describe "to find a single item" do
  253 +
  254 + before(:all) do
  255 + reset_test_db!
  256 + %w{aaa bbb ddd eee}.each do |title|
  257 + Course.new(:title => title, :active => (title == 'bbb')).save
  258 + end
  259 + end
  260 +
  261 + it "should return single matched record with find helper" do
  262 + course = Course.find_by_title('bbb')
  263 + course.should_not be_nil
  264 + course.title.should eql('bbb') # Ensure really is a Course!
  265 + end
  266 +
  267 + it "should return nil if not found" do
  268 + course = Course.find_by_title('fff')
  269 + course.should be_nil
  270 + end
  271 +
  272 + it "should peform search on view with two properties" do
  273 + course = Course.find_by_title_and_active(['bbb', true])
  274 + course.should_not be_nil
  275 + course.title.should eql('bbb') # Ensure really is a Course!
  276 + end
  277 +
  278 + it "should return nil if not found" do
  279 + course = Course.find_by_title_and_active(['bbb', false])
  280 + course.should be_nil
  281 + end
  282 +
  283 + it "should raise exception if view not present" do
  284 + lambda { Course.find_by_foobar('123') }.should raise_error(NoMethodError)
  285 + end
  286 +
  287 + end
  288 +
  289 + describe "a model class with database provided manually" do
  290 +
  291 + class Unattached < CouchRest::Model::Base
  292 + property :title
  293 + property :questions
  294 + property :professor
  295 + design do
  296 + view :by_title
  297 + end
  298 +
  299 + # Force the database to always be nil
  300 + def self.database
  301 + nil
  302 + end
  303 + end
  304 +
  305 + before(:all) do
  306 + reset_test_db!
  307 + @db = DB
  308 + %w{aaa bbb ddd eee}.each do |title|
  309 + u = Unattached.new(:title => title)
  310 + u.database = @db
  311 + u.save
  312 + @first_id ||= u.id
  313 + end
  314 + end
  315 + it "should barf on all if no database given" do
  316 + lambda{Unattached.all.first}.should raise_error
  317 + end
  318 + it "should query all" do
  319 + rs = Unattached.all.database(@db).all
  320 + rs.length.should == 4
  321 + end
  322 + it "should barf on query if no database given" do
  323 + lambda{Unattached.by_title.all}.should raise_error /Database must be defined/
  324 + end
  325 + it "should make the design doc upon first query" do
  326 + Unattached.by_title.database(@db)
  327 + doc = Unattached.design_doc
  328 + doc['views']['all']['map'].should include('Unattached')
  329 + end
  330 + it "should merge query params" do
  331 + rs = Unattached.by_title.database(@db).startkey("bbb").endkey("eee")
  332 + rs.length.should == 3
  333 + end
  334 + it "should return nil on get if no database given" do
  335 + Unattached.get("aaa").should be_nil
  336 + end
  337 + it "should barf on get! if no database given" do
  338 + lambda{Unattached.get!("aaa")}.should raise_error
  339 + end
  340 + it "should get from specific database" do
  341 + u = Unattached.get(@first_id, @db)
  342 + u.title.should == "aaa"
  343 + end
  344 + it "should barf on first if no database given" do
  345 + lambda{Unattached.first}.should raise_error
  346 + end
  347 + it "should get first" do
  348 + u = Unattached.all.database(@db).first
  349 + u.title.should =~ /\A...\z/
  350 + end
  351 + it "should get last" do
  352 + u = Unattached.all.database(@db).last
  353 + u.title.should == "aaa"
  354 + end
  355 +
  356 + end
  357 +
  358 + describe "a model with a compound key view" do
  359 + before(:all) do
  360 + reset_test_db!
  361 + written_at = Time.now - 24 * 3600 * 7
  362 + @titles = ["uniq one", "even more interesting", "less fun", "not junk"]
  363 + @user_ids = ["quentin", "aaron"]
  364 + @titles.each_with_index do |title,i|
  365 + u = i % 2
  366 + a = Article.new(:title => title, :user_id => @user_ids[u])
  367 + a.date = written_at
  368 + a.save
  369 + written_at += 24 * 3600
  370 + end
  371 + end
  372 + it "should create the design doc" do
  373 + Article.by_user_id_and_date rescue nil
  374 + doc = Article.design_doc
  375 + doc['views']['by_date'].should_not be_nil
  376 + end
  377 + it "should sort correctly" do
  378 + articles = Article.by_user_id_and_date.all
  379 + articles.collect{|a|a['user_id']}.should == ['aaron', 'aaron', 'quentin',
  380 + 'quentin']
  381 + articles[1].title.should == 'not junk'
  382 + end
  383 + it "should be queryable with couchrest options" do
  384 + articles = Article.by_user_id_and_date(:limit => 1, :startkey => 'quentin').all
  385 + articles.length.should == 1
  386 + articles[0].title.should == "even more interesting"
  387 + end
  388 + end
  389 +
  390 +
  391 + end
  392 +
  393 + end
  394 +
199 395 end
367 spec/unit/view_spec.rb
... ... @@ -1,367 +0,0 @@
1   -require "spec_helper"
2   -
3   -describe CouchRest::Model::Views do
4   -
5   - class Unattached < CouchRest::Model::Base
6   - property :title
7   - property :questions
8   - property :professor
9   - view_by :title
10   -
11   - # Force the database to always be nil
12   - def self.database
13   - nil
14   - end
15   - end
16   -
17   - describe "ClassMethods" do
18   - # NOTE! Add more unit tests!
19   -
20   - describe "#view" do
21   -
22   - it "should not alter original query" do
23   - options = { :database => DB }
24   - view = Article.view('by_date', options)
25   - options[:database].should eql(DB)
26   - end
27   -
28   - end
29   -
30   - describe "#has_view?" do
31   - it "should check the design doc" do
32   - Article.design_doc.should_receive(:has_view?).with(:test).and_return(true)
33   - Article.has_view?(:test).should be_true
34   - end
35   - end
36   -
37   - describe "#can_reduce_view?" do
38   - it "should check if view has a reduce method" do
39   - Article.design_doc.should_receive(:can_reduce_view?).with(:test).and_return(true)
40   - Article.can_reduce_view?(:test).should be_true
41   - end
42   - end
43   - end
44   -
45   - describe "a model with simple views and a default param" do
46   - before(:all) do
47   - Article.all.map{|a| a.destroy(true)}
48   - Article.database.bulk_delete
49   - written_at = Time.now - 24 * 3600 * 7
50   - @titles = ["this and that", "also interesting", "more fun", "some junk"]
51   - @titles.each do |title|
52   - a = Article.new(:title => title)
53   - a.date = written_at
54   - a.save
55   - written_at += 24 * 3600
56   - end
57   - end
58   - it "should return the matching raw view result" do
59   - view = Article.by_date :raw => true
60   - view['rows'].length.should == 4
61   - end
62   - it "should not include non-Articles" do
63   - Article.database.save_doc({"date" => 1})
64   - view = Article.by_date :raw => true
65   - view['rows'].length.should == 4
66   - end
67   - it "should return the matching objects (with default argument :descending => true)" do
68   - articles = Article.by_date
69   - articles.collect{|a|a.title}.should == @titles.reverse
70   - end
71   - it "should allow you to override default args" do
72   - articles = Article.by_date :descending => false
73   - articles.collect{|a|a.title}.should == @titles
74   - end
75   - it "should allow you to create a new view on the fly" do
76   - lambda{Article.by_title}.should raise_error
77   - Article.view_by :title
78   - lambda{Article.by_title}.should_not raise_error
79   - end
80   - end
81   -
82   - describe "another model with a simple view" do
83   - before(:all) do
84   - reset_test_db!
85   - %w{aaa bbb ddd eee}.each do |title|
86   - Course.new(:title => title).save
87   - end
88   - end
89   - it "should make the design doc upon first query" do
90   - Course.by_title
91   - doc = Course.design_doc
92   - doc['views']['all']['map'].should include('Course')
93   - end
94   - it "should can query via view" do
95   - # register methods with method-missing, for local dispatch. method
96   - # missing lookup table, no heuristics.
97   - view = Course.view :by_title
98   - designed = Course.by_title
99   - view.should == designed
100   - end
101   - it "should get them" do
102   - rs = Course.by_title
103   - rs.length.should == 4
104   - end
105   - it "should yield" do
106   - courses = []
107   - Course.view(:by_title) do |course|
108   - courses << course
109   - end
110   - courses[0]["doc"]["title"].should =='aaa'
111   - end
112   - it "should yield with by_key method" do
113   - courses = []
114   - Course.by_title do |course|
115   - courses << course
116   - end
117   - courses[0]["doc"]["title"].should =='aaa'
118   - end
119   - end
120   -
121   - describe "find a single item using a view" do
122   - before(:all) do
123   - reset_test_db!
124   - %w{aaa bbb ddd eee}.each do |title|
125   - Course.new(:title => title, :active => (title == 'bbb')).save
126   - end
127   - end
128   -
129   - it "should return single matched record with find helper" do
130   - course = Course.find_by_title('bbb')
131   - course.should_not be_nil
132   - course.title.should eql('bbb') # Ensure really is a Course!
133   - end
134   -
135   - it "should return nil if not found" do
136   - course = Course.find_by_title('fff')
137   - course.should be_nil
138   - end
139   -
140   - it "should peform search on view with two properties" do
141   - course = Course.find_by_title_and_active(['bbb', true])
142   - course.should_not be_nil
143   - course.title.should eql('bbb') # Ensure really is a Course!
144   - end
145   -
146   - it "should return nil if not found" do
147   - course = Course.find_by_title_and_active(['bbb', false])
148   - course.should be_nil
149   - end
150   -
151   - it "should raise exception if view not present" do
152   - lambda { Course.find_by_foobar('123') }.should raise_error(NoMethodError)
153   - end
154   -
155   - it "should perform a search directly with specific key" do
156   - course = Course.first_from_view('by_title', 'bbb')
157   - course.title.should eql('bbb')
158   - end
159   -
160   - it "should perform a search directly with specific key with options" do
161   - course = Course.first_from_view('by_title', 'bbb', :reverse => true)
162   - course.title.should eql('bbb')
163   - end
164   -
165   - it "should perform a search directly with range" do
166   - course = Course.first_from_view('by_title', :startkey => 'bbb', :endkey => 'eee')
167   - course.title.should eql('bbb')
168   - end
169   -
170   - it "should perform a search for first when reduce method present" do
171   - course = Course.first_from_view('by_active')
172   - course.should_not be_nil
173   - end
174   -
175   - end
176   -
177   - describe "#method_missing for find_by methods" do
178   - before(:all) { reset_test_db! }
179   -
180   - specify { Course.should respond_to :find_by_title_and_active }
181   - specify { Course.should respond_to :by_title }
182   -
183   - specify "#method should work in ruby 1.9, but not 1.8" do
184   - if RUBY_VERSION >= "1.9"
185   - Course.method(:find_by_title_and_active).should be_a Method
186   - else
187   - expect { Course.method(:find_by_title_and_active) }.to raise_error(NameError)
188   - end
189   - end
190   - end
191   -
192   - describe "a ducktype view" do
193   - before(:all) do
194   - reset_test_db!
195   - @id = DB.save_doc({:dept => true})['id']
196   - end
197   - it "should setup" do
198   - duck = Course.get(@id) # from a different db
199   - duck["dept"].should == true
200   - end
201   - it "should make the design doc" do
202   - @as = Course.by_dept
203   - @doc = Course.design_doc
204   - @doc["views"]["by_dept"]["map"].should_not include("couchrest")
205   - end
206   - it "should not look for class" do
207   - @as = Course.by_dept
208   - @as[0]['_id'].should == @id
209   - end
210   - end
211   -
212   - describe "a model class with database provided manually" do
213   - before(:all) do
214   - reset_test_db!
215   - @db = DB
216   - %w{aaa bbb ddd eee}.each do |title|
217   - u = Unattached.new(:title => title)
218   - u.database = @db
219   - u.save
220   - @first_id ||= u.id
221   - end
222   - end
223   - it "should barf on all if no database given" do
224   - lambda{Unattached.all}.should raise_error
225   - end
226   - it "should query all" do
227   - rs = Unattached.all :database => @db
228   - rs.length.should == 4
229   - end
230   - it "should barf on query if no database given" do
231   - lambda{Unattached.view :by_title}.should raise_error
232   - end
233   - it "should make the design doc upon first query" do
234   - Unattached.by_title :database => @db
235   - doc = Unattached.design_doc
236   - doc['views']['all']['map'].should include('Unattached')
237   - end
238   - it "should merge query params" do
239   - rs = Unattached.by_title :database=>@db, :startkey=>"bbb", :endkey=>"eee"
240   - rs.length.should == 3
241   - end
242   - it "should query via view" do
243   - view = Unattached.view :by_title, :database=>@db
244   - designed = Unattached.by_title :database=>@db
245   - view.should == designed
246   - end
247   - it "should yield" do
248   - things = []
249   - Unattached.view(:by_title, :database=>@db) do |thing|
250   - things << thing
251   - end
252   - things[0]["doc"]["title"].should =='aaa'
253   - end
254   - it "should yield with by_key method" do
255   - things = []
256   - Unattached.by_title(:database=>@db) do |thing|
257   - things << thing
258   - end
259   - things[0]["doc"]["title"].should =='aaa'
260   - end
261   - it "should return nil on get if no database given" do
262   - Unattached.get("aaa").should be_nil
263   - end
264   - it "should barf on get! if no database given" do
265   - lambda{Unattached.get!("aaa")}.should raise_error
266   - end
267   - it "should get from specific database" do
268   - u = Unattached.get(@first_id, @db)
269   - u.title.should == "aaa"
270   - end
271   - it "should barf on first if no database given" do
272   - lambda{Unattached.first}.should raise_error
273   - end
274   - it "should get first" do
275   - u = Unattached.first :database=>@db
276   - u.title.should =~ /\A...\z/
277   - end
278   -
279   - it "should get last" do
280   - u = Unattached.last :database=>@db
281   - u.title.should == "aaa"
282   - end
283   -
284   - end
285   -
286   - describe "a model with a compound key view" do
287   - before(:all) do
288   - Article.by_user_id_and_date.each{|a| a.destroy(true)}
289   - Article.database.bulk_delete
290   - written_at = Time.now - 24 * 3600 * 7
291   - @titles = ["uniq one", "even more interesting", "less fun", "not junk"]
292   - @user_ids = ["quentin", "aaron"]
293   - @titles.each_with_index do |title,i|
294   - u = i % 2
295   - a = Article.new(:title => title, :user_id => @user_ids[u])
296   - a.date = written_at
297   - a.save
298   - written_at += 24 * 3600
299   - end
300   - end
301   - it "should create the design doc" do
302   - Article.by_user_id_and_date rescue nil
303   - doc = Article.design_doc
304   - doc['views']['by_date'].should_not be_nil
305   - end
306   - it "should sort correctly" do
307   - articles = Article.by_user_id_and_date
308   - articles.collect{|a|a['user_id']}.should == ['aaron', 'aaron', 'quentin',
309   - 'quentin']
310   - articles[1].title.should == 'not junk'
311   - end
312   - it "should be queryable with couchrest options" do
313   - articles = Article.by_user_id_and_date :limit => 1, :startkey => 'quentin'
314   - articles.length.should == 1
315   - articles[0].title.should == "even more interesting"
316   - end
317   - end
318   -
319   - describe "with a custom view" do
320   - before(:all) do
321   - @titles = ["very uniq one", "even less interesting", "some fun",
322   - "really junk", "crazy bob"]
323   - @tags = ["cool", "lame"]
324   - @titles.each_with_index do |title,i|
325   - u = i % 2
326   - a = Article.new(:title => title, :tags => [@tags[u]])
327   - a.save
328   - end
329   - end
330   - it "should be available raw" do
331   - view = Article.by_tags :raw => true
332   - view['rows'].length.should == 5
333   - end
334   -
335   - it "should be default to :reduce => false" do
336   - ars = Article.by_tags
337   - ars.first.tags.first.should == 'cool'
338   - end
339   -
340   - it "should be raw when reduce is true" do
341   - view = Article.by_tags :reduce => true, :group => true
342   - view['rows'].find{|r|r['key'] == 'cool'}['value'].should == 3
343   - end
344   - end
345   -
346   - # TODO: moved to Design, delete
347   - describe "adding a view" do
348   - before(:each) do
349   - reset_test_db!
350   - Article.by_date
351   - @original_doc_rev = Article.stored_design_doc['_rev']
352   - @design_docs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
353   - end
354   - it "should not create a design doc on view definition" do
355   - Article.view_by :created_at
356   - newdocs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
357   - newdocs["rows"].length.should == @design_docs["rows"].length
358   - end
359   - it "should create a new version of the design document on view access" do
360   - Article.view_by :updated_at
361   - Article.by_updated_at
362   - @original_doc_rev.should_not == Article.stored_design_doc['_rev']
363   - Article.design_doc["views"].keys.should include("by_updated_at")
364   - end
365   - end
366   -
367   -end

0 comments on commit 88def18

Please sign in to comment.
Something went wrong with that request. Please try again.