couchrest_model 1.0.0.beta7
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 +176 -0
- data/README.md +320 -0
- data/Rakefile +71 -0
- data/THANKS.md +19 -0
- data/examples/model/example.rb +144 -0
- data/history.txt +180 -0
- data/lib/couchrest/model.rb +10 -0
- data/lib/couchrest/model/associations.rb +207 -0
- data/lib/couchrest/model/attribute_protection.rb +74 -0
- data/lib/couchrest/model/attributes.rb +75 -0
- data/lib/couchrest/model/base.rb +111 -0
- data/lib/couchrest/model/callbacks.rb +27 -0
- data/lib/couchrest/model/casted_array.rb +39 -0
- data/lib/couchrest/model/casted_model.rb +68 -0
- data/lib/couchrest/model/class_proxy.rb +122 -0
- data/lib/couchrest/model/collection.rb +260 -0
- data/lib/couchrest/model/design_doc.rb +126 -0
- data/lib/couchrest/model/document_queries.rb +82 -0
- data/lib/couchrest/model/errors.rb +23 -0
- data/lib/couchrest/model/extended_attachments.rb +73 -0
- data/lib/couchrest/model/persistence.rb +141 -0
- data/lib/couchrest/model/properties.rb +144 -0
- data/lib/couchrest/model/property.rb +96 -0
- data/lib/couchrest/model/support/couchrest.rb +19 -0
- data/lib/couchrest/model/support/hash.rb +9 -0
- data/lib/couchrest/model/typecast.rb +170 -0
- data/lib/couchrest/model/validations.rb +68 -0
- data/lib/couchrest/model/validations/casted_model.rb +14 -0
- data/lib/couchrest/model/validations/locale/en.yml +5 -0
- data/lib/couchrest/model/validations/uniqueness.rb +45 -0
- data/lib/couchrest/model/views.rb +167 -0
- data/lib/couchrest_model.rb +56 -0
- data/spec/couchrest/assocations_spec.rb +213 -0
- data/spec/couchrest/attachment_spec.rb +148 -0
- data/spec/couchrest/attribute_protection_spec.rb +153 -0
- data/spec/couchrest/base_spec.rb +463 -0
- data/spec/couchrest/casted_model_spec.rb +424 -0
- data/spec/couchrest/casted_spec.rb +75 -0
- data/spec/couchrest/class_proxy_spec.rb +132 -0
- data/spec/couchrest/inherited_spec.rb +40 -0
- data/spec/couchrest/persistence_spec.rb +409 -0
- data/spec/couchrest/property_spec.rb +804 -0
- data/spec/couchrest/subclass_spec.rb +99 -0
- data/spec/couchrest/validations.rb +73 -0
- data/spec/couchrest/view_spec.rb +463 -0
- data/spec/fixtures/attachments/README +3 -0
- data/spec/fixtures/attachments/couchdb.png +0 -0
- data/spec/fixtures/attachments/test.html +11 -0
- data/spec/fixtures/base.rb +139 -0
- data/spec/fixtures/more/article.rb +35 -0
- data/spec/fixtures/more/card.rb +17 -0
- data/spec/fixtures/more/cat.rb +19 -0
- data/spec/fixtures/more/course.rb +25 -0
- data/spec/fixtures/more/event.rb +8 -0
- data/spec/fixtures/more/invoice.rb +14 -0
- data/spec/fixtures/more/person.rb +9 -0
- data/spec/fixtures/more/question.rb +7 -0
- data/spec/fixtures/more/service.rb +10 -0
- data/spec/fixtures/more/user.rb +22 -0
- data/spec/fixtures/views/lib.js +3 -0
- data/spec/fixtures/views/test_view/lib.js +3 -0
- data/spec/fixtures/views/test_view/only-map.js +4 -0
- data/spec/fixtures/views/test_view/test-map.js +3 -0
- data/spec/fixtures/views/test_view/test-reduce.js +3 -0
- data/spec/spec.opts +5 -0
- data/spec/spec_helper.rb +48 -0
- data/utils/remap.rb +27 -0
- data/utils/subset.rb +30 -0
- metadata +232 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
3
|
+
module AttributeProtection
|
4
|
+
# Attribute protection from mass assignment to CouchRest properties
|
5
|
+
#
|
6
|
+
# Protected methods will be removed from
|
7
|
+
# * new
|
8
|
+
# * update_attributes
|
9
|
+
# * upate_attributes_without_saving
|
10
|
+
# * attributes=
|
11
|
+
#
|
12
|
+
# There are two modes of protection
|
13
|
+
# 1) Declare accessible poperties, assume all the rest are protected
|
14
|
+
# property :name, :accessible => true
|
15
|
+
# property :admin # this will be automatically protected
|
16
|
+
#
|
17
|
+
# 2) Declare protected properties, assume all the rest are accessible
|
18
|
+
# property :name # this will not be protected
|
19
|
+
# property :admin, :protected => true
|
20
|
+
#
|
21
|
+
# Note: you cannot set both flags in a single class
|
22
|
+
|
23
|
+
def self.included(base)
|
24
|
+
base.extend(ClassMethods)
|
25
|
+
end
|
26
|
+
|
27
|
+
module ClassMethods
|
28
|
+
def accessible_properties
|
29
|
+
properties.select { |prop| prop.options[:accessible] }
|
30
|
+
end
|
31
|
+
|
32
|
+
def protected_properties
|
33
|
+
properties.select { |prop| prop.options[:protected] }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def accessible_properties
|
38
|
+
self.class.accessible_properties
|
39
|
+
end
|
40
|
+
|
41
|
+
def protected_properties
|
42
|
+
self.class.protected_properties
|
43
|
+
end
|
44
|
+
|
45
|
+
def remove_protected_attributes(attributes)
|
46
|
+
protected_names = properties_to_remove_from_mass_assignment.map { |prop| prop.name }
|
47
|
+
return attributes if protected_names.empty?
|
48
|
+
|
49
|
+
attributes.reject! do |property_name, property_value|
|
50
|
+
protected_names.include?(property_name.to_s)
|
51
|
+
end
|
52
|
+
|
53
|
+
attributes || {}
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def properties_to_remove_from_mass_assignment
|
59
|
+
has_protected = !protected_properties.empty?
|
60
|
+
has_accessible = !accessible_properties.empty?
|
61
|
+
|
62
|
+
if !has_protected && !has_accessible
|
63
|
+
[]
|
64
|
+
elsif has_protected && !has_accessible
|
65
|
+
protected_properties
|
66
|
+
elsif has_accessible && !has_protected
|
67
|
+
properties.reject { |prop| prop.options[:accessible] }
|
68
|
+
else
|
69
|
+
raise "Set either :accessible or :protected for #{self.class}, but not both"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
3
|
+
module Attributes
|
4
|
+
|
5
|
+
## Support for handling attributes
|
6
|
+
#
|
7
|
+
# This would be better in the properties file, but due to scoping issues
|
8
|
+
# this is not yet possible.
|
9
|
+
#
|
10
|
+
|
11
|
+
def prepare_all_attributes(doc = {}, options = {})
|
12
|
+
apply_all_property_defaults
|
13
|
+
if options[:directly_set_attributes]
|
14
|
+
directly_set_read_only_attributes(doc)
|
15
|
+
else
|
16
|
+
remove_protected_attributes(doc)
|
17
|
+
end
|
18
|
+
directly_set_attributes(doc) unless doc.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
# Takes a hash as argument, and applies the values by using writer methods
|
22
|
+
# for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
|
23
|
+
# missing. In case of error, no attributes are changed.
|
24
|
+
def update_attributes_without_saving(hash)
|
25
|
+
# Remove any protected and update all the rest. Any attributes
|
26
|
+
# which do not have a property will simply be ignored.
|
27
|
+
attrs = remove_protected_attributes(hash)
|
28
|
+
directly_set_attributes(attrs)
|
29
|
+
end
|
30
|
+
alias :attributes= :update_attributes_without_saving
|
31
|
+
|
32
|
+
# Takes a hash as argument, and applies the values by using writer methods
|
33
|
+
# for each key. Raises a NoMethodError if the corresponding methods are
|
34
|
+
# missing. In case of error, no attributes are changed.
|
35
|
+
def update_attributes(hash)
|
36
|
+
update_attributes_without_saving hash
|
37
|
+
save
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def directly_set_attributes(hash)
|
43
|
+
hash.each do |attribute_name, attribute_value|
|
44
|
+
if self.respond_to?("#{attribute_name}=")
|
45
|
+
self.send("#{attribute_name}=", hash.delete(attribute_name))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def directly_set_read_only_attributes(hash)
|
51
|
+
property_list = self.properties.map{|p| p.name}
|
52
|
+
hash.each do |attribute_name, attribute_value|
|
53
|
+
next if self.respond_to?("#{attribute_name}=")
|
54
|
+
if property_list.include?(attribute_name)
|
55
|
+
write_attribute(attribute_name, hash.delete(attribute_name))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def set_attributes(hash)
|
61
|
+
attrs = remove_protected_attributes(hash)
|
62
|
+
directly_set_attributes(attrs)
|
63
|
+
end
|
64
|
+
|
65
|
+
def check_properties_exist(attrs)
|
66
|
+
property_list = self.properties.map{|p| p.name}
|
67
|
+
attrs.each do |attribute_name, attribute_value|
|
68
|
+
raise NoMethodError, "Property #{attribute_name} not created" unless respond_to?("#{attribute_name}=") or property_list.include?(attribute_name)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
3
|
+
class Base < Document
|
4
|
+
|
5
|
+
extend ActiveModel::Naming
|
6
|
+
|
7
|
+
include CouchRest::Model::Persistence
|
8
|
+
include CouchRest::Model::Callbacks
|
9
|
+
include CouchRest::Model::DocumentQueries
|
10
|
+
include CouchRest::Model::Views
|
11
|
+
include CouchRest::Model::DesignDoc
|
12
|
+
include CouchRest::Model::ExtendedAttachments
|
13
|
+
include CouchRest::Model::ClassProxy
|
14
|
+
include CouchRest::Model::Collection
|
15
|
+
include CouchRest::Model::AttributeProtection
|
16
|
+
include CouchRest::Model::Attributes
|
17
|
+
include CouchRest::Model::Associations
|
18
|
+
include CouchRest::Model::Validations
|
19
|
+
|
20
|
+
def self.subclasses
|
21
|
+
@subclasses ||= []
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.inherited(subklass)
|
25
|
+
super
|
26
|
+
subklass.send(:include, CouchRest::Model::Properties)
|
27
|
+
subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
|
28
|
+
def self.inherited(subklass)
|
29
|
+
super
|
30
|
+
subklass.properties = self.properties.dup
|
31
|
+
# This is nasty:
|
32
|
+
subklass._validators = self._validators.dup
|
33
|
+
end
|
34
|
+
EOS
|
35
|
+
subclasses << subklass
|
36
|
+
end
|
37
|
+
|
38
|
+
# Accessors
|
39
|
+
attr_accessor :casted_by
|
40
|
+
|
41
|
+
|
42
|
+
# Instantiate a new ExtendedDocument by preparing all properties
|
43
|
+
# using the provided document hash.
|
44
|
+
#
|
45
|
+
# Options supported:
|
46
|
+
#
|
47
|
+
# * :directly_set_attributes: true when data comes directly from database
|
48
|
+
#
|
49
|
+
def initialize(doc = {}, options = {})
|
50
|
+
prepare_all_attributes(doc, options)
|
51
|
+
super(doc)
|
52
|
+
unless self['_id'] && self['_rev']
|
53
|
+
self['couchrest-type'] = self.class.to_s
|
54
|
+
end
|
55
|
+
after_initialize if respond_to?(:after_initialize)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# Temp solution to make the view_by methods available
|
60
|
+
def self.method_missing(m, *args, &block)
|
61
|
+
if has_view?(m)
|
62
|
+
query = args.shift || {}
|
63
|
+
return view(m, query, *args, &block)
|
64
|
+
elsif m.to_s =~ /^find_(by_.+)/
|
65
|
+
view_name = $1
|
66
|
+
if has_view?(view_name)
|
67
|
+
return first_from_view(view_name, *args)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
super
|
71
|
+
end
|
72
|
+
|
73
|
+
### instance methods
|
74
|
+
|
75
|
+
# Gets a reference to the actual document in the DB
|
76
|
+
# Calls up to the next document if there is one,
|
77
|
+
# Otherwise we're at the top and we return self
|
78
|
+
def base_doc
|
79
|
+
return self if base_doc?
|
80
|
+
@casted_by.base_doc
|
81
|
+
end
|
82
|
+
|
83
|
+
# Checks if we're the top document
|
84
|
+
def base_doc?
|
85
|
+
!@casted_by
|
86
|
+
end
|
87
|
+
|
88
|
+
## Compatibility with ActiveSupport and older frameworks
|
89
|
+
|
90
|
+
# Hack so that CouchRest::Document, which descends from Hash,
|
91
|
+
# doesn't appear to Rails routing as a Hash of options
|
92
|
+
def is_a?(klass)
|
93
|
+
return false if klass == Hash
|
94
|
+
super
|
95
|
+
end
|
96
|
+
alias :kind_of? :is_a?
|
97
|
+
|
98
|
+
def persisted?
|
99
|
+
!new?
|
100
|
+
end
|
101
|
+
|
102
|
+
def to_key
|
103
|
+
new? ? nil : [id]
|
104
|
+
end
|
105
|
+
|
106
|
+
alias :to_param :id
|
107
|
+
alias :new_record? :new?
|
108
|
+
alias :new_document? :new?
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module CouchRest #:nodoc:
|
4
|
+
module Model #:nodoc:
|
5
|
+
|
6
|
+
module Callbacks
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
included do
|
9
|
+
extend ActiveModel::Callbacks
|
10
|
+
|
11
|
+
define_model_callbacks \
|
12
|
+
:create,
|
13
|
+
:destroy,
|
14
|
+
:save,
|
15
|
+
:update,
|
16
|
+
:validate
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
def valid?(*) #nodoc
|
21
|
+
_run_validation_callbacks { super }
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
#
|
2
|
+
# Wrapper around Array so that the casted_by attribute is set in all
|
3
|
+
# elements of the array.
|
4
|
+
#
|
5
|
+
|
6
|
+
module CouchRest::Model
|
7
|
+
class CastedArray < Array
|
8
|
+
attr_accessor :casted_by
|
9
|
+
attr_accessor :property
|
10
|
+
|
11
|
+
def initialize(array, property)
|
12
|
+
self.property = property
|
13
|
+
super(array)
|
14
|
+
end
|
15
|
+
|
16
|
+
def << obj
|
17
|
+
super(instantiate_and_cast(obj))
|
18
|
+
end
|
19
|
+
|
20
|
+
def push(obj)
|
21
|
+
super(instantiate_and_cast(obj))
|
22
|
+
end
|
23
|
+
|
24
|
+
def []= index, obj
|
25
|
+
super(index, instantiate_and_cast(obj))
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def instantiate_and_cast(obj)
|
31
|
+
if self.casted_by && self.property && obj.class != self.property.type_class
|
32
|
+
self.property.cast_value(self.casted_by, obj)
|
33
|
+
else
|
34
|
+
obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
|
35
|
+
obj
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module CouchRest::Model
|
2
|
+
module CastedModel
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include CouchRest::Model::AttributeProtection
|
8
|
+
include CouchRest::Model::Attributes
|
9
|
+
include CouchRest::Model::Callbacks
|
10
|
+
include CouchRest::Model::Properties
|
11
|
+
include CouchRest::Model::Associations
|
12
|
+
include CouchRest::Model::Validations
|
13
|
+
attr_accessor :casted_by
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(keys = {})
|
17
|
+
raise StandardError unless self.is_a? Hash
|
18
|
+
prepare_all_attributes(keys)
|
19
|
+
super()
|
20
|
+
end
|
21
|
+
|
22
|
+
def []= key, value
|
23
|
+
super(key.to_s, value)
|
24
|
+
end
|
25
|
+
|
26
|
+
def [] key
|
27
|
+
super(key.to_s)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Gets a reference to the top level extended
|
31
|
+
# document that a model is saved inside of
|
32
|
+
def base_doc
|
33
|
+
return nil unless @casted_by
|
34
|
+
@casted_by.base_doc
|
35
|
+
end
|
36
|
+
|
37
|
+
# False if the casted model has already
|
38
|
+
# been saved in the containing document
|
39
|
+
def new?
|
40
|
+
@casted_by.nil? ? true : @casted_by.new?
|
41
|
+
end
|
42
|
+
alias :new_record? :new?
|
43
|
+
|
44
|
+
def persisted?
|
45
|
+
!new?
|
46
|
+
end
|
47
|
+
|
48
|
+
# The to_param method is needed for rails to generate resourceful routes.
|
49
|
+
# In your controller, remember that it's actually the id of the document.
|
50
|
+
def id
|
51
|
+
return nil if base_doc.nil?
|
52
|
+
base_doc.id
|
53
|
+
end
|
54
|
+
alias :to_key :id
|
55
|
+
alias :to_param :id
|
56
|
+
|
57
|
+
# Sets the attributes from a hash
|
58
|
+
def update_attributes_without_saving(hash)
|
59
|
+
hash.each do |k, v|
|
60
|
+
raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
|
61
|
+
end
|
62
|
+
hash.each do |k, v|
|
63
|
+
self.send("#{k}=",v)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
alias :attributes= :update_attributes_without_saving
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module CouchRest
|
2
|
+
module Model
|
3
|
+
module ClassProxy
|
4
|
+
|
5
|
+
def self.included(base)
|
6
|
+
base.extend(ClassMethods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
|
11
|
+
# Return a proxy object which represents a model class on a
|
12
|
+
# chosen database instance. This allows you to DRY operations
|
13
|
+
# where a database is chosen dynamically.
|
14
|
+
#
|
15
|
+
# ==== Example:
|
16
|
+
#
|
17
|
+
# db = CouchRest::Database.new(...)
|
18
|
+
# articles = Article.on(db)
|
19
|
+
#
|
20
|
+
# articles.all { ... }
|
21
|
+
# articles.by_title { ... }
|
22
|
+
#
|
23
|
+
# u = articles.get("someid")
|
24
|
+
#
|
25
|
+
# u = articles.new(:title => "I like plankton")
|
26
|
+
# u.save # saved on the correct database
|
27
|
+
|
28
|
+
def on(database)
|
29
|
+
Proxy.new(self, database)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Proxy #:nodoc:
|
34
|
+
def initialize(klass, database)
|
35
|
+
@klass = klass
|
36
|
+
@database = database
|
37
|
+
end
|
38
|
+
|
39
|
+
# Base
|
40
|
+
|
41
|
+
def new(*args)
|
42
|
+
doc = @klass.new(*args)
|
43
|
+
doc.database = @database
|
44
|
+
doc
|
45
|
+
end
|
46
|
+
|
47
|
+
def method_missing(m, *args, &block)
|
48
|
+
if has_view?(m)
|
49
|
+
query = args.shift || {}
|
50
|
+
return view(m, query, *args, &block)
|
51
|
+
elsif m.to_s =~ /^find_(by_.+)/
|
52
|
+
view_name = $1
|
53
|
+
if has_view?(view_name)
|
54
|
+
return first_from_view(view_name, *args)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
super
|
58
|
+
end
|
59
|
+
|
60
|
+
# DocumentQueries
|
61
|
+
|
62
|
+
def all(opts = {}, &block)
|
63
|
+
docs = @klass.all({:database => @database}.merge(opts), &block)
|
64
|
+
docs.each { |doc| doc.database = @database if doc.respond_to?(:database) } if docs
|
65
|
+
docs
|
66
|
+
end
|
67
|
+
|
68
|
+
def count(opts = {}, &block)
|
69
|
+
@klass.all({:database => @database, :raw => true, :limit => 0}.merge(opts), &block)['total_rows']
|
70
|
+
end
|
71
|
+
|
72
|
+
def first(opts = {})
|
73
|
+
doc = @klass.first({:database => @database}.merge(opts))
|
74
|
+
doc.database = @database if doc && doc.respond_to?(:database)
|
75
|
+
doc
|
76
|
+
end
|
77
|
+
|
78
|
+
def get(id)
|
79
|
+
doc = @klass.get(id, @database)
|
80
|
+
doc.database = @database if doc && doc.respond_to?(:database)
|
81
|
+
doc
|
82
|
+
end
|
83
|
+
alias :find :get
|
84
|
+
|
85
|
+
# Views
|
86
|
+
|
87
|
+
def has_view?(view)
|
88
|
+
@klass.has_view?(view)
|
89
|
+
end
|
90
|
+
|
91
|
+
def view(name, query={}, &block)
|
92
|
+
docs = @klass.view(name, {:database => @database}.merge(query), &block)
|
93
|
+
docs.each { |doc| doc.database = @database if doc.respond_to?(:database) } if docs
|
94
|
+
docs
|
95
|
+
end
|
96
|
+
|
97
|
+
def first_from_view(name, *args)
|
98
|
+
# add to first hash available, or add to end
|
99
|
+
(args.last.is_a?(Hash) ? args.last : (args << {}).last)[:database] = @database
|
100
|
+
doc = @klass.first_from_view(name, *args)
|
101
|
+
doc.database = @database if doc && doc.respond_to?(:database)
|
102
|
+
doc
|
103
|
+
end
|
104
|
+
|
105
|
+
# DesignDoc
|
106
|
+
|
107
|
+
def design_doc
|
108
|
+
@klass.design_doc
|
109
|
+
end
|
110
|
+
|
111
|
+
def refresh_design_doc
|
112
|
+
@klass.refresh_design_doc(@database)
|
113
|
+
end
|
114
|
+
|
115
|
+
def save_design_doc
|
116
|
+
@klass.save_design_doc(@database)
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|