couchrest_model 2.0.0.beta2 → 2.0.0

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