couch_potato-rails2 0.5.6

Sign up to get free protection for your applications and to get access to all the features.
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