samlown-couchrest_extended_document 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. data/LICENSE +176 -0
  2. data/README.md +46 -0
  3. data/Rakefile +67 -0
  4. data/THANKS.md +19 -0
  5. data/examples/model/example.rb +144 -0
  6. data/history.txt +130 -0
  7. data/lib/couchrest/casted_array.rb +25 -0
  8. data/lib/couchrest/casted_model.rb +59 -0
  9. data/lib/couchrest/extended_document.rb +318 -0
  10. data/lib/couchrest/mixins.rb +12 -0
  11. data/lib/couchrest/mixins/attribute_protection.rb +74 -0
  12. data/lib/couchrest/mixins/callbacks.rb +532 -0
  13. data/lib/couchrest/mixins/class_proxy.rb +120 -0
  14. data/lib/couchrest/mixins/collection.rb +260 -0
  15. data/lib/couchrest/mixins/design_doc.rb +121 -0
  16. data/lib/couchrest/mixins/document_queries.rb +80 -0
  17. data/lib/couchrest/mixins/extended_attachments.rb +70 -0
  18. data/lib/couchrest/mixins/properties.rb +154 -0
  19. data/lib/couchrest/mixins/validation.rb +245 -0
  20. data/lib/couchrest/mixins/views.rb +148 -0
  21. data/lib/couchrest/monkeypatches.rb +5 -0
  22. data/lib/couchrest/property.rb +50 -0
  23. data/lib/couchrest/support/couchrest.rb +56 -0
  24. data/lib/couchrest/support/rails.rb +42 -0
  25. data/lib/couchrest/typecast.rb +175 -0
  26. data/lib/couchrest/validation/auto_validate.rb +156 -0
  27. data/lib/couchrest/validation/contextual_validators.rb +78 -0
  28. data/lib/couchrest/validation/validation_errors.rb +125 -0
  29. data/lib/couchrest/validation/validators/absent_field_validator.rb +74 -0
  30. data/lib/couchrest/validation/validators/confirmation_validator.rb +107 -0
  31. data/lib/couchrest/validation/validators/format_validator.rb +122 -0
  32. data/lib/couchrest/validation/validators/formats/email.rb +66 -0
  33. data/lib/couchrest/validation/validators/formats/url.rb +43 -0
  34. data/lib/couchrest/validation/validators/generic_validator.rb +120 -0
  35. data/lib/couchrest/validation/validators/length_validator.rb +139 -0
  36. data/lib/couchrest/validation/validators/method_validator.rb +89 -0
  37. data/lib/couchrest/validation/validators/numeric_validator.rb +109 -0
  38. data/lib/couchrest/validation/validators/required_field_validator.rb +114 -0
  39. data/spec/couchrest/attribute_protection_spec.rb +150 -0
  40. data/spec/couchrest/casted_extended_doc_spec.rb +79 -0
  41. data/spec/couchrest/casted_model_spec.rb +406 -0
  42. data/spec/couchrest/extended_doc_attachment_spec.rb +135 -0
  43. data/spec/couchrest/extended_doc_inherited_spec.rb +40 -0
  44. data/spec/couchrest/extended_doc_spec.rb +824 -0
  45. data/spec/couchrest/extended_doc_subclass_spec.rb +99 -0
  46. data/spec/couchrest/extended_doc_view_spec.rb +473 -0
  47. data/spec/couchrest/property_spec.rb +628 -0
  48. data/spec/fixtures/attachments/README +3 -0
  49. data/spec/fixtures/attachments/couchdb.png +0 -0
  50. data/spec/fixtures/attachments/test.html +11 -0
  51. data/spec/fixtures/more/article.rb +35 -0
  52. data/spec/fixtures/more/card.rb +22 -0
  53. data/spec/fixtures/more/cat.rb +22 -0
  54. data/spec/fixtures/more/course.rb +22 -0
  55. data/spec/fixtures/more/event.rb +8 -0
  56. data/spec/fixtures/more/invoice.rb +17 -0
  57. data/spec/fixtures/more/person.rb +9 -0
  58. data/spec/fixtures/more/question.rb +6 -0
  59. data/spec/fixtures/more/service.rb +12 -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 +49 -0
  68. data/utils/remap.rb +27 -0
  69. data/utils/subset.rb +30 -0
  70. metadata +181 -0
