couchrest 0.33 → 0.34

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/README.md +8 -127
  2. data/Rakefile +20 -36
  3. data/THANKS.md +2 -1
  4. data/history.txt +25 -0
  5. data/lib/couchrest.rb +5 -4
  6. data/lib/couchrest/core/database.rb +26 -21
  7. data/lib/couchrest/core/document.rb +4 -3
  8. data/lib/couchrest/helper/streamer.rb +11 -4
  9. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  10. data/lib/couchrest/mixins/callbacks.rb +187 -138
  11. data/lib/couchrest/mixins/collection.rb +3 -16
  12. data/lib/couchrest/mixins/extended_attachments.rb +1 -1
  13. data/lib/couchrest/mixins/extended_document_mixins.rb +1 -0
  14. data/lib/couchrest/mixins/properties.rb +71 -44
  15. data/lib/couchrest/mixins/validation.rb +18 -29
  16. data/lib/couchrest/more/casted_model.rb +29 -1
  17. data/lib/couchrest/more/extended_document.rb +73 -25
  18. data/lib/couchrest/more/property.rb +20 -1
  19. data/lib/couchrest/support/class.rb +81 -67
  20. data/lib/couchrest/support/rails.rb +12 -5
  21. data/lib/couchrest/validation/auto_validate.rb +5 -9
  22. data/lib/couchrest/validation/validators/confirmation_validator.rb +11 -3
  23. data/lib/couchrest/validation/validators/format_validator.rb +8 -3
  24. data/lib/couchrest/validation/validators/length_validator.rb +10 -5
  25. data/lib/couchrest/validation/validators/numeric_validator.rb +6 -1
  26. data/lib/couchrest/validation/validators/required_field_validator.rb +8 -3
  27. data/spec/couchrest/core/couchrest_spec.rb +48 -2
  28. data/spec/couchrest/core/database_spec.rb +22 -10
  29. data/spec/couchrest/core/document_spec.rb +9 -1
  30. data/spec/couchrest/helpers/streamer_spec.rb +31 -2
  31. data/spec/couchrest/more/attribute_protection_spec.rb +94 -0
  32. data/spec/couchrest/more/casted_extended_doc_spec.rb +2 -4
  33. data/spec/couchrest/more/casted_model_spec.rb +230 -1
  34. data/spec/couchrest/more/extended_doc_attachment_spec.rb +2 -2
  35. data/spec/couchrest/more/extended_doc_spec.rb +173 -15
  36. data/spec/couchrest/more/extended_doc_view_spec.rb +17 -10
  37. data/spec/couchrest/more/property_spec.rb +97 -3
  38. data/spec/fixtures/more/article.rb +4 -3
  39. data/spec/fixtures/more/card.rb +1 -1
  40. data/spec/fixtures/more/cat.rb +5 -3
  41. data/spec/fixtures/more/event.rb +4 -1
  42. data/spec/fixtures/more/invoice.rb +2 -2
  43. data/spec/fixtures/more/person.rb +1 -0
  44. data/spec/fixtures/more/user.rb +22 -0
  45. metadata +46 -13
@@ -1,14 +1,5 @@
1
1
  module CouchRest
2
2
  module Mixins
3
- module PaginatedResults
4
- def amount_pages
5
- @amount_pages ||= 0
6
- end
7
- def amount_pages=(value)
8
- @amount_pages = value
9
- end
10
- end
11
-
12
3
  module Collection
13
4
 
14
5
  def self.included(base)
@@ -93,8 +84,6 @@ module CouchRest
93
84
  DEFAULT_PAGE = 1
94
85
  DEFAULT_PER_PAGE = 30
95
86
 
96
- attr_accessor :amount_pages
97
-
98
87
  # Create a new CollectionProxy to represent the specified view. If a
99
88
  # container class is specified, the proxy will create an object of the
100
89
  # given type for each row that comes back from the view. If no
@@ -122,11 +111,8 @@ module CouchRest
122
111
  def paginate(options = {})
123
112
  page, per_page = parse_options(options)
124
113
  results = @database.view(@view_name, pagination_options(page, per_page))
125
- @amount_pages ||= (results['total_rows'].to_f / per_page.to_f).ceil
126
114
  remember_where_we_left_off(results, page)
127
115
  results = convert_to_container_array(results)
