couchrest_model 2.0.0.beta2 → 2.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 (41) hide show
  1. data/.travis.yml +8 -0
  2. data/Gemfile +1 -1
  3. data/README.md +1 -1
  4. data/Rakefile +9 -24
  5. data/VERSION +1 -1
  6. data/couchrest_model.gemspec +7 -5
  7. data/history.md +17 -1
  8. data/lib/couchrest/model/associations.rb +16 -11
  9. data/lib/couchrest/model/base.rb +17 -15
  10. data/lib/couchrest/model/casted_array.rb +7 -1
  11. data/lib/couchrest/model/core_extensions/time_parsing.rb +0 -23
  12. data/lib/couchrest/model/design.rb +282 -0
  13. data/lib/couchrest/model/designs/design_mapper.rb +79 -0
  14. data/lib/couchrest/model/designs/view.rb +9 -6
  15. data/lib/couchrest/model/designs.rb +37 -70
  16. data/lib/couchrest/model/persistence.rb +5 -5
  17. data/lib/couchrest/model/properties.rb +5 -16
  18. data/lib/couchrest/model/property.rb +34 -16
  19. data/lib/couchrest/model/translation.rb +22 -0
  20. data/lib/couchrest/model/typecast.rb +54 -43
  21. data/lib/couchrest/model/utils/migrate.rb +106 -0
  22. data/lib/couchrest_model.rb +4 -2
  23. data/lib/tasks/migrations.rake +5 -5
  24. data/spec/fixtures/models/course.rb +1 -0
  25. data/spec/fixtures/models/designs.rb +22 -0
  26. data/spec/spec_helper.rb +1 -0
  27. data/spec/unit/assocations_spec.rb +7 -0
  28. data/spec/unit/base_spec.rb +3 -1
  29. data/spec/unit/{designs/design_spec.rb → design_spec.rb} +6 -6
  30. data/spec/unit/designs/design_mapper_spec.rb +124 -0
  31. data/spec/unit/designs/view_spec.rb +30 -4
  32. data/spec/unit/designs_spec.rb +5 -140
  33. data/spec/unit/dirty_spec.rb +15 -1
  34. data/spec/unit/embeddable_spec.rb +2 -2
  35. data/spec/unit/property_spec.rb +70 -28
  36. data/spec/unit/translations_spec.rb +31 -0
  37. data/spec/unit/typecast_spec.rb +99 -19
  38. data/spec/unit/utils/migrate_spec.rb +25 -0
  39. metadata +43 -19
  40. data/lib/couchrest/model/designs/design.rb +0 -284
  41. data/lib/couchrest/model/migrate.rb +0 -92