@@ -0,0 +1,130 @@
1
+ == Next Version
2
+
3
+ * Major enhancements
4
+
5
+ * Minor enhancements
6
+
7
+ == 0.4.0
8
+
9
+ * Major enhancements
10
+ * Separated ExtendedDocument from main CouchRest gem (Sam Lown)
11
+
12
+ == 0.37
13
+
14
+ * Minor enhancements
15
+ * Added gemspec (needed for Bundler install) (Tapajós)
16
+
17
+ == 0.36
18
+
19
+ * Major enhancements
20
+ * Adds support for continuous replication (sauy7)
21
+ * Automatic Type Casting (Alexander Uvarov, Sam Lown, Tim Heighes, Will Leinweber)
22
+ * Added a search method to CouchRest:Database to search the documents in a given database. (Dave Farkas, Arnaud Berthomier, John Wood)
23
+
24
+ * Minor enhancements
25
+ * Provide a description of the timeout error (John Wood)
26
+
27
+ == 0.35
28
+
29
+ * Major enhancements
30
+ * CouchRest::ExtendedDocument allow chaining the inherit class callback (Kenneth Kalmer) - http://github.com/couchrest/couchrest/issues#issue/8
31
+
32
+ * Minor enhancements
33
+ * Fix attachment bug (Johannes Jörg Schmidt)
34
+ * Fix create database exception bug (Damien Mathieu)
35
+ * Compatible with restclient >= 1.4.0 new responses (Julien Kirch)
36
+ * Bug fix: Attribute protection no longer strips attributes coming from the database (Will Leinweber)
37
+ * Bug fix: Remove double CGI escape when PUTting an attachment (nzoschke)
38
+ * Bug fix: Changing Class proxy to set database on result sets (Peter Gumeson)
39
+ * Bug fix: Updated time regexp (Nolan Darilek)
40
+ * Added an update_doc method to database to handle conflicts during atomic updates. (Pierre Larochelle)
41
+ * Bug fix: http://github.com/couchrest/couchrest/issues/#issue/2 (Luke Burton)
42
+
43
+ == 0.34
44
+
45
+ * Major enhancements
46
+
47
+ * Added support for https database URIs. (Mathias Meyer)
48
+ * Changing some validations to be compatible with activemodel. (Marcos Tapajós)
49
+ * Adds attribute protection to properties. (Will Leinweber)
50
+ * Improved CouchRest::Database#save_doc, added "batch" mode to significantly speed up saves at cost of lower durability gurantees. (Igal Koshevoy)
51
+ * Added CouchRest::Database#bulk_save_doc and #batch_save_doc as human-friendlier wrappers around #save_doc. (Igal Koshevoy)
52
+
53
+ * Minor enhancements
54
+
55
+ * Fix content_type handling for attachments
56
+ * Fixed a bug in the pagination code that caused it to paginate over records outside of the scope of the view parameters.(John Wood)
57
+ * Removed amount_pages calculation for the pagination collection, since it cannot be reliably calculated without a view.(John Wood)
58
+ * Bug fix: http://github.com/couchrest/couchrest/issues/#issue/2 (Luke Burton)
59
+ * Bug fix: http://github.com/couchrest/couchrest/issues/#issue/1 (Marcos Tapajós)
60
+ * Removed the Database class deprecation notices (Matt Aimonetti)
61
+ * Adding support to :cast_as => 'Date'. (Marcos Tapajós)
62
+ * Improve documentation (Marcos Tapajós)
63
+ * Streamer fixes (Julien Sanchez)
64
+ * Fix Save on Document & ExtendedDocument crashed if bulk (Julien Sanchez)
65
+ * Fix Initialization of ExtendentDocument model shouldn't failed on a nil value in argument (deepj)
66
+ * Change to use Jeweler and Gemcutter (Marcos Tapajós)
67
+
68
+ == 0.33
69
+
70
+ * Major enhancements
71
+
72
+ * Added a new Rack logger middleware letting you log/save requests/queries (Matt Aimonetti)
73
+
74
+ * Minor enhancements
75
+
76
+ * Added #amount_pages to a paginated result array (Matt Aimonetti)
77
+ * Ruby 1.9.2 compatible (Matt Aimonetti)
78
+ * Added a property? method for property cast as :boolean (John Wood)
79
+ * Added an option to force the deletion of a attachments (bypass 409s) (Matt Aimonetti)
80
+ * Created a new abstraction layer for the REST API (Matt Aimonetti)
81
+ * Bug fix: made ExtendedDocument#all compatible with Couch 0.10 (tc)
82
+
83
+ == 0.32
84
+
85
+ * Major enhancements
86
+
87
+ * ExtendedDocument.get doesn't raise an exception anymore. If no documents are found nil is returned.
88
+ * ExtendedDocument.get! works the say #get used to work and will raise an exception if a document isn't found.
89
+
90
+ * Minor enhancements
91
+
92
+ * Bug fix: Model.all(:keys => [1,2]) was not working (Matt Aimonetti)
93
+ * Added ValidationErrors#count in order to play nicely with Rails (Peter Wagenet)
94
+ * Bug fix: class proxy design doc refresh (Daniel Kirsh)
95
+ * Bug fix: the count method on the proxy collection was missing (Daniel Kirsch)
96
+ * Added #amount_pages to a paginated collection. (Matt Aimonetti)
97
+
98
+ == 0.31
99
+
100
+ * Major enhancements
101
+
102
+ * Created an abstraction HTTP layer to support different http adapters (Matt Aimonetti)
103
+ * Added ExtendedDocument.create({}) and #create!({}) so you don't have to do Model.new.create (Matt Aimonetti)
104
+
105
+ * Minor enhancements
106
+
107
+ * Added an init.rb file for easy usage as a Rails plugin (Aaron Quint)
108
+ * Bug fix: pagination shouldn't die on empty results (Arnaud Berthomier)
109
+ * Optimized ExtendedDocument.count to run about 3x faster (Matt Aimonetti)
110
+ * Added Float casting (Ryan Felton & Matt Aimonetti)
111
+
112
+ == 0.30
113
+
114
+ * Major enhancements
115
+
116
+ * Added support for pagination (John Wood)
117
+ * Improved performance when initializing documents with timestamps (Matt Aimonetti)
118
+
119
+ * Minor enhancements
120
+
121
+ * Extended the API to retrieve an attachment URI (Matt Aimonetti)
122
+ * Bug fix: default value should be able to be set as false (Alexander Uvarov)
123
+ * Bug fix: validates_is_numeric should be able to properly validate a Float instance (Rob Kaufman)
124
+ * Bug fix: fixed the Timeout implementation (Seth Falcon)
125
+
126
+
127
+ ---
128
+
129
+ Unfortunately, before 0.30 we did not keep a track of the modifications made to CouchRest.
130
+ You can see the full commit history on GitHub: http://github.com/couchrest/couchrest/commits/master/
@@ -0,0 +1,25 @@
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
7
+ class CastedArray < Array
8
+ attr_accessor :casted_by
9
+
10
+ def << obj
11
+ obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
12
+ super(obj)
13
+ end
14
+
15
+ def push(obj)
16
+ obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
17
+ super(obj)
18
+ end
19
+
20
+ def []= index, obj
21
+ obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
22
+ super(index, obj)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,59 @@
1
+ require 'couchrest'
2
+ require File.join(File.dirname(__FILE__), 'mixins/callbacks')
3
+ require File.join(File.dirname(__FILE__), 'mixins/properties')
4
+
5
+ module CouchRest
6
+ module CastedModel
7
+
8
+ def self.included(base)
9
+ base.send(:include, ::CouchRest::Mixins::Callbacks)
10
+ base.send(:include, ::CouchRest::Mixins::Properties)
11
+ base.send(:attr_accessor, :casted_by)
12
+ base.send(:attr_accessor, :document_saved)
13
+ end
14
+
15
+ def initialize(keys={})
16
+ raise StandardError unless self.is_a? Hash
17
+ apply_defaults # defined in CouchRest::Mixins::Properties
18
+ super()
19
+ keys.each do |k,v|
20
+ self[k.to_s] = v
21
+ end if keys
22
+ cast_keys # defined in CouchRest::Mixins::Properties
23
+ end
24
+
25
+ def []= key, value
26
+ super(key.to_s, value)
27
+ end
28
+
29
+ def [] key
30
+ super(key.to_s)
31
+ end
32
+
33
+ # Gets a reference to the top level extended
34
+ # document that a model is saved inside of
35
+ def base_doc
36
+ return nil unless @casted_by
37
+ @casted_by.base_doc
38
+ end
39
+
40
+ # False if the casted model has already
41
+ # been saved in the containing document
42
+ def new?
43
+ !@document_saved
44
+ end
45
+ alias :new_record? :new?
46
+
47
+ # Sets the attributes from a hash
48
+ def update_attributes_without_saving(hash)
49
+ hash.each do |k, v|
50
+ raise NoMethodError, "#{k}= method not available, use property :#{k}" unless self.respond_to?("#{k}=")
51
+ end
52
+ hash.each do |k, v|
53
+ self.send("#{k}=",v)
54
+ end
55
+ end
56
+ alias :attributes= :update_attributes_without_saving
57
+
58
+ end
59
+ end
@@ -0,0 +1,318 @@
1
+ gem 'samlown-couchrest'
2
+ require 'couchrest'
3
+ require 'active_support'
4
+ require 'mime/types'
5
+ require "enumerator"
6
+ require File.join(File.dirname(__FILE__), "monkeypatches")
7
+ require File.join(File.dirname(__FILE__), "property")
8
+ require File.join(File.dirname(__FILE__), 'mixins')
9
+ require File.join(File.dirname(__FILE__), 'casted_model')
10
+
11
+ module CouchRest
12
+
13
+ # Same as CouchRest::Document but with properties and validations
14
+ class ExtendedDocument < Document
15
+
16
+ VERSION = "1.0.0"
17
+
18
+ include CouchRest::Mixins::Callbacks
19
+ include CouchRest::Mixins::DocumentQueries
20
+ include CouchRest::Mixins::Views
21
+ include CouchRest::Mixins::DesignDoc
22
+ include CouchRest::Mixins::ExtendedAttachments
23
+ include CouchRest::Mixins::ClassProxy
24
+ include CouchRest::Mixins::Collection
25
+ include CouchRest::Mixins::AttributeProtection
26
+
27
+ def self.subclasses
28
+ @subclasses ||= []
29
+ end
30
+
31
+ def self.inherited(subklass)
32
+ super
33
+ subklass.send(:include, CouchRest::Mixins::Properties)
34
+ subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
35
+ def self.inherited(subklass)
36
+ super
37
+ subklass.properties = self.properties.dup
38
+ end
39
+ EOS
40
+ subclasses << subklass
41
+ end
42
+
43
+ # Accessors
44
+ attr_accessor :casted_by
45
+
46
+ # Callbacks
47
+ define_callbacks :create, "result == :halt"
48
+ define_callbacks :save, "result == :halt"
49
+ define_callbacks :update, "result == :halt"
50
+ define_callbacks :destroy, "result == :halt"
51
+
52
+ # Creates a new instance, bypassing attribute protection
53
+ #
54
+ # ==== Returns
55
+ # a document instance
56
+ def self.create_from_database(passed_keys={})
57
+ new(passed_keys, :directly_set_attributes => true)
58
+ end
59
+
60
+ def initialize(passed_keys={}, options={})
61
+ apply_defaults # defined in CouchRest::Mixins::Properties
62
+ remove_protected_attributes(passed_keys) unless options[:directly_set_attributes]
63
+ directly_set_attributes(passed_keys) unless passed_keys.nil?
64
+ super(passed_keys)
65
+ cast_keys # defined in CouchRest::Mixins::Properties
66
+ unless self['_id'] && self['_rev']
67
+ self['couchrest-type'] = self.class.to_s
68
+ end
69
+ after_initialize if respond_to?(:after_initialize)
70
+ end
71
+
72
+ # Defines an instance and save it directly to the database
73
+ #
74
+ # ==== Returns
75
+ # returns the reloaded document
76
+ def self.create(options)
77
+ instance = new(options)
78
+ instance.create
79
+ instance
80
+ end
81
+
82
+ # Defines an instance and save it directly to the database
83
+ #
84
+ # ==== Returns
85
+ # returns the reloaded document or raises an exception
86
+ def self.create!(options)
87
+ instance = new(options)
88
+ instance.create!
89
+ instance
90
+ end
91
+
92
+ # Automatically set <tt>updated_at</tt> and <tt>created_at</tt> fields
93
+ # on the document whenever saving occurs. CouchRest uses a pretty
94
+ # decent time format by default. See Time#to_json
95
+ def self.timestamps!
96
+ class_eval <<-EOS, __FILE__, __LINE__
97
+ property(:updated_at, :read_only => true, :type => 'Time', :auto_validation => false)
98
+ property(:created_at, :read_only => true, :type => 'Time', :auto_validation => false)
99
+
100
+ set_callback :save, :before do |object|
101
+ object['updated_at'] = Time.now
102
+ object['created_at'] = object['updated_at'] if object.new?
103
+ end
104
+ EOS
105
+ end
106
+
107
+ # Name a method that will be called before the document is first saved,
108
+ # which returns a string to be used for the document's <tt>_id</tt>.
109
+ # Because CouchDB enforces a constraint that each id must be unique,
110
+ # this can be used to enforce eg: uniq usernames. Note that this id
111
+ # must be globally unique across all document types which share a
112
+ # database, so if you'd like to scope uniqueness to this class, you
113
+ # should use the class name as part of the unique id.
114
+ def self.unique_id method = nil, &block
115
+ if method
116
+ define_method :set_unique_id do
117
+ self['_id'] ||= self.send(method)
118
+ end
119
+ elsif block
120
+ define_method :set_unique_id do
121
+ uniqid = block.call(self)
122
+ raise ArgumentError, "unique_id block must not return nil" if uniqid.nil?
123
+ self['_id'] ||= uniqid
124
+ end
125
+ end
126
+ end
127
+
128
+ # Temp solution to make the view_by methods available
129
+ def self.method_missing(m, *args, &block)
130
+ if has_view?(m)
131
+ query = args.shift || {}
132
+ view(m, query, *args, &block)
133
+ else
134
+ super
135
+ end
136
+ end
137
+
138
+ ### instance methods
139
+
140
+ # Returns the Class properties
141
+ #
142
+ # ==== Returns
143
+ # Array:: the list of properties for the instance
144
+ def properties
145
+ self.class.properties
146
+ end
147
+
148
+ # Gets a reference to the actual document in the DB
149
+ # Calls up to the next document if there is one,
150
+ # Otherwise we're at the top and we return self
151
+ def base_doc
152
+ return self if base_doc?
153
+ @casted_by.base_doc
154
+ end
155
+
156
+ # Checks if we're the top document
157
+ def base_doc?
158
+ !@casted_by
159
+ end
160
+
161
+ # Takes a hash as argument, and applies the values by using writer methods
162
+ # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
163
+ # missing. In case of error, no attributes are changed.
164
+ def update_attributes_without_saving(hash)
165
+ # remove attributes that cannot be updated, silently ignoring them
166
+ # which matches Rails behavior when, for instance, setting created_at.
167
+ # make a copy, we don't want to change arguments
168
+ attrs = hash.dup
169
+ %w[_id _rev created_at updated_at].each {|attr| attrs.delete(attr)}
170
+ check_properties_exist(attrs)
171
+ set_attributes(attrs)
172
+ end
173
+ alias :attributes= :update_attributes_without_saving
174
+
175
+ # Takes a hash as argument, and applies the values by using writer methods
176
+ # for each key. Raises a NoMethodError if the corresponding methods are
177
+ # missing. In case of error, no attributes are changed.
178
+ def update_attributes(hash)
179
+ update_attributes_without_saving hash
180
+ save
181
+ end
182
+
183
+ # for compatibility with old-school frameworks
184
+ alias :new_record? :new?
185
+ alias :new_document? :new?
186
+
187
+ # Trigger the callbacks (before, after, around)
188
+ # and create the document
189
+ # It's important to have a create callback since you can't check if a document
190
+ # was new after you saved it
191
+ #
192
+ # When creating a document, both the create and the save callbacks will be triggered.
193
+ def create(bulk = false)
194
+ caught = catch(:halt) do
195
+ _run_create_callbacks do
196
+ _run_save_callbacks do
197
+ create_without_callbacks(bulk)
198
+ end
199
+ end
200
+ end
201
+ end
202
+
203
+ # unlike save, create returns the newly created document
204
+ def create_without_callbacks(bulk =false)
205
+ raise ArgumentError, "a document requires a database to be created to (The document or the #{self.class} default database were not set)" unless database
206
+ set_unique_id if new? && self.respond_to?(:set_unique_id)
207
+ result = database.save_doc(self, bulk)
208
+ (result["ok"] == true) ? self : false
209
+ end
210
+
211
+ # Creates the document in the db. Raises an exception
212
+ # if the document is not created properly.
213
+ def create!
214
+ raise "#{self.inspect} failed to save" unless self.create
215
+ end
216
+
217
+ # Trigger the callbacks (before, after, around)
218
+ # only if the document isn't new
219
+ def update(bulk = false)
220
+ caught = catch(:halt) do
221
+ if self.new?
222
+ save(bulk)
223
+ else
224
+ _run_update_callbacks do
225
+ _run_save_callbacks do
226
+ save_without_callbacks(bulk)
227
+ end
228
+ end
229
+ end
230
+ end
231
+ end
232
+
233
+ # Trigger the callbacks (before, after, around)
234
+ # and save the document
235
+ def save(bulk = false)
236
+ caught = catch(:halt) do
237
+ if self.new?
238
+ _run_save_callbacks do
239
+ save_without_callbacks(bulk)
240
+ end
241
+ else
242
+ update(bulk)
243
+ end
244
+ end
245
+ end
246
+
247
+ # Overridden to set the unique ID.
248
+ # Returns a boolean value
249
+ def save_without_callbacks(bulk = false)
250
+ raise ArgumentError, "a document requires a database to be saved to (The document or the #{self.class} default database were not set)" unless database
251
+ set_unique_id if new? && self.respond_to?(:set_unique_id)
252
+ result = database.save_doc(self, bulk)
253
+ mark_as_saved
254
+ result["ok"] == true
255
+ end
256
+
257
+ # Saves the document to the db using save. Raises an exception
258
+ # if the document is not saved properly.
259
+ def save!
260
+ raise "#{self.inspect} failed to save" unless self.save
261
+ true
262
+ end
263
+
264
+ # Deletes the document from the database. Runs the :destroy callbacks.
265
+ # Removes the <tt>_id</tt> and <tt>_rev</tt> fields, preparing the
266
+ # document to be saved to a new <tt>_id</tt>.
267
+ def destroy(bulk=false)
268
+ caught = catch(:halt) do
269
+ _run_destroy_callbacks do
270
+ result = database.delete_doc(self, bulk)
271
+ if result['ok']
272
+ self.delete('_rev')
273
+ self.delete('_id')
274
+ end
275
+ result['ok']
276
+ end
277
+ end
278
+ end
279
+
280
+ protected
281
+
282
+ # Set document_saved flag on all casted models to true
283
+ def mark_as_saved
284
+ self.each do |key, prop|
285
+ if prop.is_a?(Array)
286
+ prop.each do |item|
287
+ if item.respond_to?(:document_saved)
288
+ item.send(:document_saved=, true)
289
+ end
290
+ end
291
+ elsif prop.respond_to?(:document_saved)
292
+ prop.send(:document_saved=, true)
293
+ end
294
+ end
295
+ end
296
+
297
+ private
298
+
299
+ def check_properties_exist(attrs)
300
+ attrs.each do |attribute_name, attribute_value|
301
+ raise NoMethodError, "#{attribute_name}= method not available, use property :#{attribute_name}" unless self.respond_to?("#{attribute_name}=")
302
+ end
303
+ end
304
+
305
+ def directly_set_attributes(hash)
306
+ hash.each do |attribute_name, attribute_value|
307
+ if self.respond_to?("#{attribute_name}=")
308
+ self.send("#{attribute_name}=", hash.delete(attribute_name))
309
+ end
310
+ end
311
+ end
312
+
313
+ def set_attributes(hash)
314
+ attrs = remove_protected_attributes(hash)
315
+ directly_set_attributes(attrs)
316
+ end
317
+ end
318
+ end