couchrest_extended_document 1.0.0.beta6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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