couch_potato 0.2.24 → 0.2.25

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.
data/CHANGES.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## Changes
2
2
 
3
+ ### 0.2.25
4
+ * automatic view updates: when you change the definition of a view couch potato will now update the design document in the database (langalex)
5
+ * support for properties of type Date, better support for Time (langalex)
6
+ * support for default reduce count methods in custom views (jweiss)
7
+
3
8
  ### 0.2.24
4
9
  * persistent instances can now be marked as dirty with #is_dirty (langalex)
5
10
 
data/README.md CHANGED
@@ -90,7 +90,7 @@ Properties can be typed:
90
90
  end
91
91
 
92
92
  In this case Address also implements CouchPotato::Persistence which means its JSON representation will be added to the user document.
93
- Couch Potato also has support for the basic types (right now Fixnum and :boolean are supported):
93
+ Couch Potato also has support for the basic types (right now Fixnum, Date, Time and :boolean are supported):
94
94
 
95
95
  class User
96
96
  include CouchPotato::Persistence
data/VERSION.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
2
  :major: 0
3
3
  :minor: 2
4
- :patch: 24
4
+ :patch: 25
5
5
  :build:
data/lib/couch_potato.rb CHANGED
@@ -40,4 +40,4 @@ require File.dirname(__FILE__) + '/core_ext/string'
40
40
  require File.dirname(__FILE__) + '/core_ext/symbol'
41
41
  require File.dirname(__FILE__) + '/couch_potato/validation'
42
42
  require File.dirname(__FILE__) + '/couch_potato/persistence'
43
-
43
+ require File.dirname(__FILE__) + '/couch_potato/railtie' if defined?(Rails)
@@ -66,17 +66,18 @@ module CouchPotato
66
66
  instance
67
67
  end
68
68
 
69
- # Declare a property on a model class. properties are not typed by default. You can use any of the basic types by JSON (String, Integer, Fixnum, Array, Hash). If you want a property to be of a custom class you have to define it using the :class option.
69
+ # Declare a property on a model class. Properties are not typed by default.
70
+ # You can store anything in a property that can be serialized into JSON.
71
+ # If you want a property to be of a custom class you have to define it using the :type option.
70
72
  #
71
73
  # example:
72
74
  # class Book
73
75
  # property :title
74
76
  # property :year
75
- # property :publisher, :class => Publisher
77
+ # property :publisher, :type => Publisher
76
78
  # end
77
79
  def property(name, options = {})
78
- clazz = options.delete(:class)
79
- properties << (clazz || SimpleProperty).new(self, name, options)
80
+ properties << SimpleProperty.new(self, name, options)
80
81
  end
81
82
 
82
83
  end
@@ -13,21 +13,13 @@ module CouchPotato
13
13
 
14
14
  def build(object, json)
15
15
  value = json[name.to_s].nil? ? json[name.to_sym] : json[name.to_s]
16
- object.send "#{name}=", @type_caster.cast(value, type)
16
+ object.send "#{name}=", value
17
17
  end
18
18
 
19
19
  def dirty?(object)
20
20
  object.send("#{name}_changed?")
21
21
  end
22
22
 
23
- def save(object)
24
-
25
- end
26
-
27
- def destroy(object)
28
-
29
- end
30
-
31
23
  def serialize(json, object)
32
24
  json[name] = object.send name
33
25
  end
@@ -26,7 +26,7 @@ module CouchPotato
26
26
  if type == Fixnum
27
27
  value.to_s.scan(/\d/).join.to_i unless value.blank?
28
28
  else
29
- type.json_create value
29
+ type.json_create value unless value.blank?
30
30
  end
31
31
  else
32
32
  value
@@ -0,0 +1,21 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../../rails/reload_classes')
2
+
3
+ module CouchPotato
4
+
5
+ def self.rails_init
6
+ CouchPotato::Config.database_name = YAML::load(File.read(Rails.root.join('config/couchdb.yml')))[Rails.env]
7
+ end
8
+
9
+ if Rails.version >= '3'
10
+ class Railtie < Rails::Railtie
11
+ railtie_name :couch_potato
12
+
13
+ config.after_initialize do |app|
14
+ CouchPotato.rails_init
15
+ end
16
+ end
17
+ else
18
+ rails_init
19
+ end
20
+
21
+ end
@@ -18,14 +18,25 @@ module CouchPotato
18
18
  end
19
19
 
20
20
  def process_results(results)
