sporkd-couchrest 0.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (96) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +176 -0
  3. data/Rakefile +74 -0
  4. data/THANKS.md +18 -0
  5. data/examples/model/example.rb +144 -0
  6. data/examples/word_count/markov +38 -0
  7. data/examples/word_count/views/books/chunked-map.js +3 -0
  8. data/examples/word_count/views/books/united-map.js +1 -0
  9. data/examples/word_count/views/markov/chain-map.js +6 -0
  10. data/examples/word_count/views/markov/chain-reduce.js +7 -0
  11. data/examples/word_count/views/word_count/count-map.js +6 -0
  12. data/examples/word_count/views/word_count/count-reduce.js +3 -0
  13. data/examples/word_count/word_count.rb +46 -0
  14. data/examples/word_count/word_count_query.rb +40 -0
  15. data/examples/word_count/word_count_views.rb +26 -0
  16. data/history.txt +33 -0
  17. data/lib/couchrest/commands/generate.rb +71 -0
  18. data/lib/couchrest/commands/push.rb +103 -0
  19. data/lib/couchrest/core/database.rb +317 -0
  20. data/lib/couchrest/core/design.rb +79 -0
  21. data/lib/couchrest/core/document.rb +84 -0
  22. data/lib/couchrest/core/response.rb +16 -0
  23. data/lib/couchrest/core/server.rb +88 -0
  24. data/lib/couchrest/core/view.rb +4 -0
  25. data/lib/couchrest/helper/pager.rb +103 -0
  26. data/lib/couchrest/helper/streamer.rb +44 -0
  27. data/lib/couchrest/helper/upgrade.rb +51 -0
  28. data/lib/couchrest/mixins/attachments.rb +31 -0
  29. data/lib/couchrest/mixins/callbacks.rb +532 -0
  30. data/lib/couchrest/mixins/class_proxy.rb +112 -0
  31. data/lib/couchrest/mixins/collection.rb +222 -0
  32. data/lib/couchrest/mixins/design_doc.rb +98 -0
  33. data/lib/couchrest/mixins/document_queries.rb +51 -0
  34. data/lib/couchrest/mixins/extended_attachments.rb +74 -0
  35. data/lib/couchrest/mixins/extended_document_mixins.rb +8 -0
  36. data/lib/couchrest/mixins/properties.rb +181 -0
  37. data/lib/couchrest/mixins/validation.rb +246 -0
  38. data/lib/couchrest/mixins/views.rb +173 -0
  39. data/lib/couchrest/mixins.rb +4 -0
  40. data/lib/couchrest/monkeypatches.rb +113 -0
  41. data/lib/couchrest/more/casted_model.rb +57 -0
  42. data/lib/couchrest/more/extended_document.rb +283 -0
  43. data/lib/couchrest/more/property.rb +59 -0
  44. data/lib/couchrest/support/blank.rb +42 -0
  45. data/lib/couchrest/support/class.rb +190 -0
  46. data/lib/couchrest/support/rails.rb +47 -0
  47. data/lib/couchrest/validation/auto_validate.rb +161 -0
  48. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  49. data/lib/couchrest/validation/validation_errors.rb +118 -0
  50. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  51. data/lib/couchrest/validation/validators/confirmation_validator.rb +99 -0
  52. data/lib/couchrest/validation/validators/format_validator.rb +117 -0
  53. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  54. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  55. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  56. data/lib/couchrest/validation/validators/length_validator.rb +134 -0
  57. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  58. data/lib/couchrest/validation/validators/numeric_validator.rb +104 -0
  59. data/lib/couchrest/validation/validators/required_field_validator.rb +109 -0
  60. data/lib/couchrest.rb +200 -0
  61. data/spec/couchrest/core/couchrest_spec.rb +201 -0
  62. data/spec/couchrest/core/database_spec.rb +700 -0
  63. data/spec/couchrest/core/design_spec.rb +138 -0
  64. data/spec/couchrest/core/document_spec.rb +267 -0
  65. data/spec/couchrest/core/server_spec.rb +35 -0
  66. data/spec/couchrest/helpers/pager_spec.rb +122 -0
  67. data/spec/couchrest/helpers/streamer_spec.rb +23 -0
  68. data/spec/couchrest/more/casted_extended_doc_spec.rb +73 -0
  69. data/spec/couchrest/more/casted_model_spec.rb +406 -0
  70. data/spec/couchrest/more/extended_doc_attachment_spec.rb +135 -0
  71. data/spec/couchrest/more/extended_doc_spec.rb +712 -0
  72. data/spec/couchrest/more/extended_doc_subclass_spec.rb +98 -0
  73. data/spec/couchrest/more/extended_doc_view_spec.rb +415 -0
  74. data/spec/couchrest/more/property_spec.rb +233 -0
  75. data/spec/fixtures/attachments/README +3 -0
  76. data/spec/fixtures/attachments/couchdb.png +0 -0
  77. data/spec/fixtures/attachments/test.html +11 -0
  78. data/spec/fixtures/more/article.rb +34 -0
  79. data/spec/fixtures/more/card.rb +22 -0
  80. data/spec/fixtures/more/cat.rb +19 -0
  81. data/spec/fixtures/more/course.rb +14 -0
  82. data/spec/fixtures/more/event.rb +6 -0
  83. data/spec/fixtures/more/invoice.rb +17 -0
  84. data/spec/fixtures/more/person.rb +9 -0
  85. data/spec/fixtures/more/question.rb +6 -0
  86. data/spec/fixtures/more/service.rb +12 -0
  87. data/spec/fixtures/views/lib.js +3 -0
  88. data/spec/fixtures/views/test_view/lib.js +3 -0
  89. data/spec/fixtures/views/test_view/only-map.js +4 -0
  90. data/spec/fixtures/views/test_view/test-map.js +3 -0
  91. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  92. data/spec/spec.opts +6 -0
  93. data/spec/spec_helper.rb +37 -0
  94. data/utils/remap.rb +27 -0
  95. data/utils/subset.rb +30 -0
  96. metadata +194 -0
