couch_potato-rails2 0.5.6

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 (88) hide show
  1. data/.gitignore +7 -0
  2. data/.travis.yml +5 -0
  3. data/CHANGES.md +148 -0
  4. data/CREDITS +6 -0
  5. data/Gemfile +4 -0
  6. data/MIT-LICENSE.txt +19 -0
  7. data/README.md +450 -0
  8. data/Rakefile +82 -0
  9. data/couch_potato.gemspec +27 -0
  10. data/init.rb +3 -0
  11. data/lib/core_ext/date.rb +14 -0
  12. data/lib/core_ext/object.rb +5 -0
  13. data/lib/core_ext/string.rb +12 -0
  14. data/lib/core_ext/symbol.rb +15 -0
  15. data/lib/core_ext/time.rb +23 -0
  16. data/lib/couch_potato.rb +48 -0
  17. data/lib/couch_potato/database.rb +179 -0
  18. data/lib/couch_potato/persistence.rb +124 -0
  19. data/lib/couch_potato/persistence/active_model_compliance.rb +44 -0
  20. data/lib/couch_potato/persistence/attachments.rb +31 -0
  21. data/lib/couch_potato/persistence/callbacks.rb +29 -0
  22. data/lib/couch_potato/persistence/dirty_attributes.rb +56 -0
  23. data/lib/couch_potato/persistence/ghost_attributes.rb +12 -0
  24. data/lib/couch_potato/persistence/json.rb +47 -0
  25. data/lib/couch_potato/persistence/magic_timestamps.rb +23 -0
  26. data/lib/couch_potato/persistence/properties.rb +79 -0
  27. data/lib/couch_potato/persistence/simple_property.rb +82 -0
  28. data/lib/couch_potato/persistence/type_caster.rb +40 -0
  29. data/lib/couch_potato/railtie.rb +25 -0
  30. data/lib/couch_potato/rspec.rb +2 -0
  31. data/lib/couch_potato/rspec/matchers.rb +39 -0
  32. data/lib/couch_potato/rspec/matchers/json2.js +482 -0
  33. data/lib/couch_potato/rspec/matchers/list_as_matcher.rb +54 -0
  34. data/lib/couch_potato/rspec/matchers/map_to_matcher.rb +49 -0
  35. data/lib/couch_potato/rspec/matchers/print_r.js +60 -0
  36. data/lib/couch_potato/rspec/matchers/reduce_to_matcher.rb +50 -0
  37. data/lib/couch_potato/rspec/stub_db.rb +46 -0
  38. data/lib/couch_potato/validation.rb +16 -0
  39. data/lib/couch_potato/validation/with_active_model.rb +27 -0
  40. data/lib/couch_potato/validation/with_validatable.rb +41 -0
  41. data/lib/couch_potato/version.rb +3 -0
  42. data/lib/couch_potato/view/base_view_spec.rb +84 -0
  43. data/lib/couch_potato/view/custom_view_spec.rb +42 -0
  44. data/lib/couch_potato/view/custom_views.rb +52 -0
  45. data/lib/couch_potato/view/lists.rb +23 -0
  46. data/lib/couch_potato/view/model_view_spec.rb +75 -0
  47. data/lib/couch_potato/view/properties_view_spec.rb +47 -0
  48. data/lib/couch_potato/view/raw_view_spec.rb +25 -0
  49. data/lib/couch_potato/view/view_query.rb +82 -0
  50. data/rails/init.rb +4 -0
  51. data/rails/reload_classes.rb +47 -0
  52. data/spec/attachments_spec.rb +23 -0
  53. data/spec/callbacks_spec.rb +297 -0
  54. data/spec/create_spec.rb +35 -0
  55. data/spec/custom_view_spec.rb +239 -0
  56. data/spec/default_property_spec.rb +38 -0
  57. data/spec/destroy_spec.rb +29 -0
  58. data/spec/fixtures/address.rb +10 -0
  59. data/spec/fixtures/person.rb +6 -0
  60. data/spec/property_spec.rb +323 -0
  61. data/spec/rails_spec.rb +50 -0
  62. data/spec/railtie_spec.rb +65 -0
  63. data/spec/spec.opts +2 -0
  64. data/spec/spec_helper.rb +44 -0
  65. data/spec/unit/active_model_compliance_spec.rb +98 -0
  66. data/spec/unit/attributes_spec.rb +135 -0
  67. data/spec/unit/base_view_spec_spec.rb +106 -0
  68. data/spec/unit/callbacks_spec.rb +46 -0
  69. data/spec/unit/couch_potato_spec.rb +39 -0
  70. data/spec/unit/create_spec.rb +69 -0
  71. data/spec/unit/custom_views_spec.rb +15 -0
  72. data/spec/unit/database_spec.rb +317 -0
  73. data/spec/unit/date_spec.rb +22 -0
  74. data/spec/unit/dirty_attributes_spec.rb +136 -0
  75. data/spec/unit/initialize_spec.rb +38 -0
  76. data/spec/unit/json_spec.rb +30 -0
  77. data/spec/unit/lists_spec.rb +20 -0
  78. data/spec/unit/model_view_spec_spec.rb +13 -0
  79. data/spec/unit/properties_view_spec_spec.rb +31 -0
  80. data/spec/unit/rspec_matchers_spec.rb +124 -0
  81. data/spec/unit/rspec_stub_db_spec.rb +35 -0
  82. data/spec/unit/string_spec.rb +7 -0
  83. data/spec/unit/time_spec.rb +15 -0
  84. data/spec/unit/validation_spec.rb +67 -0
  85. data/spec/unit/view_query_spec.rb +86 -0
  86. data/spec/update_spec.rb +40 -0
  87. data/spec/view_updates_spec.rb +28 -0
  88. metadata +243 -0