21
- results['rows'].map do |row|
22
- if row['doc'].instance_of?(klass)
23
- row['doc']
24
- else
25
- klass.json_create row['doc'] || row['value'].merge(:_id => row['id'] || row['key'])
21
+ if count?
22
+ results['rows'].first.try(:[], 'value') || 0
23
+ else
24
+ results['rows'].map do |row|
25
+ if row['doc'].instance_of?(klass)
26
+ row['doc']
27
+ else
28
+ klass.json_create row['doc'] || row['value'].merge(:_id => row['id'] || row['key'])
29
+ end
26
30
  end
27
31
  end
28
32
  end
33
+
34
+ private
35
+
36
+ def count?
37
+ view_parameters[:reduce]
38
+ end
39
+
29
40
  end
30
41
  end
31
42
  end
@@ -11,24 +11,45 @@ module CouchPotato
11
11
  end
12
12
 
13
13
  def query_view!(parameters = {})
14
+ update_view unless view_has_been_updated?
14
15
  begin
15
16
  query_view parameters
16
17
  rescue RestClient::ResourceNotFound# => e
17
- create_view
18
+ update_view
18
19
  retry
19
20
  end
20
21
  end
21
22
 
22
23
  private
23
24
 
24
- def create_view
25
+ def update_view
25
26
  design_doc = @database.get "_design/#{@design_document_name}" rescue nil
26
- design_doc ||= {'views' => {}, "_id" => "_design/#{@design_document_name}", "language" => "javascript"}
27
- design_doc['views'][@view_name.to_s] = {
28
- 'map' => @map_function,
29
- 'reduce' => @reduce_function
30
- }
31
- @database.save_doc(design_doc)
27
+ original_views = design_doc && design_doc['views'].dup
28
+ view_updated unless design_doc.nil?
29
+ design_doc ||= empty_design_document
30
+ design_doc['views'][@view_name.to_s] = view_functions
31
+ @database.save_doc(design_doc) unless original_views == design_doc['views']
32
+ end
33
+
34
+ def view_functions
35
+ {'map' => @map_function, 'reduce' => @reduce_function}
36
+ end
37
+
38
+ def empty_design_document
39
+ {'views' => {}, "_id" => "_design/#{@design_document_name}", "language" => "javascript"}
40
+ end
41
+
42
+ def view_has_been_updated?
43
+ updated_views[[@design_document_name, @view_name]]
44
+ end
45
+
46
+ def view_updated
47
+ updated_views[[@design_document_name, @view_name]] = true
48
+ end
49
+
50
+ def updated_views
51
+ @@updated_views ||= {}
52
+ @@updated_views
32
53
  end
33
54
 
34
55
  def query_view(parameters)
data/rails/init.rb CHANGED
@@ -1,8 +1,4 @@
1
1
  # this is for rails only
2
2
 
3
3
  require File.expand_path(File.dirname(__FILE__) + '/../lib/couch_potato')
4
- require File.expand_path(File.dirname(__FILE__) + '/reload_classes')
5
-
6
- CouchPotato::Config.database_name = YAML::load(File.read(Rails.root.to_s + '/config/couchdb.yml'))[RAILS_ENV]
7
-
8
- RAILS_DEFAULT_LOGGER.info "** couch_potato: initialized from #{__FILE__}"
4
+ Rails.logger.info "** couch_potato: initialized from #{__FILE__}"
@@ -13,6 +13,7 @@ class Build
13
13
  view :custom_timeline, :map => "function(doc) { emit(doc._id, {state: 'custom_' + doc.state}); }", :type => :custom
14
14
  view :custom_timeline_returns_docs, :map => "function(doc) { emit(doc._id, null); }", :include_docs => true, :type => :custom
15
15
  view :custom_with_reduce, :map => "function(doc) {if(doc.foreign_key) {emit(doc.foreign_key, 1);} else {emit(doc.id, 1)}}", :reduce => "function(key, values) {return({\"count\": sum(values)});}", :group => true, :type => :custom
16
+ view :custom_count_with_reduce, :map => "function(doc) {if(doc.foreign_key) {emit(doc.foreign_key, 1);} else {emit(doc.id, 1)}}", :reduce => "function(key, values) {return(sum(values));}", :group => true, :type => :custom
16
17
  view :raw, :type => :raw, :map => "function(doc) {emit(doc._id, doc.state)}"
17
18
  view :filtered_raw, :type => :raw, :map => "function(doc) {emit(doc._id, doc.state)}", :results_filter => lambda{|res| res['rows'].map{|row| row['value']}}
18
19
  view :with_view_options, :group => true, :key => :time
@@ -130,7 +131,14 @@ describe 'view' do
130
131
  doc = CouchPotato.couchrest_database.save_doc({})