128
- results.extend(PaginatedResults)
129
- results.amount_pages = @amount_pages
130
116
  results
131
117
  end
132
118
 
@@ -204,8 +190,9 @@ module CouchRest
204
190
  def pagination_options(page, per_page)
205
191
  view_options = @view_options.clone
206
192
  if @last_key && @last_docid && @last_page == page - 1
207
- view_options.delete(:key)
208
- options = { :startkey => @last_key, :startkey_docid => @last_docid, :limit => per_page, :skip => 1 }
193
+ key = view_options.delete(:key)
194
+ end_key = view_options[:endkey] || key
195
+ options = { :startkey => @last_key, :endkey => end_key, :startkey_docid => @last_docid, :limit => per_page, :skip => 1 }
209
196
  else
210
197
  options = { :limit => per_page, :skip => per_page * (page - 1) }
211
198
  end
@@ -64,7 +64,7 @@ module CouchRest
64
64
  def set_attachment_attr(args)
65
65
  content_type = args[:content_type] ? args[:content_type] : get_mime_type(args[:file])
66
66
  self['_attachments'][args[:name]] = {
67
- 'content-type' => content_type,
67
+ 'content_type' => content_type,
68
68
  'data' => encode_attachment(args[:file].read)
69
69
  }
70
70
  end
@@ -6,3 +6,4 @@ require File.join(File.dirname(__FILE__), 'validation')
6
6
  require File.join(File.dirname(__FILE__), 'extended_attachments')
7
7
  require File.join(File.dirname(__FILE__), 'class_proxy')
8
8
  require File.join(File.dirname(__FILE__), 'collection')
9
+ require File.join(File.dirname(__FILE__), 'attribute_protection')
@@ -27,7 +27,7 @@ module CouchRest
27
27
  class IncludeError < StandardError; end
28
28
 
29
29
  def self.included(base)
30
- base.class_eval <<-EOS, __FILE__, __LINE__
30
+ base.class_eval <<-EOS, __FILE__, __LINE__ + 1
31
31
  extlib_inheritable_accessor(:properties) unless self.respond_to?(:properties)
32
32
  self.properties ||= []
33
33
  EOS
@@ -36,7 +36,7 @@ module CouchRest
36
36
  end
37
37
 
38
38
  def apply_defaults
39
- return if self.respond_to?(:new_document?) && (new_document? == false)
39
+ return if self.respond_to?(:new?) && (new? == false)
40
40
  return unless self.class.respond_to?(:properties)
41
41
  return if self.class.properties.empty?
42
42
  # TODO: cache the default object
@@ -56,50 +56,76 @@ module CouchRest
56
56
  def cast_keys
57
57
  return unless self.class.properties
58
58
  self.class.properties.each do |property|
59
- next unless property.casted
60
- key = self.has_key?(property.name) ? property.name : property.name.to_sym
61
- # Don't cast the property unless it has a value
62
- next unless self[key]
63
- target = property.type
64
- if target.is_a?(Array)
65
- klass = ::CouchRest.constantize(target[0])
66
- self[property.name] = self[key].collect do |value|
67
- # Auto parse Time objects
68
- obj = ( (property.init_method == 'new') && klass == Time) ? Time.parse(value) : klass.send(property.init_method, value)
69
- obj.casted_by = self if obj.respond_to?(:casted_by)
70
- obj
59
+ cast_property(property)
60
+ end
61
+ end
62
+
63
+ def cast_property(property, assigned=false)
64
+ return unless property.casted
65
+ key = self.has_key?(property.name) ? property.name : property.name.to_sym
66
+ # Don't cast the property unless it has a value
67
+ return unless self[key]
68
+ if property.type.is_a?(Array)
69
+ klass = ::CouchRest.constantize(property.type[0])
70
+ arr = self[key].dup.collect do |value|
71
+ unless value.instance_of?(klass)
72
+ value = convert_property_value(property, klass, value)
71
73
  end
74
+ associate_casted_to_parent(value, assigned)
75
+ value
76
+ end
77
+ self[key] = klass != String ? CastedArray.new(arr) : arr
78
+ self[key].casted_by = self if self[key].respond_to?(:casted_by)
79
+ else
80
+ if property.type == 'boolean'
81
+ klass = TrueClass
72
82
  else