@@ -0,0 +1,283 @@
1
+ require 'mime/types'
2
+ require File.join(File.dirname(__FILE__), "property")
3
+ require File.join(File.dirname(__FILE__), '..', 'mixins', 'extended_document_mixins')
4
+ require "enumerator"
5
+
6
+ module CouchRest
7
+
8
+ # Same as CouchRest::Document but with properties and validations
9
+ class ExtendedDocument < Document
10
+ include CouchRest::Callbacks
11
+ include CouchRest::Mixins::DocumentQueries
12
+ include CouchRest::Mixins::Views
13
+ include CouchRest::Mixins::DesignDoc
14
+ include CouchRest::Mixins::ExtendedAttachments
15
+ include CouchRest::Mixins::ClassProxy
16
+ include CouchRest::Mixins::Collection
17
+
18
+ def self.subclasses
19
+ @subclasses ||= []
20
+ end
21
+
22
+ def self.inherited(subklass)
23
+ subklass.send(:include, CouchRest::Mixins::Properties)
24
+ subklass.class_eval <<-EOS, __FILE__, __LINE__
25
+ def self.inherited(subklass)
26
+ subklass.properties = self.properties.dup
27
+ end
28
+ EOS
29
+ subclasses << subklass
30
+ end
31
+
32
+ # Accessors
33
+ attr_accessor :casted_by
34
+
35
+ # Callbacks
36
+ define_callbacks :create, "result == :halt"
37
+ define_callbacks :save, "result == :halt"
38
+ define_callbacks :update, "result == :halt"
39
+ define_callbacks :destroy, "result == :halt"
40
+
41
+ def initialize(passed_keys={})
42
+ apply_defaults # defined in CouchRest::Mixins::Properties
43
+ passed_keys.each do |k,v|
44
+ if self.respond_to?("#{k}=")
45
+ self.send("#{k}=", passed_keys.delete(k))
46
+ end
47
+ end if passed_keys
48
+ super
49
+ cast_keys # defined in CouchRest::Mixins::Properties
50
+ unless self['_id'] && self['_rev']
51
+ self['couchrest-type'] = self.class.to_s
52
+ end
53
+ end
54
+
55
+ # Defines an instance and save it directly to the database
56
+ #
57
+ # ==== Returns
58
+ # returns the reloaded document
59
+ def self.create(options)
60
+ instance = new(options)
61
+ instance.create
62
+ instance
63
+ end
64
+
65
+ # Defines an instance and save it directly to the database
66
+ #
67
+ # ==== Returns
68
+ # returns the reloaded document or raises an exception
69
+ def self.create!(options)
70
+ instance = new(options)
71
+ instance.create!
72
+ instance
73
+ end
74
+
75
+ # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
76
+ # on the document whenever saving occurs. CouchRest uses a pretty
77
+ # decent time format by default. See Time#to_json
78
+ def self.timestamps!
79
+ class_eval <<-EOS, __FILE__, __LINE__
80
+ property(:updated_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
81
+ property(:created_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
82
+
83
+ set_callback :save, :before do |object|
84
+ object['updated_at'] = Time.now
85
+ object['created_at'] = object['updated_at'] if object.new?
86
+ end
87
+ EOS
88
+ end
89
+
90
+ # Name a method that will be called before the document is first saved,
91
+ # which returns a string to be used for the document's <tt>_id</tt>.
92
+ # Because CouchDB enforces a constraint that each id must be unique,
93
+ # this can be used to enforce eg: uniq usernames. Note that this id
94
+ # must be globally unique across all document types which share a
95
+ # database, so if you'd like to scope uniqueness to this class, you
96
+ # should use the class name as part of the unique id.
97
+ def self.unique_id method = nil, &block
98
+ if method
99
+ define_method :set_unique_id do
100
+ self['_id'] ||= self.send(method)
101
+ end
102
+ elsif block
103
+ define_method :set_unique_id do
104
+ uniqid = block.call(self)
105
+ raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
106
+ self['_id'] ||= uniqid
107
+ end
108
+ end
109
+ end
110
+
111
+ # Temp solution to make the view_by methods available
112
+ def self.method_missing(m, *args, &block)
113
+ if has_view?(m)
114
+ query = args.shift || {}
115
+ view(m, query, *args, &block)
116
+ else
117
+ super
118
+ end
119
+ end
120
+
121
+ ### instance methods
122
+
123
+ # Returns the Class properties
124
+ #
125
+ # ==== Returns
126
+ # Array:: the list of properties for the instance
127
+ def properties
128
+ self.class.properties
129
+ end
130
+
131
+ # Gets a reference to the actual document in the DB
132
+ # Calls up to the next document if there is one,
133
+ # Otherwise we're at the top and we return self
134
+ def base_doc
135
+ return self if base_doc?
136
+ @casted_by.base_doc
137
+ end
138
+
139
+ # Checks if we're the top document
140
+ def base_doc?
141
+ !@casted_by
142
+ end
143
+
144
+ # Takes a hash as argument, and applies the values by using writer methods
145
+ # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
146
+ # missing. In case of error, no attributes are changed.
147
+ def update_attributes_without_saving(hash)
148
+ # remove attributes that cannot be updated, silently ignoring them
149
+ # which matches Rails behavior when, for instance, setting created_at.
150
+ # make a copy, we don't want to change arguments
151
+ attrs = hash.dup
152
+ %w[_id _rev created_at updated_at].each {|attr| attrs.delete(attr)}
153
+ attrs.each do |k, v|
154
+ raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
155
+ end
156
+ attrs.each do |k, v|
157
+ self.send("#{k}=",v)
158
+ end
159
+ end
160
+ alias :attributes= :update_attributes_without_saving
161
+
162
+ # Takes a hash as argument, and applies the values by using writer methods
163
+ # for each key. Raises a NoMethodError if the corresponding methods are
164
+ # missing. In case of error, no attributes are changed.
165
+ def update_attributes(hash)
166
+ update_attributes_without_saving hash
167
+ save
168
+ end
169
+
170
+ # for compatibility with old-school frameworks
171
+ alias :new_record? :new?
172
+
173
+ # Trigger the callbacks (before, after, around)
174
+ # and create the document
175
+ # It's important to have a create callback since you can't check if a document
176
+ # was new after you saved it
177
+ #
178
+ # When creating a document, both the create and the save callbacks will be triggered.
179
+ def create(bulk = false)
180
+ caught = catch(:halt) do
181
+ _run_create_callbacks do
182
+ _run_save_callbacks do
183
+ create_without_callbacks(bulk)
184
+ end
185
+ end
186
+ end
187
+ end
188
+
189
+ # unlike save, create returns the newly created document
190
+ def create_without_callbacks(bulk =false)
191
+ raise ArgumentError, "a document requires a database to be created to (The document or the #{self.class} default database were not set)" unless database
192
+ set_unique_id if new? && self.respond_to?(:set_unique_id)
193
+ result = database.save_doc(self, bulk)
194
+ (result["ok"] == true) ? self : false
195
+ end
196
+
197
+ # Creates the document in the db. Raises an exception
198
+ # if the document is not created properly.
199
+ def create!
200
+ raise "#{self.inspect} failed to save" unless self.create
201
+ end
202
+
203
+ # Trigger the callbacks (before, after, around)
204
+ # only if the document isn't new
205
+ def update(bulk = false)
206
+ caught = catch(:halt) do
207
+ if self.new?
208
+ save(bulk)
209
+ else
210
+ _run_update_callbacks do
211
+ _run_save_callbacks do
212
+ save_without_callbacks(bulk)
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ # Trigger the callbacks (before, after, around)
220
+ # and save the document
221
+ def save(bulk = false)
222
+ caught = catch(:halt) do
223
+ if self.new?
224
+ _run_save_callbacks do
225
+ save_without_callbacks(bulk)
226
+ end
227
+ else
228
+ update(bulk)
229
+ end
230
+ end
231
+ end
232
+
233
+ # Overridden to set the unique ID.
234
+ # Returns a boolean value
235
+ def save_without_callbacks(bulk = false)
236
+ raise ArgumentError, "a document requires a database to be saved to (The document or the #{self.class} default database were not set)" unless database
237
+ set_unique_id if new? && self.respond_to?(:set_unique_id)
238
+ result = database.save_doc(self, bulk)
239
+ mark_as_saved if result["ok"] == true
240
+ result["ok"] == true
241
+ end
242
+
243
+ # Saves the document to the db using save. Raises an exception
244
+ # if the document is not saved properly.
245
+ def save!
246
+ raise "#{self.inspect} failed to save" unless self.save
247
+ end
248
+
249
+ # Deletes the document from the database. Runs the :destroy callbacks.
250
+ # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
251
+ # document to be saved to a new <tt>_id</tt>.
252
+ def destroy(bulk=false)
253
+ caught = catch(:halt) do
254
+ _run_destroy_callbacks do
255
+ result = database.delete_doc(self, bulk)
256
+ if result['ok']
257
+ self.delete('_rev')
258
+ self.delete('_id')
259
+ end
260
+ result['ok']
261
+ end
262
+ end
263
+ end
264
+
265
+ protected
266
+
267
+ # Set document_saved flag on all casted models to true
268
+ def mark_as_saved
269
+ self.each do |key, prop|
270
+ if prop.is_a?(Array)
271
+ prop.each do |item|
272
+ if item.respond_to?(:document_saved)
273
+ item.send(:document_saved=, true)
274
+ end
275
+ end
276
+ elsif prop.respond_to?(:document_saved)
277
+ prop.send(:document_saved=, true)
278
+ end
279
+ end
280
+ end
281
+
282
+ end
283
+ end
@@ -0,0 +1,59 @@
1
+ module CouchRest
2
+
3
+ # Basic attribute support for adding getter/setter + validation
4
+ class Property
5
+ attr_reader :name, :type, :read_only, :alias, :default, :casted, :init_method, :options
6
+
7
+ # attribute to define
8
+ def initialize(name, type = nil, options = {})
9
+ @name = name.to_s
10
+ parse_type(type)
11
+ parse_options(options)
12
+ self
13
+ end
14
+
15
+
16
+ private
17
+
18
+ def parse_type(type)
19
+ if type.nil?
20
+ @type = 'String'
21
+ elsif type.is_a?(Array) && type.empty?
22
+ @type = 'Array'
23
+ else
24
+ @type = type.is_a?(Array) ? [type.first.to_s] : type.to_s
25
+ end
26
+ end
27
+
28
+ def parse_options(options)
29
+ return if options.empty?
30
+ @validation_format = options.delete(:format) if options[:format]
31
+ @read_only = options.delete(:read_only) if options[:read_only]
32
+ @alias = options.delete(:alias) if options[:alias]
33
+ @default = options.delete(:default) unless options[:default].nil?
34
+ @casted = options[:casted] ? true : false
35
+ @init_method = options[:send] ? options.delete(:send) : 'new'
36
+ @options = options
37
+ end
38
+
39
+ end
40
+ end
41
+
42
+ class CastedArray < Array
43
+ attr_accessor :casted_by
44
+
45
+ def << obj
46
+ obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
47
+ super(obj)
48
+ end
49
+
50
+ def push(obj)
51
+ obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
52
+ super(obj)
53
+ end
54
+
55
+ def []= index, obj
56
+ obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
57
+ super(index, obj)
58
+ end
59
+ end
@@ -0,0 +1,42 @@
1
+ # blank? methods for several different class types
2
+ class Object
3
+ # Returns true if the object is nil or empty (if applicable)
4
+ def blank?
5
+ nil? || (respond_to?(:empty?) && empty?)
6
+ end
7
+ end # class Object
8
+
9
+ class Numeric
10
+ # Numerics can't be blank
11
+ def blank?
12
+ false
13
+ end
14
+ end # class Numeric
15
+
16
+ class NilClass
17
+ # Nils are always blank
18
+ def blank?
19
+ true
20
+ end
21
+ end # class NilClass
22
+
23
+ class TrueClass
24
+ # True is not blank.
25
+ def blank?
26
+ false
27
+ end
28
+ end # class TrueClass
29
+
30
+ class FalseClass
31
+ # False is always blank.
32
+ def blank?
33
+ true
34
+ end
35
+ end # class FalseClass
36
+
37
+ class String
38
+ # Strips out whitespace then tests if the string is empty.
39
+ def blank?
40
+ strip.empty?
41
+ end
42
+ end # class String
@@ -0,0 +1,190 @@
1
+ # Copyright (c) 2006-2009 David Heinemeier Hansson
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.
21
+ #
22
+ # Extracted From
23
+ # http://github.com/rails/rails/commit/971e2438d98326c994ec6d3ef8e37b7e868ed6e2
24
+
25
+ # Extends the class object with class and instance accessors for class attributes,
26
+ # just like the native attr* accessors for instance attributes.
27
+ #
28
+ # class Person
29
+ # cattr_accessor :hair_colors
30
+ # end
31
+ #
32
+ # Person.hair_colors = [:brown, :black, :blonde, :red]
33
+ class Class
34
+ def cattr_reader(*syms)
35
+ syms.flatten.each do |sym|
36
+ next if sym.is_a?(Hash)
37
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
38
+ unless defined? @@#{sym} # unless defined? @@hair_colors
39
+ @@#{sym} = nil # @@hair_colors = nil
40
+ end # end
41
+ #
42
+ def self.#{sym} # def self.hair_colors
43
+ @@#{sym} # @@hair_colors
44
+ end # end
45
+ #
46
+ def #{sym} # def hair_colors
47
+ @@#{sym} # @@hair_colors
48
+ end # end
49
+ EOS
50
+ end
51
+ end unless Class.respond_to?(:cattr_reader)
52
+
53
+ def cattr_writer(*syms)
54
+ options = syms.extract_options!
55
+ syms.flatten.each do |sym|
56
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
57
+ unless defined? @@#{sym} # unless defined? @@hair_colors
58
+ @@#{sym} = nil # @@hair_colors = nil
59
+ end # end
60
+ #
61
+ def self.#{sym}=(obj) # def self.hair_colors=(obj)
62
+ @@#{sym} = obj # @@hair_colors = obj
63
+ end # end
64
+ #
65
+ #{" #
66
+ def #{sym}=(obj) # def hair_colors=(obj)
67
+ @@#{sym} = obj # @@hair_colors = obj
68
+ end # end
69
+ " unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
70
+ EOS
71
+ end
72
+ end unless Class.respond_to?(:cattr_writer)
73
+
74
+ def cattr_accessor(*syms)
75
+ cattr_reader(*syms)
76
+ cattr_writer(*syms)
77
+ end unless Class.respond_to?(:cattr_accessor)
78
+
79
+ # Defines class-level inheritable attribute reader. Attributes are available to subclasses,
80
+ # each subclass has a copy of parent's attribute.
81
+ #
82
+ # @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for.
83
+ # @return <Array[#to_s]> Array of attributes converted into inheritable_readers.
84
+ #
85
+ # @api public
86
+ #
87
+ # @todo Do we want to block instance_reader via :instance_reader => false
88
+ # @todo It would be preferable that we do something with a Hash passed in
89
+ # (error out or do the same as other methods above) instead of silently
90
+ # moving on). In particular, this makes the return value of this function
91
+ # less useful.
92
+ def extlib_inheritable_reader(*ivars)
93
+ instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash)
94
+
95
+ ivars.each do |ivar|
96
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
97
+ def self.#{ivar}
98
+ return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar})
99
+ ivar = superclass.#{ivar}
100
+ return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}")
101
+ @#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) ? ivar.dup : ivar
102
+ end
103
+ RUBY
104
+ unless instance_reader == false
105
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
106
+ def #{ivar}
107
+ self.class.#{ivar}
108
+ end
109
+ RUBY
110
+ end
111
+ end
112
+ end unless Class.respond_to?(:extlib_inheritable_reader)
113
+
114
+ # Defines class-level inheritable attribute writer. Attributes are available to subclasses,
115
+ # each subclass has a copy of parent's attribute.
116
+ #
117
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
118
+ # define inheritable writer for.
119
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
120
+ # @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers.
121
+ #
122
+ # @api public
123
+ #
124
+ # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
125
+ # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
126
+ def extlib_inheritable_writer(*ivars)
127
+ instance_writer = ivars.pop[:writer] if ivars.last.is_a?(Hash)
128
+ ivars.each do |ivar|
129
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
130
+ def self.#{ivar}=(obj)
131
+ @#{ivar} = obj
132
+ end
133
+ RUBY
134
+ unless instance_writer == false
135
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
136
+ def #{ivar}=(obj) self.class.#{ivar} = obj end
137
+ RUBY
138
+ end
139
+
140
+ self.send("#{ivar}=", yield) if block_given?
141
+ end
142
+ end unless Class.respond_to?(:extlib_inheritable_writer)
143
+
144
+ # Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
145
+ # each subclass has a copy of parent's attribute.
146
+ #
147
+ # @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
148
+ # define inheritable accessor for.
149
+ # @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
150
+ # @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
151
+ #
152
+ # @api public
153
+ def extlib_inheritable_accessor(*syms, &block)
154
+ extlib_inheritable_reader(*syms)
155
+ extlib_inheritable_writer(*syms, &block)
156
+ end unless Class.respond_to?(:extlib_inheritable_accessor)
157
+ end
158
+
159
+ class Array
160
+ # Extracts options from a set of arguments. Removes and returns the last
161
+ # element in the array if it's a hash, otherwise returns a blank hash.
162
+ #
163
+ # def options(*args)
164
+ # args.extract_options!
165
+ # end
166
+ #
167
+ # options(1, 2) # => {}
168
+ # options(1, 2, :a => :b) # => {:a=>:b}
169
+ def extract_options!
170
+ last.is_a?(::Hash) ? pop : {}
171
+ end unless Array.new.respond_to?(:extract_options!)
172
+
173
+ # Wraps the object in an Array unless it's an Array. Converts the
174
+ # object to an Array using #to_ary if it implements that.
175
+ def self.wrap(object)
176
+ case object
177
+ when nil
178
+ []
179
+ when self
180
+ object
181
+ else
182
+ if object.respond_to?(:to_ary)
183
+ object.to_ary
184
+ else
185
+ [object]
186
+ end
187
+ end
188
+ end unless Array.respond_to?(:wrap)
189
+ end
190
+
@@ -0,0 +1,47 @@
1
+ # This file contains various hacks for Rails compatibility.
2
+ class Hash
3
+ # Hack so that CouchRest::Document, which descends from Hash,
4
+ # doesn't appear to Rails routing as a Hash of options
5
+ def self.===(other)
6
+ return false if self == Hash && other.is_a?(CouchRest::Document)
7
+ super
8
+ end
9
+ end
10
+
11
+ CouchRest::Document.class_eval do
12
+ # Need this when passing doc to a resourceful route
13
+ alias_method :to_param, :id
14
+
15
+ # Hack so that CouchRest::Document, which descends from Hash,
16
+ # doesn't appear to Rails routing as a Hash of options
17
+ def is_a?(o)
18
+ return false if o == Hash
19
+ super
20
+ end
21
+ alias_method :kind_of?, :is_a?
22
+
23
+ # Gives extended doc a seamless logger
24
+ def logger
25
+ ActiveRecord::Base.logger
26
+ end
27
+ end
28
+
29
+ CouchRest::CastedModel.class_eval do
30
+ # The to_param method is needed for rails to generate resourceful routes.
31
+ # In your controller, remember that it's actually the id of the document.
32
+ def id
33
+ return nil if base_doc.nil?
34
+ base_doc.id
35
+ end
36
+ alias_method :to_param, :id
37
+ end
38
+
39
+ require Pathname.new(File.dirname(__FILE__)).join('..', 'validation', 'validation_errors')
40
+
41
+ CouchRest::Validation::ValidationErrors.class_eval do
42
+ # Returns the total number of errors added. Two errors added to the same attribute will be counted as such.
43
+ # This method is called by error_messages_for
44
+ def count
45
+ errors.values.inject(0) { |error_count, errors_for_attribute| error_count + errors_for_attribute.size }
46
+ end
47
+ end