131
132
  CouchPotato.couchrest_database.save_doc({:foreign_key => doc['id']})
132
133
  CouchPotato.database.view(Build.custom_with_reduce).map(&:_id).should == [doc['id']]
133
-
134
+ end
135
+
136
+ describe "when the additional reduce function is a typical count" do
137
+ it "should parse the reduce count" do
138
+ doc = CouchPotato.couchrest_database.save_doc({})
139
+ CouchPotato.couchrest_database.save_doc({:foreign_key => doc['id']})
140
+ CouchPotato.database.view(Build.custom_count_with_reduce(:reduce => true)).should == 1
141
+ end
134
142
  end
135
143
  end
136
144
  end
@@ -6,6 +6,7 @@ class Watch
6
6
  include CouchPotato::Persistence
7
7
 
8
8
  property :time, :type => Time
9
+ property :date, :type => Date
9
10
  property :overwritten_read
10
11
  property :overwritten_write
11
12
 
@@ -98,13 +99,6 @@ describe 'properties' do
98
99
  ]
99
100
  it_should_persist something_very_complex
100
101
  end
101
-
102
- it "should persist a Time object" do
103
- w = Watch.new :time => Time.now
104
- CouchPotato.database.save_document! w
105
- w = CouchPotato.database.load_document w.id
106
- w.time.year.should == Time.now.year
107
- end
108
102
 
109
103
  it "should persist an object" do
110
104
  p = Person.new
@@ -142,6 +136,68 @@ describe 'properties' do
142
136
  p.ship_address.should be_false
143
137
  end
144
138
 
139
+ describe "time properties" do
140
+ it "should persist a Time as utc" do
141
+ time = Time.now
142
+ w = Watch.new :time => time
143
+ CouchPotato.database.save_document! w
144
+ w = CouchPotato.database.load_document w.id
145
+ w.time.to_s.should == time.utc.to_s
146
+ end
147
+
148
+ it "should parse a string and persist it as time" do
149
+ w = Watch.new :time => '2009-01-01 13:25 UTC'
150
+ CouchPotato.database.save_document! w
151
+ w = CouchPotato.database.load_document w.id
152
+ w.time.should be_a(Time)
153
+ end
154
+
155
+ it "should store nil" do
156
+ w = Watch.new :time => nil
157
+ CouchPotato.database.save_document! w
158
+ w = CouchPotato.database.load_document w.id
159
+ w.time.should be_nil
160
+ end
161
+
162
+ it "should store an empty string as nil" do
163
+ w = Watch.new :time => ''
164
+ CouchPotato.database.save_document! w
165
+ w = CouchPotato.database.load_document w.id
166
+ w.time.should be_nil
167
+ end
168
+ end
169
+
170
+ describe "date properties" do
171
+ it "should persist a date" do
172
+ date = Date.today
173
+ w = Watch.new :date => date
174
+ CouchPotato.database.save_document! w
175
+ w = CouchPotato.database.load_document w.id
176
+ w.date.should == date
177
+ end
178
+
179
+ it "should parse a string and persist it as a date" do
180
+ w = Watch.new :date => '2009-01-10'
181
+ CouchPotato.database.save_document! w
182
+ w = CouchPotato.database.load_document w.id
183
+ w.date.should == Date.parse('2009-01-10')
184
+ end
185
+
186
+ it "should store nil" do
187
+ w = Watch.new :date => nil
188
+ CouchPotato.database.save_document! w
189
+ w = CouchPotato.database.load_document w.id
190
+ w.date.should be_nil
191
+ end
192
+
193
+ it "should store an empty string as nil" do
194
+ w = Watch.new :date => ''
195
+ CouchPotato.database.save_document! w
196
+ w = CouchPotato.database.load_document w.id
197
+ w.date.should be_nil
198
+ end
199
+ end
200
+
145
201
  describe "boolean properties" do
146
202
  it "should persist '0' for false" do
147
203
  a = Address.new
data/spec/spec_helper.rb CHANGED
@@ -5,7 +5,13 @@ $:.unshift(File.dirname(__FILE__) + '/../lib')
5
5
 
6
6
  require 'couch_potato'
7
7
 
8
- CouchPotato::Config.database_name = 'couch_potato_test'
8
+ if ENV["RUN_CODE_RUN"]
9
+ CouchPotato::Config.database_name = 'http://langalex.couch.io/couch_potato_test'
10
+ else
11
+ CouchPotato::Config.database_name = 'couch_potato_test'
12
+ end
13
+
14
+
9
15
  CouchPotato::Config.validation_framework = ENV['VALIDATION_FRAMEWORK'].to_sym unless ENV['VALIDATION_FRAMEWORK'].blank?
