langalex-couch_potato 0.2.8 → 0.2.9
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/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
|