couchrest_extended_document 1.0.0.beta5

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 (71) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +68 -0
  3. data/Rakefile +68 -0
  4. data/THANKS.md +19 -0
  5. data/examples/model/example.rb +144 -0
  6. data/history.txt +159 -0
  7. data/lib/couchrest/casted_array.rb +25 -0
  8. data/lib/couchrest/casted_model.rb +55 -0
  9. data/lib/couchrest/extended_document.rb +323 -0
  10. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  11. data/lib/couchrest/mixins/callbacks.rb +532 -0
  12. data/lib/couchrest/mixins/class_proxy.rb +120 -0
  13. data/lib/couchrest/mixins/collection.rb +260 -0
  14. data/lib/couchrest/mixins/design_doc.rb +127 -0
  15. data/lib/couchrest/mixins/document_queries.rb +82 -0
  16. data/lib/couchrest/mixins/extended_attachments.rb +73 -0
  17. data/lib/couchrest/mixins/properties.rb +162 -0
  18. data/lib/couchrest/mixins/validation.rb +245 -0
  19. data/lib/couchrest/mixins/views.rb +148 -0
  20. data/lib/couchrest/mixins.rb +11 -0
  21. data/lib/couchrest/property.rb +50 -0
  22. data/lib/couchrest/support/couchrest.rb +19 -0
  23. data/lib/couchrest/support/rails.rb +42 -0
  24. data/lib/couchrest/typecast.rb +175 -0
  25. data/lib/couchrest/validation/auto_validate.rb +156 -0
  26. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  27. data/lib/couchrest/validation/validation_errors.rb +125 -0
  28. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  29. data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
  30. data/lib/couchrest/validation/validators/format_validator.rb +122 -0
  31. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  32. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  33. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  34. data/lib/couchrest/validation/validators/length_validator.rb +139 -0
  35. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  36. data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
  37. data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
  38. data/lib/couchrest/validation.rb +245 -0
  39. data/lib/couchrest_extended_document.rb +21 -0
  40. data/spec/couchrest/attribute_protection_spec.rb +150 -0
  41. data/spec/couchrest/casted_extended_doc_spec.rb +79 -0
  42. data/spec/couchrest/casted_model_spec.rb +406 -0
  43. data/spec/couchrest/extended_doc_attachment_spec.rb +148 -0
  44. data/spec/couchrest/extended_doc_inherited_spec.rb +40 -0
  45. data/spec/couchrest/extended_doc_spec.rb +868 -0
  46. data/spec/couchrest/extended_doc_subclass_spec.rb +99 -0
  47. data/spec/couchrest/extended_doc_view_spec.rb +529 -0
  48. data/spec/couchrest/property_spec.rb +648 -0
  49. data/spec/fixtures/attachments/README +3 -0
  50. data/spec/fixtures/attachments/couchdb.png +0 -0
  51. data/spec/fixtures/attachments/test.html +11 -0
  52. data/spec/fixtures/more/article.rb +35 -0
  53. data/spec/fixtures/more/card.rb +22 -0
  54. data/spec/fixtures/more/cat.rb +22 -0
  55. data/spec/fixtures/more/course.rb +25 -0
  56. data/spec/fixtures/more/event.rb +8 -0
  57. data/spec/fixtures/more/invoice.rb +17 -0
  58. data/spec/fixtures/more/person.rb +9 -0
  59. data/spec/fixtures/more/question.rb +6 -0
  60. data/spec/fixtures/more/service.rb +12 -0
  61. data/spec/fixtures/more/user.rb +22 -0
  62. data/spec/fixtures/views/lib.js +3 -0
  63. data/spec/fixtures/views/test_view/lib.js +3 -0
  64. data/spec/fixtures/views/test_view/only-map.js +4 -0
  65. data/spec/fixtures/views/test_view/test-map.js +3 -0
  66. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  67. data/spec/spec.opts +5 -0
  68. data/spec/spec_helper.rb +49 -0
  69. data/utils/remap.rb +27 -0
  70. data/utils/subset.rb +30 -0
  71. metadata +200 -0