@@ -1,284 +0,0 @@
1
-
2
- module CouchRest
3
- module Model
4
- module Designs
5
-
6
- class Design < ::CouchRest::Design
7
-
8
- # The model Class that this design belongs to and method name
9
- attr_accessor :model, :method_name
10
-
11
- # Can this design save itself to the database?
12
- # If false, the design will be loaded automatically before a view is executed.
13
- attr_accessor :auto_update
14
-
15
-
16
- # Instantiate a new design document for this model
17
- def initialize(model, prefix = nil)
18
- self.model = model
19
- self.method_name = self.class.method_name(prefix)
20
- suffix = prefix ? "_#{prefix}" : ''
21
- self["_id"] = "_design/#{model.to_s}#{suffix}"
22
- apply_defaults
23
- end
24
-
25
- def sync(db = nil)
26
- if auto_update
27
- db ||= database
28
- if cache_checksum(db) != checksum
29
- sync!(db)
30
- set_cache_checksum(db, checksum)
31
- end
32
- end
33
- self
34
- end
35
-
36
- def sync!(db = nil)
37
- db ||= database
38
-
39
- # Load up the last copy. We never blindly overwrite the remote copy
40
- # as it may contain views that are not used or known about by
41
- # our model.
42
- doc = load_from_database(db)
43
-
44
- if !doc || doc['couchrest-hash'] != checksum
45
- # We need to save something
46
- if doc
47
- # Different! Update.
48
- doc.merge!(to_hash)
49
- else
50
- # No previous doc, use a *copy* of our version.
51
- # Using a copy prevents reverse updates.
52
- doc = to_hash.dup
53
- end
54
- db.save_doc(doc)
55
- end
56
-
57
- self
58
- end
59
-
60
- # Migrate the design document preventing downtime on a production
61
- # system. Typically this will be used when auto updates are disabled.
62
- #
63
- # Steps taken are:
64
- #
65
- # 1. Compare the checksum with the current version
66
- # 2. If different, create a new design doc with timestamp
67
- # 3. Wait until the view returns a result
68
- # 4. Copy over the original design doc
69
- #
70
- # If a block is provided, it will be called with the result of the migration:
71
- #
72
- # * :no_change - Nothing performed as there are no changes.
73
- # * :created - Add a new design doc as non existed
74
- # * :migrated - Migrated the existing design doc.
75
- #
76
- # This can be used for progressivly printing the results of the migration.
77
- #
78
- # After completion, either a "cleanup" Proc object will be provided to finalize
79
- # the process and copy the document into place, or simply nil if no cleanup is
80
- # required. For example:
81
- #
82
- # print "Synchronising Cat model designs: "
83
- # callback = Cat.design_doc.migrate do |res|
84
- # puts res.to_s
85
- # end
86
- # if callback
87
- # puts "Cleaning up."
88
- # callback.call
89
- # end
90
- #
91
- def migrate(db = nil, &block)
92
- db ||= database
93
- doc = load_from_database(db)
94
- cleanup = nil
95
- id = self['_id']
96
-
97
- if !doc
98
- # no need to migrate, just save it
99
- new_doc = to_hash.dup
100
- db.save_doc(new_doc)
101
-
102
- result = :created
103
- elsif doc['couchrest-hash'] != checksum
104
- id += "_migration"
105
-
106
- # Delete current migration if there is one
107
- old_migration = load_from_database(db, id)
108
- db.delete_doc(old_migration) if old_migration
109
-
110
- # Save new design doc
111
- new_doc = doc.merge(to_hash)
112
- new_doc['_id'] = id
113
- new_doc.delete('_rev')
114
- db.save_doc(new_doc)
115
-
116
- # Proc definition to copy the migration doc over the original
117
- cleanup = Proc.new do
118
- db.copy_doc(new_doc, doc)
119
- db.delete_doc(new_doc)
120
- self
121
- end
122
-
123
- result = :migrated
124
- else
125
- # Already up to date
126
- result = :no_change
127
- end
128
-
129
- if new_doc && !new_doc['views'].empty?
130
- # Create a view query and send
131
- name = new_doc['views'].keys.first
132
- view = new_doc['views'][name]
133
- params = {:limit => 1}
134
- params[:reduce] = false if view['reduce']
135
- db.view("#{id}/_view/#{name}", params) do |res|
136
- # Block to use streamer!
137
- end
138
- end
139
-
140
- # Provide the result in block
141
- yield result if block_given?
142
-
143
- cleanup
144
- end
145
-
146
- # Perform a single migration and inmediatly request a cleanup operation:
147
- #
148
- # print "Synchronising Cat model designs: "
149
- # Cat.design_doc.migrate! do |res|
150
- # puts res.to_s
151
- # end
152
- #
153
- def migrate!(db = nil, &block)
154
- callback = migrate(db, &block)
155
- if callback.is_a?(Proc)
156
- callback.call
157
- else
158
- callback
159
- end
160
- end
161
-
162
- def checksum
163
- sum = self['couchrest-hash']
164
- if sum && (@_original_hash == to_hash)
165
- sum
166
- else
167
- checksum!
168
- end
169
- end
170
-
171
- def database
172
- model.database
173
- end
174
-
175
- # Override the default #uri method for one that accepts
176
- # the current database.
177
- # This is used by the caching code.
178
- def uri(db = database)
179
- "#{db.root}/#{self['_id']}"
180
- end
181
-
182
-
183
- ######## VIEW HANDLING ########
184
-
185
- # Create a new view object.
186
- # This overrides the normal CouchRest Design view method
187
- def view(name, opts = {})
188
- CouchRest::Model::Designs::View.new(self, model, opts, name)
189
- end
190
-
191
- # Helper method to provide a list of all the views
192
- def view_names
193
- self['views'].keys
194
- end
195
-
196
- def has_view?(name)
197
- view_names.include?(name.to_s)
198
- end
199
-
200
- # Add the specified view to the design doc the definition was made in
201
- # and create quick access methods in the model.
202
- def create_view(name, opts = {})
203
- View.define_and_create(self, name, opts)
204
- end
205
-
206
- ######## FILTER HANDLING ########
207
-
208
- def create_filter(name, function)
209
- filters = (self['filters'] ||= {})
210
- filters[name.to_s] = function
211
- end
212
-
213
- protected
214
-
215
- def load_from_database(db = database, id = nil)
216
- id ||= self['_id']
217
- db.get(id)
218
- rescue RestClient::ResourceNotFound
219
- nil
220
- end
221
-
222
- # Calculate and update the checksum of the Design document.
223
- # Used for ensuring the latest version has been sent to the database.
224
- #
225
- # This will generate an flatterned, ordered array of all the elements of the
226
- # design document, convert to string then generate an MD5 Hash. This should
227
- # result in a consisitent Hash accross all platforms.
228
- #
229
- def checksum!
230
- # Get a deep copy of hash to compare with
231
- @_original_hash = Marshal.load(Marshal.dump(to_hash))
232
- # create a copy of basic elements
233
- base = self.dup
234
- base.delete('_id')
235
- base.delete('_rev')
236
- base.delete('couchrest-hash')
237
- result = nil
238
- flatten =
239
- lambda {|r|
240
- (recurse = lambda {|v|
241
- if v.is_a?(Hash) || v.is_a?(CouchRest::Document)
242
- v.to_a.map{|v| recurse.call(v)}.flatten
243
- elsif v.is_a?(Array)
244
- v.flatten.map{|v| recurse.call(v)}
245
- else
246
- v.to_s
247
- end
248
- }).call(r)
249
- }
250
- self['couchrest-hash'] = Digest::MD5.hexdigest(flatten.call(base).sort.join(''))
251
- end
252
-
253
- def cache
254
- Thread.current[:couchrest_design_cache] ||= {}
255
- end
256
- def cache_checksum(db)
257
- cache[uri(db)]
258
- end
259
- def set_cache_checksum(db, checksum)
260
- cache[uri(db)] = checksum
261
- end
262
-
263
- def apply_defaults
264
- merge!(
265
- "language" => "javascript",
266
- "views" => { }
267
- )
268
- end
269
-
270
-
271
- class << self
272
-
273
- def method_name(prefix = nil)
274
- (prefix ? "#{prefix}_" : '') + 'design_doc'
275
- end
276
-
277
- end
278
-
279
- end
280
- end
281
- end
282
- end
283
-
284
-
@@ -1,92 +0,0 @@
1
- module CouchRest
2
- module Model
3
-
4
- # Handle CouchDB migrations.
5
- #
6
- # Actual migrations are handled by the Design document, this serves as a utility
7
- # to find all the CouchRest Model submodels and perform the migration on them.
8
- #
9
- # Also contains some more advanced support for handling proxied models.
10
- #
11
- # Examples of usage:
12
- #
13
- # # Ensure all models have been loaded (only Rails)
14
- # CouchRest::Model::Migrate.load_all_models
15
- #
16
- # # Migrate all regular models (not proxied)
17
- # CouchRest::Model::Migrate.all_models
18
- #
19
- # # Migrate all models and submodels of proxies
20
- # CouchRest::Model::Migrate.all_models_and_proxies
21
- #
22
- class Migrate
23
-
24
- def self.all_models
25
- callbacks = migrate_each_model(find_models)
26
- cleanup(callbacks)
27
- end
28
-
29
- def self.all_models_and_proxies
30
- callbacks = migrate_each_model(find_models)
31
- callbacks += migrate_each_proxying_model(find_proxying_models)
32
- cleanup(callbacks)
33
- end
34
-
35
- def self.load_all_models
36
- # Make a reasonable effort to load all models
37
- return unless defined?(Rails)
38
- Dir[Rails.root + 'app/models/**/*.rb'].each do |path|
39
- require path
40
- end
41
- end
42
-
43
- def self.find_models
44
- CouchRest::Model::Base.subclasses.reject{|m| m.proxy_owner_method.present?}
45
- end
46
-
47
- def self.find_proxying_models
48
- CouchRest::Model::Base.subclasses.reject{|m| m.proxy_database_method.blank?}
49
- end
50
-
51
- def self.migrate_each_model(models, db = nil)
52
- callbacks = [ ]
53
- models.each do |model|
54
- model.design_docs.each do |design|
55
- callbacks << migrate_design(model, design, db)
56
- end
57
- end
58
- callbacks
59
- end
60
-
61
- def self.migrate_each_proxying_model(models)
62
- callbacks = [ ]
63
- models.each do |model|
64
- submodels = model.proxied_model_names.map{|n| n.constantize}
65
- model.all.each do |base|
66
- puts "Finding proxied models for #{model}: \"#{base.send(model.proxy_database_method)}\""
67
- callbacks += migrate_each_model(submodels, base.proxy_database)
68
- end
69
- end
70
- callbacks
71
- end
72
-
73
- def self.migrate_design(model, design, db = nil)
74
- print "Migrating #{model.to_s}##{design.method_name}... "
75
- callback = design.migrate(db) do |result|
76
- puts "#{result.to_s.gsub(/_/, ' ')}"
77
- end
78
- # Return the callback hash if there is one
79
- callback ? {:design => design, :proc => callback, :db => db || model.database} : nil
80
- end
81
-
82
- def self.cleanup(methods)
83
- methods.compact.each do |cb|
84
- name = "/#{cb[:db].name}/#{cb[:design]['_id']}"
85
- puts "Activating new design: #{name}"
86
- cb[:proc].call
87
- end
88
- end
89
-
90
- end
91
- end
92
- end