couchrest_model 1.1.0.beta2 → 1.1.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +2 -1
  2. data/README.md +50 -3
  3. data/VERSION +1 -1
  4. data/benchmarks/dirty.rb +118 -0
  5. data/couchrest_model.gemspec +1 -0
  6. data/history.txt +12 -0
  7. data/lib/couchrest/model/base.rb +15 -23
  8. data/lib/couchrest/model/casted_array.rb +26 -1
  9. data/lib/couchrest/model/casted_by.rb +23 -0
  10. data/lib/couchrest/model/casted_hash.rb +76 -0
  11. data/lib/couchrest/model/casted_model.rb +9 -6
  12. data/lib/couchrest/model/collection.rb +10 -1
  13. data/lib/couchrest/model/configuration.rb +4 -4
  14. data/lib/couchrest/model/design_doc.rb +65 -71
  15. data/lib/couchrest/model/designs/view.rb +26 -19
  16. data/lib/couchrest/model/designs.rb +0 -2
  17. data/lib/couchrest/model/dirty.rb +49 -0
  18. data/lib/couchrest/model/extended_attachments.rb +7 -1
  19. data/lib/couchrest/model/persistence.rb +15 -4
  20. data/lib/couchrest/model/properties.rb +50 -9
  21. data/lib/couchrest/model/property.rb +6 -2
  22. data/lib/couchrest/model/proxyable.rb +13 -19
  23. data/lib/couchrest/model/support/couchrest_design.rb +33 -0
  24. data/lib/couchrest/model/views.rb +4 -18
  25. data/lib/couchrest_model.rb +8 -3
  26. data/spec/couchrest/base_spec.rb +1 -28
  27. data/spec/couchrest/casted_model_spec.rb +1 -1
  28. data/spec/couchrest/collection_spec.rb +0 -1
  29. data/spec/couchrest/design_doc_spec.rb +211 -0
  30. data/spec/couchrest/designs/view_spec.rb +41 -17
  31. data/spec/couchrest/designs_spec.rb +0 -5
  32. data/spec/couchrest/dirty_spec.rb +355 -0
  33. data/spec/couchrest/property_spec.rb +5 -2
  34. data/spec/couchrest/proxyable_spec.rb +0 -11
  35. data/spec/couchrest/subclass_spec.rb +6 -16
  36. data/spec/couchrest/view_spec.rb +6 -50
  37. data/spec/fixtures/base.rb +1 -1
  38. data/spec/fixtures/more/card.rb +2 -2
  39. data/spec/spec_helper.rb +2 -0
  40. metadata +9 -4
  41. data/Gemfile.lock +0 -76
  42. data/lib/couchrest/model/support/couchrest.rb +0 -19
data/.gitignore CHANGED
@@ -6,4 +6,5 @@ pkg
6
6
  .bundle
7
7
  couchdb.std*
8
8
  *.*~
9
-
9
+ Gemfile.lock
10
+ spec.out
data/README.md CHANGED
@@ -11,6 +11,14 @@ it is not possible to load ActiveModel into programs that do not use ActiveSuppo
11
11
 
12
12
  CouchRest Model is only properly tested on CouchDB version 1.0 or newer.
13
13
 
14
+ *WARNING:* As of April 2011 and the release of version 1.1.0, the default model type key is 'model' instead of 'couchrest-type'. Simply updating your project will not work unless you migrate your data or set the configuration option in your initializers:
15
+
16
+ CouchRest::Model::Base.configure do |config|
17
+ config.model_type_key = 'couchrest-type'
18
+ end
19
+
20
+ This is because CouchRest Model's are not couchrest specific and may be used in any other system such as a Javascript library, the model type should reflect this.
21
+
14
22
  ## Install
15
23
 
16
24
  ### Gem
