couch_potato 0.7.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/.gitignore +1 -0
  2. data/.travis.yml +7 -3
  3. data/CHANGES.md +14 -0
  4. data/README.md +17 -12
  5. data/Rakefile +1 -17
  6. data/active_support_3_2 +7 -1
  7. data/active_support_4_0 +11 -0
  8. data/couch_potato.gemspec +2 -3
  9. data/lib/couch_potato/database.rb +0 -3
  10. data/lib/couch_potato/forbidden_attributes_protection.rb +15 -0
  11. data/lib/couch_potato/persistence/simple_property.rb +5 -1
  12. data/lib/couch_potato/persistence/type_caster.rb +2 -0
  13. data/lib/couch_potato/persistence.rb +9 -5
  14. data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +1 -2
  15. data/lib/couch_potato/rspec/matchers/map_reduce_to_matcher.rb +114 -0
  16. data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +1 -2
  17. data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +7 -9
  18. data/lib/couch_potato/rspec/matchers.rb +23 -7
  19. data/lib/couch_potato/version.rb +1 -1
  20. data/lib/couch_potato/view/base_view_spec.rb +6 -2
  21. data/lib/couch_potato/view/custom_view_spec.rb +20 -19
  22. data/lib/couch_potato/view/model_view_spec.rb +10 -5
  23. data/lib/couch_potato/view/raw_view_spec.rb +2 -6
  24. data/lib/couch_potato.rb +1 -0
  25. data/spec/default_property_spec.rb +6 -0
  26. data/spec/property_spec.rb +13 -3
  27. data/spec/reload_spec.rb +14 -0
  28. data/spec/spec_helper.rb +6 -0
  29. data/spec/unit/base_view_spec_spec.rb +21 -14
  30. data/spec/unit/custom_view_spec_spec.rb +24 -0
  31. data/spec/unit/forbidden_attributes_protection_spec.rb +53 -0
  32. data/spec/unit/model_view_spec_spec.rb +24 -1
  33. data/spec/unit/persistence_spec.rb +40 -0
  34. data/spec/unit/rspec_matchers_spec.rb +126 -5
  35. data/spec/views_spec.rb +16 -10
  36. metadata +62 -35
  37. data/Gemfile.lock +0 -54
  38. data/active_support_3_0 +0 -4
  39. data/active_support_3_0.lock +0 -54
  40. data/active_support_3_1 +0 -4
  41. data/active_support_3_1.lock +0 -57
  42. data/active_support_3_2.lock +0 -55
  43. data/lib/couch_potato/rspec/matchers/print_r.js +0 -60
  44. data/rails/init.rb +0 -4
data/.gitignore CHANGED
@@ -3,3 +3,4 @@
3
3
  pkg
4
4
  .rvmrc
5
5
  *.swp
6
+ *.lock
data/.travis.yml CHANGED
@@ -1,11 +1,15 @@
1
1
  rvm:
2
- - 1.9.2
3
2
  - 1.9.3
3
+ - 2.0.0
4
+ - jruby-19mode
5
+ - rbx-19mode
4
6
  gemfile:
5
- - active_support_3_0
6
- - active_support_3_1
7
7
  - active_support_3_2
8
+ - active_support_4_0
8
9
  before_script:
9
10
  - sudo sh -c 'echo "[native_query_servers]" >> /etc/couchdb/local.ini'
10
11
  - sudo sh -c 'echo "erlang = {couch_native_process, start_link, []}" >> /etc/couchdb/local.ini'
11
12
  - sudo /etc/init.d/couchdb restart
13
+ matrix:
14
+ allow_failures:
15
+ - rvm: jruby
data/CHANGES.md CHANGED
@@ -1,5 +1,19 @@
1
1
  ## Changes
2
2
 
