couchrest_model_thought 1.0.0.beta8

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.
Files changed (70) hide show
  1. data/Gemfile +9 -0
  2. data/LICENSE +176 -0
  3. data/README.md +320 -0
  4. data/Rakefile +69 -0
  5. data/THANKS.md +19 -0
  6. data/examples/model/example.rb +144 -0
  7. data/lib/couchrest/model/associations.rb +207 -0
  8. data/lib/couchrest/model/attribute_protection.rb +74 -0
  9. data/lib/couchrest/model/attributes.rb +91 -0
  10. data/lib/couchrest/model/base.rb +111 -0
  11. data/lib/couchrest/model/callbacks.rb +27 -0
  12. data/lib/couchrest/model/casted_array.rb +39 -0
  13. data/lib/couchrest/model/casted_model.rb +68 -0
  14. data/lib/couchrest/model/class_proxy.rb +122 -0
  15. data/lib/couchrest/model/collection.rb +260 -0
  16. data/lib/couchrest/model/design_doc.rb +126 -0
  17. data/lib/couchrest/model/document_queries.rb +82 -0
  18. data/lib/couchrest/model/errors.rb +23 -0
  19. data/lib/couchrest/model/extended_attachments.rb +73 -0
  20. data/lib/couchrest/model/persistence.rb +141 -0
  21. data/lib/couchrest/model/properties.rb +144 -0
  22. data/lib/couchrest/model/property.rb +96 -0
  23. data/lib/couchrest/model/support/couchrest.rb +19 -0
  24. data/lib/couchrest/model/support/hash.rb +9 -0
  25. data/lib/couchrest/model/typecast.rb +170 -0
  26. data/lib/couchrest/model/validations/casted_model.rb +14 -0
  27. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  28. data/lib/couchrest/model/validations/uniqueness.rb +42 -0
  29. data/lib/couchrest/model/validations.rb +68 -0
  30. data/lib/couchrest/model/version.rb +5 -0
  31. data/lib/couchrest/model/views.rb +160 -0
  32. data/lib/couchrest/model.rb +5 -0
  33. data/lib/couchrest_model.rb +53 -0
  34. data/spec/couchrest/assocations_spec.rb +213 -0
  35. data/spec/couchrest/attachment_spec.rb +148 -0
  36. data/spec/couchrest/attribute_protection_spec.rb +153 -0
  37. data/spec/couchrest/base_spec.rb +463 -0
  38. data/spec/couchrest/casted_model_spec.rb +424 -0
  39. data/spec/couchrest/casted_spec.rb +75 -0
  40. data/spec/couchrest/class_proxy_spec.rb +132 -0
  41. data/spec/couchrest/inherited_spec.rb +40 -0
  42. data/spec/couchrest/persistence_spec.rb +409 -0
  43. data/spec/couchrest/property_spec.rb +804 -0
  44. data/spec/couchrest/subclass_spec.rb +99 -0
  45. data/spec/couchrest/validations.rb +85 -0
  46. data/spec/couchrest/view_spec.rb +463 -0
  47. data/spec/fixtures/attachments/README +3 -0
  48. data/spec/fixtures/attachments/couchdb.png +0 -0
  49. data/spec/fixtures/attachments/test.html +11 -0
  50. data/spec/fixtures/base.rb +139 -0
  51. data/spec/fixtures/more/article.rb +35 -0
  52. data/spec/fixtures/more/card.rb +17 -0
  53. data/spec/fixtures/more/cat.rb +19 -0
  54. data/spec/fixtures/more/course.rb +25 -0
  55. data/spec/fixtures/more/event.rb +8 -0
  56. data/spec/fixtures/more/invoice.rb +14 -0
  57. data/spec/fixtures/more/person.rb +9 -0
  58. data/spec/fixtures/more/question.rb +7 -0
  59. data/spec/fixtures/more/service.rb +10 -0
  60. data/spec/fixtures/more/user.rb +22 -0
  61. data/spec/fixtures/views/lib.js +3 -0
  62. data/spec/fixtures/views/test_view/lib.js +3 -0
  63. data/spec/fixtures/views/test_view/only-map.js +4 -0
  64. data/spec/fixtures/views/test_view/test-map.js +3 -0
  65. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  66. data/spec/spec.opts +5 -0
  67. data/spec/spec_helper.rb +48 -0
  68. data/utils/remap.rb +27 -0
  69. data/utils/subset.rb +30 -0
  70. metadata +215 -0