73
- # Auto parse Time objects
74
- self[property.name] = if ((property.init_method == 'new') && target == 'Time')
75
- # Using custom time parsing method because Ruby's default method is toooo slow
76
- self[key].is_a?(String) ? Time.mktime_with_offset(self[key].dup) : self[key]
77
- # Float instances don't get initialized with #new
78
- elsif ((property.init_method == 'new') && target == 'Float')
79
- cast_float(self[key])
80
- # 'boolean' type is simply used to generate a property? accessor method
81
- elsif ((property.init_method == 'new') && target == 'boolean')
82
- self[key]
83
- else
84
- # Let people use :send as a Time parse arg
85
- klass = ::CouchRest.constantize(target)
86
- klass.send(property.init_method, self[key].dup)
87
- end
88
- self[property.name].casted_by = self if self[property.name].respond_to?(:casted_by)
89
- end
83
+ klass = ::CouchRest.constantize(property.type)
84
+ end
90
85
 
91
- end
92
-
93
- def cast_float(value)
94
- begin
95
- Float(value)
96
- rescue
97
- value
86
+ unless self[key].instance_of?(klass)
87
+ self[key] = convert_property_value(property, klass, self[property.name])
98
88
  end
89
+ associate_casted_to_parent(self[property.name], assigned)
99
90
  end
100
91
 
101
92
  end
102
93
 
94
+ def associate_casted_to_parent(casted, assigned)
95
+ casted.casted_by = self if casted.respond_to?(:casted_by)
96
+ casted.document_saved = true if !assigned && casted.respond_to?(:document_saved)
97
+ end
98
+
99
+ def convert_property_value(property, klass, value)
100
+ if ((property.init_method == 'new') && klass == Time)
101
+ # Using custom time parsing method because Ruby's default method is toooo slow
102
+ value.is_a?(String) ? Time.mktime_with_offset(value.dup) : value
103
+ # Float instances don't get initialized with #new
104
+ elsif ((property.init_method == 'new') && klass == Float)
105
+ cast_float(value)
106
+ # 'boolean' type is simply used to generate a property? accessor method
107
+ elsif ((property.init_method == 'new') && klass == TrueClass)
108
+ value
109
+ else
110
+ klass.send(property.init_method, value.dup)
111
+ end
112
+ end
113
+
114
+ def cast_property_by_name(property_name)
115
+ return unless self.class.properties
116
+ property = self.class.properties.detect{|property| property.name == property_name}
117
+ return unless property
118
+ cast_property(property, true)
119
+ end
120
+
121
+ def cast_float(value)
122
+ begin
123
+ Float(value)
124
+ rescue
125
+ value
126
+ end
127
+ end
128
+
103
129
  module ClassMethods
104
130
 
105
131
  def property(name, options={})
@@ -125,7 +151,7 @@ module CouchRest
125
151
  # defines the getter for the property (and optional aliases)
126
152
  def create_property_getter(property)
127
153
  # meth = property.name
128
- class_eval <<-EOS, __FILE__, __LINE__
154
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
129
155
  def #{property.name}
130
156
  self['#{property.name}']
131
157
  end
@@ -144,7 +170,7 @@ module CouchRest
144
170
  end
145
171
 
146
172
  if property.alias
147
- class_eval <<-EOS, __FILE__, __LINE__
173
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
148
174
  alias #{property.alias.to_sym} #{property.name.to_sym}
149
175
  EOS
150
176
  end
@@ -152,16 +178,17 @@ module CouchRest
152
178
 
153
179
  # defines the setter for the property (and optional aliases)
154
180
  def create_property_setter(property)
155
- meth = property.name
181
+ property_name = property.name
156
182
  class_eval <<-EOS
157
- def #{meth}=(value)
158
- self['#{meth}'] = value
183
+ def #{property_name}=(value)
184
+ self['#{property_name}'] = value
185
+ cast_property_by_name('#{property_name}')
159
186
  end
160
187
  EOS
161
188
 
162
189
  if property.alias
163
190
  class_eval <<-EOS
164
- alias #{property.alias.to_sym}= #{meth.to_sym}=
191
+ alias #{property.alias.to_sym}= #{property_name.to_sym}=
165
192
  EOS
166
193
  end
167
194
  end
@@ -50,7 +50,10 @@ module CouchRest
50
50
 
51
51
  def self.included(base)