@@ -57,6 +65,7 @@ Try some of these gems that add extra funcionality to couchrest_model:
57
65
  * [couch_photo](http://github.com/moonmaster9000/couch_photo) - attach images to documents with variations (Matt Parker)
58
66
  * [copycouch](http://github.com/moonmaster9000/copycouch) - single document replication on documents (Matt Parker)
59
67
  * [recloner](https://github.com/moonmaster9000/recloner) - clone documents easily (Matt Parker)
68
+ * [couchrest_localised_properties](https://github.com/samlown/couchrest_localised_properties) - Transparent support for localised properties (Sam Lown)
60
69
 
61
70
  If you have an extension that you'd us to add to this list, please get in touch!
62
71
 
@@ -148,7 +157,7 @@ Boolean or TrueClass types will create a getter with question mark at the end:
148
157
 
149
158
  @cat.awake? # true
150
159
 
151
- Adding the +:default+ option will ensure the attribute always has a value.
160
+ Adding the `:default` option will ensure the attribute always has a value.
152
161
 
153
162
  A read-only property will only have a getter method, and its value is set when the document
154
163
  is read from the database. You can however update a read-only attribute using the `write_attribute` method:
@@ -378,6 +387,43 @@ Use pagination as follows:
378
387
  # In your view, with the kaminari gem loaded:
379
388
  paginate @posts
380
389
 
390
+ ### Design Documents and Views
391
+
392
+ Views must be defined in a Design Document for CouchDB to be able to perform searches. Each model therefore must have its own Design Document. Deciding when to update the model's design doc is a difficult issue, as in production you don't want to be constantly checking for updates and in development maximum flexability is important. CouchRest Model solves this issue by providing the `auto_update_design_doc` configuration option and is true by default.
393
+
394
+ Each time a view or other design method is requested a quick GET for the design will be sent to ensure it is up to date with the latest changes. Results are cached in the current thread for the complete design document's URL, including the database, to try and limit requests. This should be fine for most projects, but dealing with multiple sub-databases may require a different strategy.
395
+
396
+ Setting the option to false will require a manual update of each model's design doc whenever you know a change has happened. This will be useful in cases when you do not want CouchRest Model to interfere with the views already store in the CouchRest database, or you'd like to deploy your own update strategy. Here's an example of a module that will update all submodules:
397
+
398
+ module CouchRestMigration
399
+ def self.update_design_docs
400
+ CouchRest::Model::Base.subclasses.each{|klass| klass.save_design_doc! if klass.respond_to?(:save_design_doc!)}
401
+ end
402
+ end
403
+
404
+ # Running this from your applications initializers would be a good idea,
405
+ # for example in Rail's application.rb or environments/production.rb:
406
+ config.after_initialize do
407
+ CouchRestMigration.update_design_docs
408
+ end
409
+
410
+ If you're dealing with multiple databases, using proxied models, or databases that are created on-the-fly, a more sophisticated approach might be required:
411
+
412
+ module CouchRestMigration
413
+ def self.update_all_design_docs
414
+ update_design_docs(COUCHREST_DATABASE)
415
+ Company.all.each do |company|
416
+ update_design_docs(company.proxy_database)
417
+ end
418
+ end
419
+ def self.update_design_docs(db)
420
+ CouchRest::Model::Base.subclasses.each{|klass| klass.save_design_doc!(db) if klass.respond_to?(:save_design_doc!}
421
+ end
422
+ end
423
+
424
+ # Command to run after a capistrano migration:
425
+ $ rails runner "CouchRestMigratin.update_all_design_docs"
426
+
381
427
 
382
428
  ## Assocations
383
429
 
@@ -546,7 +592,7 @@ such as validating for uniqueness and associations.
546
592
 
547
593
  CouchRest Model supports a few configuration options. These can be set either for the whole Model code
548
594
  base or for a specific model of your chosing. To configure globally, provide something similar to the
549
- following in your projects loading code:
595
+ following in your projects initializers or environments:
550
596
 
551
597
  CouchRest::Model::Base.configure do |config|
552
598
  config.mass_assign_any_attribute = true
@@ -562,7 +608,8 @@ To set for a specific model:
562
608
  Options currently avilable are:
563
609
 
564
610
  * `mass_assign_any_attribute` - false by default, when true any attribute may be updated via the update_attributes or attributes= methods.
565
- * `model_type_key` - 'couchrest-type' by default, is the name of property that holds the class name of each CouchRest Model.
611
+ * `model_type_key` - 'model' by default, is the name of property that holds the class name of each CouchRest Model.
612
+ * `auto_update_design_doc` - true by default, every time a view is requested and this option is true, a quick check will be performed to ensure the model's design document is up to date. When disabled, you're design documents will never be updated automatically and you'll need to perform updates manually. Results are cached on a per-database and per-design basis to help lower the number of requests. See the View section for more details.
566
613
 
567
614
 
568
615
  ## Notable Issues
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0.beta2
1
+ 1.1.0.beta3
@@ -0,0 +1,118 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'benchmark'
5
+
6
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
7
+ require 'couchrest_model'
8
+
9
+ class BenchmarkCasted < Hash
10
+ include CouchRest::Model::CastedModel
11
+
12
+ property :name
13
+ end
14
+
15
+ class BenchmarkModel < CouchRest::Model::Base
16
+ use_database CouchRest.database!(ENV['BENCHMARK_DB'] || "http://localhost:5984/test")
17
+
18
+ property :string, String
19
+ property :number, Integer
20
+ property :casted, BenchmarkCasted
21
+ property :casted_list, [BenchmarkCasted]
22
+ end
23
+
24
+ # set dirty configuration, return previous configuration setting
25
+ def set_dirty(value)
26
+ orig = nil
27
+ CouchRest::Model::Base.configure do |config|
28
+ orig = config.use_dirty
29
+ config.use_dirty = value
30
+ end
31
+ BenchmarkModel.instance_eval do
32
+ self.use_dirty = value
33
+ end
34
+ orig
35
+ end
36
+
37
+ def supports_dirty?
38
+ CouchRest::Model::Base.respond_to?(:use_dirty)
39
+ end
40
+
41
+ def run_benchmark
42
+ n = 50000 # property operation count
43
+ db_n = 1000 # database operation count
44
+ b = BenchmarkModel.new
45
+
46
+ Benchmark.bm(30) do |x|
47
+
48
+ # property assigning
49
+
50
+ x.report("assign string:") do
51
+ n.times { b.string = "test" }
52
+ end
53
+
54
+ next if ENV["BENCHMARK_STRING"]
55
+
56
+ x.report("assign integer:") do
57
+ n.times { b.number = 1 }
58
+ end
59
+
60
+ x.report("assign casted hash:") do
61
+ n.times { b.casted = { 'name' => 'test' } }
62
+ end
63
+
64
+ x.report("assign casted hash list:") do
65
+ n.times { b.casted_list = [{ 'name' => 'test' }] }
66
+ end
67
+
68
+ # property reading
69
+
70
+ x.report("read string") do
71
+ n.times { b.string }
72
+ end
73
+
74
+ x.report("read integer") do
75
+ n.times { b.number }
76
+ end
77
+
78
+ x.report("read casted hash") do
79
+ n.times { b.casted }
80
+ end
81
+
82
+ x.report("read casted hash list") do
83
+ n.times { b.casted_list }
84
+ end
85
+
86
+ if ENV['BENCHMARK_DB']
87
+ # db writing
88
+ x.report("write changed record to db") do
89
+ db_n.times { |i| b.string = "test#{i}"; b.save }
90
+ end
91
+
92
+ x.report("write unchanged record to db") do
93
+ db_n.times { b.save }
94
+ end
95
+
96
+ # db reading
97
+ x.report("read record from db") do
98
+ db_n.times { BenchmarkModel.find(b.id) }
99
+ end
100
+
101
+ end
102
+
103
+ end
104
+ end
105
+
106
+ begin
107
+ if supports_dirty?
108
+ if !ENV['BENCHMARK_DIRTY_OFF']
109
+ set_dirty(true)
110
+ puts "with use_dirty true"
111
+ run_benchmark
112
+ end
113
+ set_dirty(false)
114
+ puts "\nwith use_dirty false"
115
+ end
116
+
117
+ run_benchmark
118
+ end
@@ -30,5 +30,6 @@ Gem::Specification.new do |s|
30
30
  s.add_dependency(%q<railties>, "~> 3.0.0")
31
31
  s.add_development_dependency(%q<rspec>, ">= 2.0.0")
32
32
  s.add_development_dependency(%q<rack-test>, ">= 0.5.7")
33
+ # s.add_development_dependency("jruby-openssl", ">= 0.7.3")
33
34
  end
34
35
 
data/history.txt CHANGED
@@ -1,3 +1,15 @@
1
+ == 1.1.0.beta3
2
+
3
+ * Major changes:
4
+ * Fast Dirty Tracking! Many thanks to @sobakasu (Andrew Williams)
5
+ * Default CouchRest Model type field now set to 'model' instead of 'couchrest-type'.
6
+
7
+ * Minor enhancements:
8
+ * Adding "couchrest-hash" to Design Docs with aim to improve view update handling.
9
+ * Major changes to the way design document updates are handled internally.
10
+ * Added "auto_update_design_doc" configuration option.
11
+ * Using #descending on View object will automatically swap startkey with endkey.
12
+
1
13
  == 1.1.0.beta2
2
14
 
3
15
  * Minor enhancements:
@@ -18,14 +18,17 @@ module CouchRest
18
18
  include CouchRest::Model::Associations
19
19
  include CouchRest::Model::Validations
20
20
  include CouchRest::Model::Designs
21
+ include CouchRest::Model::Dirty
22
+ include CouchRest::Model::CastedBy
21
23
 
22
24
  def self.subclasses
23
25
  @subclasses ||= []
24
26
  end
25
-
27
+
26
28
  def self.inherited(subklass)
27
29
  super
28
30
  subklass.send(:include, CouchRest::Model::Properties)
31
+
29
32
  subklass.class_eval <<-EOS, __FILE__, __LINE__ + 1
30
33
  def self.inherited(subklass)
31
34
  super
@@ -36,16 +39,12 @@ module CouchRest
36
39
  EOS
37
40
  subclasses << subklass
38
41
  end
39
-
40
- # Accessors
41
- attr_accessor :casted_by
42
-
43
42
 
44
43
  # Instantiate a new CouchRest::Model::Base by preparing all properties
45
44
  # using the provided document hash.
46
45
  #
47
46
  # Options supported:
48
- #
47
+ #
49
48
  # * :directly_set_attributes: true when data comes directly from database
50
49
  # * :database: provide an alternative database
51
50
  #
@@ -59,8 +58,8 @@ module CouchRest
59
58
  end
60
59
  after_initialize if respond_to?(:after_initialize)
61
60
  end
62
-
63
-
61
+
62
+
64
63
  # Temp solution to make the view_by methods available
65
64
  def self.method_missing(m, *args, &block)
66
65
  if has_view?(m)
@@ -74,24 +73,17 @@ module CouchRest
74
73
  end
75
74
  super
76
75
  end
77
-
76
+
78
77
  ### instance methods
79
-
80
- # Gets a reference to the actual document in the DB
81
- # Calls up to the next document if there is one,
82
- # Otherwise we're at the top and we return self
83
- def base_doc
84
- return self if base_doc?
85
- @casted_by.base_doc
86
- end
87
-
78
+
88
79
  # Checks if we're the top document
80
+ # (overrides base_doc? in casted_by.rb)
89
81
  def base_doc?
90
82
  !@casted_by
91
83
  end
92
-
84
+
93
85
  ## Compatibility with ActiveSupport and older frameworks
94
-
86
+
95
87
  # Hack so that CouchRest::Document, which descends from Hash,
96
88
  # doesn't appear to Rails routing as a Hash of options
97
89
  def is_a?(klass)
@@ -103,14 +95,14 @@ module CouchRest
103
95
  def persisted?
104
96
  !new?
105
97
  end
106
-
98
+
107
99
  def to_key
108
- new? ? nil : [id]
100
+ new? ? nil : [id]
109
101
  end
110
102
 
111
103
  alias :to_param :id
112
104
  alias :new_record? :new?
113
105
  alias :new_document? :new?
114
- end
106
+ end
115
107
  end
116
108
  end
@@ -5,6 +5,7 @@
5
5
 
6
6
  module CouchRest::Model
7
7
  class CastedArray < Array
8
+ include CouchRest::Model::Dirty
8
9
  attr_accessor :casted_by
9
10
  attr_accessor :property
10
11
 
@@ -14,15 +15,39 @@ module CouchRest::Model
14
15
  end
15
16
 
16
17
  def << obj
18
+ couchrest_parent_will_change! if use_dirty?
17
19
  super(instantiate_and_cast(obj))
18
20
  end
19
21
 
20
22
  def push(obj)
23
+ couchrest_parent_will_change! if use_dirty?
24
+ super(instantiate_and_cast(obj))
25
+ end
26
+
27
+ def pop
28
+ couchrest_parent_will_change! if use_dirty? && self.length > 0
29
+ super
30
+ end
31
+
32
+ def shift
33
+ couchrest_parent_will_change! if use_dirty? && self.length > 0
34
+ super
35
+ end
36
+
37
+ def unshift(obj)
38
+ couchrest_parent_will_change! if use_dirty?
21
39
  super(instantiate_and_cast(obj))
22
40
  end
23
41
 
24
42
  def []= index, obj
25
- super(index, instantiate_and_cast(obj))
43
+ value = instantiate_and_cast(obj)
44
+ couchrest_parent_will_change! if use_dirty? && value != self[index]
45
+ super(index, value)
46
+ end
47
+
48
+ def clear
49
+ couchrest_parent_will_change! if use_dirty? && self.length > 0
50
+ super
26
51
  end
27
52
 
28
53
  protected
@@ -0,0 +1,23 @@
1
+
2
+ module CouchRest::Model
3
+ module CastedBy
4
+ extend ActiveSupport::Concern
5
+ included do
6
+ self.send(:attr_accessor, :casted_by)
7
+ end
8
+
9
+ # Gets a reference to the actual document in the DB
10
+ # Calls up to the next document if there is one,
11
+ # Otherwise we're at the top and we return self
12
+ def base_doc
13
+ return self if base_doc?
14
+ @casted_by ? @casted_by.base_doc : nil
15
+ end
16
+
17
+ # Checks if we're the top document
18
+ def base_doc?
19
+ false
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,76 @@
1
+ #
2
+ # Wrapper around Hash so that the casted_by attribute is set.
3
+
4
+ module CouchRest::Model
5
+ class CastedHash < Hash
6
+ include CouchRest::Model::Dirty
7
+ attr_accessor :casted_by
8
+
9
+ # needed for dirty
10
+ def attributes
11
+ self
12
+ end
13
+
14
+ def []= key, obj
15
+ couchrest_attribute_will_change!(key) if use_dirty? && obj != self[key]
16
+ super(key, obj)
17
+ end
18
+
19
+ def delete(key)
20
+ couchrest_attribute_will_change!(key) if use_dirty? && include?(key)
21
+ super(key)
22
+ end
23
+
24
+ def merge!(other_hash)
25
+ if use_dirty? && other_hash && other_hash.kind_of?(Hash)
26
+ other_hash.keys.each do |key|
27
+ if self[key] != other_hash[key] || !include?(key)
28
+ couchrest_attribute_will_change!(key)
29
+ end
30
+ end
31
+ end
32
+ super(other_hash)
33
+ end
34
+
35
+ def replace(other_hash)
36
+ if use_dirty? && other_hash && other_hash.kind_of?(Hash)
37
+ # new keys and changed keys
38
+ other_hash.keys.each do |key|
39
+ if self[key] != other_hash[key] || !include?(key)
40
+ couchrest_attribute_will_change!(key)
41
+ end
42
+ end
43
+ # old keys
44
+ old_keys = self.keys.reject { |key| other_hash.include?(key) }
45
+ old_keys.each { |key| couchrest_attribute_will_change!(key) }
46
+ end
47
+
48
+ super(other_hash)
49
+ end
50
+
51
+ def clear
52
+ self.keys.each { |key| couchrest_attribute_will_change!(key) } if use_dirty?
53
+ super
54
+ end
55
+
56
+ def delete_if
57
+ if use_dirty? && block_given?
58
+ self.keys.each do |key|
59
+ couchrest_attribute_will_change!(key) if yield key, self[key]
60
+ end
61
+ end
62
+ super
63
+ end
64
+
65
+ # ruby 1.9
66
+ def keep_if
67
+ if use_dirty? && block_given?
68
+ self.keys.each do |key|
69
+ couchrest_attribute_will_change!(key) if !yield key, self[key]
70
+ end
71
+ end
72
+ super
73
+ end
74
+
75
+ end
76
+ end
@@ -10,30 +10,32 @@ module CouchRest::Model
10
10
  include CouchRest::Model::PropertyProtection
11
11
  include CouchRest::Model::Associations
12
12
  include CouchRest::Model::Validations
13
- attr_accessor :casted_by
13
+ include CouchRest::Model::Dirty
14
+ # attr_accessor :casted_by
14
15
  end
15
-
16
+
16
17
  def initialize(keys = {})
17
18
  raise StandardError unless self.is_a? Hash
18
19
  prepare_all_attributes(keys)
19
20
  super()
20
21
  end
21
-
22
+
22
23
  def []= key, value
24
+ couchrest_attribute_will_change!(key) if self[key] != value
23
25
  super(key.to_s, value)
24
26
  end
25
-
27
+
26
28
  def [] key
27
29
  super(key.to_s)
28
30
  end
29
-
31
+
30
32
  # Gets a reference to the top level extended
31
33
  # document that a model is saved inside of
32
34
  def base_doc
33
35
  return nil unless @casted_by
34
36
  @casted_by.base_doc
35
37
  end
36
-
38
+
37
39
  # False if the casted model has already
38
40
  # been saved in the containing document
39
41
  def new?
@@ -64,5 +66,6 @@ module CouchRest::Model
64
66
  end
65
67
  end
66
68
  alias :attributes= :update_attributes_without_saving
69
+
67
70
  end
68
71
  end
@@ -1,7 +1,13 @@
1
1
  module CouchRest
2
2
  module Model
3
+ # Warning! The Collection module is seriously depricated.
4
+ # Use the new Design Views instead, as this code copies many other parts
5
+ # of CouchRest Model.
6
+ #
7
+ # Expect this to be removed soon.
8
+ #
3
9
  module Collection
4
-
10
+
5
11
  def self.included(base)
6
12
  base.extend(ClassMethods)
7
13
  end
@@ -131,6 +137,9 @@ module CouchRest
131
137
  else
132
138
  @view_name = "#{design_doc}/#{view_name}"
133
139
  end
140
+
141
+ # Save the design doc, ready for use
142
+ @container_class.save_design_doc(@database)
134
143
  end
135
144
 
136
145
  # See Collection.paginate
@@ -10,10 +10,12 @@ module CouchRest
10
10
  included do
11
11
  add_config :model_type_key
12
12
  add_config :mass_assign_any_attribute
13
-
13
+ add_config :auto_update_design_doc
14
+
14
15
  configure do |config|
15
- config.model_type_key = 'couchrest-type' # 'model'?
16
+ config.model_type_key = 'model' # was 'couchrest-type'
16
17
  config.mass_assign_any_attribute = false
18
+ config.auto_update_design_doc = true
17
19
  end
18
20
  end
19
21
 
@@ -47,5 +49,3 @@ module CouchRest
47
49
  end
48
50
  end
49
51
  end
50
-
51
-