10
16
 
11
17
  # silence deprecation warnings from ActiveModel as the Spec uses Errors#on
@@ -2,8 +2,20 @@ require File.dirname(__FILE__) + '/../spec_helper'
2
2
 
3
3
  describe CouchPotato::View::ViewQuery, 'query_view' do
4
4
  it "should not pass a key if conditions are empty" do
5
- db = mock 'db'
5
+ db = mock 'db', :get => nil, :save_doc => nil
6
6
  db.should_receive(:view).with(anything, {})
7
7
  CouchPotato::View::ViewQuery.new(db, '', '', '', '').query_view!
8
8
  end
9
+
10
+ it "should not update a view when the functions haven't changed" do
11
+ db = mock 'db', :get => {'views' => {'view' => {'map' => 'map', 'reduce' => 'reduce'}}}, :view => nil
12
+ db.should_not_receive(:save_doc)
13
+ CouchPotato::View::ViewQuery.new(db, 'design', 'view', 'map', 'reduce').query_view!
14
+ end
15
+
16
+ it "should update a view when the functions have changed" do
17
+ db = mock 'db', :get => {'views' => {'view2' => {'map' => 'map', 'reduce' => 'reduce'}}}, :view => nil
18
+ db.should_receive(:save_doc)
19
+ CouchPotato::View::ViewQuery.new(db, 'design', 'view2', 'mapnew', 'reduce').query_view!
20
+ end
9
21
  end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe "automatic view updates" do
4
+ before(:each) do
5
+ recreate_db
6
+ @db = CouchPotato.couchrest_database
7
+ end
8
+
9
+ it "should update a view that doesn't match the given functions" do
10
+ CouchPotato::View::ViewQuery.new(@db, 'test_design1', 'test_view', 'function(doc) {}', 'function() {}').query_view! # create view
11
+ CouchPotato::View::ViewQuery.new(@db, 'test_design1', 'test_view', 'function(doc) {emit(doc.id, null)}', 'function(key, values) {return sum(values)}').query_view!
12
+ CouchPotato.database.load('_design/test_design1')['views']['test_view'].should == {
13
+ 'map' => 'function(doc) {emit(doc.id, null)}',
14
+ 'reduce' => 'function(key, values) {return sum(values)}'
15
+ }
16
+ end
17
+
18
+ it "should only update a view once to avoid writing the view for every request" do
19
+ CouchPotato::View::ViewQuery.new(@db, 'test_design2', 'test_view', 'function(doc) {}', 'function() {}').query_view! # create view
20
+ CouchPotato::View::ViewQuery.new(@db, 'test_design2', 'test_view', 'function(doc) {emit(doc.id, null)}', 'function(key, values) {return sum(values)}').query_view!
21
+ CouchPotato::View::ViewQuery.new(@db, 'test_design2', 'test_view', 'function(doc) {}', 'function() {}').query_view!
22
+ CouchPotato.database.load('_design/test_design2')['views']['test_view'].should == {
23
+ 'map' => 'function(doc) {emit(doc.id, null)}',
24
+ 'reduce' => 'function(key, values) {return sum(values)}'
25
+ }
26
+ end
27
+
28
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: couch_potato
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.24
4
+ version: 0.2.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexander Lang
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-02-05 00:00:00 +01:00
12
+ date: 2010-02-17 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -74,6 +74,7 @@ files:
74
74
  - lib/couch_potato/persistence/properties.rb
75
75
  - lib/couch_potato/persistence/simple_property.rb
76
76
  - lib/couch_potato/persistence/type_caster.rb
77
+ - lib/couch_potato/railtie.rb
77
78
  - lib/couch_potato/rspec/matchers.rb
78
79
  - lib/couch_potato/rspec/matchers/map_to_matcher.rb
79
80
  - lib/couch_potato/rspec/matchers/print_r.js
@@ -118,6 +119,7 @@ files:
118
119
  - spec/unit/validation_spec.rb
119
120
  - spec/unit/view_query_spec.rb
120
121
  - spec/update_spec.rb
122
+ - spec/view_updates_spec.rb
121
123
  has_rdoc: true
122
124
  homepage: http://github.com/langalex/couch_potato
123
125
  licenses: []
@@ -174,3 +176,4 @@ test_files:
174
176
  - spec/unit/validation_spec.rb
175
177
  - spec/unit/view_query_spec.rb
176
178
  - spec/update_spec.rb
179
+ - spec/view_updates_spec.rb