52
52
  base.extlib_inheritable_accessor(:auto_validation)
53
- base.class_eval <<-EOS, __FILE__, __LINE__
53
+ base.class_eval <<-EOS, __FILE__, __LINE__ + 1
54
+ # Callbacks
55
+ define_callbacks :validate
56
+
54
57
  # Turn off auto validation by default
55
58
  self.auto_validation ||= false
56
59
 
@@ -71,9 +74,10 @@ module CouchRest
71
74
  EOS
72
75
 
73
76
  base.extend(ClassMethods)
74
- base.class_eval <<-EOS, __FILE__, __LINE__
77
+ base.class_eval <<-EOS, __FILE__, __LINE__ + 1
78
+ define_callbacks :validate
75
79
  if method_defined?(:_run_save_callbacks)
76
- save_callback :before, :check_validations
80
+ set_callback :save, :before, :check_validations
77
81
  end
78
82
  EOS
79
83
  base.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
@@ -115,8 +119,7 @@ module CouchRest
115
119
  # Check if a resource is valid in a given context
116
120
  #
117
121
  def valid?(context = :default)
118
- result = self.class.validators.execute(context, self)
119
- result && validate_casted_arrays
122
+ recursive_valid?(self, context, true)
120
123
  end
121
124
 
122
125
  # checking on casted objects
@@ -133,29 +136,24 @@ module CouchRest
133
136
  result
134
137
  end
135
138
 
136
- # Begin a recursive walk of the model checking validity
137
- #
138
- def all_valid?(context = :default)
139
- recursive_valid?(self, context, true)
140
- end
141
-
142
139
  # Do recursive validity checking
143
140
  #
144
141
  def recursive_valid?(target, context, state)
145
142
  valid = state
146
- target.instance_variables.each do |ivar|
147
- ivar_value = target.instance_variable_get(ivar)
148
- if ivar_value.validatable?
149
- valid = valid && recursive_valid?(ivar_value, context, valid)
150
- elsif ivar_value.respond_to?(:each)
151
- ivar_value.each do |item|
143
+ target.each do |key, prop|
144
+ if prop.is_a?(Array)
145
+ prop.each do |item|
152
146
  if item.validatable?
153
- valid = valid && recursive_valid?(item, context, valid)
147
+ valid = recursive_valid?(item, context, valid) && valid
154
148
  end
155
149
  end
150
+ elsif prop.validatable?
151
+ valid = recursive_valid?(prop, context, valid) && valid
156
152
  end
157
153
  end
158
- return valid && target.valid?
154
+ target._run_validate_callbacks do
155
+ target.class.validators.execute(context, target) && valid
156
+ end
159
157
  end
160
158
 
161
159
 
@@ -212,21 +210,12 @@ module CouchRest
212
210
  def create_context_instance_methods(context)
213
211
  name = "valid_for_#{context.to_s}?" # valid_for_signup?
214
212
  if !self.instance_methods.include?(name)
215
- class_eval <<-EOS, __FILE__, __LINE__
213
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
216
214
  def #{name} # def valid_for_signup?
217
215
  valid?('#{context.to_s}'.to_sym) # valid?('signup'.to_sym)
218
216
  end # end
219
217
  EOS
220
218
  end
221
-
222
- all = "all_valid_for_#{context.to_s}?" # all_valid_for_signup?
223
- if !self.instance_methods.include?(all)
224
- class_eval <<-EOS, __FILE__, __LINE__
225
- def #{all} # def all_valid_for_signup?
226
- all_valid?('#{context.to_s}'.to_sym) # all_valid?('signup'.to_sym)
227
- end # end
228
- EOS
229
- end
230
219
  end
231
220
 
232
221
  # Create a new validator of the given klazz and push it onto the
@@ -5,8 +5,10 @@ module CouchRest
5
5
  module CastedModel
6
6
 
7
7
  def self.included(base)
8
+ base.send(:include, ::CouchRest::Callbacks)
8
9
  base.send(:include, ::CouchRest::Mixins::Properties)
9
10
  base.send(:attr_accessor, :casted_by)
11
+ base.send(:attr_accessor, :document_saved)
10
12
  end
11
13
 
12
14
  def initialize(keys={})
@@ -26,5 +28,31 @@ module CouchRest
26
28
  def [] key
27
29
  super(key.to_s)
28
30
  end
