couchmodel 0.1.0.beta2
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/LICENSE +20 -0
- data/README.rdoc +156 -0
- data/Rakefile +20 -0
- data/lib/core_extension/array.rb +14 -0
- data/lib/core_extension/string.rb +12 -0
- data/lib/couch_model/active_model.rb +86 -0
- data/lib/couch_model/base/accessor.rb +39 -0
- data/lib/couch_model/base/association.rb +63 -0
- data/lib/couch_model/base/finder.rb +28 -0
- data/lib/couch_model/base/setup.rb +88 -0
- data/lib/couch_model/base.rb +117 -0
- data/lib/couch_model/collection.rb +84 -0
- data/lib/couch_model/configuration.rb +68 -0
- data/lib/couch_model/database.rb +64 -0
- data/lib/couch_model/design.rb +92 -0
- data/lib/couch_model/server.rb +44 -0
- data/lib/couch_model/transport.rb +68 -0
- data/lib/couch_model/view.rb +52 -0
- data/lib/couch_model.rb +15 -0
- data/spec/fake_transport.yml +202 -0
- data/spec/fake_transport_helper.rb +27 -0
- data/spec/integration/basic_spec.rb +125 -0
- data/spec/integration/design/membership.design +5 -0
- data/spec/integration/design/user.design +2 -0
- data/spec/lib/core_extension/array_spec.rb +24 -0
- data/spec/lib/core_extension/string_spec.rb +22 -0
- data/spec/lib/couch_model/active_model_spec.rb +228 -0
- data/spec/lib/couch_model/base_spec.rb +169 -0
- data/spec/lib/couch_model/collection_spec.rb +100 -0
- data/spec/lib/couch_model/configuration_spec.rb +117 -0
- data/spec/lib/couch_model/core/accessor_spec.rb +59 -0
- data/spec/lib/couch_model/core/association_spec.rb +114 -0
- data/spec/lib/couch_model/core/finder_spec.rb +24 -0
- data/spec/lib/couch_model/core/setup_spec.rb +88 -0
- data/spec/lib/couch_model/database_spec.rb +165 -0
- data/spec/lib/couch_model/design/association_test_model_one.design +5 -0
- data/spec/lib/couch_model/design/base_test_model.design +10 -0
- data/spec/lib/couch_model/design/setup_test_model.design +10 -0
- data/spec/lib/couch_model/design_spec.rb +144 -0
- data/spec/lib/couch_model/server_spec.rb +64 -0
- data/spec/lib/couch_model/transport_spec.rb +44 -0
- data/spec/lib/couch_model/view_spec.rb +166 -0
- data/spec/lib/couch_model_spec.rb +3 -0
- data/spec/spec_helper.rb +27 -0
- metadata +128 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2010 Philipp Brüll
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
|
2
|
+
= CouchModel
|
3
|
+
|
4
|
+
The intent of CouchModel is, to provide an easy interface handle CouchDB documents. It also comes with a ActiveModel
|
5
|
+
implementation to integrate into an Rails 3 application.
|
6
|
+
|
7
|
+
The current version is under development and open for everyone to find bugs and post them into the issue tracker.
|
8
|
+
|
9
|
+
The code has been tested Ruby 1.9.1, CouchDB 0.10.0 and Rails 3.0.0.beta.
|
10
|
+
|
11
|
+
== Dependencies
|
12
|
+
|
13
|
+
If CouchModel is used without Rails, the ruby standard library (tested with 1.9.1) if the only requirement.
|
14
|
+
|
15
|
+
If the activemodel gem is installed, CouchModel automatically provides an interface to Rails 3.
|
16
|
+
|
17
|
+
To run the test suite, <tt>rspec</tt> (tested with 1.2.9) is required. A CouchDB instance is just required for the
|
18
|
+
integration tests (task <tt>spec:integration</tt>).
|
19
|
+
|
20
|
+
== Installation
|
21
|
+
|
22
|
+
The gem is part of the gemcutter archive. It can be installed by simply type
|
23
|
+
|
24
|
+
gem install couchmodel
|
25
|
+
|
26
|
+
== Defining a model
|
27
|
+
|
28
|
+
To define a model, it's necessary to create a subclass of <tt>CouchModel::Base</tt>
|
29
|
+
|
30
|
+
class User < CouchModel::Base
|
31
|
+
|
32
|
+
setup_database :url => "http://localhost:5984/test",
|
33
|
+
:setup_on_initialization => true,
|
34
|
+
:delete_if_exists => false
|
35
|
+
|
36
|
+
key_accessor :name
|
37
|
+
key_accessor :email
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
The <tt>setup_database</tt> method defines a database for the model. The +url+ option is required and specifies the url
|
42
|
+
of the database in the scheme <em>http://[host]:[port]/[database_name]</em>. If the option
|
43
|
+
<tt>setup_on_initialization</tt> is set to true, CouchModel will try to create the database when the model is
|
44
|
+
initialized. If the option <tt>delete_if_exists</tt> is specified, the database will be deleted and re-created. If the
|
45
|
+
option <tt>setup_on_initialization</tt> is not specified or false, the database setup be done manually by calling
|
46
|
+
<tt>CouchModel::Configuration.setup_databases</tt> and <tt>CouchModel::Configuration.setup_designs</tt>.
|
47
|
+
|
48
|
+
The method <tt>key_accessor</tt> defined access methods to the given keys of the CouchDB document. It's also possible
|
49
|
+
to use <tt>key_reader</tt> and <tt>key_writer</tt> here.
|
50
|
+
|
51
|
+
== Design documents
|
52
|
+
|
53
|
+
Each defined model has a realted design document, that keeps all the views for that model. Via the command
|
54
|
+
|
55
|
+
CouchModel::Configuration.design_directory = "[directory]"
|
56
|
+
|
57
|
+
a directory is specfied that keeps all the design document. CouchModel will watch out for a file with the name
|
58
|
+
<em>[design directory]/[model_name].design</em> and will use it as the related design document. If no such file exists,
|
59
|
+
a design document will be created (but not saved to the file). The design ducument can be asscessed via
|
60
|
+
<tt>Model.design</tt>.
|
61
|
+
|
62
|
+
A design document should look like this
|
63
|
+
|
64
|
+
:id: "test_design"
|
65
|
+
:language: "javascript"
|
66
|
+
:views:
|
67
|
+
"view_name_1":
|
68
|
+
:map:
|
69
|
+
function(document) {
|
70
|
+
...
|
71
|
+
};
|
72
|
+
:reduce:
|
73
|
+
function(key, values, rereduce) {
|
74
|
+
...
|
75
|
+
};
|
76
|
+
"view_name_2":
|
77
|
+
:keys: [ "key_one", "key_two" ]
|
78
|
+
...
|
79
|
+
|
80
|
+
It will create the methods <tt>Model.view_name_1</tt> and <tt>Model.view_name_2</tt>, which returns the result of the
|
81
|
+
related view. It's also possible to pass some extra options like <tt>startkey</tt> or <tt>key</tt> to these methods.
|
82
|
+
|
83
|
+
The view can be defined by write down a map and a reduce function or provide the <tt>keys</tt> array. If the
|
84
|
+
<tt>keys</tt> array is given, CouchModel will generate a map function that emits the given array of document keys. The
|
85
|
+
reduce function will be set to null.
|
86
|
+
|
87
|
+
CouchModel also creates by default a class view. This view simply selects all documents from the corresponding model
|
88
|
+
and is assigned to the method <tt>Model.all</tt>.
|
89
|
+
|
90
|
+
== Associations
|
91
|
+
|
92
|
+
CouchModel provides support for simple association definition. Currently, the method <tt>belongs_to</tt> and
|
93
|
+
<tt>has_many</tt> are implmented.
|
94
|
+
|
95
|
+
class User < CouchModel::Base
|
96
|
+
|
97
|
+
...
|
98
|
+
|
99
|
+
belongs_to :session, :class_name => "UserSession"
|
100
|
+
|
101
|
+
has_many :memberships,
|
102
|
+
:class_name => "Membership",
|
103
|
+
:view_name => :by_user_id_and_created_at
|
104
|
+
:query => proc { |created_at| { :startkey => [ self.id, (create_at || nil) ], :endkey => [ self.id, (created_at || { }) ] } }
|
105
|
+
|
106
|
+
end
|
107
|
+
|
108
|
+
In this example, the <tt>belongs_to</tt> adds a <tt>key_accessor</tt> named <tt>session_id</tt> to the user and also
|
109
|
+
generates getters and setters for the session object itself (<tt>session</tt> and <tt>session=</tt>).
|
110
|
+
|
111
|
+
The <tt>has_many</tt> acts as a wrapper for the specified view. The previously defined view
|
112
|
+
<em>by_user_id_and_created_at</em> emits membership-documents by thier <tt>user_id</tt> and the <tt>created_at</tt>
|
113
|
+
date. The given query option specifes a method that returns a query hash for the specifed view. The arguments for this
|
114
|
+
method can be passed membership association method.
|
115
|
+
|
116
|
+
user.membership(created_at)
|
117
|
+
|
118
|
+
The returned <tt>CouchModel::Collection</tt> can be treated as an (read-only) array.
|
119
|
+
|
120
|
+
== Rails integration
|
121
|
+
|
122
|
+
The following steps has been tested with the first beta version of Rails 3 (activemodel-3.0.0.beta).
|
123
|
+
|
124
|
+
First of all, the <tt>couchmodel</tt> gem has to added to the dependencies. This can be done by adding
|
125
|
+
|
126
|
+
gem "couchmodel", :require => "couch_model"
|
127
|
+
|
128
|
+
to the <tt>Gemfile</tt>.
|
129
|
+
|
130
|
+
The configuration can be done by creating an initializer. Here is an example file
|
131
|
+
(e.g. <tt>config/initializer/couch_model.rb</tt>).
|
132
|
+
|
133
|
+
CouchModel::Configuration.design_directory = File.join(Rails.root, "app", "models", "designs")
|
134
|
+
|
135
|
+
DATABASE = {
|
136
|
+
:test => { :url => "http://localhost:5984/test", :setup_on_initialization => true, :delete_if_exists => true }
|
137
|
+
:development => { :url => "http://localhost:5984/development", :setup_on_initialization => true, :delete_if_exists => false },
|
138
|
+
:production => { :url => "http://localhost:5984/production", :setup_on_initialization => true, :delete_if_exists => false }
|
139
|
+
}[Rails.env.to_sym] unless defined?(DATABASE)
|
140
|
+
|
141
|
+
This example uses an sub-directory of <tt>app/models</tt> to search for the design documents. It also defined a constant
|
142
|
+
named <tt>DATABASE</tt> that is initialized with the right database setup for the each environment. This constant can
|
143
|
+
then be used to define the models.
|
144
|
+
|
145
|
+
class User < CouchModel::Base
|
146
|
+
|
147
|
+
setup_database DATABASE
|
148
|
+
|
149
|
+
...
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
== Development
|
154
|
+
|
155
|
+
CouchModel is still under development and needs to be tested. Any contribution is welcome!
|
156
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
gem 'rspec'
|
3
|
+
require 'spec'
|
4
|
+
require 'spec/rake/spectask'
|
5
|
+
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
desc "Run all specs in spec directory"
|
9
|
+
Spec::Rake::SpecTask.new do |task|
|
10
|
+
task.spec_files = FileList["spec/lib/**/*_spec.rb"]
|
11
|
+
end
|
12
|
+
|
13
|
+
namespace :spec do
|
14
|
+
|
15
|
+
desc "Run all integration specs in spec/integration directory"
|
16
|
+
Spec::Rake::SpecTask.new(:integration) do |task|
|
17
|
+
task.spec_files = FileList["spec/integration/**/*_spec.rb"]
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "core_extension", "array"))
|
2
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "base"))
|
3
|
+
gem 'activemodel'
|
4
|
+
require 'active_model'
|
5
|
+
|
6
|
+
module CouchModel
|
7
|
+
|
8
|
+
class Base
|
9
|
+
extend ::ActiveModel::Naming
|
10
|
+
extend ::ActiveModel::Callbacks
|
11
|
+
extend ::ActiveModel::Translation
|
12
|
+
include ::ActiveModel::Conversion
|
13
|
+
include ::ActiveModel::Dirty
|
14
|
+
include ::ActiveModel::Validations
|
15
|
+
include ::ActiveModel::Serializers::JSON
|
16
|
+
include ::ActiveModel::Serializers::Xml
|
17
|
+
|
18
|
+
CALLBACKS = [ :initialize, :save, :create, :update, :destroy ].freeze unless defined?(CALLBACKS)
|
19
|
+
|
20
|
+
define_model_callbacks *CALLBACKS
|
21
|
+
|
22
|
+
CALLBACKS.each do |method_name|
|
23
|
+
|
24
|
+
alias :"#{method_name}_without_callbacks" :"#{method_name}"
|
25
|
+
|
26
|
+
define_method :"#{method_name}" do |*arguments|
|
27
|
+
send :"_run_#{method_name}_callbacks" do
|
28
|
+
send :"#{method_name}_without_callbacks", *arguments
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
alias new_record? new?
|
35
|
+
|
36
|
+
alias destroyed? new?
|
37
|
+
|
38
|
+
alias save_without_dirty save
|
39
|
+
|
40
|
+
def save
|
41
|
+
result = save_without_dirty
|
42
|
+
discard_changes!
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def discard_changes!
|
49
|
+
@previously_changed = changes
|
50
|
+
@changed_attributes = { }
|
51
|
+
end
|
52
|
+
|
53
|
+
class << self
|
54
|
+
|
55
|
+
alias key_accessor_without_dirty key_accessor
|
56
|
+
|
57
|
+
def key_accessor(key)
|
58
|
+
add_key key
|
59
|
+
redefine_attribute_methods
|
60
|
+
|
61
|
+
key_accessor_without_dirty key
|
62
|
+
|
63
|
+
alias_method :"#{key}_without_dirty=", :"#{key}="
|
64
|
+
define_method :"#{key}=" do |value|
|
65
|
+
send :"#{key}_will_change!"
|
66
|
+
send :"#{key}_without_dirty=", value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def add_key(key)
|
73
|
+
@keys ||= [ ]
|
74
|
+
@keys << key
|
75
|
+
end
|
76
|
+
|
77
|
+
def redefine_attribute_methods
|
78
|
+
undefine_attribute_methods
|
79
|
+
define_attribute_methods @keys
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
|
2
|
+
module CouchModel
|
3
|
+
|
4
|
+
class Base
|
5
|
+
|
6
|
+
module Accessor
|
7
|
+
|
8
|
+
def self.included(base_class)
|
9
|
+
base_class.class_eval do
|
10
|
+
extend ClassMethods
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def key_reader(key)
|
17
|
+
define_method :"#{key}" do
|
18
|
+
@attributes[key.to_s]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def key_writer(key)
|
23
|
+
define_method :"#{key}=" do |value|
|
24
|
+
@attributes[key.to_s] = value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def key_accessor(key)
|
29
|
+
key_reader key
|
30
|
+
key_writer key
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
|
2
|
+
module CouchModel
|
3
|
+
|
4
|
+
class Base
|
5
|
+
|
6
|
+
module Association
|
7
|
+
|
8
|
+
def self.included(base_class)
|
9
|
+
base_class.class_eval do
|
10
|
+
extend ClassMethods
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def belongs_to(name, options = { })
|
17
|
+
class_name = options[:class_name] || name.to_s.camelize
|
18
|
+
key = options[:key] || "#{name}_id"
|
19
|
+
|
20
|
+
key_accessor key
|
21
|
+
|
22
|
+
define_method :"#{name}" do
|
23
|
+
klass = Object.const_get class_name
|
24
|
+
klass.find self.send(key)
|
25
|
+
end
|
26
|
+
|
27
|
+
define_method :"#{name}=" do |value|
|
28
|
+
klass = Object.const_get class_name
|
29
|
+
if value
|
30
|
+
raise ArgumentError, "only objects of class #{klass} are accepted" unless value.is_a?(klass)
|
31
|
+
self.send :"#{key}=", value.id
|
32
|
+
else
|
33
|
+
self.send :"#{key}=", nil
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def has_many(name, options = { })
|
39
|
+
class_name = options[:class_name] || name.to_s.camelize
|
40
|
+
view_name = options[:view_name] || raise(ArgumentError, "no view_name is given")
|
41
|
+
query = options[:query]
|
42
|
+
|
43
|
+
define_method :"#{name}_query", &query if query.is_a?(Proc)
|
44
|
+
|
45
|
+
define_method :"#{name}" do |*arguments|
|
46
|
+
klass = Object.const_get class_name
|
47
|
+
query = if self.respond_to?(:"#{name}_query")
|
48
|
+
arguments << nil while arguments.length < self.method(:"#{name}_query").arity
|
49
|
+
self.send :"#{name}_query", *arguments
|
50
|
+
else
|
51
|
+
{ :key => "\"#{self.id}\"" }
|
52
|
+
end
|
53
|
+
klass.send :"#{view_name}", query
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
module CouchModel
|
3
|
+
|
4
|
+
class Base
|
5
|
+
|
6
|
+
module Finder
|
7
|
+
|
8
|
+
def self.included(base_class)
|
9
|
+
base_class.class_eval do
|
10
|
+
extend ClassMethods
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
|
16
|
+
def find(id)
|
17
|
+
document = new :id => id
|
18
|
+
document.load
|
19
|
+
document
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
|
2
|
+
module CouchModel
|
3
|
+
|
4
|
+
class Base
|
5
|
+
|
6
|
+
module Setup
|
7
|
+
|
8
|
+
def self.included(base_class)
|
9
|
+
base_class.class_eval do
|
10
|
+
include InstanceMethods
|
11
|
+
extend ClassMethods
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
|
17
|
+
def database
|
18
|
+
self.class.database
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
module ClassMethods
|
24
|
+
|
25
|
+
def setup_database(options = { })
|
26
|
+
initialize_database options
|
27
|
+
initialize_design
|
28
|
+
generate_class_view
|
29
|
+
push_design options
|
30
|
+
end
|
31
|
+
|
32
|
+
def database
|
33
|
+
@database || raise(StandardError, "no database defined!")
|
34
|
+
end
|
35
|
+
|
36
|
+
def design
|
37
|
+
@design || raise(StandardError, "no database defined!")
|
38
|
+
end
|
39
|
+
|
40
|
+
def method_missing(method_name, *arguments, &block)
|
41
|
+
view = find_view method_name
|
42
|
+
view ? view.collection(*arguments) : super
|
43
|
+
end
|
44
|
+
|
45
|
+
def respond_to?(method_name)
|
46
|
+
view = find_view method_name
|
47
|
+
view ? true : super
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def initialize_database(options)
|
53
|
+
url = options[:url] || raise(ArgumentError, "no url was given to define the database")
|
54
|
+
setup_on_initialization = options[:setup_on_initialization] || false
|
55
|
+
|
56
|
+
uri = URI.parse url
|
57
|
+
server = Server.new :host => uri.host, :port => uri.port
|
58
|
+
database = Database.new :server => server, :name => uri.path.gsub("/", "")
|
59
|
+
@database = Configuration.register_database database
|
60
|
+
|
61
|
+
@database.setup! options if setup_on_initialization && @database === database
|
62
|
+
end
|
63
|
+
|
64
|
+
def initialize_design
|
65
|
+
@design = Design.new @database, self, :id => self.to_s.underscore
|
66
|
+
Configuration.register_design @design
|
67
|
+
end
|
68
|
+
|
69
|
+
def generate_class_view
|
70
|
+
@design.generate_view Configuration::CLASS_VIEW_NAME
|
71
|
+
end
|
72
|
+
|
73
|
+
def push_design(options)
|
74
|
+
setup_on_initialization = options[:setup_on_initialization] || false
|
75
|
+
@design.push if setup_on_initialization
|
76
|
+
end
|
77
|
+
|
78
|
+
def find_view(name)
|
79
|
+
@design ? @design.views.select{ |view| view.name == name.to_s }.first : nil
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), "..", "core_extension", "string"))
|
2
|
+
require File.join(File.dirname(__FILE__), "configuration")
|
3
|
+
require File.join(File.dirname(__FILE__), "transport")
|
4
|
+
require File.join(File.dirname(__FILE__), "server")
|
5
|
+
require File.join(File.dirname(__FILE__), "database")
|
6
|
+
require File.join(File.dirname(__FILE__), "design")
|
7
|
+
require File.join(File.dirname(__FILE__), "base", "setup")
|
8
|
+
require File.join(File.dirname(__FILE__), "base", "accessor")
|
9
|
+
require File.join(File.dirname(__FILE__), "base", "finder")
|
10
|
+
require File.join(File.dirname(__FILE__), "base", "association")
|
11
|
+
require 'uri'
|
12
|
+
|
13
|
+
module CouchModel
|
14
|
+
|
15
|
+
class Base
|
16
|
+
include CouchModel::Base::Setup
|
17
|
+
include CouchModel::Base::Accessor
|
18
|
+
include CouchModel::Base::Finder
|
19
|
+
include CouchModel::Base::Association
|
20
|
+
|
21
|
+
class Error < StandardError; end
|
22
|
+
class NotFoundError < StandardError; end
|
23
|
+
|
24
|
+
attr_reader :attributes
|
25
|
+
|
26
|
+
def initialize(attributes = { })
|
27
|
+
@attributes = { Configuration::CLASS_KEY => self.class.to_s }
|
28
|
+
self.attributes = attributes
|
29
|
+
end
|
30
|
+
|
31
|
+
def attributes=(attributes)
|
32
|
+
attributes.each { |key, value| self.send :"#{key}=", value if self.respond_to?(:"#{key}=") }
|
33
|
+
end
|
34
|
+
|
35
|
+
def id
|
36
|
+
@attributes["_id"]
|
37
|
+
end
|
38
|
+
alias :_id :id
|
39
|
+
|
40
|
+
def id=(value)
|
41
|
+
@attributes["_id"] = value
|
42
|
+
end
|
43
|
+
|
44
|
+
def rev
|
45
|
+
@attributes["_rev"]
|
46
|
+
end
|
47
|
+
alias :_rev :rev
|
48
|
+
|
49
|
+
def ==(other)
|
50
|
+
self.id == other.id
|
51
|
+
end
|
52
|
+
|
53
|
+
def new?
|
54
|
+
self.rev.nil?
|
55
|
+
end
|
56
|
+
|
57
|
+
def load
|
58
|
+
response = Transport.request :get, url, :expected_status_code => 200
|
59
|
+
|
60
|
+
self.rev = response["_rev"]
|
61
|
+
[ "_id", "_rev", Configuration::CLASS_KEY ].each { |key| response.delete key }
|
62
|
+
self.attributes = response
|
63
|
+
true
|
64
|
+
rescue Transport::UnexpectedStatusCodeError => e
|
65
|
+
raise NotFoundError if e.status_code == 404
|
66
|
+
raise e
|
67
|
+
end
|
68
|
+
|
69
|
+
def save
|
70
|
+
new? ? create : update
|
71
|
+
end
|
72
|
+
|
73
|
+
def destroy
|
74
|
+
return false if new?
|
75
|
+
Transport.request :delete, self.url, :parameters => { "rev" => self.rev }, :expected_status_code => 200
|
76
|
+
self.rev = nil
|
77
|
+
true
|
78
|
+
rescue Transport::UnexpectedStatusCodeError => e
|
79
|
+
raise NotFoundError if e.status_code == 404
|
80
|
+
raise e
|
81
|
+
end
|
82
|
+
|
83
|
+
def url
|
84
|
+
"#{self.database.url}/#{self.id}"
|
85
|
+
end
|
86
|
+
|
87
|
+
def method_missing(method_name, *arguments, &block)
|
88
|
+
return @attributes[Configuration::CLASS_KEY] if Configuration::CLASS_KEY == method_name.to_s
|
89
|
+
super
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def rev=(value)
|
95
|
+
@attributes["_rev"] = value
|
96
|
+
end
|
97
|
+
|
98
|
+
def create
|
99
|
+
response = Transport.request :post, self.database.url, :json => self.attributes, :expected_status_code => 201
|
100
|
+
self.id = response["id"]
|
101
|
+
self.rev = response["rev"]
|
102
|
+
true
|
103
|
+
rescue Transport::UnexpectedStatusCodeError
|
104
|
+
false
|
105
|
+
end
|
106
|
+
|
107
|
+
def update
|
108
|
+
response = Transport.request :put, self.url, :json => self.attributes, :expected_status_code => 200
|
109
|
+
self.rev = response["rev"]
|
110
|
+
true
|
111
|
+
rescue Transport::UnexpectedStatusCodeError
|
112
|
+
false
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|