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.
- data/.gitignore +1 -0
- data/.travis.yml +7 -3
- data/CHANGES.md +14 -0
- data/README.md +17 -12
- data/Rakefile +1 -17
- data/active_support_3_2 +7 -1
- data/active_support_4_0 +11 -0
- data/couch_potato.gemspec +2 -3
- data/lib/couch_potato/database.rb +0 -3
- data/lib/couch_potato/forbidden_attributes_protection.rb +15 -0
- data/lib/couch_potato/persistence/simple_property.rb +5 -1
- data/lib/couch_potato/persistence/type_caster.rb +2 -0
- data/lib/couch_potato/persistence.rb +9 -5
- data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +1 -2
- data/lib/couch_potato/rspec/matchers/map_reduce_to_matcher.rb +114 -0
- data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +1 -2
- data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +7 -9
- data/lib/couch_potato/rspec/matchers.rb +23 -7
- data/lib/couch_potato/version.rb +1 -1
- data/lib/couch_potato/view/base_view_spec.rb +6 -2
- data/lib/couch_potato/view/custom_view_spec.rb +20 -19
- data/lib/couch_potato/view/model_view_spec.rb +10 -5
- data/lib/couch_potato/view/raw_view_spec.rb +2 -6
- data/lib/couch_potato.rb +1 -0
- data/spec/default_property_spec.rb +6 -0
- data/spec/property_spec.rb +13 -3
- data/spec/reload_spec.rb +14 -0
- data/spec/spec_helper.rb +6 -0
- data/spec/unit/base_view_spec_spec.rb +21 -14
- data/spec/unit/custom_view_spec_spec.rb +24 -0
- data/spec/unit/forbidden_attributes_protection_spec.rb +53 -0
- data/spec/unit/model_view_spec_spec.rb +24 -1
- data/spec/unit/persistence_spec.rb +40 -0
- data/spec/unit/rspec_matchers_spec.rb +126 -5
- data/spec/views_spec.rb +16 -10
- metadata +62 -35
- data/Gemfile.lock +0 -54
- data/active_support_3_0 +0 -4
- data/active_support_3_0.lock +0 -54
- data/active_support_3_1 +0 -4
- data/active_support_3_1.lock +0 -57
- data/active_support_3_2.lock +0 -55
- data/lib/couch_potato/rspec/matchers/print_r.js +0 -60
- data/rails/init.rb +0 -4
data/.gitignore
CHANGED
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/
|
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.
|
31
|
-
* CouchDB 1.
|
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
|
-
|
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
|
-
['
|
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
data/active_support_4_0
ADDED
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', '
|
16
|
+
s.add_dependency 'couchrest', '~>1.2.0'
|
17
17
|
s.add_dependency 'activemodel'
|
18
18
|
|
19
|
-
s.add_development_dependency 'rspec', '
|
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 =
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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(
|
5
|
-
@
|
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, @
|
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,
|
17
|
-
@expected_ruby, @
|
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
|
-
|
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
|
-
|
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
|
-
|
10
|
-
|
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(
|
28
|
-
CouchPotato::RSpec::ReduceToProxy.new(
|
39
|
+
def reduce(keys, values)
|
40
|
+
CouchPotato::RSpec::ReduceToProxy.new(keys, values)
|
29
41
|
end
|
30
42
|
|
31
|
-
def rereduce(
|
32
|
-
CouchPotato::RSpec::ReduceToProxy.new(
|
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
|
|
data/lib/couch_potato/version.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|