31
+
32
+ # Gets a reference to the top level extended
33
+ # document that a model is saved inside of
34
+ def base_doc
35
+ return nil unless @casted_by
36
+ @casted_by.base_doc
37
+ end
38
+
39
+ # False if the casted model has already
40
+ # been saved in the containing document
41
+ def new?
42
+ !@document_saved
43
+ end
44
+ alias :new_record? :new?
45
+
46
+ # Sets the attributes from a hash
47
+ def update_attributes_without_saving(hash)
48
+ hash.each do |k, v|
49
+ raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
50
+ end
51
+ hash.each do |k, v|
52
+ self.send("#{k}=",v)
53
+ end
54
+ end
55
+ alias :attributes= :update_attributes_without_saving
56
+
29
57
  end
30
- end
58
+ end
@@ -14,6 +14,7 @@ module CouchRest
14
14
  include CouchRest::Mixins::ExtendedAttachments
15
15
  include CouchRest::Mixins::ClassProxy
16
16
  include CouchRest::Mixins::Collection
17
+ include CouchRest::Mixins::AttributeProtection
17
18
 
18
19
  def self.subclasses
19
20
  @subclasses ||= []
@@ -21,7 +22,7 @@ module CouchRest
21
22
 
22
23
  def self.inherited(subklass)
23
24
  subklass.send(:include, CouchRest::Mixins::Properties)
24
- subklass.class_eval <<-EOS, __FILE__, __LINE__
25
+ subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
25
26
  def self.inherited(subklass)
26
27
  subklass.properties = self.properties.dup
27
28
  end
@@ -33,18 +34,14 @@ module CouchRest
33
34
  attr_accessor :casted_by
34
35
 
35
36
  # Callbacks
36
- define_callbacks :create
37
- define_callbacks :save
38
- define_callbacks :update
39
- define_callbacks :destroy
37
+ define_callbacks :create, "result == :halt"
38
+ define_callbacks :save, "result == :halt"
39
+ define_callbacks :update, "result == :halt"
40
+ define_callbacks :destroy, "result == :halt"
40
41
 
41
42
  def initialize(passed_keys={})
42
43
  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
44
+ set_attributes(passed_keys) unless passed_keys.nil?
48
45
  super
49
46
  cast_keys # defined in CouchRest::Mixins::Properties
50
47
  unless self['_id'] && self['_rev']
@@ -76,17 +73,17 @@ module CouchRest
76
73
  # on the document whenever saving occurs. CouchRest uses a pretty
77
74
  # decent time format by default. See Time#to_json
78
75
  def self.timestamps!
