addywaddy-couch_surfer 0.0.2 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|