couch_potato 0.7.1 → 1.0.0

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.
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