couchrest_model_thought 1.0.0.beta8

Sign up to get free protection for your applications and to get access to all the features.
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