@@ -0,0 +1,54 @@
1
+ module CouchPotato
2
+ module RSpec
3
+ class ListAsProxy
4
+ def initialize(results_ruby)
5
+ @results_ruby = results_ruby
6
+ end
7
+
8
+ def as(expected_ruby)
9
+ ListAsMatcher.new(expected_ruby, @results_ruby)
10
+ end
11
+ end
12
+
13
+ class ListAsMatcher
14
+ include RunJS
15
+
16
+ def initialize(expected_ruby, results_ruby)
17
+ @expected_ruby = expected_ruby
18
+ @results_ruby = results_ruby
19
+ end
20
+
21
+ def matches?(view_spec)
22
+ js = <<-JS
23
+ #{File.read(File.dirname(__FILE__) + '/print_r.js')}
24
+ #{File.read(File.dirname(__FILE__) + '/json2.js')}
25
+ var results = #{@results_ruby.to_json};
26
+ var listed = '';
27
+ var list = #{view_spec.list_function};
28
+
29
+ var getRow = function() {
30
+ return results.rows.shift();
31
+ };
32
+ var send = function(text) {
33
+ listed = listed + text;
34
+ };
35
+ list();
36
+ print(print_r(JSON.parse(listed)));
37
+ JS
38
+
39
+ @actual_ruby = JSON.parse(run_js(js))
40
+
41
+ @expected_ruby == @actual_ruby
42
+ end
43
+
44
+ def failure_message_for_should
45
+ "Expected to list as #{@expected_ruby.inspect} but got #{@actual_ruby.inspect}."
46
+ end
47
+
48
+ def failure_message_for_should_not
49
+ "Expected to not list as #{@expected_ruby.inspect} but did."
50
+ end
51
+
52
+ end
53
+ end
54
+ end
@@ -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,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 = RSpec::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 RSpec
41
+ module Mocks
42
+ module ExampleMethods
43
+ include CouchPotato::RSpec::StubDb
44
+ end
45
+ end
46
+ 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,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,41 @@
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_with_before_validation_callback?
15
+ errors.clear
16
+ run_callbacks :validation do
17
+ before_validation_errors = errors.errors.dup
18
+ valid_without_before_validation_callback?
19
+ before_validation_errors.each do |k, v|
20
+ v.each {|message| errors.add(k, message)}
21
+ end
22
+ end
23
+ errors.empty?
24
+ end
25
+
26
+ alias_method :valid_without_before_validation_callback?, :valid?
27
+ alias_method :valid?, :valid_with_before_validation_callback?
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ # add [] method to Validatable's implementation of the Errors class
35
+ module Validatable
36
+ class Errors
37
+ def [](attribute)
38
+ [on(attribute)].flatten.compact
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module CouchPotato
2
+ VERSION = "0.5.6"
3
+ end
@@ -0,0 +1,84 @@
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
+ @design_document = translate_to_design_doc_name(klass.to_s, view_name, @list_name)
15
+ @view_name = view_name
16
+ @options = options
17
+
18
+ @list_function = klass.lists(@list_name) if @list_name
19
+ @view_parameters = {}
20
+ [:group, :include_docs, :descending, :group_level, :limit].each do |key|
21
+ @view_parameters[key] = options[key] if options.include?(key)
22
+ end
23
+ @view_parameters.merge!(normalized_view_parameters)
24
+ end
25
+
26
+ def process_results(results)
27
+ results
28
+ end
29
+
30
+ private
31
+
32
+ def normalize_view_parameters(params)
33
+ normalized_params = params.dup
34
+ hash = wrap_in_hash params
35
+ remove_nil_stale(replace_range_key(hash))
36
+ end
37
+
38
+ def remove_nil_stale(params)
39
+ params.reject{|name, value| name.to_s == 'stale' && value.nil?}
40
+ end
41
+
42
+ def wrap_in_hash(params)
43
+ if params.is_a?(Hash)
44
+ params
45
+ else
46
+ {:key => params}
47
+ end
48
+ end
49
+
50
+ def replace_range_key(params)
51
+ if((key = params[:key]).is_a?(Range))
52
+ params.delete :key
53
+ params[:startkey] = key.first
54
+ params[:endkey] = key.last
55
+ end
56
+ params
57
+ end
58
+
59
+ def assert_valid_view_parameters(params)
60
+ params.keys.each do |key|
61
+ raise ArgumentError.new("invalid view parameter: #{key}") unless valid_view_parameters.include?(key.to_s)
62
+ end
63
+ end
64
+
65
+ def valid_view_parameters
66
+ %w(key keys startkey startkey_docid endkey endkey_docid limit stale descending skip group group_level reduce include_docs inclusive_end)
67
+ end
68
+
69
+ def translate_to_design_doc_name(klass_name, view_name, list_name)
70
+ klass_name = klass_name.dup
71
+ klass_name.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
72
+ klass_name.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
73
+ klass_name.tr!("-", "_")
74
+ doc_name = klass_name.downcase
75
+
76
+ if CouchPotato::Config.split_design_documents_per_view
77
+ doc_name += "_view_#{view_name}" if view_name.present?
78
+ doc_name += "_list_#{list_name}" if list_name.present?
79
+ end
80
+ doc_name
81
+ end
82
+ end
83
+ end
84
+ 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