davber_couch_potato 0.3.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/CHANGES.md +106 -0
- data/MIT-LICENSE.txt +19 -0
- data/README.md +409 -0
- data/VERSION.yml +5 -0
- data/init.rb +3 -0
- data/lib/core_ext/date.rb +21 -0
- data/lib/core_ext/object.rb +5 -0
- data/lib/core_ext/string.rb +8 -0
- data/lib/core_ext/symbol.rb +15 -0
- data/lib/core_ext/time.rb +21 -0
- data/lib/couch_potato/database.rb +161 -0
- data/lib/couch_potato/persistence/active_model_compliance.rb +44 -0
- data/lib/couch_potato/persistence/attachments.rb +31 -0
- data/lib/couch_potato/persistence/callbacks.rb +62 -0
- data/lib/couch_potato/persistence/dirty_attributes.rb +56 -0
- data/lib/couch_potato/persistence/ghost_attributes.rb +22 -0
- data/lib/couch_potato/persistence/json.rb +46 -0
- data/lib/couch_potato/persistence/magic_timestamps.rb +20 -0
- data/lib/couch_potato/persistence/properties.rb +86 -0
- data/lib/couch_potato/persistence/simple_property.rb +72 -0
- data/lib/couch_potato/persistence/type_caster.rb +40 -0
- data/lib/couch_potato/persistence.rb +105 -0
- data/lib/couch_potato/railtie.rb +18 -0
- data/lib/couch_potato/rspec/matchers/json2.js +482 -0
- data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +54 -0
- data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +49 -0
- data/lib/couch_potato/rspec/matchers/print_r.js +60 -0
- data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +50 -0
- data/lib/couch_potato/rspec/matchers.rb +39 -0
- data/lib/couch_potato/rspec/stub_db.rb +46 -0
- data/lib/couch_potato/rspec.rb +2 -0
- data/lib/couch_potato/validation/with_active_model.rb +27 -0
- data/lib/couch_potato/validation/with_validatable.rb +37 -0
- data/lib/couch_potato/validation.rb +16 -0
- data/lib/couch_potato/view/base_view_spec.rb +67 -0
- data/lib/couch_potato/view/custom_view_spec.rb +42 -0
- data/lib/couch_potato/view/custom_views.rb +52 -0
- data/lib/couch_potato/view/lists.rb +23 -0
- data/lib/couch_potato/view/model_view_spec.rb +75 -0
- data/lib/couch_potato/view/properties_view_spec.rb +47 -0
- data/lib/couch_potato/view/raw_view_spec.rb +25 -0
- data/lib/couch_potato/view/view_query.rb +78 -0
- data/lib/couch_potato.rb +79 -0
- data/rails/init.rb +4 -0
- data/rails/reload_classes.rb +47 -0
- data/spec/attachments_spec.rb +23 -0
- data/spec/callbacks_spec.rb +308 -0
- data/spec/create_spec.rb +34 -0
- data/spec/custom_view_spec.rb +239 -0
- data/spec/default_property_spec.rb +38 -0
- data/spec/destroy_spec.rb +29 -0
- data/spec/fixtures/address.rb +10 -0
- data/spec/fixtures/person.rb +6 -0
- data/spec/property_spec.rb +315 -0
- data/spec/rails_spec.rb +51 -0
- data/spec/spec.opts +4 -0
- data/spec/spec_helper.rb +47 -0
- data/spec/unit/active_model_compliance_spec.rb +98 -0
- data/spec/unit/attributes_spec.rb +125 -0
- data/spec/unit/base_view_spec_spec.rb +73 -0
- data/spec/unit/callbacks_spec.rb +72 -0
- data/spec/unit/couch_potato_spec.rb +39 -0
- data/spec/unit/create_spec.rb +58 -0
- data/spec/unit/custom_views_spec.rb +15 -0
- data/spec/unit/database_spec.rb +266 -0
- data/spec/unit/date_spec.rb +22 -0
- data/spec/unit/dirty_attributes_spec.rb +166 -0
- data/spec/unit/json_create_id_spec.rb +14 -0
- data/spec/unit/lists_spec.rb +20 -0
- data/spec/unit/model_view_spec_spec.rb +13 -0
- data/spec/unit/properties_view_spec_spec.rb +31 -0
- data/spec/unit/rspec_matchers_spec.rb +124 -0
- data/spec/unit/rspec_stub_db_spec.rb +35 -0
- data/spec/unit/string_spec.rb +7 -0
- data/spec/unit/time_spec.rb +22 -0
- data/spec/unit/validation_spec.rb +67 -0
- data/spec/unit/view_query_spec.rb +78 -0
- data/spec/update_spec.rb +40 -0
- data/spec/view_updates_spec.rb +28 -0
- metadata +205 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
|
4
|
+
module CouchPotato
|
5
|
+
module RSpec
|
6
|
+
class MapToProxy
|
7
|
+
def initialize(input_ruby)
|
8
|
+
@input_ruby = input_ruby
|
9
|
+
end
|
10
|
+
|
11
|
+
def to(*expected_ruby)
|
12
|
+
MapToMatcher.new(expected_ruby, @input_ruby)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class MapToMatcher
|
17
|
+
include RunJS
|
18
|
+
|
19
|
+
def initialize(expected_ruby, input_ruby)
|
20
|
+
@expected_ruby = expected_ruby
|
21
|
+
@input_ruby = input_ruby
|
22
|
+
end
|
23
|
+
|
24
|
+
def matches?(view_spec)
|
25
|
+
js = <<-JS
|
26
|
+
#{File.read(File.dirname(__FILE__) + '/print_r.js')}
|
27
|
+
var doc = #{@input_ruby.to_json};
|
28
|
+
var map = #{view_spec.map_function};
|
29
|
+
var result = [];
|
30
|
+
var emit = function(key, value) {
|
31
|
+
result.push([key, value]);
|
32
|
+
};
|
33
|
+
map(doc);
|
34
|
+
print(print_r(result));
|
35
|
+
JS
|
36
|
+
@actual_ruby = JSON.parse(run_js(js))
|
37
|
+
@expected_ruby == @actual_ruby
|
38
|
+
end
|
39
|
+
|
40
|
+
def failure_message_for_should
|
41
|
+
"Expected to map to #{@expected_ruby.inspect} but got #{@actual_ruby.inspect}."
|
42
|
+
end
|
43
|
+
|
44
|
+
def failure_message_for_should_not
|
45
|
+
"Expected not to map to #{@actual_ruby.inspect} but did."
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
// taken and adapted from http://scriptnode.com/article/javascript-print_r-or-var_dump-equivalent/
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Concatenates the values of a variable into an easily readable string
|
5
|
+
* by Matt Hackett [scriptnode.com]
|
6
|
+
* @param {Object} x The variable to debug
|
7
|
+
* @param {Number} max The maximum number of recursions allowed (keep low, around 5 for HTML elements to prevent errors) [default: 10]
|
8
|
+
* @param {String} sep The separator to use between [default: a single space ' ']
|
9
|
+
* @param {Number} l The current level deep (amount of recursion). Do not use this parameter: it's for the function's own use
|
10
|
+
*/
|
11
|
+
function print_r(x, max, sep, l) {
|
12
|
+
|
13
|
+
l = l || 0;
|
14
|
+
max = max || 10;
|
15
|
+
sep = sep || ' ';
|
16
|
+
|
17
|
+
if (l > max) {
|
18
|
+
throw("Too much recursion");
|
19
|
+
};
|
20
|
+
|
21
|
+
var r = '';
|
22
|
+
|
23
|
+
if (x === null) {
|
24
|
+
r += "null";
|
25
|
+
} else if (is_array(x)) {
|
26
|
+
r += '[' + x.map(function(i) {
|
27
|
+
return print_r(i, max, sep, (l + 1));
|
28
|
+
}).join(', ') + ']';
|
29
|
+
} else if(is_object(x)) {
|
30
|
+
r += '{'
|
31
|
+
var pairs = [];
|
32
|
+
for (i in x) {
|
33
|
+
pairs.push('"' + i + '": ' + print_r(x[i], max, sep, (l + 1)));
|
34
|
+
}
|
35
|
+
r += pairs.join(', ');
|
36
|
+
r += '}';
|
37
|
+
} else if(is_string(x)) {
|
38
|
+
r += '"' + x + "\"";
|
39
|
+
} else {
|
40
|
+
r += x;
|
41
|
+
};
|
42
|
+
|
43
|
+
return r;
|
44
|
+
|
45
|
+
function is_string(a) {
|
46
|
+
return typeof a === 'string';
|
47
|
+
};
|
48
|
+
|
49
|
+
function is_array(a) {
|
50
|
+
return (a &&
|
51
|
+
typeof a === 'object' &&
|
52
|
+
a.constructor === Array);
|
53
|
+
};
|
54
|
+
|
55
|
+
function is_object(a) {
|
56
|
+
return a && typeof a == 'object'
|
57
|
+
};
|
58
|
+
|
59
|
+
};
|
60
|
+
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module RSpec
|
3
|
+
class ReduceToProxy
|
4
|
+
def initialize(docs, keys, rereduce = false)
|
5
|
+
@docs, @keys, @rereduce = docs, keys, rereduce
|
6
|
+
end
|
7
|
+
|
8
|
+
def to(expected_ruby)
|
9
|
+
ReduceToMatcher.new(expected_ruby, @docs, @keys, @rereduce)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ReduceToMatcher
|
14
|
+
include RunJS
|
15
|
+
|
16
|
+
def initialize(expected_ruby, docs, keys, rereduce = false)
|
17
|
+
@expected_ruby, @docs, @keys, @rereduce = expected_ruby, docs, keys, rereduce
|
18
|
+
end
|
19
|
+
|
20
|
+
def matches?(view_spec)
|
21
|
+
js = <<-JS
|
22
|
+
#{File.read(File.dirname(__FILE__) + '/print_r.js')}
|
23
|
+
|
24
|
+
sum = function(values) {
|
25
|
+
var rv = 0;
|
26
|
+
for (var i in values) {
|
27
|
+
rv += values[i];
|
28
|
+
}
|
29
|
+
return rv;
|
30
|
+
};
|
31
|
+
|
32
|
+
var docs = #{@docs.to_json};
|
33
|
+
var keys = #{@keys.to_json};
|
34
|
+
var reduce = #{view_spec.reduce_function};
|
35
|
+
print(print_r({result: reduce(docs, keys, #{@rereduce})}));
|
36
|
+
JS
|
37
|
+
@actual_ruby = JSON.parse(run_js(js))['result']
|
38
|
+
@expected_ruby == @actual_ruby
|
39
|
+
end
|
40
|
+
|
41
|
+
def failure_message_for_should
|
42
|
+
"Expected to reduce to #{@expected_ruby.inspect} but got #{@actual_ruby.inspect}."
|
43
|
+
end
|
44
|
+
|
45
|
+
def failure_message_for_should_not
|
46
|
+
"Expected not to reduce to #{@actual_ruby.inspect} but did."
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module RSpec
|
3
|
+
module RunJS
|
4
|
+
private
|
5
|
+
|
6
|
+
def run_js(js)
|
7
|
+
path = 'couch_potato_js_runner.js'
|
8
|
+
File.open(path, 'w') {|f| f << js}
|
9
|
+
`js #{path}`
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
require 'couch_potato/rspec/matchers/map_to_matcher'
|
17
|
+
require 'couch_potato/rspec/matchers/reduce_to_matcher'
|
18
|
+
require 'couch_potato/rspec/matchers/list_as_matcher'
|
19
|
+
|
20
|
+
module Spec
|
21
|
+
module Matchers
|
22
|
+
def map(document)
|
23
|
+
CouchPotato::RSpec::MapToProxy.new(document)
|
24
|
+
end
|
25
|
+
|
26
|
+
def reduce(docs, keys)
|
27
|
+
CouchPotato::RSpec::ReduceToProxy.new(docs, keys)
|
28
|
+
end
|
29
|
+
|
30
|
+
def rereduce(docs, keys)
|
31
|
+
CouchPotato::RSpec::ReduceToProxy.new(docs, keys, true)
|
32
|
+
end
|
33
|
+
|
34
|
+
def list(results)
|
35
|
+
CouchPotato::RSpec::ListAsProxy.new(results)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module CouchPotato::RSpec
|
2
|
+
|
3
|
+
module StubView
|
4
|
+
class ViewStub
|
5
|
+
def initialize(clazz, view, db)
|
6
|
+
@clazz = clazz
|
7
|
+
@view = view
|
8
|
+
@db = db
|
9
|
+
end
|
10
|
+
|
11
|
+
def with(*args)
|
12
|
+
@args = args
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def and_return(return_value)
|
17
|
+
view_stub = Spec::Mocks::Mock.new("#{@clazz}.#{@view}(#{@args.try(:join, ', ')}) view")
|
18
|
+
_stub = @clazz.stub(@view)
|
19
|
+
_stub.with(*@args) if @args
|
20
|
+
_stub.and_return(view_stub)
|
21
|
+
@db.stub(:view).with(view_stub).and_return(return_value)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def stub_view(clazz, view)
|
26
|
+
ViewStub.new clazz, view, self
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
module StubDb
|
31
|
+
def stub_db(options = {})
|
32
|
+
db = stub('db', options)
|
33
|
+
db.extend CouchPotato::RSpec::StubView
|
34
|
+
CouchPotato.stub(:database => db)
|
35
|
+
db
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
module Spec
|
41
|
+
module Mocks
|
42
|
+
module ExampleMethods
|
43
|
+
include CouchPotato::RSpec::StubDb
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module Validation
|
3
|
+
module WithActiveModel
|
4
|
+
def self.included(base)
|
5
|
+
require 'active_model'
|
6
|
+
require 'active_model/translation'
|
7
|
+
base.send :include, ::ActiveModel::Validations
|
8
|
+
base.instance_eval do
|
9
|
+
def before_validation(*names)
|
10
|
+
names.each do |name|
|
11
|
+
validate name
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# provide same interface to errors object as in Validatable
|
21
|
+
module ::ActiveModel
|
22
|
+
class Errors
|
23
|
+
def errors
|
24
|
+
self
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module Validation
|
3
|
+
module WithValidatable
|
4
|
+
def self.included(base)
|
5
|
+
begin
|
6
|
+
require 'validatable'
|
7
|
+
rescue LoadError
|
8
|
+
puts "Please install the gem validatable using 'gem install validatable'"
|
9
|
+
raise
|
10
|
+
end
|
11
|
+
base.send :include, ::Validatable
|
12
|
+
base.class_eval do
|
13
|
+
# Override the validate method to first run before_validation callback
|
14
|
+
def valid?
|
15
|
+
errors.clear
|
16
|
+
run_callbacks :before_validation
|
17
|
+
before_validation_errors = errors.errors.dup
|
18
|
+
super
|
19
|
+
before_validation_errors.each do |k, v|
|
20
|
+
v.each {|message| errors.add(k, message)}
|
21
|
+
end
|
22
|
+
errors.empty?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# add [] method to Validatable's implementation of the Errors class
|
31
|
+
module Validatable
|
32
|
+
class Errors
|
33
|
+
def [](attribute)
|
34
|
+
[on(attribute)].flatten.compact
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module Validation
|
3
|
+
def self.included(base) #:nodoc:
|
4
|
+
case CouchPotato::Config.validation_framework
|
5
|
+
when :validatable
|
6
|
+
require 'couch_potato/validation/with_validatable'
|
7
|
+
base.send :include, CouchPotato::Validation::WithValidatable
|
8
|
+
when :active_model
|
9
|
+
require 'couch_potato/validation/with_active_model'
|
10
|
+
base.send :include, CouchPotato::Validation::WithActiveModel
|
11
|
+
else
|
12
|
+
raise "Unknown CouchPotato::Config.validation_framework #{CouchPotato::Config.validation_framework.inspect}, options are :validatable or :active_model"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module View
|
3
|
+
class BaseViewSpec
|
4
|
+
attr_reader :reduce_function, :list_name, :list_function, :design_document, :view_name, :view_parameters, :klass, :options
|
5
|
+
private :klass, :options
|
6
|
+
|
7
|
+
def initialize(klass, view_name, options, view_parameters)
|
8
|
+
normalized_view_parameters = normalize_view_parameters view_parameters
|
9
|
+
|
10
|
+
@list_name = normalized_view_parameters.delete(:list) || options[:list]
|
11
|
+
|
12
|
+
assert_valid_view_parameters normalized_view_parameters
|
13
|
+
@klass = klass
|
14
|
+
# If there is a specified design name generator, we use that one
|
15
|
+
@design_document = CouchPotato.design_name_fun.call klass
|
16
|
+
@view_name = view_name
|
17
|
+
@options = options
|
18
|
+
|
19
|
+
@list_function = klass.lists(@list_name) if @list_name
|
20
|
+
@view_parameters = {}
|
21
|
+
[:group, :include_docs, :descending, :group_level, :limit].each do |key|
|
22
|
+
@view_parameters[key] = options[key] if options.include?(key)
|
23
|
+
end
|
24
|
+
@view_parameters.merge!(normalized_view_parameters)
|
25
|
+
end
|
26
|
+
|
27
|
+
def process_results(results)
|
28
|
+
results
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def normalize_view_parameters(params)
|
34
|
+
normalized_params = params.dup
|
35
|
+
hash = wrap_in_hash params
|
36
|
+
replace_range_key hash
|
37
|
+
end
|
38
|
+
|
39
|
+
def wrap_in_hash(params)
|
40
|
+
if params.is_a?(Hash)
|
41
|
+
params
|
42
|
+
else
|
43
|
+
{:key => params}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def replace_range_key(params)
|
48
|
+
if((key = params[:key]).is_a?(Range))
|
49
|
+
params.delete :key
|
50
|
+
params[:startkey] = key.first
|
51
|
+
params[:endkey] = key.last
|
52
|
+
end
|
53
|
+
params
|
54
|
+
end
|
55
|
+
|
56
|
+
def assert_valid_view_parameters(params)
|
57
|
+
params.keys.each do |key|
|
58
|
+
raise ArgumentError.new("invalid view parameter: #{key}") unless valid_view_parameters.include?(key.to_s)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def valid_view_parameters
|
63
|
+
%w(key keys startkey startkey_docid endkey endkey_docid limit stale descending skip group group_level reduce include_docs inclusive_end)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module View
|
3
|
+
# a view for custom map/reduce functions that still returns model instances
|
4
|
+
#
|
5
|
+
# example:
|
6
|
+
# view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :include_docs => true, :type => :custom, :reduce => nil
|
7
|
+
class CustomViewSpec < BaseViewSpec
|
8
|
+
def map_function
|
9
|
+
options[:map]
|
10
|
+
end
|
11
|
+
|
12
|
+
def reduce_function
|
13
|
+
options[:reduce]
|
14
|
+
end
|
15
|
+
|
16
|
+
def view_parameters
|
17
|
+
{:include_docs => options[:include_docs] || false}.merge(super)
|
18
|
+
end
|
19
|
+
|
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
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def count?
|
37
|
+
view_parameters[:reduce]
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'couch_potato/view/base_view_spec'
|
2
|
+
require 'couch_potato/view/model_view_spec'
|
3
|
+
require 'couch_potato/view/properties_view_spec'
|
4
|
+
require 'couch_potato/view/custom_view_spec'
|
5
|
+
require 'couch_potato/view/raw_view_spec'
|
6
|
+
|
7
|
+
|
8
|
+
module CouchPotato
|
9
|
+
module View
|
10
|
+
module CustomViews
|
11
|
+
|
12
|
+
def self.included(base) #:nodoc:
|
13
|
+
base.extend ClassMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def views(view_name = nil) #:nodoc:
|
18
|
+
if view_name
|
19
|
+
_find_view(view_name)
|
20
|
+
else
|
21
|
+
@views ||= {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def execute_view(view_name, view_parameters) #:nodoc:
|
26
|
+
view_spec_class(views(view_name)[:type]).new(self, view_name, views(view_name), view_parameters)
|
27
|
+
end
|
28
|
+
|
29
|
+
# Declare a CouchDB view, for examples on how to use see the *ViewSpec classes in CouchPotato::View
|
30
|
+
def view(view_name, options)
|
31
|
+
view_name = view_name.to_s
|
32
|
+
views[view_name] = options
|
33
|
+
method_str = "def #{view_name}(view_parameters = {}); execute_view(\"#{view_name}\", view_parameters); end"
|
34
|
+
self.instance_eval(method_str)
|
35
|
+
end
|
36
|
+
|
37
|
+
def view_spec_class(type) #:nodoc:
|
38
|
+
if type && type.is_a?(Class)
|
39
|
+
type
|
40
|
+
else
|
41
|
+
name = type.nil? ? 'Model' : type.to_s.camelize
|
42
|
+
CouchPotato::View.const_get("#{name}ViewSpec")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def _find_view(view) #:nodoc:
|
47
|
+
(@views && @views[view]) || (superclass._find_view(view) if superclass.respond_to?(:_find_view))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module View
|
3
|
+
module Lists
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def list(name, function)
|
10
|
+
lists[name] = function
|
11
|
+
end
|
12
|
+
|
13
|
+
def lists(name = nil)
|
14
|
+
if name.nil?
|
15
|
+
@lists ||= {}
|
16
|
+
else
|
17
|
+
(@lists && @lists[name]) || (superclass.lists(name) if superclass.respond_to?(:lists))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module View
|
3
|
+
# A view to return model instances by searching its properties.
|
4
|
+
# If you pass reduce => true will count instead
|
5
|
+
#
|
6
|
+
# example:
|
7
|
+
# view :my_view, :key => :name
|
8
|
+
#
|
9
|
+
# in addition you can pass in conditions as a javascript string
|
10
|
+
# view :my_view_only_completed, :key => :name, :conditions => 'doc.completed = true'
|
11
|
+
class ModelViewSpec < BaseViewSpec
|
12
|
+
|
13
|
+
def view_parameters
|
14
|
+
_super = super
|
15
|
+
if _super[:reduce]
|
16
|
+
_super
|
17
|
+
else
|
18
|
+
{:include_docs => true, :reduce => false}.merge(_super)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def map_function
|
23
|
+
map_body do
|
24
|
+
"emit(#{formatted_key(key)}, 1);"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def reduce_function
|
29
|
+
"function(key, values) {
|
30
|
+
return sum(values);
|
31
|
+
}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_results(results)
|
35
|
+
if count?
|
36
|
+
results['rows'].first.try(:[], 'value') || 0
|
37
|
+
else
|
38
|
+
results['rows'].map { |row| row['doc'] || row['id'] }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def map_body(&block)
|
45
|
+
"function(doc) {
|
46
|
+
if(doc.#{JSON.create_id} && doc.#{JSON.create_id} == '#{@klass.name}'#{conditions_js}) {
|
47
|
+
" + yield + "
|
48
|
+
}
|
49
|
+
}"
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
def conditions_js
|
54
|
+
" && (#{options[:conditions]})" if options[:conditions]
|
55
|
+
end
|
56
|
+
|
57
|
+
def count?
|
58
|
+
view_parameters[:reduce]
|
59
|
+
end
|
60
|
+
|
61
|
+
def key
|
62
|
+
options[:key]
|
63
|
+
end
|
64
|
+
|
65
|
+
def formatted_key(key)
|
66
|
+
if key.is_a? Array
|
67
|
+
'[' + key.map{|attribute| formatted_key(attribute)}.join(', ') + ']'
|
68
|
+
else
|
69
|
+
"doc['#{key}']"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module View
|
3
|
+
# A view to return model instances with only some properties poulated by searching its properties, e.g. for very large documents where you are only interested in some of their data
|
4
|
+
#
|
5
|
+
# example:
|
6
|
+
# view :my_view, :key => :name, :properties => [:name, :author], :type => :properties
|
7
|
+
class PropertiesViewSpec < ModelViewSpec
|
8
|
+
def map_function
|
9
|
+
map_body do
|
10
|
+
"emit(#{formatted_key(key)}, #{properties_for_map(properties)});"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def reduce_function
|
15
|
+
<<-JS
|
16
|
+
function(key, values, rereduce) {
|
17
|
+
if(rereduce) {
|
18
|
+
return sum(values);
|
19
|
+
} else {
|
20
|
+
return values.length;
|
21
|
+
}
|
22
|
+
}
|
23
|
+
JS
|
24
|
+
end
|
25
|
+
|
26
|
+
def process_results(results)
|
27
|
+
results['rows'].map do |row|
|
28
|
+
klass.json_create row['value'].merge(:_id => row['id'])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def view_parameters
|
33
|
+
{:include_docs => false}.merge(super)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def properties
|
39
|
+
options[:properties]
|
40
|
+
end
|
41
|
+
|
42
|
+
def properties_for_map(properties)
|
43
|
+
'{' + properties.map{|p| "#{p}: doc.#{p}"}.join(', ') + '}'
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module CouchPotato
|
2
|
+
module View
|
3
|
+
# A view for custom map/reduce functions that returns the raw data fromcouchdb
|
4
|
+
#
|
5
|
+
# example:
|
6
|
+
# view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :type => :raw, :reduce => nil
|
7
|
+
# optionally you can pass in a results filter which you can use to process the raw couchdb results before returning them
|
8
|
+
#
|
9
|
+
# example:
|
10
|
+
# view :my_custom_view, :map => "function(doc) { emit(doc._id, null); }", :type => :raw, :results_filter => lambda{|results| results['rows].map{|row| row['value']}}
|
11
|
+
class RawViewSpec < BaseViewSpec
|
12
|
+
def map_function
|
13
|
+
options[:map]
|
14
|
+
end
|
15
|
+
|
16
|
+
def process_results(results)
|
17
|
+
options[:results_filter] ? options[:results_filter].call(results) : results
|
18
|
+
end
|
19
|
+
|
20
|
+
def reduce_function
|
21
|
+
options[:reduce]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|