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,50 @@
1
+ require 'spec_helper'
2
+
3
+ module LoadConst
4
+ def const_missing(name)
5
+ name = "#{self.name}::#{name}"
6
+ eval("#{name} = Class.new; #{name}.send(:include, CouchPotato::Persistence); #{name}.extend(LoadConst)")
7
+ end
8
+ end
9
+
10
+ class Autoloader
11
+ extend LoadConst
12
+ end
13
+
14
+ class WithUnloadedEmbedded
15
+ include CouchPotato::Persistence
16
+ extend LoadConst
17
+
18
+ property :embedded
19
+
20
+ view :all, :type => :custom, :map => 'function(doc) {emit(doc._id, null)}', :include_docs => true
21
+ end
22
+
23
+
24
+ describe CouchPotato::Database, 'rails specific behavior' do
25
+
26
+ before(:each) do
27
+ recreate_db
28
+ end
29
+
30
+ context 'load a document' do
31
+ it "should load models whose constants are currently uninitialized (like with rails in development mode)" do
32
+ CouchPotato.couchrest_database.save_doc(JSON.create_id => 'Autoloader::Uninitialized', '_id' => '1')
33
+ CouchPotato.database.load('1').class.name.should == 'Autoloader::Uninitialized'
34
+ end
35
+
36
+ it "should load nested models" do
37
+ CouchPotato.couchrest_database.save_doc(JSON.create_id => 'Autoloader::Nested::Nested2', '_id' => '1')
38
+ CouchPotato.database.load('1').class.name.should == 'Autoloader::Nested::Nested2'
39
+ end
40
+ end
41
+
42
+ context 'load documents using a view' do
43
+ it "should load models from a view whose constants are currently uninitialized" do
44
+ doc = {JSON.create_id => 'WithUnloadedEmbedded', '_id' => '1', 'embedded' => {JSON.create_id => 'WithUnloadedEmbedded::Uninitialized'}}
45
+ CouchPotato.couchrest_database.save_doc(doc)
46
+ CouchPotato.database.view(WithUnloadedEmbedded.all).first.embedded.class.name.should == 'WithUnloadedEmbedded::Uninitialized'
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require 'yaml'
3
+ require 'spec/mocks'
4
+
5
+ module Rails
6
+ def self.env
7
+ 'test'
8
+ end
9
+ class Railtie
10
+ def self.initializer(*args)
11
+ end
12
+ end
13
+
14
+ def self.root
15
+ RSpec::Mocks::Mock.new :join => ''
16
+ end
17
+ end
18
+
19
+ require 'couch_potato/railtie'
20
+
21
+ describe "railtie" do
22
+ before(:all) do
23
+ @validation_framework = CouchPotato::Config.validation_framework
24
+ end
25
+
26
+ after(:all) do
27
+ CouchPotato::Config.validation_framework = @validation_framework
28
+ end
29
+
30
+ context 'yaml file contains only database names' do
31
+ it "should set the database name from the yaml file" do
32
+ File.stub(:read => "test: test_db")
33
+
34
+ CouchPotato::Config.should_receive(:database_name=).with('test_db')
35
+
36
+ CouchPotato.rails_init
37
+ end
38
+ end
39
+
40
+ context 'yaml file contains more configuration' do
41
+ before(:each) do
42
+ File.stub(:read => "test: \n database: test_db\n validation_framework: :active_model")
43
+ end
44
+
45
+ it "should set the database name from the yaml file" do
46
+ CouchPotato::Config.should_receive(:database_name=).with('test_db')
47
+
48
+ CouchPotato.rails_init
49
+ end
50
+
51
+ it "should set the validation framework from the yaml file" do
52
+ CouchPotato::Config.should_receive(:validation_framework=).with(:active_model)
53
+
54
+ CouchPotato.rails_init
55
+ end
56
+ end
57
+
58
+ it "should process the yml file with erb" do
59
+ File.stub(:read => "test: \n database: <%= 'db' %>")
60
+
61
+ CouchPotato::Config.should_receive(:database_name=).with('db')
62
+
63
+ CouchPotato.rails_init
64
+ end
65
+ end
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format progress
@@ -0,0 +1,44 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+ require 'time'
4
+ require 'active_support'
5
+ require 'timecop'
6
+
7
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
8
+
9
+ require 'couch_potato'
10
+
11
+ CouchPotato::Config.database_name = ENV['DATABASE'] || 'couch_potato_test'
12
+ CouchPotato::Config.validation_framework = ENV['VALIDATION_FRAMEWORK'].to_sym unless ENV['VALIDATION_FRAMEWORK'].blank?
13
+
14
+ # silence deprecation warnings from ActiveModel as the Spec uses Errors#on
15
+ begin
16
+ ActiveSupport::Deprecation.silenced = true
17
+ rescue
18
+ # ignore errors, ActiveSupport is probably not installed
19
+ end
20
+
21
+ class Child
22
+ include CouchPotato::Persistence
23
+
24
+ property :text
25
+ end
26
+
27
+ class Comment
28
+ include CouchPotato::Persistence
29
+
30
+ validates_presence_of :title
31
+
32
+ property :title
33
+ end
34
+
35
+ def recreate_db
36
+ CouchPotato.couchrest_database.recreate!
37
+ end
38
+ recreate_db
39
+
40
+ RSpec::Matchers.define :string_matching do |regex|
41
+ match do |string|
42
+ string =~ regex
43
+ end
44
+ end
@@ -0,0 +1,98 @@
1
+ require 'spec_helper'
2
+
3
+ begin
4
+ require 'active_model'
5
+
6
+ describe 'ActiveModel conformance of couch potato objects' do
7
+ include ActiveModel::Lint::Tests
8
+
9
+ instance_methods.sort.select{|method| method.to_s.scan(/^test_/).first}.each do |method|
10
+ it "should #{method.to_s.sub(/^test_/, '').gsub('_', ' ')}" do
11
+ send method
12
+ end
13
+ end
14
+
15
+ class ActiveComment
16
+ include CouchPotato::Persistence
17
+ property :name
18
+ property :email
19
+ validates_presence_of :name, :email
20
+ validates_format_of :email, :with => /.+@.+/
21
+ end
22
+
23
+ before(:each) do
24
+ @model = ActiveComment.new
25
+ end
26
+
27
+ describe "#persisted?" do
28
+ it "should return false if it is a new document " do
29
+ @model.should_not be_persisted
30
+ end
31
+
32
+ it "should be true if it was saved" do
33
+ @comment = ActiveComment.new(:name => 'Thilo', :email => 'test@local.host')
34
+ CouchPotato.database.save_document! @comment
35
+ @comment.should be_persisted
36
+ end
37
+ end
38
+
39
+ describe "#to_key" do
40
+ it "should return nil if the document was not persisted" do
41
+ @model.to_key.should be_nil
42
+ end
43
+
44
+ it "should return the id of the document if it was persisted" do
45
+ @comment = ActiveComment.new(:name => 'Thilo', :email => 'test@local.host')
46
+ CouchPotato.database.save_document! @comment
47
+ @comment.to_key.should == [@comment.id]
48
+ end
49
+ end
50
+
51
+
52
+ describe "#errors" do
53
+ it "should return a single error as array" do
54
+ @model.valid?
55
+ @model.errors[:name].should be_kind_of(Array)
56
+ end
57
+
58
+ it "should return multiple errors as array" do
59
+ @model.valid?
60
+ @model.errors[:email].size.should == 2
61
+ end
62
+
63
+ it "should return no error as an empty array" do
64
+ @model.errors[:name].should == []
65
+ end
66
+
67
+ it "should be able to be Marshal.dump'ed" do
68
+ lambda { Marshal.dump(@model.errors) }.should_not raise_error
69
+ end
70
+ end
71
+
72
+ describe "#destroyed" do
73
+ it "should return destroyed if the object is deleted" do
74
+ @model._deleted = true
75
+ @model.should be_destroyed
76
+ end
77
+
78
+ it "should not return destroyed if it's not deleted" do
79
+ @model.should_not be_destroyed
80
+ end
81
+ end
82
+
83
+ def assert(boolean, message = '')
84
+ boolean || raise(message)
85
+ end
86
+
87
+ def assert_kind_of(klass, object)
88
+ object.should be_a(klass)
89
+ end
90
+ end
91
+
92
+ rescue LoadError
93
+ STDERR.puts "WARNING: active_model gem not installed. Not running ActiveModel specs."
94
+ end
95
+
96
+
97
+
98
+
@@ -0,0 +1,135 @@
1
+ require 'spec_helper'
2
+
3
+ class Plant
4
+ include CouchPotato::Persistence
5
+ property :leaf_count
6
+ property :typed_leaf_count, :type => Fixnum
7
+ property :typed_leaf_size, :type => Float
8
+ end
9
+
10
+ describe "attributes" do
11
+
12
+ describe 'attributes=' do
13
+ it "should assign the attributes" do
14
+ plant = Plant.new
15
+ plant.attributes = {:leaf_count => 1}
16
+ plant.leaf_count.should == 1
17
+ end
18
+ end
19
+
20
+ describe "attributes" do
21
+ it "should return the attributes" do
22
+ plant = Plant.new(:leaf_count => 1)
23
+ plant.attributes.should == {:leaf_count => 1, :created_at => nil, :updated_at => nil, :typed_leaf_count => nil, :typed_leaf_size => nil}
24
+ end
25
+ end
26
+
27
+ # useful when loading models from custom views
28
+ describe "accessing ghost attributes" do
29
+ it "should allow me to access attributes that are in the couchdb document but not defined as a property" do
30
+ plant = Plant.json_create({JSON.create_id => "Plant", "color" => "red", "leaf_count" => 1})
31
+ plant.color.should == 'red'
32
+ end
33
+
34
+ it "should raise a no method error when trying to read attributes that are not in the document" do
35
+ plant = Plant.json_create({JSON.create_id => "Plant", "leaf_count" => 1})
36
+ lambda do
37
+ plant.length
38
+ end.should raise_error(NoMethodError)
39
+ end
40
+
41
+ it "should raise a no method error if the document hasn't been loaded from the database" do
42
+ plant = Plant.new
43
+ lambda do
44
+ plant.length
45
+ end.should raise_error(NoMethodError, /undefined method `length'/)
46
+ end
47
+ end
48
+
49
+ describe 'typed attributes' do
50
+
51
+ before(:each) do
52
+ @plant = Plant.new
53
+ end
54
+
55
+ describe "fixnum" do
56
+ it "should convert a string into a fixnum" do
57
+ @plant.typed_leaf_count = '4'
58
+ @plant.typed_leaf_count.should == 4
59
+ end
60
+
61
+ it "should convert a string into a negative fixnum" do
62
+ @plant.typed_leaf_count = '-4'
63
+ @plant.typed_leaf_count.should == -4
64
+ end
65
+
66
+ it "should leave a fixnum as is" do
67
+ @plant.typed_leaf_count = 4
68
+ @plant.typed_leaf_count.should == 4
69
+ end
70
+
71
+ it "should leave nil as is" do
72
+ @plant.typed_leaf_count = nil
73
+ @plant.typed_leaf_count.should be_nil
74
+ end
75
+
76
+ it "should set the attributes to zero if a string given" do
77
+ @plant.typed_leaf_count = 'x'
78
+ @plant.typed_leaf_count.should == 0
79
+ end
80
+
81
+ it "should parse numbers out of a string" do
82
+ @plant.typed_leaf_count = 'x123'
83
+ @plant.typed_leaf_count.should == 123
84
+ end
85
+
86
+ it "should set the attributes to nil if given a blank string" do
87
+ @plant.typed_leaf_count = ''
88
+ @plant.typed_leaf_count.should be_nil
89
+ end
90
+ end
91
+
92
+ describe "float" do
93
+ it "should convert a number in a string with a decimal place" do
94
+ @plant.typed_leaf_size = '0.5001'
95
+ @plant.typed_leaf_size.should == 0.5001
96
+ end
97
+
98
+ it "should convert a number in a string without a decimal place" do
99
+ @plant.typed_leaf_size = '5'
100
+ @plant.typed_leaf_size.should == 5.0
101
+ end
102
+
103
+ it "should convert a negative number in a string" do
104
+ @plant.typed_leaf_size = '-5.0'
105
+ @plant.typed_leaf_size.should == -5.0
106
+ end
107
+
108
+ it "should leave a float as it is" do
109
+ @plant.typed_leaf_size = 0.5
110
+ @plant.typed_leaf_size.should == 0.5
111
+ end
112
+
113
+ it "should leave nil as is" do
114
+ @plant.typed_leaf_size = nil
115
+ @plant.typed_leaf_size.should be_nil
116
+ end
117
+
118
+ it "should set the attributes to zero if a string given" do
119
+ @plant.typed_leaf_size = 'x'
120
+ @plant.typed_leaf_size.should == 0
121
+ end
122
+
123
+ it "should parse numbers out of a string" do
124
+ @plant.typed_leaf_size = 'x00.123'
125
+ @plant.typed_leaf_size.should == 0.123
126
+ end
127
+
128
+ it "should set the attributes to nil if given a blank string" do
129
+ @plant.typed_leaf_size = ''
130
+ @plant.typed_leaf_size.should be_nil
131
+ end
132
+ end
133
+ end
134
+ end
135
+
@@ -0,0 +1,106 @@
1
+ require 'spec_helper'
2
+
3
+ describe CouchPotato::View::BaseViewSpec, 'initialize' do
4
+ describe "view parameters" do
5
+ before(:each) do
6
+ CouchPotato::Config.split_design_documents_per_view = false
7
+ end
8
+
9
+ it "should raise an error when passing invalid view parameters" do
10
+ lambda {
11
+ CouchPotato::View::BaseViewSpec.new Object, 'all', {}, {:start_key => '1'}
12
+ }.should raise_error(ArgumentError, "invalid view parameter: start_key")
13
+ end
14
+
15
+ it "should not raise an error when passing valid view parameters" do
16
+ lambda {
17
+ CouchPotato::View::BaseViewSpec.new Object, 'all', {}, {
18
+ :key => 'keyvalue',
19
+ :startkey => 'keyvalue',
20
+ :startkey_docid => 'docid',
21
+ :endkey => 'keyvalue',
22
+ :endkey_docid => 'docid',
23
+ :limit => 3,
24
+ :stale => 'ok',
25
+ :descending => true,
26
+ :skip => 1,
27
+ :group => true,
28
+ :group_level => 1,
29
+ :reduce => false,
30
+ :include_docs => true,
31
+ :inclusive_end => true
32
+ }
33
+ }.should_not raise_error
34
+ end
35
+
36
+ it "should remove stale when it's nil" do
37
+ spec = CouchPotato::View::BaseViewSpec.new Object, 'all', {}, {:stale => nil}
38
+ spec.view_parameters.should == {}
39
+ end
40
+
41
+ it "should convert a range passed as key into startkey and endkey" do
42
+ spec = CouchPotato::View::BaseViewSpec.new Object, 'all', {}, {:key => '1'..'2'}
43
+ spec.view_parameters.should == {:startkey => '1', :endkey => '2'}
44
+ end
45
+
46
+ it "should convert a plain value to a hash with a key" do
47
+ spec = CouchPotato::View::BaseViewSpec.new Object, 'all', {}, '2'
48
+ spec.view_parameters.should == {:key => '2'}
49
+ end
50
+
51
+ it "should generate the design document path by snake_casing the class name but keeping double colons" do
52
+ spec = CouchPotato::View::BaseViewSpec.new 'Foo::BarBaz', '', {}, ''
53
+ spec.design_document.should == 'foo::bar_baz'
54
+ end
55
+
56
+ it "should generate the design document independent of the view name by default" do
57
+ CouchPotato::Config.split_design_documents_per_view = false
58
+ spec = CouchPotato::View::BaseViewSpec.new 'User', 'by_login_and_email', {}, ''
59
+ spec.design_document.should == 'user'
60
+ end
61
+
62
+ it "should generate the design document per view if configured to" do
63
+ CouchPotato::Config.split_design_documents_per_view = true
64
+ spec = CouchPotato::View::BaseViewSpec.new 'User', 'by_login_and_email', {}, ''
65
+ spec.design_document.should == 'user_view_by_login_and_email'
66
+ end
67
+
68
+ it "should generate the design document independent of the list name by default" do
69
+ CouchPotato::Config.split_design_documents_per_view = false
70
+ spec = CouchPotato::View::BaseViewSpec.new stub(:lists => nil, :to_s => 'User'), '', {:list => 'test_list'}, {}
71
+ spec.design_document.should == 'user'
72
+ end
73
+
74
+ it "should generate the design document per view if configured to" do
75
+ CouchPotato::Config.split_design_documents_per_view = true
76
+ spec = CouchPotato::View::BaseViewSpec.new stub(:lists => nil, :to_s => 'User'), '', {:list => :test_list}, {}
77
+ spec.design_document.should == 'user_list_test_list'
78
+ end
79
+
80
+ it "should extract the list name from the options" do
81
+ spec = CouchPotato::View::BaseViewSpec.new stub(:lists => nil), 'all', {:list => :test_list}, {}
82
+ spec.list_name.should == :test_list
83
+ end
84
+
85
+ it "should extract the list from the view parameters" do
86
+ spec = CouchPotato::View::BaseViewSpec.new stub(:lists => nil), 'all', {}, {:list => :test_list}
87
+ spec.list_name.should == :test_list
88
+ end
89
+
90
+ it "should prefer the list name from the view parameters over the one from the options" do
91
+ spec = CouchPotato::View::BaseViewSpec.new stub(:lists => nil), 'all', {:list => 'my_list'}, {:list => :test_list}
92
+ spec.list_name.should == :test_list
93
+ end
94
+
95
+ it "should return the list function" do
96
+ klass = stub 'class'
97
+ klass.stub(:lists).with('test_list').and_return('<list_code>')
98
+ spec = CouchPotato::View::BaseViewSpec.new klass, 'all', {:list => 'test_list'}, {}
99
+ spec.list_function.should == '<list_code>'
100
+ end
101
+
102
+ end
103
+
104
+ end
105
+
106
+