mattetti-couchrest 0.2.1.0 → 0.13
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.
- 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