@@ -0,0 +1,245 @@
1
+ # Extracted from dm-validations 0.9.10
2
+ #
3
+ # Copyright (c) 2007 Guy van den Berg
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ class Object
25
+ def validatable?
26
+ false
27
+ end
28
+ end
29
+
30
+ require 'pathname'
31
+
32
+ dir = File.join(Pathname(__FILE__).dirname.expand_path, 'validation')
33
+
34
+ require File.join(dir, 'validation_errors')
35
+ require File.join(dir, 'contextual_validators')
36
+ require File.join(dir, 'auto_validate')
37
+
38
+ require File.join(dir, 'validators', 'generic_validator')
39
+ require File.join(dir, 'validators', 'required_field_validator')
40
+ require File.join(dir, 'validators', 'absent_field_validator')
41
+ require File.join(dir, 'validators', 'format_validator')
42
+ require File.join(dir, 'validators', 'length_validator')
43
+ require File.join(dir, 'validators', 'numeric_validator')
44
+ require File.join(dir, 'validators', 'method_validator')
45
+ require File.join(dir, 'validators', 'confirmation_validator')
46
+
47
+ module CouchRest
48
+ module Validation
49
+
50
+ def self.included(base)
51
+ base.extlib_inheritable_accessor(:auto_validation)
52
+ base.class_eval <<-EOS, __FILE__, __LINE__ + 1
53
+ # Callbacks
54
+ define_callbacks :validate
55
+
56
+ # Turn off auto validation by default
57
+ self.auto_validation ||= false
58
+
59
+ # Force the auto validation for the class properties
60
+ # This feature is still not fully ported over,
61
+ # test are lacking, so please use with caution
62
+ def self.auto_validate!
63
+ self.auto_validation = true
64
+ end
65
+
66
+ # share the validations with subclasses
67
+ def self.inherited(subklass)
68
+ self.validators.contexts.each do |k, v|
69
+ subklass.validators.contexts[k] = v.dup
70
+ end
71
+ super
72
+ end
73
+ EOS
74
+
75
+ base.extend(ClassMethods)
76
+ base.class_eval <<-EOS, __FILE__, __LINE__ + 1
77
+ define_callbacks :validate
78
+ if method_defined?(:_run_save_callbacks)
79
+ set_callback :save, :before, :check_validations
80
+ end
81
+ EOS
82
+ base.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
83
+ def self.define_property(name, options={})
84
+ super
85
+ auto_generate_validations(properties.last) if properties && properties.size > 0
86
+ autovalidation_check = true
87
+ end
88
+ RUBY_EVAL
89
+ end
90
+
91
+ # Ensures the object is valid for the context provided, and otherwise
92
+ # throws :halt and returns false.
93
+ #
94
+ def check_validations(context = :default)
95
+ throw(:halt, false) unless context.nil? || valid?(context)
96
+ end
97
+
98
+ # Return the ValidationErrors
99
+ #
100
+ def errors
101
+ @errors ||= ValidationErrors.new
102
+ end
103
+
104
+ # Mark this resource as validatable. When we validate associations of a
105
+ # resource we can check if they respond to validatable? before trying to
106
+ # recursivly validate them
107
+ #
108
+ def validatable?
109
+ true
110
+ end
111
+
112
+ # Alias for valid?(:default)
113
+ #
114
+ def valid_for_default?
115
+ valid?(:default)
116
+ end
117
+
118
+ # Check if a resource is valid in a given context
119
+ #
120
+ def valid?(context = :default)
121
+ recursive_valid?(self, context, true)
122
+ end
123
+
124
+ # checking on casted objects
125
+ def validate_casted_arrays
126
+ result = true
127
+ array_casted_properties = self.class.properties.select { |property| property.casted && property.type.instance_of?(Array) }
128
+ array_casted_properties.each do |property|
129
+ casted_values = self.send(property.name)
130
+ next unless casted_values.is_a?(Array) && casted_values.first.respond_to?(:valid?)
131
+ casted_values.each do |value|
132
+ result = (result && value.valid?) if value.respond_to?(:valid?)
133
+ end
134
+ end
135
+ result
136
+ end
137
+
138
+ # Do recursive validity checking
139
+ #
140
+ def recursive_valid?(target, context, state)
141
+ valid = state
142
+ target.each do |key, prop|
143
+ if prop.is_a?(Array)
144
+ prop.each do |item|
145
+ if item.validatable?
146
+ valid = recursive_valid?(item, context, valid) && valid
147
+ end
148
+ end
149
+ elsif prop.validatable?
150
+ valid = recursive_valid?(prop, context, valid) && valid
151
+ end
152
+ end
153
+ target._run_validate_callbacks do
154
+ target.class.validators.execute(context, target) && valid
155
+ end
156
+ end
157
+
158
+
159
+ def validation_property_value(name)
160
+ self.respond_to?(name, true) ? self.send(name) : nil
161
+ end
162
+
163
+ # Get the corresponding Object property, if it exists.
164
+ def validation_property(field_name)
165
+ properties.find{|p| p.name == field_name}
166
+ end
167
+
168
+ module ClassMethods
169
+ include CouchRest::Validation::ValidatesPresent
170
+ include CouchRest::Validation::ValidatesAbsent
171
+ include CouchRest::Validation::ValidatesIsConfirmed
172
+ # include CouchRest::Validation::ValidatesIsPrimitive
173
+ # include CouchRest::Validation::ValidatesIsAccepted
174
+ include CouchRest::Validation::ValidatesFormat
175
+ include CouchRest::Validation::ValidatesLength
176
+ # include CouchRest::Validation::ValidatesWithin
177
+ include CouchRest::Validation::ValidatesIsNumber
178
+ include CouchRest::Validation::ValidatesWithMethod
179
+ # include CouchRest::Validation::ValidatesWithBlock
180
+ # include CouchRest::Validation::ValidatesIsUnique
181
+ include CouchRest::Validation::AutoValidate
182
+
183
+ # Return the set of contextual validators or create a new one
184
+ #
185
+ def validators
186
+ @validations ||= ContextualValidators.new
187
+ end
188
+
189
+ # Clean up the argument list and return a opts hash, including the
190
+ # merging of any default opts. Set the context to default if none is
191
+ # provided. Also allow :context to be aliased to :on, :when & group
192
+ #
193
+ def opts_from_validator_args(args, defaults = nil)
194
+ opts = args.last.kind_of?(Hash) ? args.pop : {}
195
+ context = :default
196
+ context = opts[:context] if opts.has_key?(:context)
197
+ context = opts.delete(:on) if opts.has_key?(:on)
198
+ context = opts.delete(:when) if opts.has_key?(:when)
199
+ context = opts.delete(:group) if opts.has_key?(:group)
200
+ opts[:context] = context
201
+ opts.merge!(defaults) unless defaults.nil?
202
+ opts
203
+ end
204
+
205
+ # Given a new context create an instance method of
206
+ # valid_for_<context>? which simply calls valid?(context)
207
+ # if it does not already exist
208
+ #
209
+ def create_context_instance_methods(context)
210
+ name = "valid_for_#{context.to_s}?" # valid_for_signup?
211
+ if !self.instance_methods.include?(name)
212
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
213
+ def #{name} # def valid_for_signup?
214
+ valid?('#{context.to_s}'.to_sym) # valid?('signup'.to_sym)
215
+ end # end
216
+ EOS
217
+ end
218
+ end
219
+
220
+ # Create a new validator of the given klazz and push it onto the
221
+ # requested context for each of the attributes in the fields list
222
+ #
223
+ def add_validator_to_context(opts, fields, klazz)
224
+ fields.each do |field|
225
+ validator = klazz.new(field.to_sym, opts)
226
+ if opts[:context].is_a?(Symbol)
227
+ unless validators.context(opts[:context]).include?(validator)
228
+ validators.context(opts[:context]) << validator
229
+ create_context_instance_methods(opts[:context])
230
+ end
231
+ elsif opts[:context].is_a?(Array)
232
+ opts[:context].each do |c|
233
+ unless validators.context(c).include?(validator)
234
+ validators.context(c) << validator
235
+ create_context_instance_methods(c)
236
+ end
237
+ end
238
+ end
239
+ end
240
+ end
241
+
242
+ end # module ClassMethods
243
+ end # module Validation
244
+
245
+ end # module CouchRest
@@ -0,0 +1,21 @@
1
+
2
+ # require File.join(File.dirname(__FILE__), "couchrest", "extended_document")
3
+
4
+ gem 'couchrest'
5
+
6
+ require 'couchrest'
7
+
8
+ require 'active_support/core_ext'
9
+ require 'mime/types'
10
+ require "enumerator"
11
+
12
+ # Monkey patches applied to couchrest
13
+ require File.join(File.dirname(__FILE__), 'couchrest', 'support', 'couchrest')
14
+
15
+ # Base libraries
16
+ require File.join(File.dirname(__FILE__), 'couchrest', 'extended_document')
17
+ require File.join(File.dirname(__FILE__), 'couchrest', 'casted_model')
18
+
19
+ # Add rails support *after* everything has loaded
20
+ require File.join(File.dirname(__FILE__), 'couchrest', 'support', 'rails') if defined?(Rails)
21
+
@@ -0,0 +1,150 @@
1
+ require File.expand_path("../../spec_helper", __FILE__)
2
+
3
+ describe "ExtendedDocument", "no declarations" do
4
+ class NoProtection < CouchRest::ExtendedDocument
5
+ use_database TEST_SERVER.default_database
6
+ property :name
7
+ property :phone
8
+ end
9
+
10
+ it "should not protect anything through new" do
11
+ user = NoProtection.new(:name => "will", :phone => "555-5555")
12
+
13
+ user.name.should == "will"
14
+ user.phone.should == "555-5555"
15
+ end
16
+
17
+ it "should not protect anything through attributes=" do
18
+ user = NoProtection.new
19
+ user.attributes = {:name => "will", :phone => "555-5555"}
20
+
21
+ user.name.should == "will"
22
+ user.phone.should == "555-5555"
23
+ end
24
+
25
+ it "should recreate from the database properly" do
26
+ user = NoProtection.new
27
+ user.name = "will"
28
+ user.phone = "555-5555"
29
+ user.save!
30
+
31
+ user = NoProtection.get(user.id)
32
+ user.name.should == "will"
33
+ user.phone.should == "555-5555"
34
+ end
35
+ end
36
+
37
+ describe "ExtendedDocument", "accessible flag" do
38
+ class WithAccessible < CouchRest::ExtendedDocument
39
+ use_database TEST_SERVER.default_database
40
+ property :name, :accessible => true
41
+ property :admin, :default => false
42
+ end
43
+
44
+ it "should recognize accessible properties" do
45
+ props = WithAccessible.accessible_properties.map { |prop| prop.name}
46
+ props.should include("name")
47
+ props.should_not include("admin")
48
+ end
49
+
50
+ it "should protect non-accessible properties set through new" do
51
+ user = WithAccessible.new(:name => "will", :admin => true)
52
+
53
+ user.name.should == "will"
54
+ user.admin.should == false
55
+ end
56
+
57
+ it "should protect non-accessible properties set through attributes=" do
58
+ user = WithAccessible.new
59
+ user.attributes = {:name => "will", :admin => true}
60
+
61
+ user.name.should == "will"
62
+ user.admin.should == false
63
+ end
64
+ end
65
+
66
+ describe "ExtendedDocument", "protected flag" do
67
+ class WithProtected < CouchRest::ExtendedDocument
68
+ use_database TEST_SERVER.default_database
69
+ property :name
70
+ property :admin, :default => false, :protected => true
71
+ end
72
+
73
+ it "should recognize protected properties" do
74
+ props = WithProtected.protected_properties.map { |prop| prop.name}
75
+ props.should_not include("name")
76
+ props.should include("admin")
77
+ end
78
+
79
+ it "should protect non-accessible properties set through new" do
80
+ user = WithProtected.new(:name => "will", :admin => true)
81
+
82
+ user.name.should == "will"
83
+ user.admin.should == false
84
+ end
85
+
86
+ it "should protect non-accessible properties set through attributes=" do
87
+ user = WithProtected.new
88
+ user.attributes = {:name => "will", :admin => true}
89
+
90
+ user.name.should == "will"
91
+ user.admin.should == false
92
+ end
93
+ end
94
+
95
+ describe "ExtendedDocument", "protected flag" do
96
+ class WithBoth < CouchRest::ExtendedDocument
97
+ use_database TEST_SERVER.default_database
98
+ property :name, :accessible => true
99
+ property :admin, :default => false, :protected => true
100
+ end
101
+
102
+ it "should raise an error when both are set" do
103
+ lambda { WithBoth.new }.should raise_error
104
+ end
105
+ end
106
+
107
+ describe "ExtendedDocument", "from database" do
108
+ class WithProtected < CouchRest::ExtendedDocument
109
+ use_database TEST_SERVER.default_database
110
+ property :name
111
+ property :admin, :default => false, :protected => true
112
+ view_by :name
113
+ end
114
+
115
+ before(:each) do
116
+ @user = WithProtected.new
117
+ @user.name = "will"
118
+ @user.admin = true
119
+ @user.save!
120
+ end
121
+
122
+ def verify_attrs(user)
123
+ user.name.should == "will"
124
+ user.admin.should == true
125
+ end
126
+
127
+ it "ExtendedDocument#get should not strip protected attributes" do
128
+ reloaded = WithProtected.get( @user.id )
129
+ verify_attrs reloaded
130
+ end
131
+
132
+ it "ExtendedDocument#get! should not strip protected attributes" do
133
+ reloaded = WithProtected.get!( @user.id )
134
+ verify_attrs reloaded
135
+ end
136
+
137
+ it "ExtendedDocument#all should not strip protected attributes" do
138
+ # all creates a CollectionProxy
139
+ docs = WithProtected.all(:key => @user.id)
140
+ docs.size.should == 1
141
+ reloaded = docs.first
142
+ verify_attrs reloaded
143
+ end
144
+
145
+ it "views should not strip protected attributes" do
146
+ docs = WithProtected.by_name(:startkey => "will", :endkey => "will")
147
+ reloaded = docs.first
148
+ verify_attrs reloaded
149
+ end
150
+ end
@@ -0,0 +1,79 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+ require File.join(FIXTURE_PATH, 'more', 'cat')
3
+ require File.join(FIXTURE_PATH, 'more', 'person')
4
+ require File.join(FIXTURE_PATH, 'more', 'card')
5
+
6
+ class Driver < CouchRest::ExtendedDocument
7
+ use_database TEST_SERVER.default_database
8
+ # You have to add a casted_by accessor if you want to reach a casted extended doc parent
9
+ attr_accessor :casted_by
10
+
11
+ property :name
12
+ end
13
+
14
+ class Car < CouchRest::ExtendedDocument
15
+ use_database TEST_SERVER.default_database
16
+
17
+ property :name
18
+ property :driver, :cast_as => 'Driver'
19
+ property :backseat_driver, :cast_as => Driver
20
+ end
21
+
22
+ describe "casting an extended document" do
23
+
24
+ before(:each) do
25
+ @driver = Driver.new(:name => 'Matt')
26
+ @car = Car.new(:name => 'Renault 306', :driver => @driver)
27
+ @car2 = Car.new(:name => 'Renault 306', :backseat_driver => @driver.dup)
28
+ end
29
+
30
+ it "should retain all properties of the casted attribute" do
31
+ @car.driver.should == @driver
32
+ @car2.backseat_driver.should == @driver
33
+ end
34
+
35
+ it "should let the casted document know who casted it" do
36
+ @car.driver.casted_by.should == @car
37
+ @car2.backseat_driver.casted_by.should == @car2
38
+ end
39
+ end
40
+
41
+ describe "assigning a value to casted attribute after initializing an object" do
42
+
43
+ before(:each) do
44
+ @car = Car.new(:name => 'Renault 306')
45
+ @driver = Driver.new(:name => 'Matt')
46
+ end
47
+
48
+ it "should not create an empty casted object" do
49
+ @car.driver.should be_nil
50
+ end
51
+
52
+ it "should let you assign the value" do
53
+ @car.driver = @driver
54
+ @car.driver.name.should == 'Matt'
55
+ end
56
+
57
+ it "should cast attribute" do
58
+ @car.driver = JSON.parse(@driver.to_json)
59
+ @car.driver.should be_instance_of(Driver)
60
+ end
61
+
62
+ end
63
+
64
+ describe "casting an extended document from parsed JSON" do
65
+
66
+ before(:each) do
67
+ @driver = Driver.new(:name => 'Matt')
68
+ @car = Car.new(:name => 'Renault 306', :driver => @driver)
69
+ @new_car = Car.new(JSON.parse(@car.to_json))
70
+ end
71
+
72
+ it "should cast casted attribute" do
73
+ @new_car.driver.should be_instance_of(Driver)
74
+ end
75
+
76
+ it "should retain all properties of the casted attribute" do
77
+ @new_car.driver.should == @driver
78
+ end
79
+ end