couch_surfer 0.3.2

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.
@@ -0,0 +1,34 @@
1
+ module CouchSurfer
2
+ module Query
3
+ module ClassMethods
4
+ def query_processor(name)
5
+ self.external = name
6
+ end
7
+ def query(view_name, query_string, query_options = {})
8
+ payload = {
9
+ :design => self.send(:design_doc_slug),
10
+ :view => set_view_options(view_name, query_options),
11
+ :external => set_external_options(query_string),
12
+ }
13
+ result = CouchRest.post "http://#{database}/_mix", payload
14
+ result['rows'].collect{|r|new(r['doc'])} if result['rows']
15
+ end
16
+
17
+ private
18
+
19
+ def set_view_options(view_name, query_options)
20
+ {:name => view_name, :query => {:include_docs => true}.merge(query_options)}
21
+ end
22
+
23
+ def set_external_options(query_string)
24
+ {:name => external, :query => {:q => query_string}, :include_docs => true}
25
+ end
26
+ end
27
+ def self.included(receiver)
28
+ receiver.extend ClassMethods
29
+ receiver.class_eval do
30
+ class_inheritable_accessor :external
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,49 @@
1
+ require 'validatable'
2
+
3
+ module CouchSurfer
4
+ module Validations
5
+ module ClassMethods
6
+ def validates_uniqueness_of *args
7
+ # add view, validation and before callbacks
8
+ options = args.last.is_a?(Hash) ? args.pop : {}
9
+ field = args.first
10
+ class_eval do
11
+ #view_by *args
12
+ validates_true_for args.first, :logic => lambda { is_unique?(field, options) }, :message => options[:message] || "is taken"
13
+ end
14
+ end
15
+ end
16
+
17
+ module InstanceMethods
18
+ def is_unique?(field, options)
19
+ if options[:view]
20
+ view_name = options[:view]
21
+ query = options[:query].is_a?(Proc) ? self.instance_eval(&options[:query]) : nil
22
+ end
23
+ view_name ||= "by_#{field}"
24
+ query ||= {:key => self.send(field)}
25
+ result = self.class.send(view_name, query)
26
+ if result.blank?
27
+ return true
28
+ else
29
+ return !id.blank? && (id == result.first.id)
30
+ end
31
+ end
32
+
33
+ def validate_instance
34
+ throw(:halt, false) unless valid?
35
+ end
36
+ end
37
+
38
+ def self.included(receiver)
39
+ receiver.extend ClassMethods
40
+ receiver.send :include, Validatable
41
+ receiver.send :include, InstanceMethods
42
+ receiver.class_eval do
43
+ [:save].each do |method|
44
+ before method, :validate_instance
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,51 @@
1
+ module CouchRest
2
+ class Database
3
+ # Query a CouchDB view as defined by a <tt>_design</tt> document. Accepts
4
+ # paramaters as described in http://wiki.apache.org/couchdb/HttpViewApi
5
+ #/db/_design/examples/_list/index-posts/posts-by-date
6
+
7
+ def list(list_name, view_name, doc_name, params = {})
8
+ url = CouchRest.paramify_url "#{@root}/_design/#{doc_name}/_list/#{list_name}/#{view_name}", params
9
+ response = RestClient.get(url)
10
+ JSON.parse(response)
11
+ rescue
12
+ response
13
+ end
14
+ end
15
+
16
+ class Design
17
+ # Dispatches to any named view.
18
+ # (using the database where this design doc was saved)
19
+ def list(list_name, view_name, query = {})
20
+ database.list(list_name, view_name.to_s, name, query)
21
+ end
22
+
23
+ # Dispatches to any named view.
24
+ # (using the database where this design doc was saved)
25
+ def view view_name, query={}, &block
26
+ view_on database, view_name, query, &block
27
+ end
28
+
29
+ # Dispatches to any named view in a specific database
30
+ def view_on db, view_name, query={}, &block
31
+ view_name = view_name.to_s
32
+ view_slug = "#{name}/#{view_name}"
33
+ defaults = (self['views'][view_name] && self['views'][view_name]["couchrest-defaults"]) || {}
34
+ db.view(view_slug, defaults.merge(query), &block)
35
+ end
36
+
37
+ private
38
+
39
+ # returns stored defaults if the there is a view named this in the design doc
40
+ def has_view?(view)
41
+ view = view.to_s
42
+ self['views'][view] &&
43
+ (self['views'][view]["couchrest-defaults"]||{})
44
+ end
45
+
46
+ def fetch_view view_name, opts, &block
47
+ database.view(view_name, opts, &block)
48
+ end
49
+
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ This is an example README file.
2
+
3
+ More of the README, whee.
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Test</title>
5
+ </head>
6
+ <body>
7
+ <p>
8
+ Test
9
+ </p>
10
+ </body>
11
+ </html>
@@ -0,0 +1,3 @@
1
+ function globalLib() {
2
+ return "fixture";
3
+ };
@@ -0,0 +1,3 @@
1
+ function justThisView() {
2
+ return "fixture";
3
+ };
@@ -0,0 +1,4 @@
1
+ function(doc) {
2
+ //include-lib
3
+ emit(null, null);
4
+ };
@@ -0,0 +1,3 @@
1
+ function(doc) {
2
+ emit(null, null);
3
+ };
@@ -0,0 +1,3 @@
1
+ function(ks,vs,co) {
2
+ return vs.length;
3
+ };
@@ -0,0 +1,187 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ class Account
4
+ include CouchSurfer::Model
5
+ include CouchSurfer::Associations
6
+
7
+ key_accessor :name
8
+
9
+ has_many :employees,
10
+ :view => :by_account_id_and_role,
11
+ :query => lambda{ {:startkey => [id, nil], :endkey => [id, {}]} }
12
+
13
+ has_many :programmers, :class_name => :employee,
14
+ :view => :by_account_id_and_role,
15
+ :query => lambda{ {:startkey => [id, "Programmer"], :endkey => [id, "Programmer"]} }
16
+
17
+ has_many :projects
18
+
19
+ end
20
+
21
+ class Project
22
+ include CouchSurfer::Model
23
+ include CouchSurfer::Associations
24
+
25
+ key_accessor :name, :account_id
26
+
27
+ belongs_to :account
28
+ has_many :members, :through => :memberships, :class_name => :employee
29
+
30
+ view_by :account_id
31
+ view_by :id
32
+ end
33
+
34
+ class Membership
35
+ include CouchSurfer::Model
36
+ include CouchSurfer::Associations
37
+
38
+ key_accessor :project_id, :employee_id
39
+ belongs_to :project
40
+ belongs_to :employee
41
+
42
+ view_by :project_id
43
+ view_by :employee_id
44
+ end
45
+
46
+
47
+ class Employee
48
+ include CouchSurfer::Model
49
+ include CouchSurfer::Associations
50
+
51
+ key_accessor :email, :account_id, :role
52
+
53
+ belongs_to :employer, :class_name => :account
54
+ has_many :shirts, :inline => true
55
+ has_many :projects, :through => :memberships
56
+
57
+ view_by :account_id, :role
58
+ end
59
+
60
+ class Shirt
61
+ include CouchSurfer::Model
62
+ include CouchSurfer::Associations
63
+
64
+ key_accessor :color, :bought_on
65
+ cast :bought_on, :as => "Time"
66
+
67
+ def change_color(new_color)
68
+ self.color = new_color
69
+ end
70
+ end
71
+
72
+ describe CouchSurfer::Associations do
73
+ before(:all) do
74
+ db = CouchRest.database!('couch_surfer-test')
75
+ db.delete!
76
+ CouchSurfer::Model.default_database = CouchRest.database!('http://127.0.0.1:5984/couch_surfer-test')
77
+ @account = Account.create(:name => "My Account")
78
+ 2.times do |i|
79
+ Employee.create(:email => "foo#{i+1}@bar.com", :account_id => @account.id, :role => "Programmer")
80
+ Employee.create(:email => "foo#{i+3}@bar.com", :account_id => @account.id, :role => "Engineer")
81
+ Project.create(:name => "Project No. #{i}", :account_id => @account.id)
82
+ end
83
+ @employee = @account.employees.first
84
+ @project = @account.projects.first
85
+ end
86
+
87
+ describe "belongs_to" do
88
+ it "should return it's parent" do
89
+ @project.account.should == @account
90
+ end
91
+
92
+ it "should take the class_name into consideration" do
93
+ @employee.employer.should == @account
94
+ end
95
+ end
96
+
97
+ describe "has_many" do
98
+ describe "vanilla" do
99
+ it "should return it's children" do
100
+ @account.employees.length.should == 4
101
+ @account.employees.map{|employee| employee.email}.sort.should == ["foo1@bar.com", "foo2@bar.com", "foo3@bar.com", "foo4@bar.com"]
102
+ end
103
+ end
104
+
105
+ describe ":class_name" do
106
+ it "should return it's children" do
107
+ @account.programmers.length.should == 2
108
+ end
109
+ end
110
+ describe ":inline" do
111
+ before(:all) do
112
+ @employee.shirts.clear
113
+ @white_shirt = Shirt.new(:color => "White")
114
+ @pink_shirt = Shirt.new(:color => "Pink")
115
+ @employee.shirts << {:color => "Blue"}
116
+ @employee.shirts << @white_shirt
117
+ @employee.save
118
+ @employee = Employee.get(@employee.id)
119
+ @employee.shirts << @pink_shirt
120
+ @employee.save
121
+ end
122
+ it "should return it's children" do
123
+ employee = Employee.get(@employee.id)
124
+ employee.shirts.each do |shirt|
125
+ shirt.should be_kind_of(Shirt)
126
+ end
127
+ employee.shirts.map{|shirt| shirt.color}.should == %w(Blue White Pink)
128
+ end
129
+
130
+ it "should be able to delete a child" do
131
+ employee = Employee.get(@employee.id)
132
+ employee.shirts.delete(@pink_shirt)
133
+ employee.save
134
+ employee = Employee.get(@employee.id)
135
+ employee.shirts.map{|shirt| shirt.color}.should == %w(Blue White)
136
+ end
137
+
138
+ it "should have an append method" do
139
+ employee = Employee.get(@employee.id)
140
+ employee.shirts << {:color => "Black"}
141
+ employee.save
142
+ employee = Employee.get(@employee.id)
143
+ employee.shirts.map{|shirt| shirt.color}.should == %w(Blue White Black)
144
+ end
145
+
146
+ it "should cast the childrens methods appropriately" do
147
+ employee = Employee.new(:email => "foofoo@bar.com", :account_id => @account.id, :role => "Programmer")
148
+ employee.save
149
+ employee.shirts << Shirt.new(:color => "Mauve", :bought_on => Time.now)
150
+ employee.shirts.last.bought_on.should be_an_instance_of(Time)
151
+ employee.shirts.last.color.should == "Mauve"
152
+ # Save ..
153
+ employee.save
154
+ employee.shirts.last.bought_on.should be_an_instance_of(Time)
155
+ employee.shirts.last.color.should == "Mauve"
156
+ # Reload ..
157
+ employee = Employee.get(employee.id)
158
+ employee.shirts.last.bought_on.should be_an_instance_of(Time)
159
+ employee.shirts.last.color.should == "Mauve"
160
+ end
161
+
162
+ end
163
+
164
+ describe ":through" do
165
+ it "should return it's 'through' children" do
166
+ @employee.memberships.should be_empty
167
+ end
168
+
169
+ it "should return it's children" do
170
+ 3.times do |i|
171
+ p = Project.create(:name => "Project with Invitations No. #{i}", :account_id => @account.id)
172
+ Membership.create(:employee_id => @employee.id, :project_id => p.id)
173
+ end
174
+
175
+ 7.times do |i|
176
+ e = Employee.create(:email => "bar#{i}@bar.com", :account_id => @account.id)
177
+ Membership.create(:employee_id => e.id, :project_id => @project.id)
178
+ end
179
+
180
+ @employee.memberships.size.should == 3
181
+ @employee.projects.size.should == 3
182
+ @project.memberships.size.should == 7
183
+ @project.members.size.should == 7
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,52 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ class Person
4
+ include CouchSurfer::Model
5
+ include CouchSurfer::Attachments
6
+
7
+ key_accessor :name, :email
8
+
9
+ end
10
+
11
+ describe CouchSurfer::Attachments do
12
+ before(:all) do
13
+ db = CouchRest.database!('couch_surfer-test')
14
+ db.delete!
15
+ CouchSurfer::Model.default_database = CouchRest.database!('http://127.0.0.1:5984/couch_surfer-test')
16
+ end
17
+
18
+ before do
19
+ @person = Person.create(:name => "John", :email => "john@mail.com")
20
+ end
21
+
22
+ describe "attaching a file" do
23
+ it "should return true" do
24
+ @person.put_attachment("couchdb.png", open_fixture("attachments/couchdb.png")).should be_true
25
+ end
26
+
27
+ it "should raise an exception if the record is not yet saved" do
28
+ person = Person.new(:name => "John", :email => "john@mail.com")
29
+ lambda {person.put_attachment("couchdb.png", open_fixture("attachments/couchdb.png"))}.should raise_error(ArgumentError)
30
+ end
31
+ end
32
+
33
+ describe "fetching a file" do
34
+ it "should return the file" do
35
+ @person.put_attachment("couchdb.png", open_fixture("attachments/couchdb.png")).should be_true
36
+ @person.fetch_attachment("couchdb.png").size.should >= 3000
37
+ end
38
+
39
+ it "should raise an exception if the record is not yet saved" do
40
+ person = Person.new(:name => "John", :email => "john@mail.com")
41
+ lambda {person.fetch_attachment("couchdb.png")}.should raise_error(ArgumentError)
42
+ end
43
+ end
44
+
45
+ describe "deleting a file" do
46
+ it "should return true" do
47
+ @person.put_attachment("couchdb.png", open_fixture("attachments/couchdb.png")).should be_true
48
+ @person.delete_attachment("couchdb.png").should be_true
49
+ lambda {@person.fetch_attachment("couchdb.png")}.should raise_error(RestClient::ResourceNotFound)
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,29 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper.rb'
2
+
3
+ describe "Extensions" do
4
+ before(:all) do
5
+ @cr = CouchRest.new(COUCHHOST)
6
+ @db = @cr.database(TESTDB)
7
+ @db.delete! rescue nil
8
+ @db = @cr.create_db(TESTDB) rescue nil
9
+ @adb = @cr.database('couchrest-model-test')
10
+ @adb.delete! rescue nil
11
+ CouchSurfer::Model.default_database = CouchRest.database!('http://127.0.0.1:5984/couch_surfer-test')
12
+ @des = CouchRest::Design.new
13
+ @des.database = @db
14
+ @des.name = "MyDesignDoc"
15
+ @des.save
16
+ end
17
+
18
+ describe CouchRest::Database do
19
+ it "should have a list method" do
20
+ @db.should respond_to(:list)
21
+ end
22
+ end
23
+
24
+ describe CouchRest::Document do
25
+ it "should have a list method" do
26
+ @des.should respond_to(:list)
27
+ end
28
+ end
29
+ end