mattetti-couchrest 0.2.1.0 → 0.13
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +6 -31
- data/Rakefile +1 -1
- data/examples/model/example.rb +13 -19
- data/lib/couchrest/core/database.rb +4 -3
- data/lib/couchrest/core/model.rb +615 -0
- data/lib/couchrest/core/response.rb +4 -5
- data/lib/couchrest/core/server.rb +1 -1
- data/lib/couchrest/mixins/callbacks.rb +33 -74
- data/lib/couchrest/mixins/design_doc.rb +1 -2
- data/lib/couchrest/mixins/document_queries.rb +1 -1
- data/lib/couchrest/mixins/extended_document_mixins.rb +1 -2
- data/lib/couchrest/mixins/properties.rb +3 -8
- data/lib/couchrest/mixins/validation.rb +1 -2
- data/lib/couchrest/mixins/views.rb +5 -5
- data/lib/couchrest/monkeypatches.rb +48 -65
- data/lib/couchrest/more/extended_document.rb +8 -75
- data/lib/couchrest/more/property.rb +1 -12
- data/lib/couchrest/support/class.rb +132 -148
- data/lib/couchrest.rb +8 -33
- data/spec/couchrest/core/database_spec.rb +23 -28
- data/spec/couchrest/core/model_spec.rb +856 -0
- data/spec/couchrest/more/casted_model_spec.rb +0 -14
- data/spec/couchrest/more/extended_doc_spec.rb +4 -450
- data/spec/couchrest/more/property_spec.rb +5 -16
- data/spec/spec_helper.rb +0 -4
- metadata +3 -16
- data/lib/couchrest/mixins/extended_attachments.rb +0 -68
- data/lib/couchrest/validation/validators/confirmation_validator.rb +0 -99
- data/spec/couchrest/more/casted_extended_doc_spec.rb +0 -40
- data/spec/couchrest/more/extended_doc_attachment_spec.rb +0 -129
- data/spec/couchrest/more/extended_doc_view_spec.rb +0 -206
- data/spec/couchrest/support/class_spec.rb +0 -59
- data/spec/fixtures/more/article.rb +0 -34
- data/spec/fixtures/more/course.rb +0 -14
- data/spec/fixtures/more/event.rb +0 -6
- data/spec/fixtures/more/person.rb +0 -8
- data/spec/fixtures/more/question.rb +0 -6
@@ -1,68 +0,0 @@
|
|
1
|
-
module CouchRest
|
2
|
-
module Mixins
|
3
|
-
module ExtendedAttachments
|
4
|
-
|
5
|
-
# creates a file attachment to the current doc
|
6
|
-
def create_attachment(args={})
|
7
|
-
raise ArgumentError unless args[:file] && args[:name]
|
8
|
-
return if has_attachment?(args[:name])
|
9
|
-
self['_attachments'] ||= {}
|
10
|
-
set_attachment_attr(args)
|
11
|
-
rescue ArgumentError => e
|
12
|
-
raise ArgumentError, 'You must specify :file and :name'
|
13
|
-
end
|
14
|
-
|
15
|
-
# reads the data from an attachment
|
16
|
-
def read_attachment(attachment_name)
|
17
|
-
Base64.decode64(database.fetch_attachment(self, attachment_name))
|
18
|
-
end
|
19
|
-
|
20
|
-
# modifies a file attachment on the current doc
|
21
|
-
def update_attachment(args={})
|
22
|
-
raise ArgumentError unless args[:file] && args[:name]
|
23
|
-
return unless has_attachment?(args[:name])
|
24
|
-
delete_attachment(args[:name])
|
25
|
-
set_attachment_attr(args)
|
26
|
-
rescue ArgumentError => e
|
27
|
-
raise ArgumentError, 'You must specify :file and :name'
|
28
|
-
end
|
29
|
-
|
30
|
-
# deletes a file attachment from the current doc
|
31
|
-
def delete_attachment(attachment_name)
|
32
|
-
return unless self['_attachments']
|
33
|
-
self['_attachments'].delete attachment_name
|
34
|
-
end
|
35
|
-
|
36
|
-
# returns true if attachment_name exists
|
37
|
-
def has_attachment?(attachment_name)
|
38
|
-
!!(self['_attachments'] && self['_attachments'][attachment_name] && !self['_attachments'][attachment_name].empty?)
|
39
|
-
end
|
40
|
-
|
41
|
-
# returns URL to fetch the attachment from
|
42
|
-
def attachment_url(attachment_name)
|
43
|
-
return unless has_attachment?(attachment_name)
|
44
|
-
"#{database.root}/#{self.id}/#{attachment_name}"
|
45
|
-
end
|
46
|
-
|
47
|
-
private
|
48
|
-
|
49
|
-
def encode_attachment(data)
|
50
|
-
::Base64.encode64(data).gsub(/\r|\n/,'')
|
51
|
-
end
|
52
|
-
|
53
|
-
def get_mime_type(file)
|
54
|
-
::MIME::Types.type_for(file.path).empty? ?
|
55
|
-
'text\/plain' : MIME::Types.type_for(file.path).first.content_type.gsub(/\//,'\/')
|
56
|
-
end
|
57
|
-
|
58
|
-
def set_attachment_attr(args)
|
59
|
-
content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file])
|
60
|
-
self['_attachments'][args[:name]] = {
|
61
|
-
'content-type' => content_type,
|
62
|
-
'data' => encode_attachment(args[:file].read)
|
63
|
-
}
|
64
|
-
end
|
65
|
-
|
66
|
-
end # module ExtendedAttachments
|
67
|
-
end
|
68
|
-
end
|
@@ -1,99 +0,0 @@
|
|
1
|
-
# Extracted from dm-validations 0.9.10
|
2
|
-
#
|
3
|
-
# Copyright (c) 2007 Guy van den Berg
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining
|
6
|
-
# a copy of this software and associated documentation files (the
|
7
|
-
# "Software"), to deal in the Software without restriction, including
|
8
|
-
# without limitation the rights to use, copy, modify, merge, publish,
|
9
|
-
# distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
-
# permit persons to whom the Software is furnished to do so, subject to
|
11
|
-
# the following conditions:
|
12
|
-
#
|
13
|
-
# The above copyright notice and this permission notice shall be
|
14
|
-
# included in all copies or substantial portions of the Software.
|
15
|
-
#
|
16
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
-
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
-
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
-
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
-
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
-
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
-
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
23
|
-
|
24
|
-
module CouchRest
|
25
|
-
module Validation
|
26
|
-
|
27
|
-
##
|
28
|
-
#
|
29
|
-
# @author Guy van den Berg
|
30
|
-
# @since 0.9
|
31
|
-
class ConfirmationValidator < GenericValidator
|
32
|
-
|
33
|
-
def initialize(field_name, options = {})
|
34
|
-
super
|
35
|
-
@options = options
|
36
|
-
@field_name, @confirm_field_name = field_name, (options[:confirm] || "#{field_name}_confirmation").to_sym
|
37
|
-
@options[:allow_nil] = true unless @options.has_key?(:allow_nil)
|
38
|
-
end
|
39
|
-
|
40
|
-
def call(target)
|
41
|
-
unless valid?(target)
|
42
|
-
error_message = @options[:message] || ValidationErrors.default_error_message(:confirmation, field_name)
|
43
|
-
add_error(target, error_message, field_name)
|
44
|
-
return false
|
45
|
-
end
|
46
|
-
|
47
|
-
return true
|
48
|
-
end
|
49
|
-
|
50
|
-
def valid?(target)
|
51
|
-
field_value = target.send(field_name)
|
52
|
-
return true if @options[:allow_nil] && field_value.nil?
|
53
|
-
return false if !@options[:allow_nil] && field_value.nil?
|
54
|
-
|
55
|
-
confirm_value = target.instance_variable_get("@#{@confirm_field_name}")
|
56
|
-
field_value == confirm_value
|
57
|
-
end
|
58
|
-
|
59
|
-
end # class ConfirmationValidator
|
60
|
-
|
61
|
-
module ValidatesIsConfirmed
|
62
|
-
|
63
|
-
##
|
64
|
-
# Validates that the given attribute is confirmed by another attribute.
|
65
|
-
# A common use case scenario is when you require a user to confirm their
|
66
|
-
# password, for which you use both password and password_confirmation
|
67
|
-
# attributes.
|
68
|
-
#
|
69
|
-
# @option :allow_nil<Boolean> true/false (default is true)
|
70
|
-
# @option :confirm<Symbol> the attribute that you want to validate
|
71
|
-
# against (default is firstattr_confirmation)
|
72
|
-
#
|
73
|
-
# @example [Usage]
|
74
|
-
#
|
75
|
-
# class Page < Hash
|
76
|
-
# include CouchRest::ExtendedModel
|
77
|
-
# include CouchRest::Validations
|
78
|
-
#
|
79
|
-
# property :password, String
|
80
|
-
# property :email, String
|
81
|
-
# attr_accessor :password_confirmation
|
82
|
-
# attr_accessor :email_repeated
|
83
|
-
#
|
84
|
-
# validates_is_confirmed :password
|
85
|
-
# validates_is_confirmed :email, :confirm => :email_repeated
|
86
|
-
#
|
87
|
-
# # a call to valid? will return false unless:
|
88
|
-
# # password == password_confirmation
|
89
|
-
# # and
|
90
|
-
# # email == email_repeated
|
91
|
-
#
|
92
|
-
def validates_is_confirmed(*fields)
|
93
|
-
opts = opts_from_validator_args(fields)
|
94
|
-
add_validator_to_context(opts, fields, CouchRest::Validation::ConfirmationValidator)
|
95
|
-
end
|
96
|
-
|
97
|
-
end # module ValidatesIsConfirmed
|
98
|
-
end # module Validation
|
99
|
-
end # module CouchRest
|
@@ -1,40 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
2
|
-
require File.join(FIXTURE_PATH, 'more', 'card')
|
3
|
-
|
4
|
-
class Car < CouchRest::ExtendedDocument
|
5
|
-
use_database TEST_SERVER.default_database
|
6
|
-
|
7
|
-
property :name
|
8
|
-
property :driver, :cast_as => 'Driver'
|
9
|
-
end
|
10
|
-
|
11
|
-
class Driver < CouchRest::ExtendedDocument
|
12
|
-
use_database TEST_SERVER.default_database
|
13
|
-
# You have to add a casted_by accessor if you want to reach a casted extended doc parent
|
14
|
-
attr_accessor :casted_by
|
15
|
-
|
16
|
-
property :name
|
17
|
-
end
|
18
|
-
|
19
|
-
describe "casting an extended document" do
|
20
|
-
|
21
|
-
before(:each) do
|
22
|
-
@car = Car.new(:name => 'Renault 306')
|
23
|
-
@driver = Driver.new(:name => 'Matt')
|
24
|
-
end
|
25
|
-
|
26
|
-
# it "should not create an empty casted object" do
|
27
|
-
# @car.driver.should be_nil
|
28
|
-
# end
|
29
|
-
|
30
|
-
it "should let you assign the casted attribute after instantializing an object" do
|
31
|
-
@car.driver = @driver
|
32
|
-
@car.driver.name.should == 'Matt'
|
33
|
-
end
|
34
|
-
|
35
|
-
it "should let the casted document who casted it" do
|
36
|
-
Car.new(:name => 'Renault 306', :driver => @driver)
|
37
|
-
@car.driver.casted_by.should == @car
|
38
|
-
end
|
39
|
-
|
40
|
-
end
|
@@ -1,129 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
-
|
3
|
-
describe "ExtendedDocument attachments" do
|
4
|
-
|
5
|
-
describe "#has_attachment?" do
|
6
|
-
before(:each) do
|
7
|
-
@obj = Basic.new
|
8
|
-
@obj.save.should == true
|
9
|
-
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
10
|
-
@attachment_name = 'my_attachment'
|
11
|
-
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
12
|
-
end
|
13
|
-
|
14
|
-
it 'should return false if there is no attachment' do
|
15
|
-
@obj.has_attachment?('bogus').should be_false
|
16
|
-
end
|
17
|
-
|
18
|
-
it 'should return true if there is an attachment' do
|
19
|
-
@obj.has_attachment?(@attachment_name).should be_true
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'should return true if an object with an attachment is reloaded' do
|
23
|
-
@obj.save.should be_true
|
24
|
-
reloaded_obj = Basic.get(@obj.id)
|
25
|
-
reloaded_obj.has_attachment?(@attachment_name).should be_true
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'should return false if an attachment has been removed' do
|
29
|
-
@obj.delete_attachment(@attachment_name)
|
30
|
-
@obj.has_attachment?(@attachment_name).should be_false
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
describe "creating an attachment" do
|
35
|
-
before(:each) do
|
36
|
-
@obj = Basic.new
|
37
|
-
@obj.save.should == true
|
38
|
-
@file_ext = File.open(FIXTURE_PATH + '/attachments/test.html')
|
39
|
-
@file_no_ext = File.open(FIXTURE_PATH + '/attachments/README')
|
40
|
-
@attachment_name = 'my_attachment'
|
41
|
-
@content_type = 'media/mp3'
|
42
|
-
end
|
43
|
-
|
44
|
-
it "should create an attachment from file with an extension" do
|
45
|
-
@obj.create_attachment(:file => @file_ext, :name => @attachment_name)
|
46
|
-
@obj.save.should == true
|
47
|
-
reloaded_obj = Basic.get(@obj.id)
|
48
|
-
reloaded_obj['_attachments'][@attachment_name].should_not be_nil
|
49
|
-
end
|
50
|
-
|
51
|
-
it "should create an attachment from file without an extension" do
|
52
|
-
@obj.create_attachment(:file => @file_no_ext, :name => @attachment_name)
|
53
|
-
@obj.save.should == true
|
54
|
-
reloaded_obj = Basic.get(@obj.id)
|
55
|
-
reloaded_obj['_attachments'][@attachment_name].should_not be_nil
|
56
|
-
end
|
57
|
-
|
58
|
-
it 'should raise ArgumentError if :file is missing' do
|
59
|
-
lambda{ @obj.create_attachment(:name => @attachment_name) }.should raise_error
|
60
|
-
end
|
61
|
-
|
62
|
-
it 'should raise ArgumentError if :name is missing' do
|
63
|
-
lambda{ @obj.create_attachment(:file => @file_ext) }.should raise_error
|
64
|
-
end
|
65
|
-
|
66
|
-
it 'should set the content-type if passed' do
|
67
|
-
@obj.create_attachment(:file => @file_ext, :name => @attachment_name, :content_type => @content_type)
|
68
|
-
@obj['_attachments'][@attachment_name]['content-type'].should == @content_type
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
describe 'reading, updating, and deleting an attachment' do
|
73
|
-
before(:each) do
|
74
|
-
@obj = Basic.new
|
75
|
-
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
76
|
-
@attachment_name = 'my_attachment'
|
77
|
-
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
78
|
-
@obj.save.should == true
|
79
|
-
@file.rewind
|
80
|
-
@content_type = 'media/mp3'
|
81
|
-
end
|
82
|
-
|
83
|
-
it 'should read an attachment that exists' do
|
84
|
-
@obj.read_attachment(@attachment_name).should == @file.read
|
85
|
-
end
|
86
|
-
|
87
|
-
it 'should update an attachment that exists' do
|
88
|
-
file = File.open(FIXTURE_PATH + '/attachments/README')
|
89
|
-
@file.should_not == file
|
90
|
-
@obj.update_attachment(:file => file, :name => @attachment_name)
|
91
|
-
@obj.save
|
92
|
-
reloaded_obj = Basic.get(@obj.id)
|
93
|
-
file.rewind
|
94
|
-
reloaded_obj.read_attachment(@attachment_name).should_not == @file.read
|
95
|
-
reloaded_obj.read_attachment(@attachment_name).should == file.read
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'should se the content-type if passed' do
|
99
|
-
file = File.open(FIXTURE_PATH + '/attachments/README')
|
100
|
-
@file.should_not == file
|
101
|
-
@obj.update_attachment(:file => file, :name => @attachment_name, :content_type => @content_type)
|
102
|
-
@obj['_attachments'][@attachment_name]['content-type'].should == @content_type
|
103
|
-
end
|
104
|
-
|
105
|
-
it 'should delete an attachment that exists' do
|
106
|
-
@obj.delete_attachment(@attachment_name)
|
107
|
-
@obj.save
|
108
|
-
lambda{Basic.get(@obj.id).read_attachment(@attachment_name)}.should raise_error
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
describe "#attachment_url" do
|
113
|
-
before(:each) do
|
114
|
-
@obj = Basic.new
|
115
|
-
@file = File.open(FIXTURE_PATH + '/attachments/test.html')
|
116
|
-
@attachment_name = 'my_attachment'
|
117
|
-
@obj.create_attachment(:file => @file, :name => @attachment_name)
|
118
|
-
@obj.save.should == true
|
119
|
-
end
|
120
|
-
|
121
|
-
it 'should return nil if attachment does not exist' do
|
122
|
-
@obj.attachment_url('bogus').should be_nil
|
123
|
-
end
|
124
|
-
|
125
|
-
it 'should return the attachment URL as specified by CouchDB HttpDocumentApi' do
|
126
|
-
@obj.attachment_url(@attachment_name).should == "#{Basic.database}/#{@obj.id}/#{@attachment_name}"
|
127
|
-
end
|
128
|
-
end
|
129
|
-
end
|
@@ -1,206 +0,0 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
-
require File.join(FIXTURE_PATH, 'more', 'article')
|
3
|
-
require File.join(FIXTURE_PATH, 'more', 'course')
|
4
|
-
|
5
|
-
describe "ExtendedDocument views" do
|
6
|
-
|
7
|
-
describe "a model with simple views and a default param" do
|
8
|
-
before(:all) do
|
9
|
-
Article.all.map{|a| a.destroy(true)}
|
10
|
-
Article.database.bulk_delete
|
11
|
-
written_at = Time.now - 24 * 3600 * 7
|
12
|
-
@titles = ["this and that", "also interesting", "more fun", "some junk"]
|
13
|
-
@titles.each do |title|
|
14
|
-
a = Article.new(:title => title)
|
15
|
-
a.date = written_at
|
16
|
-
a.save
|
17
|
-
written_at += 24 * 3600
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
it "should have a design doc" do
|
22
|
-
Article.design_doc["views"]["by_date"].should_not be_nil
|
23
|
-
end
|
24
|
-
|
25
|
-
it "should save the design doc" do
|
26
|
-
Article.by_date #rescue nil
|
27
|
-
doc = Article.database.get Article.design_doc.id
|
28
|
-
doc['views']['by_date'].should_not be_nil
|
29
|
-
end
|
30
|
-
|
31
|
-
it "should return the matching raw view result" do
|
32
|
-
view = Article.by_date :raw => true
|
33
|
-
view['rows'].length.should == 4
|
34
|
-
end
|
35
|
-
|
36
|
-
it "should not include non-Articles" do
|
37
|
-
Article.database.save_doc({"date" => 1})
|
38
|
-
view = Article.by_date :raw => true
|
39
|
-
view['rows'].length.should == 4
|
40
|
-
end
|
41
|
-
|
42
|
-
it "should return the matching objects (with default argument :descending => true)" do
|
43
|
-
articles = Article.by_date
|
44
|
-
articles.collect{|a|a.title}.should == @titles.reverse
|
45
|
-
end
|
46
|
-
|
47
|
-
it "should allow you to override default args" do
|
48
|
-
articles = Article.by_date :descending => false
|
49
|
-
articles.collect{|a|a.title}.should == @titles
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
describe "another model with a simple view" do
|
54
|
-
before(:all) do
|
55
|
-
reset_test_db!
|
56
|
-
%w{aaa bbb ddd eee}.each do |title|
|
57
|
-
Course.new(:title => title).save
|
58
|
-
end
|
59
|
-
end
|
60
|
-
it "should make the design doc upon first query" do
|
61
|
-
Course.by_title
|
62
|
-
doc = Course.design_doc
|
63
|
-
doc['views']['all']['map'].should include('Course')
|
64
|
-
end
|
65
|
-
it "should can query via view" do
|
66
|
-
# register methods with method-missing, for local dispatch. method
|
67
|
-
# missing lookup table, no heuristics.
|
68
|
-
view = Course.view :by_title
|
69
|
-
designed = Course.by_title
|
70
|
-
view.should == designed
|
71
|
-
end
|
72
|
-
it "should get them" do
|
73
|
-
rs = Course.by_title
|
74
|
-
rs.length.should == 4
|
75
|
-
end
|
76
|
-
it "should yield" do
|
77
|
-
courses = []
|
78
|
-
rs = Course.by_title # remove me
|
79
|
-
Course.view(:by_title) do |course|
|
80
|
-
courses << course
|
81
|
-
end
|
82
|
-
courses[0]["doc"]["title"].should =='aaa'
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
|
87
|
-
describe "a ducktype view" do
|
88
|
-
before(:all) do
|
89
|
-
@id = TEST_SERVER.default_database.save_doc({:dept => true})['id']
|
90
|
-
end
|
91
|
-
it "should setup" do
|
92
|
-
duck = Course.get(@id) # from a different db
|
93
|
-
duck["dept"].should == true
|
94
|
-
end
|
95
|
-
it "should make the design doc" do
|
96
|
-
@as = Course.by_dept
|
97
|
-
@doc = Course.design_doc
|
98
|
-
@doc["views"]["by_dept"]["map"].should_not include("couchrest")
|
99
|
-
end
|
100
|
-
it "should not look for class" do |variable|
|
101
|
-
@as = Course.by_dept
|
102
|
-
@as[0]['_id'].should == @id
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
describe "a model with a compound key view" do
|
107
|
-
before(:all) do
|
108
|
-
Article.design_doc_fresh = false
|
109
|
-
Article.by_user_id_and_date.each{|a| a.destroy(true)}
|
110
|
-
Article.database.bulk_delete
|
111
|
-
written_at = Time.now - 24 * 3600 * 7
|
112
|
-
@titles = ["uniq one", "even more interesting", "less fun", "not junk"]
|
113
|
-
@user_ids = ["quentin", "aaron"]
|
114
|
-
@titles.each_with_index do |title,i|
|
115
|
-
u = i % 2
|
116
|
-
a = Article.new(:title => title, :user_id => @user_ids[u])
|
117
|
-
a.date = written_at
|
118
|
-
a.save
|
119
|
-
written_at += 24 * 3600
|
120
|
-
end
|
121
|
-
end
|
122
|
-
it "should create the design doc" do
|
123
|
-
Article.by_user_id_and_date rescue nil
|
124
|
-
doc = Article.design_doc
|
125
|
-
doc['views']['by_date'].should_not be_nil
|
126
|
-
end
|
127
|
-
it "should sort correctly" do
|
128
|
-
articles = Article.by_user_id_and_date
|
129
|
-
articles.collect{|a|a['user_id']}.should == ['aaron', 'aaron', 'quentin',
|
130
|
-
'quentin']
|
131
|
-
articles[1].title.should == 'not junk'
|
132
|
-
end
|
133
|
-
it "should be queryable with couchrest options" do
|
134
|
-
articles = Article.by_user_id_and_date :limit => 1, :startkey => 'quentin'
|
135
|
-
articles.length.should == 1
|
136
|
-
articles[0].title.should == "even more interesting"
|
137
|
-
end
|
138
|
-
end
|
139
|
-
|
140
|
-
describe "with a custom view" do
|
141
|
-
before(:all) do
|
142
|
-
@titles = ["very uniq one", "even less interesting", "some fun",
|
143
|
-
"really junk", "crazy bob"]
|
144
|
-
@tags = ["cool", "lame"]
|
145
|
-
@titles.each_with_index do |title,i|
|
146
|
-
u = i % 2
|
147
|
-
a = Article.new(:title => title, :tags => [@tags[u]])
|
148
|
-
a.save
|
149
|
-
end
|
150
|
-
end
|
151
|
-
it "should be available raw" do
|
152
|
-
view = Article.by_tags :raw => true
|
153
|
-
view['rows'].length.should == 5
|
154
|
-
end
|
155
|
-
|
156
|
-
it "should be default to :reduce => false" do
|
157
|
-
ars = Article.by_tags
|
158
|
-
ars.first.tags.first.should == 'cool'
|
159
|
-
end
|
160
|
-
|
161
|
-
it "should be raw when reduce is true" do
|
162
|
-
view = Article.by_tags :reduce => true, :group => true
|
163
|
-
view['rows'].find{|r|r['key'] == 'cool'}['value'].should == 3
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
# TODO: moved to Design, delete
|
168
|
-
describe "adding a view" do
|
169
|
-
before(:each) do
|
170
|
-
reset_test_db!
|
171
|
-
Article.by_date
|
172
|
-
@design_docs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
|
173
|
-
end
|
174
|
-
it "should not create a design doc on view definition" do
|
175
|
-
Article.view_by :created_at
|
176
|
-
newdocs = Article.database.documents :startkey => "_design/", :endkey => "_design/\u9999"
|
177
|
-
newdocs["rows"].length.should == @design_docs["rows"].length
|
178
|
-
end
|
179
|
-
it "should create a new version of the design document on view access" do
|
180
|
-
old_design_doc = Article.database.documents(:key => @design_docs["rows"].first["key"], :include_docs => true)["rows"][0]["doc"]
|
181
|
-
Article.view_by :updated_at
|
182
|
-
Article.by_updated_at
|
183
|
-
newdocs = Article.database.documents({:startkey => "_design/", :endkey => "_design/\u9999"})
|
184
|
-
|
185
|
-
doc = Article.database.documents(:key => @design_docs["rows"].first["key"], :include_docs => true)["rows"][0]["doc"]
|
186
|
-
doc["_rev"].should_not == old_design_doc["_rev"]
|
187
|
-
doc["views"].keys.should include("by_updated_at")
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
describe "with a lot of designs left around" do
|
192
|
-
before(:each) do
|
193
|
-
Article.by_date
|
194
|
-
Article.view_by :field
|
195
|
-
Article.by_field
|
196
|
-
end
|
197
|
-
it "should clean them up" do
|
198
|
-
ddocs = Article.all_design_doc_versions
|
199
|
-
Article.view_by :stream
|
200
|
-
Article.by_stream
|
201
|
-
Article.cleanup_design_docs!
|
202
|
-
ddocs = Article.all_design_doc_versions
|
203
|
-
ddocs["rows"].length.should == 1
|
204
|
-
end
|
205
|
-
end
|
206
|
-
end
|
@@ -1,59 +0,0 @@
|
|
1
|
-
require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper')
|
2
|
-
require File.join(File.dirname(__FILE__), '..', '..', '..', 'lib', 'couchrest', 'support', 'class')
|
3
|
-
|
4
|
-
describe CouchRest::ClassExtension do
|
5
|
-
|
6
|
-
before :all do
|
7
|
-
class FullyDefinedClassExtensions
|
8
|
-
def self.respond_to?(method)
|
9
|
-
if CouchRest::ClassExtension::InstanceMethods.instance_methods.include?(method)
|
10
|
-
true
|
11
|
-
else
|
12
|
-
super
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
class PartDefinedClassExtensions
|
18
|
-
def self.respond_to?(method)
|
19
|
-
methods = CouchRest::ClassExtension::InstanceMethods.instance_methods
|
20
|
-
methods.delete('cattr_reader')
|
21
|
-
|
22
|
-
if methods.include?(method)
|
23
|
-
false
|
24
|
-
else
|
25
|
-
super
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
class NoClassExtensions
|
31
|
-
def self.respond_to?(method)
|
32
|
-
if CouchRest::ClassExtension::InstanceMethods.instance_methods.include?(method)
|
33
|
-
false
|
34
|
-
else
|
35
|
-
super
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
|
41
|
-
end
|
42
|
-
|
43
|
-
it "should not include InstanceMethods if the class extensions are already defined" do
|
44
|
-
FullyDefinedClassExtensions.send(:include, CouchRest::ClassExtension)
|
45
|
-
FullyDefinedClassExtensions.ancestors.should_not include(CouchRest::ClassExtension::InstanceMethods)
|
46
|
-
end
|
47
|
-
|
48
|
-
it "should raise RuntimeError if the class extensions are only partially defined" do
|
49
|
-
lambda {
|
50
|
-
PartDefinedClassExtensions.send(:include, CouchRest::ClassExtension)
|
51
|
-
}.should raise_error(RuntimeError)
|
52
|
-
end
|
53
|
-
|
54
|
-
it "should include class extensions if they are not already defined" do
|
55
|
-
NoClassExtensions.send(:include, CouchRest::ClassExtension)
|
56
|
-
NoClassExtensions.ancestors.should include(CouchRest::ClassExtension::InstanceMethods)
|
57
|
-
end
|
58
|
-
|
59
|
-
end
|
@@ -1,34 +0,0 @@
|
|
1
|
-
class Article < CouchRest::ExtendedDocument
|
2
|
-
use_database TEST_SERVER.default_database
|
3
|
-
unique_id :slug
|
4
|
-
|
5
|
-
view_by :date, :descending => true
|
6
|
-
view_by :user_id, :date
|
7
|
-
|
8
|
-
view_by :tags,
|
9
|
-
:map =>
|
10
|
-
"function(doc) {
|
11
|
-
if (doc['couchrest-type'] == 'Article' && doc.tags) {
|
12
|
-
doc.tags.forEach(function(tag){
|
13
|
-
emit(tag, 1);
|
14
|
-
});
|
15
|
-
}
|
16
|
-
}",
|
17
|
-
:reduce =>
|
18
|
-
"function(keys, values, rereduce) {
|
19
|
-
return sum(values);
|
20
|
-
}"
|
21
|
-
|
22
|
-
property :date
|
23
|
-
property :slug, :read_only => true
|
24
|
-
property :title
|
25
|
-
property :tags
|
26
|
-
|
27
|
-
timestamps!
|
28
|
-
|
29
|
-
save_callback :before, :generate_slug_from_title
|
30
|
-
|
31
|
-
def generate_slug_from_title
|
32
|
-
self['slug'] = title.downcase.gsub(/[^a-z0-9]/,'-').squeeze('-').gsub(/^\-|\-$/,'') if new_document?
|
33
|
-
end
|
34
|
-
end
|
@@ -1,14 +0,0 @@
|
|
1
|
-
require File.join(FIXTURE_PATH, 'more', 'question')
|
2
|
-
require File.join(FIXTURE_PATH, 'more', 'person')
|
3
|
-
|
4
|
-
class Course < CouchRest::ExtendedDocument
|
5
|
-
use_database TEST_SERVER.default_database
|
6
|
-
|
7
|
-
property :title
|
8
|
-
property :questions, :cast_as => ['Question']
|
9
|
-
property :professor, :cast_as => 'Person'
|
10
|
-
property :final_test_at, :cast_as => 'Time'
|
11
|
-
|
12
|
-
view_by :title
|
13
|
-
view_by :dept, :ducktype => true
|
14
|
-
end
|
data/spec/fixtures/more/event.rb
DELETED