openlogic-couchrest_model 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (107) hide show
  1. data/.gitignore +11 -0
  2. data/.rspec +4 -0
  3. data/Gemfile +4 -0
  4. data/LICENSE +176 -0
  5. data/README.md +137 -0
  6. data/Rakefile +38 -0
  7. data/THANKS.md +21 -0
  8. data/VERSION +1 -0
  9. data/benchmarks/dirty.rb +118 -0
  10. data/couchrest_model.gemspec +36 -0
  11. data/history.md +309 -0
  12. data/init.rb +1 -0
  13. data/lib/couchrest/model.rb +10 -0
  14. data/lib/couchrest/model/associations.rb +231 -0
  15. data/lib/couchrest/model/base.rb +129 -0
  16. data/lib/couchrest/model/callbacks.rb +28 -0
  17. data/lib/couchrest/model/casted_array.rb +83 -0
  18. data/lib/couchrest/model/casted_by.rb +33 -0
  19. data/lib/couchrest/model/casted_hash.rb +84 -0
  20. data/lib/couchrest/model/class_proxy.rb +135 -0
  21. data/lib/couchrest/model/collection.rb +273 -0
  22. data/lib/couchrest/model/configuration.rb +67 -0
  23. data/lib/couchrest/model/connection.rb +70 -0
  24. data/lib/couchrest/model/core_extensions/hash.rb +9 -0
  25. data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
  26. data/lib/couchrest/model/design_doc.rb +128 -0
  27. data/lib/couchrest/model/designs.rb +91 -0
  28. data/lib/couchrest/model/designs/view.rb +513 -0
  29. data/lib/couchrest/model/dirty.rb +39 -0
  30. data/lib/couchrest/model/document_queries.rb +99 -0
  31. data/lib/couchrest/model/embeddable.rb +78 -0
  32. data/lib/couchrest/model/errors.rb +25 -0
  33. data/lib/couchrest/model/extended_attachments.rb +83 -0
  34. data/lib/couchrest/model/persistence.rb +178 -0
  35. data/lib/couchrest/model/properties.rb +228 -0
  36. data/lib/couchrest/model/property.rb +114 -0
  37. data/lib/couchrest/model/property_protection.rb +71 -0
  38. data/lib/couchrest/model/proxyable.rb +183 -0
  39. data/lib/couchrest/model/support/couchrest_database.rb +13 -0
  40. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  41. data/lib/couchrest/model/typecast.rb +154 -0
  42. data/lib/couchrest/model/validations.rb +80 -0
  43. data/lib/couchrest/model/validations/casted_model.rb +16 -0
  44. data/lib/couchrest/model/validations/locale/en.yml +5 -0
  45. data/lib/couchrest/model/validations/uniqueness.rb +69 -0
  46. data/lib/couchrest/model/views.rb +151 -0
  47. data/lib/couchrest/railtie.rb +24 -0
  48. data/lib/couchrest_model.rb +66 -0
  49. data/lib/rails/generators/couchrest_model.rb +16 -0
  50. data/lib/rails/generators/couchrest_model/config/config_generator.rb +18 -0
  51. data/lib/rails/generators/couchrest_model/config/templates/couchdb.yml +21 -0
  52. data/lib/rails/generators/couchrest_model/model/model_generator.rb +27 -0
  53. data/lib/rails/generators/couchrest_model/model/templates/model.rb +2 -0
  54. data/spec/.gitignore +1 -0
  55. data/spec/fixtures/attachments/README +3 -0
  56. data/spec/fixtures/attachments/couchdb.png +0 -0
  57. data/spec/fixtures/attachments/test.html +11 -0
  58. data/spec/fixtures/config/couchdb.yml +10 -0
  59. data/spec/fixtures/models/article.rb +36 -0
  60. data/spec/fixtures/models/base.rb +164 -0
  61. data/spec/fixtures/models/card.rb +19 -0
  62. data/spec/fixtures/models/cat.rb +23 -0
  63. data/spec/fixtures/models/client.rb +6 -0
  64. data/spec/fixtures/models/course.rb +27 -0
  65. data/spec/fixtures/models/event.rb +8 -0
  66. data/spec/fixtures/models/invoice.rb +14 -0
  67. data/spec/fixtures/models/key_chain.rb +5 -0
  68. data/spec/fixtures/models/membership.rb +4 -0
  69. data/spec/fixtures/models/person.rb +11 -0
  70. data/spec/fixtures/models/project.rb +6 -0
  71. data/spec/fixtures/models/question.rb +7 -0
  72. data/spec/fixtures/models/sale_entry.rb +9 -0
  73. data/spec/fixtures/models/sale_invoice.rb +14 -0
  74. data/spec/fixtures/models/service.rb +10 -0
  75. data/spec/fixtures/models/user.rb +22 -0
  76. data/spec/fixtures/views/lib.js +3 -0
  77. data/spec/fixtures/views/test_view/lib.js +3 -0
  78. data/spec/fixtures/views/test_view/only-map.js +4 -0
  79. data/spec/fixtures/views/test_view/test-map.js +3 -0
  80. data/spec/fixtures/views/test_view/test-reduce.js +3 -0
  81. data/spec/functional/validations_spec.rb +8 -0
  82. data/spec/spec_helper.rb +60 -0
  83. data/spec/unit/active_model_lint_spec.rb +30 -0
  84. data/spec/unit/assocations_spec.rb +242 -0
  85. data/spec/unit/attachment_spec.rb +176 -0
  86. data/spec/unit/base_spec.rb +537 -0
  87. data/spec/unit/casted_spec.rb +72 -0
  88. data/spec/unit/class_proxy_spec.rb +167 -0
  89. data/spec/unit/collection_spec.rb +86 -0
  90. data/spec/unit/configuration_spec.rb +77 -0
  91. data/spec/unit/connection_spec.rb +148 -0
  92. data/spec/unit/core_extensions/time_parsing.rb +77 -0
  93. data/spec/unit/design_doc_spec.rb +241 -0
  94. data/spec/unit/designs/view_spec.rb +831 -0
  95. data/spec/unit/designs_spec.rb +134 -0
  96. data/spec/unit/dirty_spec.rb +436 -0
  97. data/spec/unit/embeddable_spec.rb +498 -0
  98. data/spec/unit/inherited_spec.rb +33 -0
  99. data/spec/unit/persistence_spec.rb +481 -0
  100. data/spec/unit/property_protection_spec.rb +192 -0
  101. data/spec/unit/property_spec.rb +481 -0
  102. data/spec/unit/proxyable_spec.rb +376 -0
  103. data/spec/unit/subclass_spec.rb +85 -0
  104. data/spec/unit/typecast_spec.rb +521 -0
  105. data/spec/unit/validations_spec.rb +140 -0
  106. data/spec/unit/view_spec.rb +367 -0
  107. metadata +301 -0
