langalex-couch_potato 0.2.8 → 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.md +12 -0
- data/README.md +27 -3
- data/VERSION.yml +2 -2
- data/lib/couch_potato.rb +4 -4
- data/lib/couch_potato/database.rb +24 -17
- data/lib/couch_potato/persistence.rb +4 -2
- data/lib/couch_potato/persistence/attachments.rb +26 -0
- data/lib/couch_potato/persistence/dirty_attributes.rb +24 -2
- data/lib/couch_potato/persistence/ghost_attributes.rb +22 -0
- data/lib/couch_potato/persistence/properties.rb +1 -6
- data/lib/couch_potato/persistence/simple_property.rb +48 -42
- data/spec/attachments_spec.rb +19 -0
- data/spec/default_property_spec.rb +34 -0
- data/spec/property_spec.rb +19 -1
- data/spec/spec_helper.rb +0 -2
- data/spec/unit/attributes_spec.rb +15 -0
- data/spec/unit/couch_potato_spec.rb +20 -0
- data/spec/unit/database_spec.rb +13 -1
- data/spec/unit/dirty_attributes_spec.rb +19 -1
- metadata +15 -6
- data/lib/couch_potato/persistence/belongs_to_property.rb +0 -58
data/CHANGES.md
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
## Changes
|
2
|
+
|
3
|
+
### 0.2.9
|
4
|
+
|
5
|
+
* allow to overwrite attribute accessor of properties and use super to call the original accessors
|
6
|
+
* allow read access to attributes that are present in the Couchdb document but not defined as properties
|
7
|
+
* support default values for properties via the :default parameter
|
8
|
+
* support attachments via the _attachments property
|
9
|
+
* support for namespaces models
|
10
|
+
* removed belongs_to macro for now
|
11
|
+
* removed CouchPotato::Config.database_server, just set CouchPotato::Config.database_name to the full url if you are not using localhost:5984
|
12
|
+
* Ruby 1.9 was broken and is now working again
|
data/README.md
CHANGED
@@ -35,11 +35,11 @@ Alternatively you can download or clone the source repository and then require l
|
|
35
35
|
|
36
36
|
You MUST specificy the name of the database:
|
37
37
|
|
38
|
-
CouchPotato::Config.database_name = '
|
38
|
+
CouchPotato::Config.database_name = 'name_of_the_db'
|
39
39
|
|
40
|
-
The server URL will default to http://localhost:5984/ unless specified
|
40
|
+
The server URL will default to http://localhost:5984/ unless specified:
|
41
41
|
|
42
|
-
CouchPotato::Config.
|
42
|
+
CouchPotato::Config.database_name = "http://example.com:5984/name_of_the_db"
|
43
43
|
|
44
44
|
#### Using with Rails
|
45
45
|
|
@@ -88,6 +88,14 @@ Properties can be of any type:
|
|
88
88
|
property :address, :type => Address
|
89
89
|
end
|
90
90
|
|
91
|
+
Properties can have a default value
|
92
|
+
|
93
|
+
class User
|
94
|
+
include CouchPotato::Persistence
|
95
|
+
|
96
|
+
property :active, :default => true
|
97
|
+
end
|
98
|
+
|
91
99
|
Now you can save your objects. All database operations are encapsulated in the CouchPotato::Database class. This separates your domain logic from the database access logic which makes it easier to write tests and also keeps you models smaller and cleaner.
|
92
100
|
|
93
101
|
user = User.new :name => 'joe'
|
@@ -237,6 +245,22 @@ Supported callbacks are: :before_validation, :before_validation_on_create, :befo
|
|
237
245
|
|
238
246
|
If you need access to the database in a callback: Couch Potato automatically assigns a database instance to the model before saving and when loading. It is available as _database_ accessor from within your model instance.
|
239
247
|
|
248
|
+
#### Attachments
|
249
|
+
|
250
|
+
There is basic attachment support: if you want to store any attachments set that _attachments attribute of a model before saving like this:
|
251
|
+
|
252
|
+
class User
|
253
|
+
include CouchPotato::Persistence
|
254
|
+
end
|
255
|
+
|
256
|
+
user = User.new
|
257
|
+
user._attachments = {'photo' => {'data' => '[image byte data]', 'content_type' => 'image/png'}}
|
258
|
+
|
259
|
+
When saving this object an attachment with the name _photo_ will be uploaded into CouchDB. It will be available under the url of the user object + _/photo_. When loading the user at a later time you still have access to the _content_type_ and additionally to the _length_ of the attachment:
|
260
|
+
|
261
|
+
user_reloaded = CouchPotato.database.load user.id
|
262
|
+
user_reloaded._attachments['photo'] # => {'content_type' => 'image/png', 'length' => 37861}
|
263
|
+
|
240
264
|
#### Testing
|
241
265
|
|
242
266
|
To make testing easier and faster database logic has been put into its own class, which you can replace and stub out in whatever way you want:
|
data/VERSION.yml
CHANGED
data/lib/couch_potato.rb
CHANGED
@@ -7,7 +7,7 @@ require 'ostruct'
|
|
7
7
|
|
8
8
|
|
9
9
|
module CouchPotato
|
10
|
-
Config =
|
10
|
+
Config = Struct.new(:database_name).new
|
11
11
|
|
12
12
|
# Returns a database instance which you can then use to create objects and query views. You have to set the CouchPotato::Config.database_name before this works.
|
13
13
|
def self.database
|
@@ -23,10 +23,10 @@ module CouchPotato
|
|
23
23
|
|
24
24
|
def self.full_url_to_database
|
25
25
|
raise('No Database configured. Set CouchPotato::Config.database_name') unless CouchPotato::Config.database_name
|
26
|
-
if CouchPotato::Config.
|
27
|
-
|
26
|
+
if CouchPotato::Config.database_name[0,7] == 'http://'
|
27
|
+
CouchPotato::Config.database_name
|
28
28
|
else
|
29
|
-
|
29
|
+
"http://127.0.0.1:5984/#{CouchPotato::Config.database_name}"
|
30
30
|
end
|
31
31
|
end
|
32
32
|
end
|
@@ -1,24 +1,24 @@
|
|
1
1
|
module CouchPotato
|
2
2
|
class Database
|
3
|
-
|
3
|
+
|
4
4
|
class ValidationsFailedError < ::StandardError; end
|
5
|
-
|
5
|
+
|
6
6
|
def initialize(couchrest_database)
|
7
7
|
@database = couchrest_database
|
8
8
|
begin
|
9
|
-
couchrest_database.info
|
9
|
+
couchrest_database.info
|
10
10
|
rescue RestClient::ResourceNotFound
|
11
11
|
raise "Database '#{couchrest_database.name}' does not exist."
|
12
12
|
end
|
13
13
|
end
|
14
|
-
|
14
|
+
|
15
15
|
def view(spec)
|
16
16
|
results = CouchPotato::View::ViewQuery.new(database,
|
17
17
|
spec.design_document, spec.view_name, spec.map_function,
|
18
18
|
spec.reduce_function).query_view!(spec.view_parameters)
|
19
19
|
spec.process_results results
|
20
20
|
end
|
21
|
-
|
21
|
+
|
22
22
|
def save_document(document)
|
23
23
|
return true unless document.dirty?
|
24
24
|
if document.new?
|
@@ -28,12 +28,12 @@ module CouchPotato
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
alias_method :save, :save_document
|
31
|
-
|
31
|
+
|
32
32
|
def save_document!(document)
|
33
33
|
save_document(document) || raise(ValidationsFailedError.new(document.errors.full_messages))
|
34
34
|
end
|
35
35
|
alias_method :save!, :save_document!
|
36
|
-
|
36
|
+
|
37
37
|
def destroy_document(document)
|
38
38
|
document.run_callbacks :before_destroy
|
39
39
|
document._deleted = true
|
@@ -43,12 +43,13 @@ module CouchPotato
|
|
43
43
|
document._rev = nil
|
44
44
|
end
|
45
45
|
alias_method :destroy, :destroy_document
|
46
|
-
|
46
|
+
|
47
47
|
def load_document(id)
|
48
48
|
raise "Can't load a document without an id (got nil)" if id.nil?
|
49
49
|
begin
|
50
50
|
json = database.get(id)
|
51
|
-
|
51
|
+
klass = json['ruby_class'].split('::').inject(Class){|scope, const| scope.const_get(const)}
|
52
|
+
instance = klass.json_create json
|
52
53
|
instance.database = self
|
53
54
|
instance
|
54
55
|
rescue(RestClient::ResourceNotFound)
|
@@ -56,13 +57,19 @@ module CouchPotato
|
|
56
57
|
end
|
57
58
|
end
|
58
59
|
alias_method :load, :load_document
|
59
|
-
|
60
|
+
|
60
61
|
def inspect
|
61
62
|
"#<CouchPotato::Database>"
|
62
63
|
end
|
63
|
-
|
64
|
+
|
64
65
|
private
|
65
|
-
|
66
|
+
|
67
|
+
def clean_hash(hash)
|
68
|
+
hash.each do |k,v|
|
69
|
+
hash.delete k unless v
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
66
73
|
def create_document(document)
|
67
74
|
document.database = self
|
68
75
|
document.run_callbacks :before_validation_on_save
|
@@ -70,30 +77,30 @@ module CouchPotato
|
|
70
77
|
return unless document.valid?
|
71
78
|
document.run_callbacks :before_save
|
72
79
|
document.run_callbacks :before_create
|
73
|
-
res = database.save_doc document.to_hash
|
80
|
+
res = database.save_doc clean_hash(document.to_hash)
|
74
81
|
document._rev = res['rev']
|
75
82
|
document._id = res['id']
|
76
83
|
document.run_callbacks :after_save
|
77
84
|
document.run_callbacks :after_create
|
78
85
|
true
|
79
86
|
end
|
80
|
-
|
87
|
+
|
81
88
|
def update_document(document)
|
82
89
|
document.run_callbacks :before_validation_on_save
|
83
90
|
document.run_callbacks :before_validation_on_update
|
84
91
|
return unless document.valid?
|
85
92
|
document.run_callbacks :before_save
|
86
93
|
document.run_callbacks :before_update
|
87
|
-
res = database.save_doc document.to_hash
|
94
|
+
res = database.save_doc clean_hash(document.to_hash)
|
88
95
|
document._rev = res['rev']
|
89
96
|
document.run_callbacks :after_save
|
90
97
|
document.run_callbacks :after_update
|
91
98
|
true
|
92
99
|
end
|
93
|
-
|
100
|
+
|
94
101
|
def database
|
95
102
|
@database
|
96
103
|
end
|
97
|
-
|
104
|
+
|
98
105
|
end
|
99
106
|
end
|
@@ -5,6 +5,8 @@ require File.dirname(__FILE__) + '/persistence/magic_timestamps'
|
|
5
5
|
require File.dirname(__FILE__) + '/persistence/callbacks'
|
6
6
|
require File.dirname(__FILE__) + '/persistence/json'
|
7
7
|
require File.dirname(__FILE__) + '/persistence/dirty_attributes'
|
8
|
+
require File.dirname(__FILE__) + '/persistence/ghost_attributes'
|
9
|
+
require File.dirname(__FILE__) + '/persistence/attachments'
|
8
10
|
require File.dirname(__FILE__) + '/persistence/validation'
|
9
11
|
require File.dirname(__FILE__) + '/view/custom_views'
|
10
12
|
require File.dirname(__FILE__) + '/view/view_query'
|
@@ -15,10 +17,10 @@ module CouchPotato
|
|
15
17
|
|
16
18
|
def self.included(base)
|
17
19
|
base.send :include, Properties, Callbacks, Validation, Json, CouchPotato::View::CustomViews
|
18
|
-
base.send :include, DirtyAttributes
|
20
|
+
base.send :include, DirtyAttributes, GhostAttributes, Attachments
|
19
21
|
base.send :include, MagicTimestamps
|
20
22
|
base.class_eval do
|
21
|
-
attr_accessor :_id, :_rev, :
|
23
|
+
attr_accessor :_id, :_rev, :_deleted, :database
|
22
24
|
alias_method :id, :_id
|
23
25
|
end
|
24
26
|
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module Attachments
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
attr_accessor :_attachments
|
6
|
+
base.extend ClassMethods
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_hash
|
11
|
+
if _attachments
|
12
|
+
super.merge('_attachments' => _attachments)
|
13
|
+
else
|
14
|
+
super
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def json_create(json)
|
20
|
+
instance = super
|
21
|
+
instance._attachments = json['_attachments'] if json
|
22
|
+
instance
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -5,6 +5,11 @@ module CouchPotato
|
|
5
5
|
def self.included(base)
|
6
6
|
base.class_eval do
|
7
7
|
after_save :reset_dirty_attributes
|
8
|
+
|
9
|
+
def initialize(attributes = {})
|
10
|
+
super
|
11
|
+
assign_attribute_copies_for_dirty_tracking
|
12
|
+
end
|
8
13
|
end
|
9
14
|
end
|
10
15
|
|
@@ -17,11 +22,28 @@ module CouchPotato
|
|
17
22
|
|
18
23
|
private
|
19
24
|
|
25
|
+
def assign_attribute_copies_for_dirty_tracking
|
26
|
+
attributes.each do |name, value|
|
27
|
+
self.instance_variable_set("@#{name}_was", clone_attribute(value))
|
28
|
+
end if attributes
|
29
|
+
end
|
30
|
+
|
20
31
|
def reset_dirty_attributes
|
21
32
|
self.class.properties.each do |property|
|
22
|
-
instance_variable_set("@#{property.name}_was", send(property.name))
|
33
|
+
instance_variable_set("@#{property.name}_was", clone_attribute(send(property.name)))
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def clone_attribute(value)
|
38
|
+
if [Fixnum, Symbol, TrueClass, FalseClass, NilClass, Float].include?(value.class)
|
39
|
+
value
|
40
|
+
elsif [Hash, Array].include?(value.class)
|
41
|
+
#Deep clone
|
42
|
+
Marshal::load(Marshal::dump(value))
|
43
|
+
else
|
44
|
+
value.clone
|
23
45
|
end
|
24
46
|
end
|
25
47
|
end
|
26
48
|
end
|
27
|
-
end
|
49
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module GhostAttributes
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
attr_accessor :_document
|
6
|
+
def self.json_create(json)
|
7
|
+
instance = super
|
8
|
+
instance._document = json if json
|
9
|
+
instance
|
10
|
+
end
|
11
|
+
|
12
|
+
def method_missing(name, *args)
|
13
|
+
if(value = _document[name.to_s])
|
14
|
+
value
|
15
|
+
else
|
16
|
+
super
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require File.dirname(__FILE__) + '/simple_property'
|
2
|
-
require File.dirname(__FILE__) + '/belongs_to_property'
|
3
2
|
|
4
3
|
module CouchPotato
|
5
4
|
module Persistence
|
@@ -13,7 +12,7 @@ module CouchPotato
|
|
13
12
|
end
|
14
13
|
end
|
15
14
|
end
|
16
|
-
|
15
|
+
|
17
16
|
module ClassMethods
|
18
17
|
# returns all the property names of a model class that have been defined using the #property method
|
19
18
|
#
|
@@ -47,10 +46,6 @@ module CouchPotato
|
|
47
46
|
properties << (clazz || SimpleProperty).new(self, name, options)
|
48
47
|
end
|
49
48
|
|
50
|
-
def belongs_to(name) #:nodoc:
|
51
|
-
property name, :class => BelongsToProperty
|
52
|
-
end
|
53
|
-
|
54
49
|
end
|
55
50
|
end
|
56
51
|
end
|
@@ -6,55 +6,18 @@ module CouchPotato
|
|
6
6
|
def initialize(owner_clazz, name, options = {})
|
7
7
|
self.name = name
|
8
8
|
self.type = options[:type]
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
def initialize(attributes = {})
|
13
|
-
super attributes
|
14
|
-
assign_attribute_copies_for_dirty_tracking
|
15
|
-
end
|
16
|
-
|
17
|
-
def assign_attribute_copies_for_dirty_tracking
|
18
|
-
attributes.each do |name, value|
|
19
|
-
self.instance_variable_set("@#{name}_was", clone_attribute(value))
|
20
|
-
end if attributes
|
21
|
-
end
|
22
|
-
private :assign_attribute_copies_for_dirty_tracking
|
23
|
-
|
24
|
-
def clone_attribute(value)
|
25
|
-
if [Fixnum, Symbol, TrueClass, FalseClass, NilClass, Float].include?(value.class)
|
26
|
-
value
|
27
|
-
else
|
28
|
-
value.clone
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
define_method "#{name}=" do |value|
|
33
|
-
self.instance_variable_set("@#{name}", value)
|
34
|
-
end
|
35
|
-
|
36
|
-
define_method "#{name}?" do
|
37
|
-
!self.send(name).nil? && !self.send(name).try(:blank?)
|
38
|
-
end
|
39
|
-
|
40
|
-
define_method "#{name}_changed?" do
|
41
|
-
!self.instance_variable_get("@#{name}_not_changed") && self.send(name) != self.send("#{name}_was")
|
42
|
-
end
|
43
|
-
|
44
|
-
define_method "#{name}_not_changed" do
|
45
|
-
self.instance_variable_set("@#{name}_not_changed", true)
|
46
|
-
end
|
47
|
-
end
|
9
|
+
|
10
|
+
define_accessors accessors_module_for(owner_clazz), name, options
|
48
11
|
end
|
49
12
|
|
50
13
|
def build(object, json)
|
51
14
|
value = json[name.to_s] || json[name.to_sym]
|
52
|
-
|
15
|
+
typecast_value = if type
|
53
16
|
type.json_create value
|
54
17
|
else
|
55
18
|
value
|
56
19
|
end
|
57
|
-
object.send "#{name}=",
|
20
|
+
object.send "#{name}=", typecast_value
|
58
21
|
end
|
59
22
|
|
60
23
|
def dirty?(object)
|
@@ -72,6 +35,49 @@ module CouchPotato
|
|
72
35
|
def serialize(json, object)
|
73
36
|
json[name] = object.send name
|
74
37
|
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def accessors_module_for(clazz)
|
42
|
+
unless clazz.const_defined?('AccessorMethods')
|
43
|
+
accessors_module = clazz.const_set('AccessorMethods', Module.new)
|
44
|
+
clazz.send(:include, accessors_module)
|
45
|
+
end
|
46
|
+
clazz.const_get('AccessorMethods')
|
47
|
+
end
|
48
|
+
|
49
|
+
def define_accessors(base, name, options)
|
50
|
+
base.class_eval do
|
51
|
+
attr_reader "#{name}_was"
|
52
|
+
|
53
|
+
define_method "#{name}" do
|
54
|
+
value = self.instance_variable_get("@#{name}")
|
55
|
+
if value.blank? && options[:default]
|
56
|
+
default = clone_attribute(options[:default])
|
57
|
+
self.instance_variable_set("@#{name}", default)
|
58
|
+
default
|
59
|
+
else
|
60
|
+
value
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
define_method "#{name}=" do |value|
|
65
|
+
self.instance_variable_set("@#{name}", value)
|
66
|
+
end
|
67
|
+
|
68
|
+
define_method "#{name}?" do
|
69
|
+
!self.send(name).nil? && !self.send(name).try(:blank?)
|
70
|
+
end
|
71
|
+
|
72
|
+
define_method "#{name}_changed?" do
|
73
|
+
!self.instance_variable_get("@#{name}_not_changed") && self.send(name) != self.send("#{name}_was")
|
74
|
+
end
|
75
|
+
|
76
|
+
define_method "#{name}_not_changed" do
|
77
|
+
self.instance_variable_set("@#{name}_not_changed", true)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
75
81
|
end
|
76
82
|
end
|
77
|
-
end
|
83
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe CouchPotato, 'attachments' do
|
4
|
+
it "should persist an attachment" do
|
5
|
+
comment = Comment.new :title => 'nil'
|
6
|
+
comment._attachments = {'body' => {'data' => 'a useful comment', 'content_type' => 'text/plain'}}
|
7
|
+
CouchPotato.database.save! comment
|
8
|
+
CouchPotato.couchrest_database.fetch_attachment(comment.to_hash, 'body').to_s.should == 'a useful comment'
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should give me information about the attachments of a document" do
|
12
|
+
comment = Comment.new :title => 'nil'
|
13
|
+
comment._attachments = {'body' => {'data' => 'a useful comment', 'content_type' => 'text/plain'}}
|
14
|
+
CouchPotato.database.save! comment
|
15
|
+
comment_reloaded = CouchPotato.database.load comment.id
|
16
|
+
comment_reloaded._attachments.should == {"body" => {"content_type" => "text/plain", "stub" => true, "length" => 16}}
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
class Test
|
4
|
+
include CouchPotato::Persistence
|
5
|
+
|
6
|
+
property :test, :default => 'Test value'
|
7
|
+
property :complex, :default => []
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'default properties' do
|
11
|
+
before(:all) do
|
12
|
+
recreate_db
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should use the default value if nothing is supplied" do
|
16
|
+
t = Test.new
|
17
|
+
|
18
|
+
t.test.should == 'Test value'
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should persist the default value if nothing is supplied" do
|
22
|
+
t = Test.new
|
23
|
+
CouchPotato.database.save_document! t
|
24
|
+
|
25
|
+
t = CouchPotato.database.load_document t.id
|
26
|
+
t.test.should == 'Test value'
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should not have the same default for two instances of the object" do
|
30
|
+
t = Test.new
|
31
|
+
t2 = Test.new
|
32
|
+
t.complex.object_id.should_not == t2.complex.object_id
|
33
|
+
end
|
34
|
+
end
|
data/spec/property_spec.rb
CHANGED
@@ -6,6 +6,16 @@ class Watch
|
|
6
6
|
include CouchPotato::Persistence
|
7
7
|
|
8
8
|
property :time, :type => Time
|
9
|
+
property :overwritten_read
|
10
|
+
property :overwritten_write
|
11
|
+
|
12
|
+
def overwritten_read
|
13
|
+
super.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
def overwritten_write=(value)
|
17
|
+
super value.to_s
|
18
|
+
end
|
9
19
|
end
|
10
20
|
|
11
21
|
|
@@ -14,8 +24,16 @@ describe 'properties' do
|
|
14
24
|
recreate_db
|
15
25
|
end
|
16
26
|
|
27
|
+
it "should allow me to overwrite read accessor and call super" do
|
28
|
+
Watch.new(:overwritten_read => 1).overwritten_read.should == '1'
|
29
|
+
end
|
30
|
+
|
31
|
+
it "should allow me to overwrite write accessor and call super" do
|
32
|
+
Watch.new(:overwritten_write => 1).overwritten_write.should == '1'
|
33
|
+
end
|
34
|
+
|
17
35
|
it "should return the property names" do
|
18
|
-
Comment.property_names.should == [:created_at, :updated_at, :title
|
36
|
+
Comment.property_names.should == [:created_at, :updated_at, :title]
|
19
37
|
end
|
20
38
|
|
21
39
|
it "should persist a string" do
|
data/spec/spec_helper.rb
CHANGED
@@ -6,7 +6,6 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
|
|
6
6
|
require 'couch_potato'
|
7
7
|
|
8
8
|
CouchPotato::Config.database_name = 'couch_potato_test'
|
9
|
-
CouchPotato::Config.database_server = 'http://127.0.0.1:5984/'
|
10
9
|
|
11
10
|
|
12
11
|
class Comment
|
@@ -15,7 +14,6 @@ class Comment
|
|
15
14
|
validates_presence_of :title
|
16
15
|
|
17
16
|
property :title
|
18
|
-
belongs_to :commenter
|
19
17
|
end
|
20
18
|
|
21
19
|
def recreate_db
|
@@ -21,6 +21,21 @@ describe "attributes" do
|
|
21
21
|
plant.attributes.should == {:leaf_count => 1, :created_at => nil, :updated_at => nil}
|
22
22
|
end
|
23
23
|
end
|
24
|
+
|
25
|
+
# useful when loading models from custom views
|
26
|
+
describe "accessing non-property attributes" do
|
27
|
+
it "should allow me to access attributes that are in the couchdb document " do
|
28
|
+
plant = Plant.json_create({"ruby_class" => "Plant", "color" => "red", "leaf_count" => 1})
|
29
|
+
plant.color.should == 'red'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should raise a no method error when trying to read attributes that are not in the document" do
|
33
|
+
plant = Plant.json_create({"ruby_class" => "Plant", "leaf_count" => 1})
|
34
|
+
lambda do
|
35
|
+
plant.length
|
36
|
+
end.should raise_error(NoMethodError)
|
37
|
+
end
|
38
|
+
end
|
24
39
|
|
25
40
|
end
|
26
41
|
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe CouchPotato, 'full_url_to_database' do
|
4
|
+
before(:each) do
|
5
|
+
@original_database_name = CouchPotato::Config.database_name
|
6
|
+
end
|
7
|
+
after(:each) do
|
8
|
+
CouchPotato::Config.database_name = @original_database_name
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should add the default localhost and port if only a name is set" do
|
12
|
+
CouchPotato::Config.database_name = 'test'
|
13
|
+
CouchPotato.full_url_to_database.should == 'http://127.0.0.1:5984/test'
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should return the set url" do
|
17
|
+
CouchPotato::Config.database_name = 'http://db.local/test'
|
18
|
+
CouchPotato.full_url_to_database.should == 'http://db.local/test'
|
19
|
+
end
|
20
|
+
end
|
data/spec/unit/database_spec.rb
CHANGED
@@ -3,6 +3,13 @@ require File.dirname(__FILE__) + '/../spec_helper'
|
|
3
3
|
class DbTestUser
|
4
4
|
end
|
5
5
|
|
6
|
+
# namespaced model
|
7
|
+
module Parent
|
8
|
+
class Child
|
9
|
+
include CouchPotato::Persistence
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
6
13
|
describe CouchPotato::Database, 'new' do
|
7
14
|
it "should raise an exception if the database doesn't exist" do
|
8
15
|
lambda {
|
@@ -18,7 +25,7 @@ describe CouchPotato::Database, 'load' do
|
|
18
25
|
db.load nil
|
19
26
|
}.should raise_error("Can't load a document without an id (got nil)")
|
20
27
|
end
|
21
|
-
|
28
|
+
|
22
29
|
it "should set itself on the model" do
|
23
30
|
user = mock 'user'
|
24
31
|
DbTestUser.stub!(:new).and_return(user)
|
@@ -26,6 +33,11 @@ describe CouchPotato::Database, 'load' do
|
|
26
33
|
user.should_receive(:database=).with(db)
|
27
34
|
db.load '1'
|
28
35
|
end
|
36
|
+
|
37
|
+
it "should load namespaced models" do
|
38
|
+
db = CouchPotato::Database.new(stub('couchrest db', :info => nil, :get => {'ruby_class' => 'Parent::Child'}))
|
39
|
+
db.load('1').class.should == Parent::Child
|
40
|
+
end
|
29
41
|
end
|
30
42
|
|
31
43
|
describe CouchPotato::Database, 'save_document' do
|
@@ -33,6 +33,24 @@ describe 'dirty attribute tracking' do
|
|
33
33
|
@couchrest_db.should_receive(:save_doc)
|
34
34
|
@db.save_document(plate)
|
35
35
|
end
|
36
|
+
|
37
|
+
it "should correctly track dirty hashes (deep clone)" do
|
38
|
+
plate = Plate.new :food => {:veggies => ['carrots', 'peas']}
|
39
|
+
@db.save_document(plate)
|
40
|
+
plate.food[:veggies] << 'beans'
|
41
|
+
@couchrest_db.should_receive(:save_doc)
|
42
|
+
@db.save_document(plate)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should correctly track dirty hashes (deep clone) after a save" do
|
46
|
+
plate = Plate.new :food => {:veggies => ['carrots', 'peas']}
|
47
|
+
@db.save_document(plate)
|
48
|
+
plate.food[:veggies] << 'beans'
|
49
|
+
@db.save_document(plate)
|
50
|
+
plate.food[:veggies] << 'cauliflower'
|
51
|
+
@couchrest_db.should_receive(:save_doc)
|
52
|
+
@db.save_document(plate)
|
53
|
+
end
|
36
54
|
end
|
37
55
|
|
38
56
|
describe "newly created object" do
|
@@ -110,4 +128,4 @@ describe 'dirty attribute tracking' do
|
|
110
128
|
end
|
111
129
|
end
|
112
130
|
|
113
|
-
end
|
131
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: langalex-couch_potato
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexander Lang
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-09-18 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -51,6 +51,7 @@ extensions: []
|
|
51
51
|
extra_rdoc_files:
|
52
52
|
- README.md
|
53
53
|
files:
|
54
|
+
- CHANGES.md
|
54
55
|
- MIT-LICENSE.txt
|
55
56
|
- README.md
|
56
57
|
- VERSION.yml
|
@@ -63,9 +64,10 @@ files:
|
|
63
64
|
- lib/couch_potato.rb
|
64
65
|
- lib/couch_potato/database.rb
|
65
66
|
- lib/couch_potato/persistence.rb
|
66
|
-
- lib/couch_potato/persistence/
|
67
|
+
- lib/couch_potato/persistence/attachments.rb
|
67
68
|
- lib/couch_potato/persistence/callbacks.rb
|
68
69
|
- lib/couch_potato/persistence/dirty_attributes.rb
|
70
|
+
- lib/couch_potato/persistence/ghost_attributes.rb
|
69
71
|
- lib/couch_potato/persistence/json.rb
|
70
72
|
- lib/couch_potato/persistence/magic_timestamps.rb
|
71
73
|
- lib/couch_potato/persistence/properties.rb
|
@@ -79,9 +81,11 @@ files:
|
|
79
81
|
- lib/couch_potato/view/raw_view_spec.rb
|
80
82
|
- lib/couch_potato/view/view_query.rb
|
81
83
|
- rails/init.rb
|
84
|
+
- spec/attachments_spec.rb
|
82
85
|
- spec/callbacks_spec.rb
|
83
86
|
- spec/create_spec.rb
|
84
87
|
- spec/custom_view_spec.rb
|
88
|
+
- spec/default_property_spec.rb
|
85
89
|
- spec/destroy_spec.rb
|
86
90
|
- spec/fixtures/address.rb
|
87
91
|
- spec/fixtures/person.rb
|
@@ -90,6 +94,7 @@ files:
|
|
90
94
|
- spec/spec_helper.rb
|
91
95
|
- spec/unit/attributes_spec.rb
|
92
96
|
- spec/unit/callbacks_spec.rb
|
97
|
+
- spec/unit/couch_potato_spec.rb
|
93
98
|
- spec/unit/create_spec.rb
|
94
99
|
- spec/unit/customs_views_spec.rb
|
95
100
|
- spec/unit/database_spec.rb
|
@@ -97,8 +102,9 @@ files:
|
|
97
102
|
- spec/unit/string_spec.rb
|
98
103
|
- spec/unit/view_query_spec.rb
|
99
104
|
- spec/update_spec.rb
|
100
|
-
has_rdoc:
|
105
|
+
has_rdoc: false
|
101
106
|
homepage: http://github.com/langalex/couch_potato
|
107
|
+
licenses:
|
102
108
|
post_install_message:
|
103
109
|
rdoc_options:
|
104
110
|
- --charset=UTF-8
|
@@ -119,14 +125,16 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
119
125
|
requirements: []
|
120
126
|
|
121
127
|
rubyforge_project:
|
122
|
-
rubygems_version: 1.
|
128
|
+
rubygems_version: 1.3.5
|
123
129
|
signing_key:
|
124
|
-
specification_version:
|
130
|
+
specification_version: 3
|
125
131
|
summary: Ruby persistence layer for CouchDB
|
126
132
|
test_files:
|
133
|
+
- spec/attachments_spec.rb
|
127
134
|
- spec/callbacks_spec.rb
|
128
135
|
- spec/create_spec.rb
|
129
136
|
- spec/custom_view_spec.rb
|
137
|
+
- spec/default_property_spec.rb
|
130
138
|
- spec/destroy_spec.rb
|
131
139
|
- spec/fixtures/address.rb
|
132
140
|
- spec/fixtures/person.rb
|
@@ -134,6 +142,7 @@ test_files:
|
|
134
142
|
- spec/spec_helper.rb
|
135
143
|
- spec/unit/attributes_spec.rb
|
136
144
|
- spec/unit/callbacks_spec.rb
|
145
|
+
- spec/unit/couch_potato_spec.rb
|
137
146
|
- spec/unit/create_spec.rb
|
138
147
|
- spec/unit/customs_views_spec.rb
|
139
148
|
- spec/unit/database_spec.rb
|
@@ -1,58 +0,0 @@
|
|
1
|
-
module CouchPotato
|
2
|
-
module Persistence
|
3
|
-
class BelongsToProperty #:nodoc:
|
4
|
-
attr_accessor :name
|
5
|
-
|
6
|
-
def initialize(owner_clazz, name, options = {})
|
7
|
-
self.name = name
|
8
|
-
accessors = <<-ACCESSORS
|
9
|
-
def #{name}
|
10
|
-
return @#{name} if instance_variable_defined?(:@#{name})
|
11
|
-
@#{name} = @#{name}_id ? #{item_class_name}.find(@#{name}_id) : nil
|
12
|
-
end
|
13
|
-
|
14
|
-
def #{name}=(value)
|
15
|
-
@#{name} = value
|
16
|
-
if value.nil?
|
17
|
-
@#{name}_id = nil
|
18
|
-
else
|
19
|
-
@#{name}_id = value.id
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def #{name}_id=(id)
|
24
|
-
remove_instance_variable(:@#{name}) if instance_variable_defined?(:@#{name})
|
25
|
-
@#{name}_id = id
|
26
|
-
end
|
27
|
-
ACCESSORS
|
28
|
-
owner_clazz.class_eval accessors
|
29
|
-
owner_clazz.send :attr_reader, "#{name}_id"
|
30
|
-
end
|
31
|
-
|
32
|
-
def save(object)
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
def dirty?(object)
|
37
|
-
false
|
38
|
-
end
|
39
|
-
|
40
|
-
def destroy(object)
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
def build(object, json)
|
45
|
-
object.send "#{name}_id=", json["#{name}_id"]
|
46
|
-
end
|
47
|
-
|
48
|
-
def serialize(json, object)
|
49
|
-
json["#{name}_id"] = object.send("#{name}_id") if object.send("#{name}_id")
|
50
|
-
end
|
51
|
-
|
52
|
-
def item_class_name
|
53
|
-
@name.to_s.camelize
|
54
|
-
end
|
55
|
-
|
56
|
-
end
|
57
|
-
end
|
58
|
-
end
|