3
+ ### 1.0.0
4
+
5
+ * adds `reload` method (Alexander Lang)
6
+ * removes `total_rows` from database results (Alexander Lang)
7
+ * changes `==` to use ids instead of comparing all attributes (orders of magnitude faster) ([Jochen Kramer](https://github.com/freetwix))
8
+ * fixes decoding JSON objects for newer versions of the JSON gem (Alexander Lang)
9
+ * adds support for testing map/reduce/rereduce (Andy Morris)
10
+ * fixes serializing dates in map/reduce specs (Andy Morris)
11
+ * adds support for Rails4 forbidden attributes protection (Alexander Lang)
12
+ * adds Rails4, drops 3.0/3.1 support (Alexander Lang)
13
+ * adds property default values returned by Procs (Andy Morris)
14
+ * adds suppot for BigDecimal properties (Fredrik Rubensson)
15
+ * adds support for 2.0, Rubinius, 1.9.3, drops Ruby 1.8, 1.9.2
16
+
3
17
  ### 0.7.1
4
18
 
5
19
  * fixes a bug when trying to bulk-load non-existant documents
data/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  [![Dependencies](https://gemnasium.com/langalex/couch_potato.png)](https://gemnasium.com/langalex/couch_potato)
8
8
 
9
- [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/langalex/couch_potato)
9
+ [![Code Climate](https://codeclimate.com/github/langalex/couch_potato.png)](https://codeclimate.com/github/langalex/couch_potato)
10
10
 
11
11
 
12
12
  ### Mission
@@ -27,8 +27,9 @@ Lastly Couch Potato aims to provide a seamless integration with Ruby on Rails, e
27
27
 
28
28
  ### Supported Environments
29
29
 
30
- * Ruby 1.9.2
31
- * CouchDB 1.1.0
30
+ * Ruby 1.9.3
31
+ * CouchDB 1.2.0
32
+ * ActiveSupport 3.2, 4.0
32
33
 
33
34
  (Supported means I run the specs against those before releasing a new gem.)
34
35
 
@@ -36,7 +37,7 @@ Lastly Couch Potato aims to provide a seamless integration with Ruby on Rails, e
36
37
 
37
38
  Couch Potato is hosted as a gem which you can install like this:
38
39
 
39
- (sudo) gem install couch_potato
40
+ gem install couch_potato
40
41
 
41
42
  #### Using with your ruby application:
42
43
 
@@ -81,13 +82,6 @@ Create a config/couchdb.yml:
81
82
  <<: *default
82
83
  database: <%= ENV['DB_NAME'] %>
83
84
 
84
- #### Rails 2.x
85
-
86
- Add to your _config/environment.rb_:
87
-
88
- config.gem 'couch_potato', :source => 'http://gemcutter.org'
89
- config.frameworks -= [:active_record] # if you switch completely
90
-
91
85
  #### Rails 3.x
92
86
 
93
87
  Add to your _Gemfile_:
@@ -98,6 +92,7 @@ Add to your _Gemfile_:
98
92
  gem 'actionmailer'
99
93
  gem 'activemodel'
100
94
  gem "couch_potato"
95
+ gem 'tzinfo'
101
96
 
102
97
  Note: please make sure that when you run `Date.today.as_json` in the Rails console it returns something like `2010/12/10` and not `2010-12-10` - if it does another gem has overwritten Couch Potato's Date patches - in this case move Couch Potato further down in your Gemfile or whereever you load it.
103
98
 
@@ -153,6 +148,7 @@ Properties can have a default value:
153
148
  include CouchPotato::Persistence
154
149
 
155
150
  property :active, :default => true
151
+ property :signed_up, :default => Proc.new { Time.now }
156
152
  end
157
153
 
158
154
  Now you can save your objects. All database operations are encapsulated in the CouchPotato::Database class. This separates your domain logic from the database access logic which makes it easier to write tests and also keeps you models smaller and cleaner.
@@ -450,6 +446,9 @@ Couch Potato provides custom RSpec matchers for testing the map and reduce funct
450
446
 
451
447
  view :by_name, :key => :name
452
448
  view :by_age, :key => :age
449
+ view :oldest_by_name,
450
+ :map => "function(doc) { emit(doc.name, doc.age); }",
451
+ :reduce => "function(keys, values, rereduce) { return Math.max.apply(this, values); }"
453
452
  end
454
453
 
455
454
  #RSpec
@@ -463,9 +462,15 @@ Couch Potato provides custom RSpec matchers for testing the map and reduce funct
463
462
  it "should reduce the users to the sum of their age" do
464
463
  User.by_age.should reduce([], [23, 22]).to(45)
465
464
  end
465
+
466
+ it "should map/reduce users to the oldest age by name" do
467
+ docs = [User.new(:name => "John", :age => 25), User.new(:name => "John", :age => 30), User.new(:name => "Jane", :age => 20)]
468
+ User.oldest_by_name.should map_reduce(docs).with_options(:group => true).to(
469
+ {"key" => "John", "value" => 30}, {"key" => "Jane", "value" => 20})
470
+ end
466
471
  end
467
472
 
468
- This will actually run your map/reduce functions in a JavaScript interpreter, passing the arguments as JSON and converting the results back to Ruby. For more examples see the [spec](http://github.com/langalex/couch_potato/blob/master/spec/unit/rspec_matchers_spec.rb).
473
+ This will actually run your map/reduce functions in a JavaScript interpreter, passing the arguments as JSON and converting the results back to Ruby. ```map_reduce``` specs map the input documents, reduce the emitted keys/values, and rereduce the results while also respecting the ```:group``` and ```:group_level``` couchdb options. For more examples see the [spec](http://github.com/langalex/couch_potato/blob/master/spec/unit/rspec_matchers_spec.rb).
469
474
 
470
475
  In order for this to work you must have the `js` executable in your PATH. This is usually part of the _spidermonkey_ package/port. (On MacPorts that's _spidemonkey_, on Linux it could be one of _libjs_, _libmozjs_ or _libspidermonkey_). When you installed CouchDB via your packet manager Spidermonkey should already be there.
471
476
 
data/Rakefile CHANGED
@@ -3,11 +3,6 @@ Bundler::GemHelper.install_tasks
3
3
 
4
4
  require 'rake'
5
5
  require "rspec/core/rake_task"
6
- begin
7
- require 'rdoc/task'
8
- rescue LoadError
9
- require 'rake/rdoctask'
10
- end
11
6
 
12
7
  task :default => :spec
13
8
 
@@ -29,7 +24,7 @@ task :spec do
29
24
  Rake::Task[:spec_unit].execute
30
25
  Rake::Task[:spec_functional].execute
31
26
  else
32
- ['3_0', '3_1', '3_2'].each do |version|
27
+ ['3_2', '4_0'].each do |version|
33
28
  Bundler.with_clean_env do
34
29
  puts "Running tests with ActiveSupport #{version.sub('_', '.')}"
35
30
  sh "env BUNDLE_GEMFILE=active_support_#{version} bundle install"
@@ -37,15 +32,4 @@ task :spec do
37
32
  end
38
33
  end
39
34
  end
40
-
41
- end
42
-
43
- desc 'Generate documentation'
44
- Rake::RDocTask.new(:rdoc) do |rdoc|
45
- rdoc.rdoc_dir = 'rdoc'
46
- rdoc.title = 'Couch Potato'
47
- rdoc.options << '--line-numbers' << '--inline-source'
48
- rdoc.rdoc_files.include('README.md')
49
- rdoc.rdoc_files.include('lib/couch_potato.rb')
50
- rdoc.rdoc_files.include('lib/couch_potato/**/*.rb')
51
35
  end
data/active_support_3_2 CHANGED
@@ -1,4 +1,10 @@
1
- source :rubygems
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gem 'activemodel', '~>3.2'
4
+ if RUBY_PLATFORM =~ /java/
5
+ gem 'therubyrhino'
6
+ else
7
+ gem 'therubyracer'
8
+ end
9
+
4
10
  gemspec
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'activemodel', '~>4.0.0.rc1'
4
+ gem 'rails', '~>4.0.0.rc1'
5
+ if RUBY_PLATFORM =~ /java/
6
+ gem 'therubyrhino'
7
+ else
8
+ gem 'therubyracer'
9
+ end
10
+
11
+ gemspec
data/couch_potato.gemspec CHANGED
@@ -13,14 +13,13 @@ Gem::Specification.new do |s|
13
13
  s.platform = Gem::Platform::RUBY
14
14
 
15
15
  s.add_dependency 'json', '~> 1.6'
16
- s.add_dependency 'couchrest', '>=1.0.1'
16
+ s.add_dependency 'couchrest', '~>1.2.0'
17
17
  s.add_dependency 'activemodel'
18
18
 
19
- s.add_development_dependency 'rspec', '>=2.0'
19
+ s.add_development_dependency 'rspec', '~>2.11.0'
20
20
  s.add_development_dependency 'timecop'
21
21
  s.add_development_dependency 'tzinfo'
22
22
  s.add_development_dependency 'rake'
23
- s.add_development_dependency 'therubyracer'
24
23
 
25
24
  s.files = `git ls-files`.split("\n")
26
25
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -14,7 +14,6 @@ module CouchPotato
14
14
 
15
15
  # executes a view and return the results. you pass in a view spec
16
16
  # which is usually a result of a SomePersistentClass.some_view call.
17
- # also return the total_rows returned by CouchDB as an accessor on the results.
18
17
  #
19
18
  # Example:
20
19
  #
@@ -26,7 +25,6 @@ module CouchPotato
26
25
  # db = CouchPotato.database
27
26
  #
28
27
  # db.view(User.all) # => [user1, user2]
29
- # db.view(User.all).total_rows # => 2
30
28
  #
31
29
  # You can pass the usual parameters you can pass to a couchdb view to the view:
32
30
  #
@@ -55,7 +53,6 @@ module CouchPotato
55
53
  spec.language
56
54
  ).query_view!(spec.view_parameters)
57
55
  processed_results = spec.process_results results
58
- processed_results.instance_eval "def total_rows; #{results['total_rows']}; end" if results['total_rows']
59
56
  processed_results.each do |document|
60
57
  document.database = self if document.respond_to?(:database=)
61
58
  end if processed_results.respond_to?(:each)
@@ -0,0 +1,15 @@
1
+ module CouchPotato
2
+ module ForbiddenAttributesProtection
3
+ def self.included(base)
4
+ base.class_eval do
5
+ if defined?(ActiveModel::ForbiddenAttributesProtection)
6
+ include ActiveModel::ForbiddenAttributesProtection
7
+
8
+ def attributes=(attributes)
9
+ super sanitize_for_mass_assignment(attributes)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -56,7 +56,11 @@ module CouchPotato
56
56
  load_attribute_from_document(name) unless instance_variable_defined?("@#{name}")
57
57
  value = instance_variable_get("@#{name}")
58
58
  if value.nil? && !options[:default].nil?
59
- default = clone_attribute(options[:default])
59
+ default = if options[:default].respond_to?(:call)
60
+ options[:default].call
61
+ else
62
+ clone_attribute(options[:default])
63
+ end
60
64
  self.instance_variable_set("@#{name}", default)
61
65
  default
62
66
  else
@@ -32,6 +32,8 @@ module CouchPotato
32
32
  BigDecimal.new(value.to_s.scan(NUMBER_REGEX).join).round unless value.blank?
33
33
  elsif type == Float
34
34
  value.to_s.scan(NUMBER_REGEX).join.to_f unless value.blank?
35
+ elsif type == BigDecimal
36
+ BigDecimal.new(value.to_s) unless value.blank?
35
37
  else
36
38
  type.json_create value unless value.blank?
37
39
  end
@@ -9,6 +9,7 @@ require File.dirname(__FILE__) + '/persistence/dirty_attributes'
9
9
  require File.dirname(__FILE__) + '/persistence/ghost_attributes'
10
10
  require File.dirname(__FILE__) + '/persistence/attachments'
11
11
  require File.dirname(__FILE__) + '/persistence/type_caster'
12
+ require File.dirname(__FILE__) + '/forbidden_attributes_protection'
12
13
  require File.dirname(__FILE__) + '/view/custom_views'
13
14
  require File.dirname(__FILE__) + '/view/lists'
14
15
  require File.dirname(__FILE__) + '/view/view_query'
@@ -20,7 +21,7 @@ module CouchPotato
20
21
  def self.included(base) #:nodoc:
21
22
  base.send :include, Properties, Callbacks, Json, CouchPotato::View::CustomViews, CouchPotato::View::Lists
22
23
  base.send :include, DirtyAttributes, GhostAttributes, Attachments
23
- base.send :include, MagicTimestamps, ActiveModelCompliance
24
+ base.send :include, MagicTimestamps, ActiveModelCompliance, ForbiddenAttributesProtection
24
25
  base.send :include, Validation
25
26
  base.class_eval do
26
27
  attr_accessor :_id, :_rev, :_deleted, :database
@@ -51,9 +52,7 @@ module CouchPotato
51
52
  def initialize(attributes = {})
52
53
  if attributes
53
54
  @skip_dirty_tracking = true
54
- attributes.each do |name, value|
55
- self.send("#{name}=", value)
56
- end
55
+ self.attributes = attributes
57
56
  @skip_dirty_tracking = false
58
57
  end
59
58
  yield self if block_given?
@@ -121,7 +120,7 @@ module CouchPotato
121
120
  end
122
121
 
123
122
  def ==(other) #:nodoc:
124
- other.class == self.class && self.to_json == other.to_json
123
+ super || (self.class == other.class && self._id.present? && self._id == other._id)
125
124
  end
126
125
 
127
126
  def eql?(other)
@@ -132,6 +131,11 @@ module CouchPotato
132
131
  _id.hash * (_id.hash.to_s.size ** 10) + _rev.hash
133
132
  end
134
133
 
134
+ # Returns a reloaded instance. Does not touch the original instance.
135
+ def reload
136
+ database.load id
137
+ end
138
+
135
139
  def inspect
136
140
  attributes_as_string = attributes.map {|attribute, value| "#{attribute}: #{value.inspect}"}.join(", ")
137
141
  %Q{#<#{self.class} _id: "#{_id}", _rev: "#{_rev}", #{attributes_as_string}>}
@@ -20,7 +20,6 @@ module CouchPotato
20
20
 
21
21
  def matches?(view_spec)
22
22
  js = <<-JS
23
- #{File.read(File.dirname(__FILE__) + '/print_r.js')}
24
23
  #{File.read(File.dirname(__FILE__) + '/json2.js')}
25
24
  var results = #{@results_ruby.to_json};
26
25
  var listed = '';
@@ -33,7 +32,7 @@ module CouchPotato
33
32
  listed = listed + text;
34
33
  };
35
34
  list();
36
- print_r(JSON.parse(listed));
35
+ JSON.stringify(JSON.parse(listed));
37
36
  JS
38
37
 
39
38
  @actual_ruby = JSON.parse(run_js(js))
@@ -0,0 +1,114 @@
1
+ require 'json'
2
+
3
+ module CouchPotato
4
+ module RSpec
5
+ class MapReduceToProxy
6
+ def initialize(*input_ruby)
7
+ @input_ruby, @options = input_ruby.flatten, {}
8
+ end
9
+
10
+ def with_options(options)
11
+ @options = options
12
+ self
13
+ end
14
+
15
+ def to(*expected_ruby)
16
+ MapReduceToMatcher.new(expected_ruby, @input_ruby, @options)
17
+ end
18
+ end
19
+
20
+ class MapReduceToMatcher
21
+ include RunJS
22
+
23
+ def initialize(expected_ruby, input_ruby, options)
24
+ @expected_ruby = expected_ruby
25
+ @input_ruby = input_ruby
26
+ @options = options
27
+ end
28
+
29
+ def matches?(view_spec)
30
+ js = <<-JS
31
+ var docs = #{@input_ruby.to_json};
32
+ var options = #{@options.to_json};
33
+ var map = #{view_spec.map_function};
34
+ var reduce = #{view_spec.reduce_function};
35
+
36
+ // Map the input docs
37
+ var mapResults = [];
38
+ var emit = function(key, value) {
39
+ mapResults.push({key: key, value: value});
40
+ };
41
+ for (var i in docs) {
42
+ map(docs[i]);
43
+ }
44
+
45
+ // Group the map results, honoring the group and group_level options
46
+ var grouped = [];
47
+ if (options.group || options.group_level) {
48
+ var groupLevel = options.group_level;
49
+ if (groupLevel == "exact" || options.group == true)
50
+ groupLevel = 9999;
51
+ var keysEqual = function(a, b) { return !(a < b || b < a); }
52
+
53
+ for (var mr in mapResults) {
54
+ var mapResult = mapResults[mr];
55
+ var groupedKey = mapResult.key.slice(0, groupLevel);
56
+ var groupFound = false;
57
+ for (var g in grouped) {
58
+ var group = grouped[g];
59
+ if (keysEqual(groupedKey, group.groupedKey)) {
60
+ group.keys.push(mapResult.key);
61
+ group.values.push(mapResult.value);
62
+ groupFound = true;
63
+ break;
64
+ }
65
+ }
66
+
67
+ if (!groupFound)
68
+ grouped.push({keys: [mapResult.key], groupedKey: groupedKey, values: [mapResult.value]});
69
+ }
70
+ } else {
71
+ var group = {keys: null, groupedKey: null, values: []};
72
+ for (var mr in mapResults)
73
+ group.values.push(mapResults[mr].value);
74
+ grouped.push(group);
75
+ }
76
+
77
+ // Reduce the grouped map results
78
+ var results = [];
79
+ for (var g in grouped) {
80
+ var group = grouped[g], reduced = null;
81
+ if (group.values.length >= 2) {
82
+ // Split the values into two parts, reduce each part, then rereduce those results
83
+ var mid = parseInt(group.values.length / 2);
84
+ var keys1 = (group.keys || []).slice(0, mid),
85
+ values1 = group.values.slice(0, mid);
86
+ var reduced1 = reduce(keys1, values1, false);
87
+ var keys2 = (group.keys || []).slice(mid, group.values.length),
88
+ values2 = group.values.slice(mid, group.values.length);
89
+ var reduced2 = reduce(keys2, values2, false);
90
+ reduced = reduce(null, [reduced1, reduced2], true);
91
+ } else {
92
+ // Not enough values to split, so just reduce, and then rereduce the single result
93
+ reduced = reduce(group.keys, group.values, false);
94
+ reduced = reduce(null, [reduced], true);
95
+ }
96
+ results.push({key: group.groupedKey, value: reduced});
97
+ }
98
+
99
+ JSON.stringify(results);
100
+ JS
101
+ @actual_ruby = JSON.parse(run_js(js))
102
+ @expected_ruby == @actual_ruby
103
+ end
104
+
105
+ def failure_message_for_should
106
+ "Expected to map/reduce to #{@expected_ruby.inspect} but got #{@actual_ruby.inspect}."
107
+ end
108
+
109
+ def failure_message_for_should_not
110
+ "Expected not to map/reduce to #{@actual_ruby.inspect} but did."
111
+ end
112
+ end
113
+ end
114
+ end
@@ -23,7 +23,6 @@ module CouchPotato
23
23
 
24
24
  def matches?(view_spec)
25
25
  js = <<-JS
26
- #{File.read(File.dirname(__FILE__) + '/print_r.js')}
27
26
  var doc = #{@input_ruby.to_json};
28
27
  var map = #{view_spec.map_function};
29
28
  var result = [];
@@ -31,7 +30,7 @@ module CouchPotato
31
30
  result.push([key, value]);
32
31
  };
33
32
  map(doc);
34
- print_r(result);
33
+ JSON.stringify(result);
35
34
  JS
36
35
  @actual_ruby = JSON.parse(run_js(js))
37
36
  @expected_ruby == @actual_ruby
@@ -1,26 +1,24 @@
1
1
  module CouchPotato
2
2
  module RSpec
3
3
  class ReduceToProxy
4
- def initialize(docs, keys, rereduce = false)
5
- @docs, @keys, @rereduce = docs, keys, rereduce
4
+ def initialize(keys, values, rereduce = false)
5
+ @keys, @values, @rereduce = keys, values, rereduce
6
6
  end
7
7
 
8
8
  def to(expected_ruby)
9
- ReduceToMatcher.new(expected_ruby, @docs, @keys, @rereduce)
9
+ ReduceToMatcher.new(expected_ruby, @keys, @values, @rereduce)
10
10
  end
11
11
  end
12
12
 
13
13
  class ReduceToMatcher
14
14
  include RunJS
15
15
 
16
- def initialize(expected_ruby, docs, keys, rereduce = false)
17
- @expected_ruby, @docs, @keys, @rereduce = expected_ruby, docs, keys, rereduce
16
+ def initialize(expected_ruby, keys, values, rereduce = false)
17
+ @expected_ruby, @keys, @values, @rereduce = expected_ruby, keys, values, rereduce
18
18
  end
19
19
 
20
20
  def matches?(view_spec)
21
21
  js = <<-JS
22
- #{File.read(File.dirname(__FILE__) + '/print_r.js')}
23
-
24
22
  sum = function(values) {
25
23
  var rv = 0;
26
24
  for (var i in values) {
@@ -29,10 +27,10 @@ module CouchPotato
29
27
  return rv;
30
28
  };
31
29
 
32
- var docs = #{@docs.to_json};
33
30
  var keys = #{@keys.to_json};
31
+ var values = #{@values.to_json};
34
32
  var reduce = #{view_spec.reduce_function};
35
- print_r({result: reduce(docs, keys, #{@rereduce})});
33
+ JSON.stringify({result: reduce(keys, values, #{@rereduce})});
36
34
  JS
37
35
  @actual_ruby = JSON.parse(run_js(js))['result']
38
36
  @expected_ruby == @actual_ruby
@@ -1,4 +1,12 @@
1
- require 'v8'
1
+ begin
2
+ require 'v8'
3
+ rescue LoadError
4
+ begin
5
+ require 'rhino'
6
+ rescue LoadError
7
+ STDERR.puts "You need to install therubyracer (or therhinoracer on jruby) to use matchers"
8
+ end
9
+ end
2
10
 
3
11
  module CouchPotato
4
12
  module RSpec
@@ -6,8 +14,11 @@ module CouchPotato
6
14
  private
7
15
 
8
16
  def run_js(js)
9
- cxt = V8::Context.new
10
- cxt.eval(js)
17
+ if defined?(V8)
18
+ V8::Context.new.eval(js)
19
+ else
20
+ Rhino::Context.open{|cxt| cxt.eval(js)}
21
+ end
11
22
  end
12
23
  end
13
24
  end
@@ -16,6 +27,7 @@ end
16
27
 
17
28
  require 'couch_potato/rspec/matchers/map_to_matcher'
18
29
  require 'couch_potato/rspec/matchers/reduce_to_matcher'
30
+ require 'couch_potato/rspec/matchers/map_reduce_to_matcher'
19
31
  require 'couch_potato/rspec/matchers/list_as_matcher'
20
32
 
21
33
  module RSpec
@@ -24,17 +36,21 @@ module RSpec
24
36
  CouchPotato::RSpec::MapToProxy.new(document)
25
37
  end
26
38
 
27
- def reduce(docs, keys)
28
- CouchPotato::RSpec::ReduceToProxy.new(docs, keys)
39
+ def reduce(keys, values)
40
+ CouchPotato::RSpec::ReduceToProxy.new(keys, values)
29
41
  end
30
42
 
31
- def rereduce(docs, keys)
32
- CouchPotato::RSpec::ReduceToProxy.new(docs, keys, true)
43
+ def rereduce(keys, values)
44
+ CouchPotato::RSpec::ReduceToProxy.new(keys, values, true)
33
45
  end
34
46
 
35
47
  def list(results)
36
48
  CouchPotato::RSpec::ListAsProxy.new(results)
37
49
  end
50
+
51
+ def map_reduce(*docs)
52
+ CouchPotato::RSpec::MapReduceToProxy.new(docs)
53
+ end
38
54
  end
39
55
  end
40
56
 
@@ -1,3 +1,3 @@
1
1
  module CouchPotato
2
- VERSION = "0.7.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -3,7 +3,7 @@ module CouchPotato
3
3
  class BaseViewSpec
4
4
  attr_reader :reduce_function, :list_name, :list_function, :design_document, :view_name, :klass, :options, :language
5
5
  attr_accessor :view_parameters
6
-
6
+
7
7
  private :klass, :options
8
8
 
9
9
  def initialize(klass, view_name, options, view_parameters)
@@ -27,7 +27,11 @@ module CouchPotato
27
27
  end
28
28
 
29
29
  def process_results(results)
30
- results
30
+ if (filter = options[:results_filter])
31
+ filter.call results
32
+ else
33
+ results
34
+ end
31
35
  end
32
36
 
33
37
  private
@@ -8,35 +8,36 @@ module CouchPotato
8
8
  def map_function
9
9
  options[:map]
10
10
  end
11
-
11
+
12
12
  def reduce_function
13
13
  options[:reduce]
14
14
  end
15
-
15
+
16
16
  def view_parameters
17
17
  {:include_docs => options[:include_docs] || false}.merge(super)
18
18
  end
19
-
19
+
20
20
  def process_results(results)
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
30
- end
31
- end
21
+ processed = 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
+ result = row['doc'] || (row['value'].merge(:_id => row['id'] || row['key']) unless view_parameters[:include_docs])
29
+ klass.json_create result if result
30
+ end
31
+ end.compact
32
+ end
33
+ super processed
32
34
  end
33
-
34
- private
35
-
35
+
36
+ private
37
+
36
38
  def count?
37
39
  view_parameters[:reduce]
38
40
  end
39
-
40
41
  end
41
42
  end
42
- end
43
+ end