@@ -0,0 +1,36 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{openlogic-couchrest_model}
5
+ s.version = `cat VERSION`.strip
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["J. Chris Anderson", "Matt Aimonetti", "Marcos Tapajos", "Will Leinweber", "Sam Lown"]
9
+ s.date = File.mtime('VERSION')
10
+ s.description = %q{CouchRest Model provides aditional features to the standard CouchRest Document class such as properties, view designs, associations, callbacks, typecasting and validations.}
11
+ s.email = %q{jchris@apache.org}
12
+ s.extra_rdoc_files = [
13
+ "LICENSE",
14
+ "README.md",
15
+ "THANKS.md"
16
+ ]
17
+ s.homepage = %q{http://github.com/couchrest/couchrest_model}
18
+ s.rubygems_version = %q{1.3.7}
19
+ s.summary = %q{Extends the CouchRest Document for advanced modelling.}
20
+
21
+ s.files = `git ls-files`.split("\n")
22
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
23
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
24
+ s.require_paths = ["lib"]
25
+
26
+ s.add_dependency(%q<couchrest>, "~> 1.1.2")
27
+ s.add_dependency(%q<mime-types>, "~> 1.15")
28
+ s.add_dependency(%q<activemodel>, "~> 3.0")
29
+ s.add_dependency(%q<tzinfo>, "~> 0.3.22")
30
+ s.add_development_dependency(%q<rspec>, "~> 2.6.0")
31
+ s.add_development_dependency(%q<json>, ["~> 1.5.1"])
32
+ s.add_development_dependency(%q<rack-test>, ">= 0.5.7")
33
+ s.add_development_dependency("rake", ">= 0.8.0")
34
+ # s.add_development_dependency("jruby-openssl", ">= 0.7.3")
35
+ end
36
+
@@ -0,0 +1,309 @@
1
+ # CouchRest Model Change History
2
+
3
+ ## 1.1.3
4
+
5
+ * CouchRest::Model::Base.respond_to_missing? and respond_to? (Kim Burgestrand)
6
+
7
+ ## 1.1.2 - 2011-07-23
8
+
9
+ * Minor fixes
10
+ * Upgrade to couchrest 1.1.2
11
+ * Override as_couch_json to ensure nil values not stored
12
+ * Removing restriction that prohibited objects that cast as an array to be loaded.
13
+
14
+ ## 1.1.1 - 2011-07-04
15
+
16
+ * Minor fix
17
+ * Bumping CouchRest version dependency for important initialize method fix.
18
+ * Ensuring super on Embeddable#initialize can be called.
19
+
20
+ ## 1.1.0 - 2011-06-25
21
+
22
+ * Major Alterations
23
+ * CastedModel no longer requires a Hash. Automatically includes all required methods.
24
+ * CastedModel module renamed to Embeddable (old still works!)
25
+
26
+ * Minor Fixes
27
+ * Validation callbacks now support context (thanks kostia)
28
+ * Document comparisons now performed using database and document ID (pointer by neocsr)
29
+ * Automatic config generation now supported (thanks lucasrenan)
30
+ * Comparing documents resorts to Hash comparison if both IDs are nil. (pointer by kostia)
31
+
32
+ ## 1.1.0.rc1 - 2011-06-08
33
+
34
+ * New Features
35
+ * Properties with a nil value are now no longer sent to the database.
36
+ * Now possible to build new objects via CastedArray#build
37
+ * Implement #get! and #find! class methods
38
+ * Now is possible delete particular elements in casted array(Kostiantyn Kahanskyi)
39
+
40
+ * Minor fixes
41
+ * #as_json now correctly uses ActiveSupports methods.
42
+ * Rails 3.1 support (Peter Williams)
43
+ * Initialization blocks when creating new models (Peter Williams)
44
+ * Removed railties dependency (DAddYE)
45
+ * DesignDoc cache refreshed if a database is deleted.
46
+ * Fixing dirty tracking on collection_of association.
47
+ * Uniqueness Validation views created on initialization, not on demand!
48
+ * #destroy freezes object instead of removing _id and _rev, better for callbacks (pointer by karmi)
49
+ * #destroyed? method now available
50
+ * #reload no longer uses Hash#merge! which was causing issues with dirty tracking on casted models. (pointer by kostia)
51
+ * Non-property mass assignment on #new no longer possible without :directly_set_attributes option.
52
+ * Using CouchRest 1.1.0.pre3. (No more Hashes!)
53
+ * Fixing problem assigning a CastedHash to a property declared as a Hash (Kostiantyn Kahanskyi, gfmtim)
54
+
55
+ ## 1.1.0.beta5 - 2011-04-30
56
+
57
+ * Major changes:
58
+ * Database auto configuration, with connection options!
59
+ * Changed default CouchRest Model type to 'type' to be more consistent with ActiveRecord's reserverd words we're all used to (sorry for the change again!!)
60
+
61
+ * Minor changes
62
+ * Added filter option to designs (Used with CouchDB _changes feeds)
63
+
64
+ ## 1.1.0.beta4
65
+
66
+ * Major changes:
67
+ * Fast Dirty Tracking! Many thanks to @sobakasu (Andrew Williams)
68
+ * Default CouchRest Model type field now set to 'model' instead of 'couchrest-type'.
69
+
70
+ * Minor enhancements:
71
+ * Adding "couchrest-hash" to Design Docs with aim to improve view update handling.
72
+ * Major changes to the way design document updates are handled internally.
73
+ * Added "auto_update_design_doc" configuration option.
74
+ * Using #descending on View object will automatically swap startkey with endkey.
75
+
76
+ ## 1.1.0.beta3
77
+
78
+ * Removed
79
+
80
+ ## 1.1.0.beta2
81
+
82
+ * Minor enhancements:
83
+ * Time handling improved in accordance with CouchRest 1.1.0. Always set to UTC.
84
+ * Refinements to associations and uniqueness validation for proxy (based on issue found by Gleb Kanterov)
85
+ * Added :allow_nil and :allow_blank options when creating a new view
86
+ * Unique Validation now supports scopes!
87
+ * Added support for #keys with list on Design View.
88
+
89
+ ## 1.1.0.beta
90
+
91
+ * Epic enhancements:
92
+ * Added "View" object for dynamic view queries
93
+ * Added easy to use proxy_for and proxied_by class methods for proxying data
94
+
95
+ * Minor enhancements:
96
+ * A yield parameter in an anonymous casted model property block is no longer required (@samlown)
97
+ * Narrow the rescued exception to avoid catching class evaluation errors that has nothing to to with the association (thanks Simone Carletti)
98
+ * Fix validate uniqueness test that was never executed (thanks Simone Carletti)
99
+ * Adds a #reload method to reload document attributes (thanks Simone Carletti)
100
+ * Numeric types can be casted from strings with leading or trailing whitespace (thanks chrisdurtschi)
101
+ * CollectionProxy no longer provided by default with simple views (pending deprication)
102
+
103
+ ## CouchRest Model 1.0.0
104
+
105
+ * Major enhancements
106
+ * Support for configuration module and "model_type_key" option for overriding model's type key
107
+ * Added "mass_assign_any_attribute" configuration option to allow setting anything via the attribute= method.
108
+
109
+ * Minor enhancements
110
+ * Fixing find("") issue (thanks epochwolf)
111
+ * Altered protected attributes so that hash provided to #attributes= is not modified
112
+ * Altering typecasting for floats to better handle commas and points
113
+ * Fixing the lame pagination bug where database url (and pass!!) were included in view requests (Thanks James Hayton)
114
+
115
+ Notes:
116
+
117
+ * 2010-10-22 @samlown:
118
+ * ActiveModel Attribute support was added but has now been removed due to major performance issues.
119
+ Until these have been resolved (if possible?!) they should not be included. See the
120
+ 'active_model_attrs' if you'd like to test.
121
+
122
+ ## CouchRest Model 1.0.0.beta8
123
+
124
+ * Major enhancements
125
+ * Added model generator
126
+
127
+ * Minor enhancements
128
+ * Raise error on adding objects to "collection_of" without an id
129
+ * Allow mixing of protected and accessible properties. Any unspecified properties are now assumed to be protected by default
130
+ * Parsing times without zone
131
+ * Using latest rspec (2.0.0.beta.19)
132
+
133
+ ## CouchRest Model 1.0.0.beta7
134
+
135
+ * Major enhancements
136
+ * Renamed ExtendedDocument to CouchRest::Model
137
+ * Added initial support for simple belongs_to associations
138
+ * Added support for basic collection_of association (unique to document databases!)
139
+ * Moved Validation to ActiveModel
140
+ * Moved Callbacks to ActiveModel
141
+ * Removed support for properties defined using a string for the type instead of a class
142
+ * Validation always included
143
+ * Uniqueness validation now available
144
+
145
+ * Minor enhancements
146
+ * Removed support for auto_validate! and :length on properties
147
+
148
+
149
+ ## 1.0.0.beta6
150
+
151
+ * Major enhancements
152
+ * Added support for anonymous CastedModels defined in Documents
153
+
154
+ * Minor enhancements
155
+ * Added 'find_by_*' alias for finding first item in view with matching key.
156
+ * Fixed issue with active_support in Rails3 and text in README for JSON.
157
+ * Refactoring of properties, added read_attribute and write_attribute methods.
158
+ * Now possible to send anything to update_attribtues method. Invalid or readonly attributes will be ignored.
159
+ * Attributes with arrays are *always* instantiated as a CastedArray.
160
+ * Setting a property of type Array (or keyed hash) must be an array or an error will be raised.
161
+ * Now possible to set Array attribute from hash where keys determine order.
162
+
163
+ ## 1.0.0.beta5
164
+
165
+ * Minor enhancements
166
+ * Added 'find' alias for 'get' for easier rails transition
167
+
168
+ ## 1.0.0.beta3
169
+
170
+ * Minor enhancements
171
+ * Removed Validation by default, requires too many structure changes (FAIL)
172
+ * Added support for instantiation of documents read from database as couchrest-type provided (Sam Lown)
173
+ * Improved attachment handling for detecting file type (Sam Lown)
174
+ * Removing some monkey patches and relying on active_support for constantize and humanize (Sam Lown)
175
+ * Added support for setting type directly on property (Sam Lown)
176
+
177
+
178
+ ## 1.0.0.beta2
179
+
180
+ * Minor enhancements
181
+ * Enable Validation by default and refactored location (Sam Lown)
182
+
183
+ ## 1.0.0.beta
184
+
185
+ * Major enhancements
186
+ * Separated ExtendedDocument from main CouchRest gem (Sam Lown)
187
+
188
+ * Minor enhancements
189
+ * active_support included by default
190
+
191
+ ## 0.37
192
+
193
+ * Minor enhancements
194
+ * Added gemspec (needed for Bundler install) (Tapajós)
195
+
196
+ ## 0.36
197
+
198
+ * Major enhancements
199
+ * Adds support for continuous replication (sauy7)
200
+ * Automatic Type Casting (Alexander Uvarov, Sam Lown, Tim Heighes, Will Leinweber)
201
+ * Added a search method to CouchRest:Database to search the documents in a given database. (Dave Farkas, Arnaud Berthomier, John Wood)
202
+
203
+ * Minor enhancements
204
+ * Provide a description of the timeout error (John Wood)
205
+
206
+ ## 0.35
207
+
208
+ * Major enhancements
209
+ * CouchRest::ExtendedDocument allow chaining the inherit class callback (Kenneth Kalmer) - http://github.com/couchrest/couchrest/issues#issue/8
210
+
211
+ * Minor enhancements
212
+ * Fix attachment bug (Johannes Jörg Schmidt)
213
+ * Fix create database exception bug (Damien Mathieu)
214
+ * Compatible with restclient >= 1.4.0 new responses (Julien Kirch)
215
+ * Bug fix: Attribute protection no longer strips attributes coming from the database (Will Leinweber)
216
+ * Bug fix: Remove double CGI escape when PUTting an attachment (nzoschke)
217
+ * Bug fix: Changing Class proxy to set database on result sets (Peter Gumeson)
218
+ * Bug fix: Updated time regexp (Nolan Darilek)
219
+ * Added an update_doc method to database to handle conflicts during atomic updates. (Pierre Larochelle)
220
+ * Bug fix: http://github.com/couchrest/couchrest/issues/#issue/2 (Luke Burton)
221
+
222
+ ## 0.34
223
+
224
+ * Major enhancements
225
+
226
+ * Added support for https database URIs. (Mathias Meyer)
227
+ * Changing some validations to be compatible with activemodel. (Marcos Tapajós)
228
+ * Adds attribute protection to properties. (Will Leinweber)
229
+ * Improved CouchRest::Database#save_doc, added "batch" mode to significantly speed up saves at cost of lower durability gurantees. (Igal Koshevoy)
230
+ * Added CouchRest::Database#bulk_save_doc and #batch_save_doc as human-friendlier wrappers around #save_doc. (Igal Koshevoy)
231
+
232
+ * Minor enhancements
233
+
234
+ * Fix content_type handling for attachments
235
+ * Fixed a bug in the pagination code that caused it to paginate over records outside of the scope of the view parameters.(John Wood)
236
+ * Removed amount_pages calculation for the pagination collection, since it cannot be reliably calculated without a view.(John Wood)
237
+ * Bug fix: http://github.com/couchrest/couchrest/issues/#issue/2 (Luke Burton)
238
+ * Bug fix: http://github.com/couchrest/couchrest/issues/#issue/1 (Marcos Tapajós)
239
+ * Removed the Database class deprecation notices (Matt Aimonetti)
240
+ * Adding support to :cast_as => 'Date'. (Marcos Tapajós)
241
+ * Improve documentation (Marcos Tapajós)
242
+ * Streamer fixes (Julien Sanchez)
243
+ * Fix Save on Document & ExtendedDocument crashed if bulk (Julien Sanchez)
244
+ * Fix Initialization of ExtendentDocument model shouldn't failed on a nil value in argument (deepj)
245
+ * Change to use Jeweler and Gemcutter (Marcos Tapajós)
246
+
247
+ ## 0.33
248
+
249
+ * Major enhancements
250
+
251
+ * Added a new Rack logger middleware letting you log/save requests/queries (Matt Aimonetti)
252
+
253
+ * Minor enhancements
254
+
255
+ * Added #amount_pages to a paginated result array (Matt Aimonetti)
256
+ * Ruby 1.9.2 compatible (Matt Aimonetti)
257
+ * Added a property? method for property cast as :boolean (John Wood)
258
+ * Added an option to force the deletion of a attachments (bypass 409s) (Matt Aimonetti)
259
+ * Created a new abstraction layer for the REST API (Matt Aimonetti)
260
+ * Bug fix: made ExtendedDocument#all compatible with Couch 0.10 (tc)
261
+
262
+ ## 0.32
263
+
264
+ * Major enhancements
265
+
266
+ * ExtendedDocument.get doesn't raise an exception anymore. If no documents are found nil is returned.
267
+ * ExtendedDocument.get! works the say #get used to work and will raise an exception if a document isn't found.
268
+
269
+ * Minor enhancements
270
+
271
+ * Bug fix: Model.all(:keys => [1,2]) was not working (Matt Aimonetti)
272
+ * Added ValidationErrors#count in order to play nicely with Rails (Peter Wagenet)
273
+ * Bug fix: class proxy design doc refresh (Daniel Kirsh)
274
+ * Bug fix: the count method on the proxy collection was missing (Daniel Kirsch)
275
+ * Added #amount_pages to a paginated collection. (Matt Aimonetti)
276
+
277
+ ## 0.31
278
+
279
+ * Major enhancements
280
+
281
+ * Created an abstraction HTTP layer to support different http adapters (Matt Aimonetti)
282
+ * Added ExtendedDocument.create({}) and #create!({}) so you don't have to do Model.new.create (Matt Aimonetti)
283
+
284
+ * Minor enhancements
285
+
286
+ * Added an init.rb file for easy usage as a Rails plugin (Aaron Quint)
287
+ * Bug fix: pagination shouldn't die on empty results (Arnaud Berthomier)
288
+ * Optimized ExtendedDocument.count to run about 3x faster (Matt Aimonetti)
289
+ * Added Float casting (Ryan Felton & Matt Aimonetti)
290
+
291
+ ## 0.30
292
+
293
+ * Major enhancements
294
+
295
+ * Added support for pagination (John Wood)
296
+ * Improved performance when initializing documents with timestamps (Matt Aimonetti)
297
+
298
+ * Minor enhancements
299
+
300
+ * Extended the API to retrieve an attachment URI (Matt Aimonetti)
301
+ * Bug fix: default value should be able to be set as false (Alexander Uvarov)
302
+ * Bug fix: validates_is_numeric should be able to properly validate a Float instance (Rob Kaufman)
303
+ * Bug fix: fixed the Timeout implementation (Seth Falcon)
304
+
305
+
306
+ ---
307
+
308
+ Unfortunately, before 0.30 we did not keep a track of the modifications made to CouchRest.
309
+ You can see the full commit history on GitHub: http://github.com/couchrest/couchrest/commits/master/
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__),'lib', 'couchrest', 'model')
@@ -0,0 +1,10 @@
1
+
2
+ module CouchRest
3
+
4
+ module Model
5
+
6
+ VERSION = File.read(File.expand_path('../../../VERSION', __FILE__)).strip
7
+
8
+ end
9
+
10
+ end
@@ -0,0 +1,231 @@
1
+ module CouchRest
2
+ module Model
3
+ module Associations
4
+
5
+ # Basic support for relationships between CouchRest::Model::Base
6
+
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ end
10
+
11
+ module ClassMethods
12
+
13
+ # Define an association that this object belongs to.
14
+ #
15
+ # An attribute will be created matching the name of the attribute
16
+ # with '_id' on the end, or the foreign key (:foreign_key) provided.
17
+ #
18
+ # Searching for the assocated object is performed using a string
19
+ # (:proxy) to be evaulated in the context of the owner. Typically
20
+ # this will be set to the class name (:class_name), or determined
21
+ # automatically if the owner belongs to a proxy object.
22
+ #
23
+ # If the association owner is proxied by another model, than an attempt will
24
+ # be made to automatically determine the correct place to request
25
+ # the documents. Typically, this is a method with the pluralized name of the
26
+ # association inside owner's owner, or proxy.
27
+ #
28
+ # For example, imagine a company acts as a proxy for invoices and clients.
29
+ # If an invoice belongs to a client, the invoice will need to access the
30
+ # list of clients via the proxy. So a request to search for the associated
31
+ # client from an invoice would look like:
32
+ #
33
+ # self.company.clients
34
+ #
35
+ # If the name of the collection proxy is not the pluralized assocation name,
36
+ # it can be set with the :proxy_name option.
37
+ #
38
+ def belongs_to(attrib, *options)
39
+ opts = merge_belongs_to_association_options(attrib, options.first)
40
+
41
+ property(opts[:foreign_key], opts)
42
+
43
+ create_belongs_to_getter(attrib, opts)
44
+ create_belongs_to_setter(attrib, opts)
45
+ end
46
+
47
+ # Provide access to a collection of objects where the associated
48
+ # property contains a list of the collection item ids.
49
+ #
50
+ # The following:
51
+ #
52
+ # collection_of :groups
53
+ #
54
+ # creates a pseudo property called "groups" which allows access
55
+ # to a CollectionOfProxy object. Adding, replacing or removing entries in this
56
+ # proxy will cause the matching property array, in this case "group_ids", to
57
+ # be kept in sync.
58
+ #
59
+ # Any manual changes made to the collection ids property (group_ids), unless replaced, will require
60
+ # a reload of the CollectionOfProxy for the two sets of data to be in sync:
61
+ #
62
+ # group_ids = ['123']
63
+ # groups == [Group.get('123')]
64
+ # group_ids << '321'
65
+ # groups == [Group.get('123')]
66
+ # groups(true) == [Group.get('123'), Group.get('321')]
67
+ #
68
+ # Of course, saving the parent record will store the collection ids as they are
69
+ # found.
70
+ #
71
+ # The CollectionOfProxy supports the following array functions, anything else will cause
72
+ # a mismatch between the collection objects and collection ids:
73
+ #
74
+ # groups << obj
75
+ # groups.push obj
76
+ # groups.unshift obj
77
+ # groups[0] = obj
78
+ # groups.pop == obj
79
+ # groups.shift == obj
80
+ #
81
+ # Addtional options match those of the the belongs_to method.
82
+ #
83
+ # NOTE: This method is *not* recommended for large collections or collections that change
84
+ # frequently! Use with prudence.
85
+ #
86
+ def collection_of(attrib, *options)
87
+ opts = merge_belongs_to_association_options(attrib, options.first)
88
+ opts[:foreign_key] = opts[:foreign_key].pluralize
89
+ opts[:readonly] = true
90
+
91
+ property(opts[:foreign_key], [], opts)
92
+
93
+ create_collection_of_property_setter(attrib, opts)
94
+ create_collection_of_getter(attrib, opts)
95
+ create_collection_of_setter(attrib, opts)
96
+ end
97
+
98
+
99
+ private
100
+
101
+ def merge_belongs_to_association_options(attrib, options = nil)
102
+ opts = {
103
+ :foreign_key => attrib.to_s.singularize + '_id',
104
+ :class_name => attrib.to_s.singularize.camelcase,
105
+ :proxy_name => attrib.to_s.pluralize
106
+ }
107
+ opts.merge!(options) if options.is_a?(Hash)
108
+
109
+ # Generate a string for the proxy method call
110
+ # Assumes that the proxy_owner_method from "proxyable" is available.
111
+ if opts[:proxy].to_s.empty?
112
+ opts[:proxy] = if proxy_owner_method
113
+ "self.#{proxy_owner_method}.#{opts[:proxy_name]}"
114
+ else
115
+ opts[:class_name]
116
+ end
117
+ end
118
+
119
+ opts
120
+ end
121
+
122
+ def create_belongs_to_getter(attrib, options)
123
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
124
+ def #{attrib}
125
+ @#{attrib} ||= #{options[:foreign_key]}.nil? ? nil : #{options[:proxy]}.get(self.#{options[:foreign_key]})
126
+ end
127
+ EOS
128
+ end
129
+
130
+ def create_belongs_to_setter(attrib, options)
131
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
132
+ def #{attrib}=(value)
133
+ self.#{options[:foreign_key]} = value.nil? ? nil : value.id
134
+ @#{attrib} = value
135
+ end
136
+ EOS
137
+ end
138
+
139
+ ### collection_of support methods
140
+
141
+ def create_collection_of_property_setter(attrib, options)
142
+ # ensure CollectionOfProxy is nil, ready to be reloaded on request
143
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
144
+ def #{options[:foreign_key]}=(value)
145
+ @#{attrib} = nil
146
+ write_attribute("#{options[:foreign_key]}", value)
147
+ end
148
+ EOS
149
+ end
150
+
151
+ def create_collection_of_getter(attrib, options)
152
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
153
+ def #{attrib}(reload = false)
154
+ return @#{attrib} unless @#{attrib}.nil? or reload
155
+ ary = self.#{options[:foreign_key]}.collect{|i| #{options[:proxy]}.get(i)}
156
+ @#{attrib} = ::CouchRest::Model::CollectionOfProxy.new(ary, find_property('#{options[:foreign_key]}'), self)
157
+ end
158
+ EOS
159
+ end
160
+
161
+ def create_collection_of_setter(attrib, options)
162
+ class_eval <<-EOS, __FILE__, __LINE__ + 1
163
+ def #{attrib}=(value)
164
+ @#{attrib} = ::CouchRest::Model::CollectionOfProxy.new(value, find_property('#{options[:foreign_key]}'), self)
165
+ end
166
+ EOS
167
+ end
168
+
169
+ end
170
+
171
+ end
172
+
173
+ # Special proxy for a collection of items so that adding and removing
174
+ # to the list automatically updates the associated property.
175
+ class CollectionOfProxy < CastedArray
176
+
177
+ def initialize(array, property, parent)
178
+ (array ||= []).compact!
179
+ super(array, property, parent)
180
+ casted_by[casted_by_property.to_s] = [] # replace the original array!
181
+ array.compact.each do |obj|
182
+ check_obj(obj)
183
+ casted_by[casted_by_property.to_s] << obj.id
184
+ end
185
+ end
186
+
187
+ def << obj
188
+ check_obj(obj)
189
+ casted_by[casted_by_property.to_s] << obj.id
190
+ super(obj)
191
+ end
192
+
193
+ def push(obj)
194
+ check_obj(obj)
195
+ casted_by[casted_by_property.to_s].push obj.id
196
+ super(obj)
197
+ end
198
+
199
+ def unshift(obj)
200
+ check_obj(obj)
201
+ casted_by[casted_by_property.to_s].unshift obj.id
202
+ super(obj)
203
+ end
204
+
205
+ def []= index, obj
206
+ check_obj(obj)
207
+ casted_by[casted_by_property.to_s][index] = obj.id
208
+ super(index, obj)
209
+ end
210
+
211
+ def pop
212
+ casted_by[casted_by_property.to_s].pop
213
+ super
214
+ end
215
+
216
+ def shift
217
+ casted_by[casted_by_property.to_s].shift
218
+ super
219
+ end
220
+
221
+ protected
222
+
223
+ def check_obj(obj)
224
+ raise "Object cannot be added to #{casted_by.class.to_s}##{casted_by_property.to_s} collection unless saved" if obj.new?
225
+ end
226
+
227
+ end
228
+
229
+ end
230
+
231
+ end