couchrest_extended_document 1.0.0.beta6 → 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.
data/README.md CHANGED
@@ -28,6 +28,177 @@ Note: CouchRest::ExtendedDocument only supports CouchDB 0.10.0 or newer.
28
28
 
29
29
  end
30
30
 
31
+ @cat = Cat.new(:name => 'Felix', :nicknames => ['so cute', 'sweet kitty'])
32
+
33
+ @cat.new? # true
34
+ @cat.save
35
+
36
+ @cat['name'] # "Felix"
37
+
38
+ @cat.nicknames << 'getoffdamntable'
39
+
40
+ @cat = Cat.new
41
+ @cat.update_attributes(:name => 'Felix', :random_text => 'feline')
42
+ @cat.new? # false
43
+ @cat.random_text # Raises error!
44
+
45
+
46
+ ### Properties
47
+
48
+ Only attributes with a property definition will be stored be ExtendedDocument (as opposed
49
+ to a normal CouchRest Document which will store everything). To help prevent confusion,
50
+ a property should be considered as the definition of an attribute. An attribute must be associated
51
+ with a property, but a property may not have any attributes associated if none have been set.
52
+
53
+
54
+ In its simplest form, a property
55
+ will only create a getter and setter passing all attribute data directly to the database. Assuming the attribute
56
+ provided responds to +to_json+, there will not be any problems saving it, but when loading the
57
+ data back it will either be a string, number, array, or hash:
58
+
59
+ class Cat < CouchRest::ExtendedDocument
60
+ property :name
61
+ property :birthday
62
+ end
63
+
64
+ @cat = Cat.new(:name => 'Felix', :birthday => 2.years.ago)
65
+ @cat.name # 'Felix'
66
+ @cat.birthday.is_a?(Time) # True!
67
+ @cat.save
68
+ @cat = Cat.find(@cat.id)
69
+ @cat.name # 'Felix'
70
+ @cat.birthday.is_a?(Time) # False!
71
+
72
+ Properties create getters and setters similar to the following:
73
+
74
+ def name
75
+ read_attribute('name')
76
+ end
77
+
78
+ def name=(value)
79
+ write_attribute('name', value)
80
+ end
81
+
82
+ Properties can also have a type which
83
+ will be used for casting data retrieved from CouchDB when the attribute is set:
84
+
85
+ class Cat < CouchRest::ExtendedDocument
86
+ property :name, String
87
+ property :last_fed_at, Time
88
+ end
89
+
90
+ @cat = Cat.new(:name => 'Felix', :last_fed_at => 10.minutes.ago)
91
+ @cat.last_fed_at.is_a?(Time) # True!
92
+ @cat.save
93
+ @cat = Cat.find(@cat.id)
94
+ @cat.last_fed_at < 20.minutes.ago # True!
95
+
96
+
97
+ Booleans or TrueClass will also create a getter with question mark at the end:
98
+
99
+ class Cat < CouchRest::ExtendedDocument
100
+ property :awake, TrueClass, :default => true
101
+ end
102
+
103
+ @cat.awake? # true
104
+
105
+ Adding the +:default+ option will ensure the attribute always has a value.
106
+
107
+ Defining a property as read-only will mean that its value is set only when read from the
108
+ database and that it will not have a setter method. You can however update a read-only
109
+ attribute using the +write_attribute+ method:
110
+
111
+ class Cat < CouchRest::ExtendedDocument
112
+ property :name, String
113
+ property :lives, Integer, :default => 9, :readonly => true
114
+
115
+ def fall_off_balcony!
116
+ write_attribute(:lives, lives - 1)
117
+ save
118
+ end
119
+ end
120
+
121
+ @cat = Cat.new(:name => "Felix")
122
+ @cat.fall_off_balcony!
123
+ @cat.lives # Now 8!
124
+
125
+
126
+ ### Property Arrays
127
+
128
+ An attribute may also contain an array of data. ExtendedDocument handles this, along
129
+ with casting, by defining the class of the child attributes inside an Array:
130
+
131
+ class Cat < CouchRest::ExtendedDocument
132
+ property :name, String
133
+ property :nicknames, [String]
134
+ end
135
+
136
+ By default, the array will be ready to use from the moment the object as been instantiated:
137
+
138
+ @cat = Cat.new(:name => 'Fluffy')
139
+ @cat.nicknames << 'Buffy'
140
+
141
+ @cat.nicknames == ['Buffy']
142
+
143
+ When anything other than a string is set as the class of a property, the array will be converted
144
+ into special wrapper called a CastedArray. If the child objects respond to the 'casted_by' method
145
+ (such as those created with CastedModel, below) it will contain a reference to the parent.
146
+
147
+ ### Casted Models
148
+
149
+ ExtendedDocument allows you to take full advantage of CouchDB's ability to store complex
150
+ documents and retrieve them using the CastedModel module. Simply include the module in
151
+ a Hash (or other model that responds to the [] and []= methods) and set any properties
152
+ you'd like to use. For example:
153
+
154
+ class CatToy << Hash
155
+ include CouchRest::CastedModel
156
+
157
+ property :name, String
158
+ property :purchased, Date
159
+ end
160
+
161
+ class Cat << CouchRest::ExtendedDocument
162
+ property :name, String
163
+ property :toys, [CatToy]
164
+ end
165
+
166
+ @cat = Cat.new(:name => 'Felix', :toys => [{:name => 'mouse', :purchases => 1.month.ago}])
167
+ @cat.toys.first.class == CatToy
168
+ @cat.toys.first.name == 'mouse'
169
+
170
+ Additionally, any hashes sent to the property will automatically be converted:
171
+
172
+ @cat.toys << {:name => 'catnip ball'}
173
+ @cat.toys.last.is_a?(CatToy) # True!
174
+
175
+ Of course, to use your own classes they *must* be defined before the parent uses them otherwise
176
+ Ruby will bring up a missing constant error. To avoid this, or if you have a really simple array of data
177
+ you'd like to model, the latest version of ExtendedDocument (> 1.0.0) supports creating
178
+ anonymous classes:
179
+
180
+ class Cat << CouchRest::ExtendedDocument
181
+ property :name, String
182
+
183
+ property :toys do |toy|
184
+ toy.property :name, String
185
+ toy.property :rating, Integer
186
+ end
187
+ end
188
+
189
+ @cat = Cat.new(:name => 'Felix', :toys => [{:name => 'mouse', :rating => 3}, {:name => 'catnip ball', :rating => 5}])
190
+ @cat.toys.last.rating == 5
191
+ @cat.toys.last.name == 'catnip ball'
192
+
193
+ Using this method of anonymous classes will *only* create arrays of objects.
194
+
195
+ ### Notable Issues
196
+
197
+ ExtendedDocument uses active_support for some of its internals. Ensure you have a stable active support gem installed
198
+ or at least 3.0.0.beta4.
199
+
200
+ JSON gem versions 1.4.X are kown to cause problems with stack overflows and general badness. Version 1.2.4 appears to work fine.
201
+
31
202
  ### Ruby on Rails