@@ -0,0 +1,144 @@
1
+ require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'couchrest')
2
+
3
+ def show obj
4
+ puts obj.inspect
5
+ puts
6
+ end
7
+
8
+ SERVER = CouchRest.new
9
+ SERVER.default_database = 'couchrest-extendeddoc-example'
10
+
11
+ class Author < CouchRest::ExtendedDocument
12
+ use_database SERVER.default_database
13
+ property :name
14
+
15
+ def drink_scotch
16
+ puts "... glug type glug ... I'm #{name} ... type glug glug ..."
17
+ end
18
+ end
19
+
20
+ class Post < CouchRest::ExtendedDocument
21
+ use_database SERVER.default_database
22
+
23
+ property :title
24
+ property :body
25
+ property :author, :cast_as => 'Author'
26
+
27
+ timestamps!
28
+ end
29
+
30
+ class Comment < CouchRest::ExtendedDocument
31
+ use_database SERVER.default_database
32
+
33
+ property :commenter, :cast_as => 'Author'
34
+ timestamps!
35
+
36
+ def post= post
37
+ self["post_id"] = post.id
38
+ end
39
+ def post
40
+ Post.get(self['post_id']) if self['post_id']
41
+ end
42
+
43
+ end
44
+
45
+ puts "Act I: CRUD"
46
+ puts
47
+ puts "(pause for dramatic effect)"
48
+ puts
49
+ sleep 2
50
+
51
+ puts "Create an author."
52
+ quentin = Author.new("name" => "Quentin Hazel")
53
+ show quentin
54
+
55
+ puts "Create a new post."
56
+ post = Post.new(:title => "First Post", :body => "Lorem ipsum dolor sit amet, consectetur adipisicing elit...")
57
+ show post
58
+
59
+ puts "Add the author to the post."
60
+ post.author = quentin
61
+ show post
62
+
63
+ puts "Save the post."
64
+ post.save
65
+ show post
66
+
67
+ puts "Load the post."
68
+ reloaded = Post.get(post.id)
69
+ show reloaded
70
+
71
+ puts "The author of the post is an instance of Author."
72
+ reloaded.author.drink_scotch
73
+
74
+ puts "\nAdd some comments to the post."
75
+ comment_one = Comment.new :text => "Blah blah blah", :commenter => {:name => "Joe Sixpack"}
76
+ comment_two = Comment.new :text => "Yeah yeah yeah", :commenter => {:name => "Jane Doe"}
77
+ comment_three = Comment.new :text => "Whatever...", :commenter => {:name => "John Stewart"}
78
+
79
+ # TODO - maybe add some magic here?
80
+ comment_one.post = post
81
+ comment_two.post = post
82
+ comment_three.post = post
83
+ comment_one.save
84
+ comment_two.save
85
+ comment_three.save
86
+
87
+ show comment_one
88
+ show comment_two
89
+ show comment_three
90
+
91
+ puts "We can load a post through its comment (no magic here)."
92
+ show post = comment_one.post
93
+
94
+ puts "Commenters are also authors."
95
+ comment_two['commenter'].drink_scotch
96
+ comment_one['commenter'].drink_scotch
97
+ comment_three['commenter'].drink_scotch
98
+
99
+ puts "\nLet's save an author to her own document."
100
+ jane = comment_two['commenter']
101
+ jane.save
102
+ show jane
103
+
104
+ puts "Oh, that's neat! Because Ruby passes hash valuee by reference, Jane's new id has been added to the comment she left."
105
+ show comment_two
106
+
107
+ puts "Of course, we'd better remember to save it."
108
+ comment_two.save
109
+ show comment_two
110
+
111
+ puts "Oooh, denormalized... feel the burn!"
112
+ puts
113
+ puts
114
+ puts
115
+ puts "Act II: Views"
116
+ puts
117
+ puts
118
+ sleep 2
119
+
120
+ puts "Let's find all the comments that go with our post."
121
+ puts "Our post has id #{post.id}, so lets find all the comments with that post_id."
122
+ puts
123
+
124
+ class Comment
125
+ view_by :post_id
126
+ end
127
+
128
+ comments = Comment.by_post_id :key => post.id
129
+ show comments
130
+
131
+ puts "That was too easy."
132
+ puts "We can even wrap it up in a finder on the Post class."
133
+ puts
134
+
135
+ class Post
136
+ def comments
137
+ Comment.by_post_id :key => id
138
+ end
139
+ end
140
+
141
+ show post.comments
142
+ puts "Gimme 5 minutes and I'll roll this into the framework. ;)"
143
+ puts
144
+ puts "There is a lot more that can be done with views, but a lot of the interesting stuff is joins, which of course range across types. We'll pick up where we left off, next time."
@@ -0,0 +1,207 @@
1
+ module CouchRest
2
+ module Model
3
+ module Associations
4
+
5
+ # Basic support for relationships between ExtendedDocuments
6
+
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ # Define an association that this object belongs to.
14
+ #
15
+ def belongs_to(attrib, *options)
16
+ opts = {
17
+ :foreign_key => attrib.to_s + '_id',
18
+ :class_name => attrib.to_s.camelcase,
19
+ :proxy => nil
20
+ }
21
+ case options.first
22
+ when Hash
23
+ opts.merge!(options.first)
24
+ end
25
+
26
+ begin
27
+ opts[:class] = opts[:class_name].constantize
28
+ rescue
29
+ raise "Unable to convert class name into Constant for #{self.name}##{attrib}"
30
+ end
31
+
32
+ prop = property(opts[:foreign_key], opts)
33
+
34
+ create_belongs_to_getter(attrib, prop, opts)
35
+ create_belongs_to_setter(attrib, prop, opts)
36
+
37
+ prop
38
+ end
39
+
40
+ # Provide access to a collection of objects where the associated
41
+ # property contains a list of the collection item ids.
42
+ #
43
+ # The following:
44
+ #
45
+ # collection_of :groups
46
+ #
47
+ # creates a pseudo property called "groups" which allows access
48
+ # to a CollectionOfProxy object. Adding, replacing or removing entries in this
49
+ # proxy will cause the matching property array, in this case "group_ids", to
50
+ # be kept in sync.
51
+ #
52
+ # Any manual changes made to the collection ids property (group_ids), unless replaced, will require
53
+ # a reload of the CollectionOfProxy for the two sets of data to be in sync:
54
+ #
55
+ # group_ids = ['123']
56
+ # groups == [Group.get('123')]
57
+ # group_ids << '321'
58
+ # groups == [Group.get('123')]
59
+ # groups(true) == [Group.get('123'), Group.get('321')]
60
+ #
61
+ # Of course, saving the parent record will store the collection ids as they are
62
+ # found.
63
+ #
64
+ # The CollectionOfProxy supports the following array functions, anything else will cause
65
+ # a mismatch between the collection objects and collection ids:
66
+ #
67
+ # groups << obj
68
+ # groups.push obj
69
+ # groups.unshift obj
70
+ # groups[0] = obj
71
+ # groups.pop == obj
72
+ # groups.shift == obj
73
+ #
74
+ # Addtional options match those of the the belongs_to method.
75
+ #
76
+ # NOTE: This method is *not* recommended for large collections or collections that change
77
+ # frequently! Use with prudence.
78
+ #
79
+ def collection_of(attrib, *options)
80
+ opts = {
81
+ :foreign_key => attrib.to_s.singularize + '_ids',
82
+ :class_name => attrib.to_s.singularize.camelcase,
83
+ :proxy => nil
84
+ }
85
+ case options.first
86
+ when Hash
87
+ opts.merge!(options.first)
88
+ end
89
+ begin
90
+ opts[:class] = opts[:class_name].constantize
91
+ rescue
92
+ raise "Unable to convert class name into Constant for #{self.name}##{attrib}"
93
+ end
94
+ opts[:readonly] = true
95
+
96
+ prop = property(opts[:foreign_key], [], opts)
97
+
98
+ create_collection_of_property_setter(attrib, prop, opts)
99
+ create_collection_of_getter(attrib, prop, opts)
100
+ create_collection_of_setter(attrib, prop, opts)
101
+
102
+ prop
103
+ end
104
+
105
+
106
+ private
107
+
108
+ def create_belongs_to_getter(attrib, property, options)
109
+ base = options[:proxy] || options[:class_name]
110
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
111
+ def #{attrib}
112
+ @#{attrib} ||= #{options[:foreign_key]}.nil? ? nil : #{base}.get(self.#{options[:foreign_key]})
113
+ end
114
+ EOS
115
+ end
116
+
117
+ def create_belongs_to_setter(attrib, property, options)
118
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
119
+ def #{attrib}=(value)
120
+ self.#{options[:foreign_key]} = value.nil? ? nil : value.id
121
+ @#{attrib} = value
122
+ end
123
+ EOS
124
+ end
125
+
126
+ ### collection_of support methods
127
+
128
+ def create_collection_of_property_setter(attrib, property, options)
129
+ # ensure CollectionOfProxy is nil, ready to be reloaded on request
130
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
131
+ def #{options[:foreign_key]}=(value)
132
+ @#{attrib} = nil
133
+ write_attribute("#{options[:foreign_key]}", value)
134
+ end
135
+ EOS
136
+ end
137
+
138
+ def create_collection_of_getter(attrib, property, options)
139
+ base = options[:proxy] || options[:class_name]
140
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
141
+ def #{attrib}(reload = false)
142
+ return @#{attrib} unless @#{attrib}.nil? or reload
143
+ ary = self.#{options[:foreign_key]}.collect{|i| #{base}.get(i)}
144
+ @#{attrib} = ::CouchRest::CollectionOfProxy.new(ary, self, '#{options[:foreign_key]}')
145
+ end
146
+ EOS
147
+ end
148
+
149
+ def create_collection_of_setter(attrib, property, options)
150
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
151
+ def #{attrib}=(value)
152
+ @#{attrib} = ::CouchRest::CollectionOfProxy.new(value, self, '#{options[:foreign_key]}')
153
+ end
154
+ EOS
155
+ end
156
+
157
+ end
158
+
159
+ end
160
+ end
161
+
162
+ # Special proxy for a collection of items so that adding and removing
163
+ # to the list automatically updates the associated property.
164
+ class CollectionOfProxy < Array
165
+ attr_accessor :property
166
+ attr_accessor :casted_by
167
+
168
+ def initialize(array, casted_by, property)
169
+ self.property = property
170
+ self.casted_by = casted_by
171
+ (array ||= []).compact!
172
+ casted_by[property.to_s] = [] # replace the original array!
173
+ array.compact.each do |obj|
174
+ casted_by[property.to_s] << obj.id
175
+ end
176
+ super(array)
177
+ end
178
+ def << obj
179
+ casted_by[property.to_s] << obj.id
180
+ super(obj)
181
+ end
182
+ def push(obj)
183
+ casted_by[property.to_s].push obj.id
184
+ super(obj)
185
+ end
186
+ def unshift(obj)
187
+ casted_by[property.to_s].unshift obj.id
188
+ super(obj)
189
+ end
190
+
191
+ def []= index, obj
192
+ casted_by[property.to_s][index] = obj.id
193
+ super(index, obj)
194
+ end
195
+
196
+ def pop
197
+ casted_by[property.to_s].pop
198
+ super
199
+ end
200
+ def shift
201
+ casted_by[property.to_s].shift
202
+ super
203
+ end
204
+ end
205
+
206
+
207
+ end
@@ -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,91 @@
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 = merge_multiparameter_attributes hash
44
+ hash.each do |attribute_name, attribute_value|
45
+ if self.respond_to?("#{attribute_name}=")
46
+ self.send("#{attribute_name}=", hash.delete(attribute_name))
47
+ end
48
+ end
49
+ end
50
+
51
+ def directly_set_read_only_attributes(hash)
52
+ property_list = self.properties.map{|p| p.name}
53
+ hash.each do |attribute_name, attribute_value|
54
+ next if self.respond_to?("#{attribute_name}=")
55
+ if property_list.include?(attribute_name)
56
+ write_attribute(attribute_name, hash.delete(attribute_name))
57
+ end
58
+ end
59
+ end
60
+
61
+ def set_attributes(hash)
62
+ attrs = remove_protected_attributes(hash)
63
+ directly_set_attributes(attrs)
64
+ end
65
+
66
+ def check_properties_exist(attrs)
67
+ property_list = self.properties.map{|p| p.name}
68
+ attrs.each do |attribute_name, attribute_value|
69
+ raise NoMethodError, "Property #{attribute_name} not created" unless respond_to?("#{attribute_name}=") or property_list.include?(attribute_name)
70
+ end
71
+ end
72
+
73
+ def merge_multiparameter_attributes(attributes)
74
+ self.properties.each do |property|
75
+ case property.type.to_s
76
+ when Date.to_s
77
+ parameters = attributes.values_at(*(1..3).map{ |index| "#{property.name}(#{index}i)" }).map(&:to_i)
78
+ attributes.values_at(*(1..3).map { |index| attributes.delete("#{property.name}(#{index}i)") })
79
+ attributes[property.name] = Date.civil *parameters rescue nil unless attributes[property.name].is_a?(Date)
80
+ when Time.to_s
81
+ parameters = attributes.values_at(*(1..6).map{ |index| "#{property.name}(#{index}i)" }).map(&:to_i)
82
+ attributes.values_at(*(1..3).map{ |index| attributes.delete("#{property.name}(#{index}i)") })
83
+ attributes[property.name] = Time.mktime *parameters rescue nil unless attributes[property.name].is_a?(Time)
84
+ end
85
+ end
86
+ attributes
87
+ end
88
+
89
+ end
90
+ end
91
+ end
@@ -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