addywaddy-couch_surfer 0.0.2 → 0.0.4
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/CHANGELOG.md +13 -1
- data/README.md +114 -40
- data/Rakefile +1 -1
- data/lib/couch_surfer/associations.rb +61 -23
- data/lib/couch_surfer/model.rb +14 -4
- data/lib/couch_surfer/validations.rb +3 -3
- data/lib/couch_surfer.rb +1 -1
- data/spec/lib/associations_spec.rb +113 -23
- data/spec/lib/model_spec.rb +2 -2
- data/spec/lib/validations_spec.rb +1 -1
- metadata +2 -2
data/CHANGELOG.md
CHANGED
@@ -1,7 +1,19 @@
|
|
1
|
+
0.0.4 (2009-02-04)
|
2
|
+
------------------
|
3
|
+
- `has_many :through`
|
4
|
+
- `has_many :inline`
|
5
|
+
- `create` was in 0.0.2 but forgot to mention it :)
|
6
|
+
- `all` now emits (id, null) so you can use `:keys => [1,2,3,4]` in your query
|
7
|
+
- `format_utc_offset` helper (zdzolton)
|
8
|
+
- Syntax change:
|
9
|
+
- OLD: `:view => {:name => :by_this_and_that, :query => lambda { {:key => [this_id, id]} }}`
|
10
|
+
- NEW: `:view => :by_this_and_that, :query => lambda { {:key => [this_id, id]} }`
|
11
|
+
|
1
12
|
0.0.2 (2009-01-24)
|
2
13
|
------------------
|
3
14
|
- `timestamps!` class method now adds instance methods
|
4
15
|
- `created_at` and `updated_at` always return an instance of `Time`
|
16
|
+
|
5
17
|
0.0.1 (2009-01-21)
|
6
18
|
------------------
|
7
|
-
- Initial Release
|
19
|
+
- Initial Release
|
data/README.md
CHANGED
@@ -1,49 +1,114 @@
|
|
1
|
-
CouchSurfer
|
2
|
-
|
3
|
-
|
1
|
+
CouchSurfer - CouchDB ORM
|
2
|
+
=========================
|
3
|
+
---
|
4
4
|
Description
|
5
5
|
-----------
|
6
|
-
CouchSurfer is an extraction of CouchRest::Model from the excellent [CouchRest](http://github.com/jchris/couchrest/ "CouchRest") gem by J. Chris Anderson.
|
6
|
+
CouchSurfer is an extraction of CouchRest::Model from the excellent [CouchRest](http://github.com/jchris/couchrest/ "CouchRest") gem by J. Chris Anderson. In addition, it provides association and validation methods.
|
7
|
+
|
8
|
+
Associations
|
9
|
+
------------
|
10
|
+
CouchSurfer provides the following 4 association kinds:
|
11
|
+
|
12
|
+
- `belongs_to`
|
13
|
+
- `has_many`
|
14
|
+
- `has_many :inline`; and
|
15
|
+
- `has_many :through`
|
16
|
+
|
17
|
+
All association kinds take an optional `:class_name` option should you want your association to be named differently to the associated model.
|
18
|
+
|
19
|
+
class Page
|
20
|
+
…
|
21
|
+
belongs_to :owner, :class_name => :user
|
22
|
+
…
|
23
|
+
end
|
24
|
+
|
25
|
+
page = Page.create(…)
|
26
|
+
page.owner # returns an instance of user
|
27
|
+
|
28
|
+
The `belongs_to` associations accept two additional options - `:view` and `query`, enabling you to customise your associations to fit your needs. You must explicitly declare the view on the child model for associations to work
|
29
|
+
|
30
|
+
**Example 1: basic**
|
7
31
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
32
|
+
class User
|
33
|
+
…
|
34
|
+
has_many :pages
|
35
|
+
…
|
36
|
+
end
|
37
|
+
|
38
|
+
class Page
|
39
|
+
…
|
40
|
+
view_by :user_id
|
41
|
+
…
|
42
|
+
end
|
43
|
+
|
44
|
+
user = User.create(…)
|
45
|
+
10.times {Page.create(…, :user_id => user.id)}
|
46
|
+
user.pages
|
47
|
+
|
14
48
|
|
15
|
-
|
16
|
-
- All validations from the [Validatable](http://github.com/jrun/validatable/ "Validatable") gem
|
17
|
-
- `validates_uniqueness_of`
|
49
|
+
**Example 2: with options**
|
18
50
|
|
19
|
-
Examples
|
20
|
-
--------
|
21
51
|
class Account
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
# Uses a custom view and key
|
31
|
-
has_many :employees, :view => {:name => :by_account_id_and_email,
|
32
|
-
:query => lambda{ {:startkey => [id, nil], :endkey => [id, {}]} }}
|
52
|
+
…
|
53
|
+
has_many :employees,
|
54
|
+
:class_name, :user,
|
55
|
+
:view => :by_account_id_and_email,
|
56
|
+
:query => lambda { {:startkey => [account_id, nil], :endkey => [account_id, {}]} }
|
57
|
+
…
|
58
|
+
end
|
33
59
|
|
60
|
+
class User
|
61
|
+
…
|
62
|
+
view_by :account_id, :email # see validation examples below
|
63
|
+
…
|
34
64
|
end
|
35
65
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
66
|
+
account = Acccount.create(…)
|
67
|
+
10.times {User.create(…, :account_id => acount.id)}
|
68
|
+
account.employees
|
69
|
+
|
70
|
+
**Example 2: :through**
|
71
|
+
|
72
|
+
class Account
|
73
|
+
…
|
74
|
+
has_many :projects,
|
75
|
+
:through => :memberships
|
76
|
+
…
|
77
|
+
end
|
78
|
+
|
79
|
+
class Membership
|
80
|
+
…
|
44
81
|
view_by :account_id
|
82
|
+
…
|
83
|
+
end
|
84
|
+
|
85
|
+
class Project
|
86
|
+
…
|
87
|
+
view_by :account_id, :email # see validation examples below
|
88
|
+
…
|
45
89
|
end
|
46
90
|
|
91
|
+
account = Acccount.create(…)
|
92
|
+
10.times do
|
93
|
+
p = Project.create(…)
|
94
|
+
Membership.create(…, :account_id => account.id, :project_id => p.id)
|
95
|
+
end
|
96
|
+
account.projects
|
97
|
+
|
98
|
+
**Note on HasManyThrough**
|
99
|
+
|
100
|
+
With reference to the above example, HasManyThrough works by retrieving all memberships associated with the account, collecting their ids and running a [bulk retrieval](http://wiki.apache.org/couchdb/HTTP_view_API "Query Options") on the Project.all view, which is implicitly created for all models and, as of 0.0.4, emits it's id (see CHANGELOG).
|
101
|
+
|
102
|
+
*Caveats*
|
103
|
+
|
104
|
+
- Sorting needs to be done client side
|
105
|
+
- The results do not contain any extra information that may be present on the ':through' model.
|
106
|
+
|
107
|
+
Validations
|
108
|
+
-----------
|
109
|
+
Validations, with the exception of `validates_uniqueness_of`, have been implemented using the [Validatable](http://github.com/jrun/validatable/ "Validatable") gem.
|
110
|
+
|
111
|
+
**Example**
|
47
112
|
|
48
113
|
class Employee
|
49
114
|
include CouchSurfer::Model
|
@@ -62,12 +127,21 @@ Examples
|
|
62
127
|
validates_length_of :postcode, :is => 7, :message => "not the correct length"
|
63
128
|
|
64
129
|
# Will use the Employee.by_name view with {:key => employee_instance.name}
|
65
|
-
validates_uniqueness_of :name
|
130
|
+
validates_uniqueness_of :name
|
66
131
|
|
67
|
-
# Uses a custom view and key
|
68
|
-
validates_uniqueness_of :email,
|
69
|
-
|
132
|
+
# Uses a custom view and key for uniqueness within a specific scope
|
133
|
+
validates_uniqueness_of :email,
|
134
|
+
:view => :by_account_id_and_email,
|
135
|
+
:query => lambda{ {:key => [account_id, email]} },
|
136
|
+
:message => "The email address for this account is taken"
|
70
137
|
|
71
138
|
end
|
72
|
-
|
73
|
-
|
139
|
+
|
140
|
+
|
141
|
+
Please check out the specs as well :)
|
142
|
+
|
143
|
+
Next
|
144
|
+
----
|
145
|
+
- Error handling
|
146
|
+
- association methods with arguments:
|
147
|
+
`@account.projects(:limit => 2, :offset => 1)`
|
data/Rakefile
CHANGED
@@ -19,7 +19,7 @@ spec = Gem::Specification.new do |s|
|
|
19
19
|
s.version = CouchSurfer::VERSION
|
20
20
|
s.date = "2009-01-22"
|
21
21
|
s.summary = "ORM based on CouchRest::Model"
|
22
|
-
s.email = "
|
22
|
+
s.email = "adam.groves@gmail.com"
|
23
23
|
s.homepage = "http://github.com/addywaddy/couchsurfer"
|
24
24
|
s.description = "CouchSurfer provides an ORM for CouchDB, as well as supporting association and validation declarations."
|
25
25
|
s.has_rdoc = true
|
@@ -1,40 +1,56 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'extlib'
|
3
|
-
|
3
|
+
module CouchSurfer
|
4
|
+
class InlineCollection < Array
|
5
|
+
def << child
|
6
|
+
child = child.kind_of?( CouchSurfer::Model) ? child.attributes : child
|
7
|
+
super(child)
|
8
|
+
end
|
9
|
+
|
10
|
+
def delete(child)
|
11
|
+
child = child.kind_of?( CouchSurfer::Model) ? child.attributes : child
|
12
|
+
super(child)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
4
16
|
module CouchSurfer
|
5
17
|
module Associations
|
6
18
|
module ClassMethods
|
7
19
|
def has_many *args
|
8
|
-
options = args
|
20
|
+
options = extract_options!(args)
|
9
21
|
children = args.first
|
10
|
-
|
11
|
-
query_params = args.last.is_a?(Hash) ? args.pop : nil
|
22
|
+
if options[:inline]
|
12
23
|
name = ::Extlib::Inflection.camelize(children.to_s.singular)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
24
|
+
cast children, :as => [name]
|
25
|
+
before(:save) do
|
26
|
+
if self[children.to_s]
|
27
|
+
self[children.to_s].map!{|child| child.kind_of?( CouchSurfer::Model) ? child.attributes : child}
|
28
|
+
end
|
17
29
|
end
|
18
|
-
|
19
|
-
|
20
|
-
|
30
|
+
define_method children do |*args|
|
31
|
+
self[children.to_s] ||= CouchSurfer::InlineCollection.new
|
32
|
+
end
|
33
|
+
return
|
34
|
+
end
|
35
|
+
if options[:through]
|
36
|
+
define_method_for_children(options[:through], options)
|
37
|
+
define_method children do
|
38
|
+
name = options[:class_name] || children.to_s.singular
|
39
|
+
class_name = ::Extlib::Inflection.camelize(name)
|
40
|
+
klass = ::Extlib::Inflection.constantize(class_name)
|
41
|
+
through_items = self.send("#{options[:through]}")
|
42
|
+
query ||= {:keys => through_items.map{|child| child.send("#{name}_id")}}
|
43
|
+
view_name ||= "by_#{self.class.name.downcase}_id"
|
44
|
+
klass.send("all", query)
|
45
|
+
end
|
46
|
+
return
|
21
47
|
end
|
48
|
+
define_method_for_children(children, options, options[:class_name])
|
22
49
|
end
|
23
50
|
|
24
51
|
def belongs_to *args
|
25
|
-
options = args
|
52
|
+
options = extract_options!(args)
|
26
53
|
parent = args.first
|
27
|
-
# view_key = "#{parent}_id".to_sym
|
28
|
-
# if options[:identifiers]
|
29
|
-
# if options[:prepend]
|
30
|
-
# view_key = options[:identifiers] << view_key
|
31
|
-
# else
|
32
|
-
# view_key = options[:identifiers].unshift(view_key)
|
33
|
-
# end
|
34
|
-
# end
|
35
|
-
# class_eval do
|
36
|
-
# view_by *view_key
|
37
|
-
# end
|
38
54
|
define_method parent do
|
39
55
|
name = ::Extlib::Inflection.camelize(parent.to_s)
|
40
56
|
klass = ::Extlib::Inflection.constantize(name)
|
@@ -48,6 +64,28 @@ module CouchSurfer
|
|
48
64
|
self["#{parent_obj.class.name.downcase}_id"] = parent_obj.id
|
49
65
|
end
|
50
66
|
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def extract_options!(args)
|
71
|
+
args.last.is_a?(Hash) ? args.pop : {}
|
72
|
+
end
|
73
|
+
|
74
|
+
def define_method_for_children(children, options, name = nil)
|
75
|
+
class_name = ::Extlib::Inflection.camelize(name || children.to_s.singular)
|
76
|
+
define_method children do
|
77
|
+
klass = ::Extlib::Inflection.constantize(class_name)
|
78
|
+
if options[:view]
|
79
|
+
view_name = options[:view]
|
80
|
+
end
|
81
|
+
if options[:query].is_a?(Proc)
|
82
|
+
query = self.instance_eval(&options[:query])
|
83
|
+
end
|
84
|
+
view_name ||= "by_#{self.class.name.downcase}_id"
|
85
|
+
query ||= {:key => self.id}
|
86
|
+
klass.send(view_name, query)
|
87
|
+
end
|
88
|
+
end
|
51
89
|
end
|
52
90
|
|
53
91
|
def self.included(receiver)
|
data/lib/couch_surfer/model.rb
CHANGED
@@ -15,6 +15,15 @@ module CouchSurfer
|
|
15
15
|
@database = db
|
16
16
|
end
|
17
17
|
|
18
|
+
# Adapted from ActiveSupport Time#formatted_offset
|
19
|
+
def self.format_utc_offset(time)
|
20
|
+
seconds_offset_from_utc = time.utc_offset
|
21
|
+
sign = (seconds_offset_from_utc < 0 ? -1 : 1)
|
22
|
+
hours = seconds_offset_from_utc.abs / 3600
|
23
|
+
minutes = (seconds_offset_from_utc.abs % 3600) / 60
|
24
|
+
"%+03d%02d" % [ hours * sign, minutes ]
|
25
|
+
end
|
26
|
+
|
18
27
|
module ClassMethods
|
19
28
|
# override the CouchSurfer::Model-wide default_database
|
20
29
|
def use_database db
|
@@ -154,8 +163,8 @@ module CouchSurfer
|
|
154
163
|
end
|
155
164
|
before(:save) do
|
156
165
|
time = Time.now
|
157
|
-
|
158
|
-
self['updated_at'] = time.strftime("%Y/%m/%d %H:%M:%S.#{time.usec}
|
166
|
+
utc_offset = CouchSurfer::Model.format_utc_offset(time)
|
167
|
+
self['updated_at'] = time.strftime("%Y/%m/%d %H:%M:%S.#{time.usec} #{utc_offset}")
|
159
168
|
self['created_at'] = self['updated_at'] if new_document?
|
160
169
|
end
|
161
170
|
end
|
@@ -290,7 +299,7 @@ module CouchSurfer
|
|
290
299
|
# Deletes any non-current design docs that were created by this class.
|
291
300
|
# Running this when you're deployed version of your application is steadily
|
292
301
|
# and consistently using the latest code, is the way to clear out old design
|
293
|
-
# docs. Running it
|
302
|
+
# docs. Running it too early could mean that live code has to regenerate
|
294
303
|
# potentially large indexes.
|
295
304
|
def cleanup_design_docs!
|
296
305
|
ddocs = all_design_doc_versions
|
@@ -359,7 +368,7 @@ module CouchSurfer
|
|
359
368
|
'all' => {
|
360
369
|
'map' => "function(doc) {
|
361
370
|
if (doc['couchrest-type'] == '#{self.to_s}') {
|
362
|
-
emit(
|
371
|
+
emit(doc['_id'],null);
|
363
372
|
}
|
364
373
|
}"
|
365
374
|
}
|
@@ -385,6 +394,7 @@ module CouchSurfer
|
|
385
394
|
end
|
386
395
|
self.design_doc_fresh = true
|
387
396
|
end
|
397
|
+
|
388
398
|
end
|
389
399
|
|
390
400
|
module InstanceMethods
|
@@ -18,9 +18,9 @@ module CouchSurfer
|
|
18
18
|
|
19
19
|
module InstanceMethods
|
20
20
|
def is_unique?(field, options)
|
21
|
-
if options[:view]
|
22
|
-
view_name = options[:view]
|
23
|
-
query = options[:
|
21
|
+
if options[:view]
|
22
|
+
view_name = options[:view]
|
23
|
+
query = options[:query].is_a?(Proc) ? self.instance_eval(&options[:query]) : nil
|
24
24
|
end
|
25
25
|
view_name ||= "by_#{field}"
|
26
26
|
query ||= {:key => self.send(field)}
|
data/lib/couch_surfer.rb
CHANGED
@@ -2,7 +2,7 @@ $:.unshift(File.dirname(__FILE__)) unless
|
|
2
2
|
$:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
|
3
3
|
|
4
4
|
module CouchSurfer
|
5
|
-
VERSION = '0.0.
|
5
|
+
VERSION = '0.0.4'
|
6
6
|
autoload :Model, 'couch_surfer/model'
|
7
7
|
autoload :Validations, 'couch_surfer/validations'
|
8
8
|
autoload :Associations, 'couch_surfer/associations'
|
@@ -3,10 +3,17 @@ require File.dirname(__FILE__) + '/../spec_helper.rb'
|
|
3
3
|
class Account
|
4
4
|
include CouchSurfer::Model
|
5
5
|
include CouchSurfer::Associations
|
6
|
-
|
6
|
+
|
7
7
|
key_accessor :name
|
8
|
-
|
9
|
-
has_many :employees,
|
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
|
+
|
10
17
|
has_many :projects
|
11
18
|
|
12
19
|
end
|
@@ -18,8 +25,22 @@ class Project
|
|
18
25
|
key_accessor :name, :account_id
|
19
26
|
|
20
27
|
belongs_to :account
|
28
|
+
has_many :members, :through => :memberships, :class_name => :employee
|
21
29
|
|
22
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
|
23
44
|
end
|
24
45
|
|
25
46
|
|
@@ -27,48 +48,117 @@ class Employee
|
|
27
48
|
include CouchSurfer::Model
|
28
49
|
include CouchSurfer::Associations
|
29
50
|
|
30
|
-
key_accessor :email, :account_id
|
51
|
+
key_accessor :email, :account_id, :role
|
52
|
+
|
31
53
|
belongs_to :account
|
54
|
+
has_many :shirts, :inline => true
|
55
|
+
has_many :projects, :through => :memberships
|
32
56
|
|
33
|
-
view_by :account_id, :
|
57
|
+
view_by :account_id, :role
|
34
58
|
|
35
59
|
end
|
36
60
|
|
61
|
+
class Shirt
|
62
|
+
include CouchSurfer::Model
|
63
|
+
include CouchSurfer::Associations
|
64
|
+
|
65
|
+
key_accessor :color
|
66
|
+
end
|
67
|
+
|
37
68
|
describe CouchSurfer::Associations do
|
38
69
|
before(:all) do
|
39
70
|
db = CouchRest.database!('couch_surfer-test')
|
40
71
|
db.delete!
|
41
72
|
CouchSurfer::Model.default_database = CouchRest.database!('http://127.0.0.1:5984/couch_surfer-test')
|
42
73
|
@account = Account.create(:name => "My Account")
|
43
|
-
|
44
|
-
Employee.create(:email => "foo#{i}@bar.com", :account_id => @account.id)
|
74
|
+
2.times do |i|
|
75
|
+
Employee.create(:email => "foo#{i+1}@bar.com", :account_id => @account.id, :role => "Programmer")
|
76
|
+
Employee.create(:email => "foo#{i+3}@bar.com", :account_id => @account.id, :role => "Engineer")
|
45
77
|
Project.create(:name => "Project No. #{i}", :account_id => @account.id)
|
46
78
|
end
|
79
|
+
@employee = @account.employees.first
|
80
|
+
@project = @account.projects.first
|
47
81
|
end
|
48
82
|
|
49
|
-
describe "
|
50
|
-
it "should return it's
|
51
|
-
@other_employee = Employee.create(:email => "woo@war.com", :account_id => "ANOTHER_ACCOUNT_ID")
|
52
|
-
@account.employees.length.should == 5
|
53
|
-
@account.employees.should_not include(@other_employee)
|
54
|
-
end
|
55
|
-
|
56
|
-
it "should return it's parent account" do
|
83
|
+
describe "belongs_to" do
|
84
|
+
it "should return it's parent" do
|
57
85
|
@employee = @account.employees.first
|
58
86
|
@employee.account.should == @account
|
59
87
|
end
|
60
88
|
end
|
61
89
|
|
62
|
-
describe "
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
90
|
+
describe "has_many" do
|
91
|
+
describe "vanilla" do
|
92
|
+
it "should return it's children" do
|
93
|
+
@account.employees.length.should == 4
|
94
|
+
@account.employees.map{|employee| employee.email}.sort.should == ["foo1@bar.com", "foo2@bar.com", "foo3@bar.com", "foo4@bar.com"]
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe ":class_name" do
|
99
|
+
it "should return it's children" do
|
100
|
+
@account.programmers.length.should == 2
|
101
|
+
end
|
102
|
+
end
|
103
|
+
describe ":inline" do
|
104
|
+
before(:all) do
|
105
|
+
@employee.shirts.clear
|
106
|
+
@blue_shirt = Shirt.create(:color => "White")
|
107
|
+
@pink_shirt = Shirt.create(:color => "Pink")
|
108
|
+
@employee.shirts << {:color => "Blue"}
|
109
|
+
@employee.shirts << @blue_shirt
|
110
|
+
@employee.save
|
111
|
+
@employee = Employee.get(@employee.id)
|
112
|
+
@employee.shirts << @pink_shirt
|
113
|
+
@employee.save
|
114
|
+
end
|
115
|
+
it "should return it's children" do
|
116
|
+
employee = Employee.get(@employee.id)
|
117
|
+
employee.shirts.each do |shirt|
|
118
|
+
shirt.should be_kind_of(Shirt)
|
119
|
+
end
|
120
|
+
employee.shirts.map{|shirt| shirt.color}.should == %w(Blue White Pink)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should have a delete method" do
|
124
|
+
employee = Employee.get(@employee.id)
|
125
|
+
employee.shirts.delete(@pink_shirt)
|
126
|
+
employee.save
|
127
|
+
employee = Employee.get(@employee.id)
|
128
|
+
employee.shirts.map{|shirt| shirt.color}.should == %w(Blue White)
|
129
|
+
end
|
130
|
+
|
131
|
+
it "should have an append method" do
|
132
|
+
employee = Employee.get(@employee.id)
|
133
|
+
employee.shirts << {:color => "Black"}
|
134
|
+
employee.save
|
135
|
+
employee = Employee.get(@employee.id)
|
136
|
+
employee.shirts.map{|shirt| shirt.color}.should == %w(Blue White Black)
|
137
|
+
end
|
138
|
+
|
67
139
|
end
|
68
140
|
|
69
|
-
|
70
|
-
|
71
|
-
|
141
|
+
describe ":through" do
|
142
|
+
it "should return it's 'through' children" do
|
143
|
+
@employee.memberships.should be_empty
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should return it's children" do
|
147
|
+
3.times do |i|
|
148
|
+
p = Project.create(:name => "Project with Invitations No. #{i}", :account_id => @account.id)
|
149
|
+
Membership.create(:employee_id => @employee.id, :project_id => p.id)
|
150
|
+
end
|
151
|
+
|
152
|
+
7.times do |i|
|
153
|
+
e = Employee.create(:email => "bar#{i}@bar.com", :account_id => @account.id)
|
154
|
+
Membership.create(:employee_id => e.id, :project_id => @project.id)
|
155
|
+
end
|
156
|
+
|
157
|
+
@employee.memberships.size.should == 3
|
158
|
+
@employee.projects.size.should == 3
|
159
|
+
@project.memberships.size.should == 7
|
160
|
+
@project.members.size.should == 7
|
161
|
+
end
|
72
162
|
end
|
73
163
|
end
|
74
164
|
end
|
data/spec/lib/model_spec.rb
CHANGED
@@ -72,7 +72,6 @@ class Question
|
|
72
72
|
include CouchSurfer::Model
|
73
73
|
|
74
74
|
key_accessor :q, :a
|
75
|
-
couchrest_type = 'Question'
|
76
75
|
end
|
77
76
|
|
78
77
|
class Person
|
@@ -351,7 +350,7 @@ describe CouchSurfer::Model do
|
|
351
350
|
@event = Event.get e['id']
|
352
351
|
end
|
353
352
|
it "should cast created_at to Time" do
|
354
|
-
@event
|
353
|
+
@event.occurs_at.should be_an_instance_of(Time)
|
355
354
|
end
|
356
355
|
end
|
357
356
|
|
@@ -534,6 +533,7 @@ describe CouchSurfer::Model do
|
|
534
533
|
foundart.created_at.should == foundart.updated_at
|
535
534
|
end
|
536
535
|
it "should set the time on update" do
|
536
|
+
sleep 1 # HACK!! Sometimes takes less than a second to call save the second time. Really should mock this!
|
537
537
|
@art.save
|
538
538
|
@art.created_at.should < @art.updated_at
|
539
539
|
end
|
@@ -15,7 +15,7 @@ class User
|
|
15
15
|
validates_format_of :email, :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i
|
16
16
|
validates_length_of :postcode, :is => 7, :message => "not the correct length"
|
17
17
|
validates_uniqueness_of :name, :message => "No two Beatles have the same name"
|
18
|
-
validates_uniqueness_of :email, :view =>
|
18
|
+
validates_uniqueness_of :email, :view => :by_account_id_and_email, :query => lambda{ {:key => [account_id, email]} }, :message => "Already taken!"
|
19
19
|
end
|
20
20
|
|
21
21
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: addywaddy-couch_surfer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adam Groves
|
@@ -40,7 +40,7 @@ dependencies:
|
|
40
40
|
version: 0.12.2
|
41
41
|
version:
|
42
42
|
description: CouchSurfer provides an ORM for CouchDB, as well as supporting association and validation declarations.
|
43
|
-
email:
|
43
|
+
email: adam.groves@gmail.com
|
44
44
|
executables: []
|
45
45
|
|
46
46
|
extensions: []
|