79
- class_eval <<-EOS, __FILE__, __LINE__
76
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
80
77
  property(:updated_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
81
78
  property(:created_at, :read_only => true, :cast_as => 'Time', :auto_validation => false)
82
79
 
83
- save_callback :before do |object|
80
+ set_callback :save, :before do |object|
84
81
  object['updated_at'] = Time.now
85
- object['created_at'] = object['updated_at'] if object.new_document?
82
+ object['created_at'] = object['updated_at'] if object.new?
86
83
  end
87
84
  EOS
88
85
  end
89
-
86
+
90
87
  # Name a method that will be called before the document is first saved,
91
88
  # which returns a string to be used for the document's <tt>_id</tt>.
92
89
  # Because CouchDB enforces a constraint that each id must be unique,
@@ -128,17 +125,32 @@ module CouchRest
128
125
  self.class.properties
129
126
  end
130
127
 
128
+ # Gets a reference to the actual document in the DB
129
+ # Calls up to the next document if there is one,
130
+ # Otherwise we're at the top and we return self
131
+ def base_doc
132
+ return self if base_doc?
133
+ @casted_by.base_doc
134
+ end
135
+
136
+ # Checks if we're the top document
137
+ def base_doc?
138
+ !@casted_by
139
+ end
140
+
131
141
  # Takes a hash as argument, and applies the values by using writer methods
132
142
  # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
133
143
  # missing. In case of error, no attributes are changed.
134
144
  def update_attributes_without_saving(hash)
135
- hash.each do |k, v|
136
- raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
137
- end
138
- hash.each do |k, v|
139
- self.send("#{k}=",v)
140
- end
145
+ # remove attributes that cannot be updated, silently ignoring them
146
+ # which matches Rails behavior when, for instance, setting created_at.
147
+ # make a copy, we don't want to change arguments
148
+ attrs = hash.dup
149
+ %w[_id _rev created_at updated_at].each {|attr| attrs.delete(attr)}
150
+ check_properties_exist(attrs)
151
+ set_attributes(attrs)
141
152
  end
153
+ alias :attributes= :update_attributes_without_saving
142
154
 
143
155
  # Takes a hash as argument, and applies the values by using writer methods
144
156
  # for each key. Raises a NoMethodError if the corresponding methods are
@@ -149,7 +161,8 @@ module CouchRest
149
161
  end
150
162
 
151
163
  # for compatibility with old-school frameworks
152
- alias :new_record? :new_document?
164
+ alias :new_record? :new?
165
+ alias :new_document? :new?
153
166
 
154
167
  # Trigger the callbacks (before, after, around)
155
168
  # and create the document
@@ -170,7 +183,7 @@ module CouchRest
170
183
  # unlike save, create returns the newly created document
171
184
  def create_without_callbacks(bulk =false)
172
185
  raise ArgumentError, "a document requires a database to be created to (The document or the #{self.class} default database were not set)" unless database
173
- set_unique_id if new_document? && self.respond_to?(:set_unique_id)
186
+ set_unique_id if new? && self.respond_to?(:set_unique_id)
174
187
  result = database.save_doc(self, bulk)
175
188
  (result["ok"] == true) ? self : false
176
189
  end
@@ -185,7 +198,7 @@ module CouchRest
185
198
  # only if the document isn't new
186
199
  def update(bulk = false)
187
200
  caught = catch(:halt) do
188
- if self.new_document?
201
+ if self.new?
189
202
  save(bulk)
190
203
  else
191
204
  _run_update_callbacks do
@@ -201,7 +214,7 @@ module CouchRest
201
214
  # and save the document
202
215
  def save(bulk = false)
203
216
  caught = catch(:halt) do
204
- if self.new_document?
217
+ if self.new?
205
218
  _run_save_callbacks do
206
219
  save_without_callbacks(bulk)
207
220
  end
@@ -215,8 +228,9 @@ module CouchRest
215
228
  # Returns a boolean value
216
229
  def save_without_callbacks(bulk = false)
217
230
  raise ArgumentError, "a document requires a database to be saved to (The document or the #{self.class} default database were not set)" unless database
218
- set_unique_id if new_document? && self.respond_to?(:set_unique_id)
231
+ set_unique_id if new? && self.respond_to?(:set_unique_id)
219
232
  result = database.save_doc(self, bulk)
233
+ mark_as_saved
220
234
  result["ok"] == true
221
235
  end
222
236
 
@@ -224,6 +238,7 @@ module CouchRest
224
238
  # if the document is not saved properly.
225
239
  def save!
226
240
  raise "#{self.inspect} failed to save" unless self.save
241
+ true
227
242
  end
228
243
 
229
244
  # Deletes the document from the database. Runs the :destroy callbacks.
@@ -242,5 +257,38 @@ module CouchRest
242
257
  end
243
258
  end
244
259
 
260
+ protected
261
+
262
+ # Set document_saved flag on all casted models to true
263
+ def mark_as_saved
264
+ self.each do |key, prop|
265
+ if prop.is_a?(Array)
266
+ prop.each do |item|
267
+ if item.respond_to?(:document_saved)
268
+ item.send(:document_saved=, true)
269
+ end
270
+ end
271
+ elsif prop.respond_to?(:document_saved)
272
+ prop.send(:document_saved=, true)
273
+ end
274
+ end
275
+ end
276
+
277
+ private
278
+
279
+ def check_properties_exist(attrs)
280
+ attrs.each do |attribute_name, attribute_value|
281
+ raise NoMethodError, "#{attribute_name}= method not available, use property :#{attribute_name}" unless self.respond_to?("#{attribute_name}=")
282
+ end
283
+ end
284
+
285
+ def set_attributes(hash)
286
+ attrs = remove_protected_attributes(hash)
287
+ attrs.each do |attribute_name, attribute_value|
288
+ if self.respond_to?("#{attribute_name}=")
289
+ self.send("#{attribute_name}=", attrs.delete(attribute_name))
290
+ end
291
+ end
292
+ end
245
293
  end
246
294
  end