32
203
 
33
204
  CouchRest::ExtendedDocument is compatible with rails and provides some ActiveRecord-like methods.
@@ -54,13 +225,13 @@ CouchRest install, from the project root directory run `rake`, or `autotest`
54
225
 
55
226
  API: [http://rdoc.info/projects/couchrest/couchrest_extended_document](http://rdoc.info/projects/couchrest/couchrest_extended_document)
56
227
 
57
- Check the wiki for documentation and examples [http://wiki.github.com/couchrest/couchrest](http://wiki.github.com/couchrest/couchrest)
228
+ Check the wiki for documentation and examples [http://wiki.github.com/couchrest/couchrest_extended_document](http://wiki.github.com/couchrest/couchrest_extended_document)
58
229
 
59
230
 
60
231
 
61
232
  ## Contact
62
233
 
63
- Please post bugs, suggestions and patches to the bug tracker at [http://github.com/couchrest/couchrest/issues](http://github.com/couchrest/couchrest/issues).
234
+ Please post bugs, suggestions and patches to the bug tracker at [http://github.com/couchrest/couchrest_extended_document/issues](http://github.com/couchrest/couchrest_extended_document/issues).
64
235
 
65
236
  Follow us on Twitter: [http://twitter.com/couchrest](http://twitter.com/couchrest)
66
237
 
data/Rakefile CHANGED
@@ -20,16 +20,16 @@ begin
20
20
  gemspec.description = "CouchRest::ExtendedDocument provides aditional features to the standard CouchRest::Document class such as properties, view designs, callbacks, typecasting and validations."
21
21
  gemspec.email = "jchris@apache.org"
22
22
  gemspec.homepage = "http://github.com/couchrest/couchrest_extended_document"
23
- gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber"]
23
+ gemspec.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber", "Sam Lown"]
24
24
  gemspec.extra_rdoc_files = %w( README.md LICENSE THANKS.md )
25
25
  gemspec.files = %w( LICENSE README.md Rakefile THANKS.md history.txt couchrest.gemspec) + Dir["{examples,lib,spec,utils}/**/*"] - Dir["spec/tmp"]
26
26
  gemspec.has_rdoc = true
27
- gemspec.add_dependency("couchrest", ">= 1.0.0.beta")
27
+ gemspec.add_dependency("couchrest", ">= 1.0.0")
28
28
  gemspec.add_dependency("mime-types", ">= 1.15")
29
29
  gemspec.add_dependency("activesupport", ">= 2.3.0")
30
30
  gemspec.add_dependency("builder", ">=2.1.2")
31
31
  gemspec.version = CouchRest::ExtendedDocument::VERSION
32
- gemspec.date = "2010-07-01"
32
+ gemspec.date = Time.now.strftime("%Y-%m-%d")
33
33
  gemspec.require_path = "lib"
34
34
  end
35
35
  rescue LoadError
data/THANKS.md CHANGED
@@ -13,7 +13,10 @@ changes. A list of these people is included below.
13
13
  * [Matt Lyon](http://mattly.tumblr.com/)
14
14
  * Simon Rozet (simon /at/ rozet /dot/ name)
15
15
  * [Marcos Tapajós](http://tapajos.me)
16
+ * [Sam Lown](http://github.com/samlown)
17
+ * [Will Leinweber](http://github.com/will)
18
+
16
19
 
17
- Patches are welcome. The primary source for this software project is [on Github](http://github.com/couchrest/couchrest)
20
+ Patches are welcome. The primary source for this software project is [on Github](http://github.com/couchrest/couchrest_extended_document)
18
21
 
19
22
  A lot of people have active forks - thank you all - even the patches I don't end up using are helpful.
@@ -8,7 +8,13 @@
8
8
 
9
9
  * Minor enhancements
10
10
  * Added 'find_by_*' alias for finding first item in view with matching key.
11
- * Fixed issue with active_support in Rails3.
11
+ * Fixed issue with active_support in Rails3 and text in README for JSON.
12
+ * Refactoring of properties, added read_attribute and write_attribute methods.
13
+ * Now possible to send anything to update_attribtues method. Invalid or readonly attributes will be ignored.
14
+ * Updating to use couchrest_inheritable_attributes to avoid possible Rails conflicts
15
+
16
+ * Major enhancements
17
+ * Added support for anonymous CastedModels defined in Documents
12
18
 
13
19
  == 1.0.0.beta5
14
20
 
@@ -6,20 +6,34 @@
6
6
  module CouchRest
7
7
  class CastedArray < Array
8
8
  attr_accessor :casted_by
9
+ attr_accessor :property
10
+
11
+ def initialize(array, property)
12
+ self.property = property
13
+ super(array)
14
+ end
9
15
 
10
16
  def << obj
11
- obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
12
- super(obj)
17
+ super(instantiate_and_cast(obj))
13
18
  end
14
19
 
15
20
  def push(obj)
16
- obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
17
- super(obj)
21
+ super(instantiate_and_cast(obj))
18
22
  end
19
23
 
20
24
  def []= index, obj
21
- obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
22
- super(index, obj)
25
+ super(index, instantiate_and_cast(obj))
26
+ end
27
+
28
+ protected
29
+
30
+ def instantiate_and_cast(obj)
31
+ if self.casted_by && self.property && obj.class != self.property.type_class
32
+ self.property.cast_value(self.casted_by, obj)
33
+ else
34
+ obj.casted_by = self.casted_by if obj.respond_to?(:casted_by)
35
+ obj
36
+ end
23
37
  end
24
38
  end
25
39
  end
@@ -5,17 +5,15 @@ module CouchRest
5
5
  base.send(:include, ::CouchRest::Mixins::Callbacks)
6
6
  base.send(:include, ::CouchRest::Mixins::Properties)
7
7
  base.send(:attr_accessor, :casted_by)
8
- base.send(:attr_accessor, :document_saved)
9
8
  end
10
9
 
11
10
  def initialize(keys={})
12
11
  raise StandardError unless self.is_a? Hash
13
- apply_defaults # defined in CouchRest::Mixins::Properties
12
+ apply_all_property_defaults # defined in CouchRest::Mixins::Properties
14
13
  super()
15
14
  keys.each do |k,v|
16
- self[k.to_s] = v
15
+ write_attribute(k.to_s, v)
17
16
  end if keys
18
- cast_keys # defined in CouchRest::Mixins::Properties
19
17
  end
20
18
 
21
19
  def []= key, value
@@ -36,7 +34,7 @@ module CouchRest
36
34
  # False if the casted model has already
37
35
  # been saved in the containing document
38
36
  def new?
39
- !@document_saved
37
+ @casted_by.nil? ? true : @casted_by.new?
40
38
  end
41
39
  alias :new_record? :new?
42
40
 
@@ -8,7 +8,7 @@ module CouchRest
8
8
  # Same as CouchRest::Document but with properties and validations
9
9
  class ExtendedDocument < Document
10
10
 
11
- VERSION = "1.0.0.beta6"
11
+ VERSION = "1.0.0"
12
12
 
13
13
  include CouchRest::Mixins::Callbacks
14
14
  include CouchRest::Mixins::DocumentQueries
@@ -18,6 +18,7 @@ module CouchRest
18
18
  include CouchRest::Mixins::ClassProxy
19
19
  include CouchRest::Mixins::Collection
20
20
  include CouchRest::Mixins::AttributeProtection
21
+ include CouchRest::Mixins::Attributes
21
22
 
22
23
  # Including validation here does not work due to the way inheritance is handled.
23
24
  #include CouchRest::Validation
@@ -57,12 +58,17 @@ module CouchRest
57
58
  base.new(doc, :directly_set_attributes => true)
58
59
  end
59
60
 
61
+
62
+ # Instantiate a new ExtendedDocument by preparing all properties
63
+ # using the provided document hash.
64
+ #
65
+ # Options supported:
66
+ #
67
+ # * :directly_set_attributes: true when data comes directly from database
68
+ #
60
69
  def initialize(doc = {}, options = {})
61
- apply_defaults # defined in CouchRest::Mixins::Properties
62
- remove_protected_attributes(doc) unless options[:directly_set_attributes]
63
- directly_set_attributes(doc) unless doc.nil?
70
+ prepare_all_attributes(doc, options) # defined in CouchRest::Mixins::Attributes
64
71
  super(doc)
65
- cast_keys # defined in CouchRest::Mixins::Properties
66
72
  unless self['_id'] && self['_rev']
67
73
  self['couchrest-type'] = self.class.to_s
68
74
  end
@@ -94,12 +100,12 @@ module CouchRest
94
100
  # decent time format by default. See Time#to_json
95
101
  def self.timestamps!
96
102
  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)
103
+ property(:updated_at, Time, :read_only => true, :protected => true, :auto_validation => false)
104
+ property(:created_at, Time, :read_only => true, :protected => true, :auto_validation => false)
99
105
 
100
106
  set_callback :save, :before do |object|
101
- object['updated_at'] = Time.now
102
- object['created_at'] = object['updated_at'] if object.new?
107
+ write_attribute('updated_at', Time.now)
108
+ write_attribute('created_at', Time.now) if object.new?
103
109
  end
104
110
  EOS
105
111
  end
@@ -142,14 +148,6 @@ module CouchRest
142
148
 
143
149
  ### instance methods
144
150
 
145
- # Returns the Class properties
146
- #
147
- # ==== Returns
148
- # Array:: the list of properties for the instance
149
- def properties
150
- self.class.properties
151
- end
152
-
153
151
  # Gets a reference to the actual document in the DB
154
152
  # Calls up to the next document if there is one,
155
153
  # Otherwise we're at the top and we return self
@@ -163,28 +161,6 @@ module CouchRest
163
161
  !@casted_by
164
162
  end
165
163
 
166
- # Takes a hash as argument, and applies the values by using writer methods
167
- # for each key. It doesn't save the document at the end. Raises a NoMethodError if the corresponding methods are
168
- # missing. In case of error, no attributes are changed.
169
- def update_attributes_without_saving(hash)
170
- # remove attributes that cannot be updated, silently ignoring them
171
- # which matches Rails behavior when, for instance, setting created_at.
172
- # make a copy, we don't want to change arguments
173
- attrs = hash.dup
174
- %w[_id _rev created_at updated_at].each {|attr| attrs.delete(attr)}
175
- check_properties_exist(attrs)
176
- set_attributes(attrs)
177
- end
178
- alias :attributes= :update_attributes_without_saving
179
-
180
- # Takes a hash as argument, and applies the values by using writer methods
181
- # for each key. Raises a NoMethodError if the corresponding methods are
182
- # missing. In case of error, no attributes are changed.
183
- def update_attributes(hash)
184
- update_attributes_without_saving hash
185
- save
186
- end
187
-
188
164
  # for compatibility with old-school frameworks
189
165
  alias :new_record? :new?
190
166
  alias :new_document? :new?
@@ -255,7 +231,6 @@ module CouchRest
255
231
  raise ArgumentError, "a document requires a database to be saved to (The document or the #{self.class} default database were not set)" unless database
256
232
  set_unique_id if new? && self.respond_to?(:set_unique_id)
257
233
  result = database.save_doc(self, bulk)
258
- mark_as_saved
259
234
  result["ok"] == true
260
235
  end
261
236
 
@@ -281,43 +256,6 @@ module CouchRest
281
256
  end
282
257
  end
283
258
  end
284
-
285
- protected
286
-
287
- # Set document_saved flag on all casted models to true
288
- def mark_as_saved
289
- self.each do |key, prop|
290
- if prop.is_a?(Array)
291
- prop.each do |item|
292
- if item.respond_to?(:document_saved)
293
- item.send(:document_saved=, true)
294
- end
295
- end
296
- elsif prop.respond_to?(:document_saved)
297
- prop.send(:document_saved=, true)
298
- end
299
- end
300
- end
301
-
302
- private
303
-
304
- def check_properties_exist(attrs)
305
- attrs.each do |attribute_name, attribute_value|
306
- raise NoMethodError, "#{attribute_name}= method not available, use property :#{attribute_name}" unless self.respond_to?("#{attribute_name}=")
307
- end
308
- end
309
-
310
- def directly_set_attributes(hash)
311
- hash.each do |attribute_name, attribute_value|
312
- if self.respond_to?("#{attribute_name}=")
313
- self.send("#{attribute_name}=", hash.delete(attribute_name))
314
- end
315
- end
316
- end
317
-
318
- def set_attributes(hash)
319
- attrs = remove_protected_attributes(hash)
320
- directly_set_attributes(attrs)
321
- end
259
+
322
260
  end
323
261
  end