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.
- 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
|
[](https://gemnasium.com/langalex/couch_potato)
|
8
8
|
|
9
|
-
[](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
|