couch_potato 0.2.24 → 0.2.25

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