seomoz-ripple 1.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. data/Gemfile +20 -0
  2. data/Guardfile +15 -0
  3. data/Rakefile +88 -0
  4. data/lib/rails/generators/ripple/configuration/configuration_generator.rb +13 -0
  5. data/lib/rails/generators/ripple/configuration/templates/ripple.yml +24 -0
  6. data/lib/rails/generators/ripple/js/js_generator.rb +13 -0
  7. data/lib/rails/generators/ripple/js/templates/js/contrib.js +63 -0
  8. data/lib/rails/generators/ripple/js/templates/js/iso8601.js +76 -0
  9. data/lib/rails/generators/ripple/js/templates/js/ripple.js +132 -0
  10. data/lib/rails/generators/ripple/model/model_generator.rb +20 -0
  11. data/lib/rails/generators/ripple/model/templates/model.rb +10 -0
  12. data/lib/rails/generators/ripple/observer/observer_generator.rb +16 -0
  13. data/lib/rails/generators/ripple/observer/templates/observer.rb +4 -0
  14. data/lib/rails/generators/ripple/test/templates/test_server.rb +46 -0
  15. data/lib/rails/generators/ripple/test/test_generator.rb +39 -0
  16. data/lib/rails/generators/ripple_generator.rb +78 -0
  17. data/lib/ripple.rb +79 -0
  18. data/lib/ripple/associations.rb +356 -0
  19. data/lib/ripple/associations/embedded.rb +35 -0
  20. data/lib/ripple/associations/instantiators.rb +26 -0
  21. data/lib/ripple/associations/linked.rb +65 -0
  22. data/lib/ripple/associations/many.rb +38 -0
  23. data/lib/ripple/associations/many_embedded_proxy.rb +38 -0
  24. data/lib/ripple/associations/many_linked_proxy.rb +66 -0
  25. data/lib/ripple/associations/many_reference_proxy.rb +93 -0
  26. data/lib/ripple/associations/many_stored_key_proxy.rb +76 -0
  27. data/lib/ripple/associations/one.rb +20 -0
  28. data/lib/ripple/associations/one_embedded_proxy.rb +35 -0
  29. data/lib/ripple/associations/one_key_proxy.rb +58 -0
  30. data/lib/ripple/associations/one_linked_proxy.rb +22 -0
  31. data/lib/ripple/associations/one_stored_key_proxy.rb +43 -0
  32. data/lib/ripple/associations/proxy.rb +118 -0
  33. data/lib/ripple/attribute_methods.rb +118 -0
  34. data/lib/ripple/attribute_methods/dirty.rb +50 -0
  35. data/lib/ripple/attribute_methods/query.rb +34 -0
  36. data/lib/ripple/attribute_methods/read.rb +26 -0
  37. data/lib/ripple/attribute_methods/write.rb +25 -0
  38. data/lib/ripple/callbacks.rb +74 -0
  39. data/lib/ripple/conflict/basic_resolver.rb +82 -0
  40. data/lib/ripple/conflict/document_hooks.rb +20 -0
  41. data/lib/ripple/conflict/resolver.rb +71 -0
  42. data/lib/ripple/conflict/test_helper.rb +33 -0
  43. data/lib/ripple/conversion.rb +28 -0
  44. data/lib/ripple/core_ext.rb +2 -0
  45. data/lib/ripple/core_ext/casting.rb +148 -0
  46. data/lib/ripple/core_ext/object.rb +8 -0
  47. data/lib/ripple/document.rb +104 -0
  48. data/lib/ripple/document/bucket_access.rb +25 -0
  49. data/lib/ripple/document/finders.rb +131 -0
  50. data/lib/ripple/document/key.rb +43 -0
  51. data/lib/ripple/document/link.rb +30 -0
  52. data/lib/ripple/document/persistence.rb +115 -0
  53. data/lib/ripple/embedded_document.rb +64 -0
  54. data/lib/ripple/embedded_document/around_callbacks.rb +18 -0
  55. data/lib/ripple/embedded_document/finders.rb +26 -0
  56. data/lib/ripple/embedded_document/persistence.rb +77 -0
  57. data/lib/ripple/i18n.rb +2 -0
  58. data/lib/ripple/inspection.rb +32 -0
  59. data/lib/ripple/locale/en.yml +21 -0
  60. data/lib/ripple/nested_attributes.rb +265 -0
  61. data/lib/ripple/observable.rb +28 -0
  62. data/lib/ripple/properties.rb +73 -0
  63. data/lib/ripple/property_type_mismatch.rb +12 -0
  64. data/lib/ripple/railtie.rb +13 -0
  65. data/lib/ripple/serialization.rb +84 -0
  66. data/lib/ripple/timestamps.rb +27 -0
  67. data/lib/ripple/translation.rb +14 -0
  68. data/lib/ripple/validations.rb +67 -0
  69. data/lib/ripple/validations/associated_validator.rb +43 -0
  70. data/ripple.gemspec +46 -0
  71. data/seomoz-ripple.gemspec +46 -0
  72. data/spec/fixtures/config.yml +8 -0
  73. data/spec/integration/ripple/associations_spec.rb +220 -0
  74. data/spec/integration/ripple/conflict_resolution_spec.rb +293 -0
  75. data/spec/integration/ripple/nested_attributes_spec.rb +264 -0
  76. data/spec/integration/ripple/persistence_spec.rb +57 -0
  77. data/spec/integration/ripple/search_associations_spec.rb +42 -0
  78. data/spec/ripple/associations/many_embedded_proxy_spec.rb +122 -0
  79. data/spec/ripple/associations/many_linked_proxy_spec.rb +191 -0
  80. data/spec/ripple/associations/many_reference_proxy_spec.rb +170 -0
  81. data/spec/ripple/associations/many_stored_key_proxy_spec.rb +158 -0
  82. data/spec/ripple/associations/one_embedded_proxy_spec.rb +125 -0
  83. data/spec/ripple/associations/one_key_proxy_spec.rb +82 -0
  84. data/spec/ripple/associations/one_linked_proxy_spec.rb +91 -0
  85. data/spec/ripple/associations/one_stored_key_proxy_spec.rb +72 -0
  86. data/spec/ripple/associations/proxy_spec.rb +84 -0
  87. data/spec/ripple/associations_spec.rb +129 -0
  88. data/spec/ripple/attribute_methods/dirty_spec.rb +80 -0
  89. data/spec/ripple/attribute_methods_spec.rb +230 -0
  90. data/spec/ripple/bucket_access_spec.rb +25 -0
  91. data/spec/ripple/callbacks_spec.rb +176 -0
  92. data/spec/ripple/conflict/resolver_spec.rb +42 -0
  93. data/spec/ripple/conversion_spec.rb +22 -0
  94. data/spec/ripple/core_ext_spec.rb +103 -0
  95. data/spec/ripple/document/link_spec.rb +67 -0
  96. data/spec/ripple/document_spec.rb +96 -0
  97. data/spec/ripple/embedded_document/finders_spec.rb +29 -0
  98. data/spec/ripple/embedded_document/persistence_spec.rb +80 -0
  99. data/spec/ripple/embedded_document_spec.rb +84 -0
  100. data/spec/ripple/finders_spec.rb +217 -0
  101. data/spec/ripple/inspection_spec.rb +51 -0
  102. data/spec/ripple/key_spec.rb +30 -0
  103. data/spec/ripple/observable_spec.rb +121 -0
  104. data/spec/ripple/persistence_spec.rb +326 -0
  105. data/spec/ripple/properties_spec.rb +262 -0
  106. data/spec/ripple/ripple_spec.rb +71 -0
  107. data/spec/ripple/serialization_spec.rb +51 -0
  108. data/spec/ripple/timestamps_spec.rb +76 -0
  109. data/spec/ripple/validations/associated_validator_spec.rb +77 -0
  110. data/spec/ripple/validations_spec.rb +104 -0
  111. data/spec/spec_helper.rb +26 -0
  112. data/spec/support/associations.rb +1 -0
  113. data/spec/support/associations/proxies.rb +17 -0
  114. data/spec/support/integration_setup.rb +11 -0
  115. data/spec/support/mocks.rb +4 -0
  116. data/spec/support/models.rb +4 -0
  117. data/spec/support/models/address.rb +12 -0
  118. data/spec/support/models/box.rb +13 -0
  119. data/spec/support/models/car.rb +16 -0
  120. data/spec/support/models/cardboard_box.rb +3 -0
  121. data/spec/support/models/clock.rb +12 -0
  122. data/spec/support/models/clock_observer.rb +3 -0
  123. data/spec/support/models/company.rb +23 -0
  124. data/spec/support/models/customer.rb +4 -0
  125. data/spec/support/models/driver.rb +6 -0
  126. data/spec/support/models/email.rb +4 -0
  127. data/spec/support/models/engine.rb +5 -0
  128. data/spec/support/models/family.rb +16 -0
  129. data/spec/support/models/favorite.rb +4 -0
  130. data/spec/support/models/invoice.rb +7 -0
  131. data/spec/support/models/late_invoice.rb +3 -0
  132. data/spec/support/models/ninja.rb +9 -0
  133. data/spec/support/models/note.rb +5 -0
  134. data/spec/support/models/page.rb +4 -0
  135. data/spec/support/models/paid_invoice.rb +4 -0
  136. data/spec/support/models/passenger.rb +6 -0
  137. data/spec/support/models/profile.rb +10 -0
  138. data/spec/support/models/seat.rb +5 -0
  139. data/spec/support/models/subscription.rb +27 -0
  140. data/spec/support/models/tasks.rb +14 -0
  141. data/spec/support/models/team.rb +11 -0
  142. data/spec/support/models/transactions.rb +17 -0
  143. data/spec/support/models/tree.rb +4 -0
  144. data/spec/support/models/user.rb +10 -0
  145. data/spec/support/models/wheel.rb +6 -0
  146. data/spec/support/models/widget.rb +22 -0
  147. data/spec/support/test_server.rb +18 -0
  148. data/spec/support/test_server.yml.example +2 -0
  149. metadata +362 -0
@@ -0,0 +1,77 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ripple::Validations::AssociatedValidator do
4
+ context 'for a one association' do
5
+ require 'support/models/family'
6
+
7
+ let(:child) { Child.new }
8
+ let(:parent) { Parent.new }
9
+ before(:each) { parent.child = child }
10
+
11
+ it "is invalid when the associated record is invalid" do
12
+ child.should_not be_valid
13
+ parent.should_not be_valid
14
+ end
15
+
16
+ it "includes the associated record's validation error messages in the error message" do
17
+ parent.valid?
18
+ parent.errors[:child].size.should == 1
19
+ parent.errors[:child].first.should =~ /is invalid \(Name can't be blank and Age can't be blank\)/
20
+ end
21
+
22
+ it "is valid when the associated record is valid" do
23
+ child.name = 'Coen'
24
+ child.age = 1
25
+ child.should be_valid
26
+ parent.should be_valid
27
+ end
28
+ end
29
+
30
+ context 'for a many association' do
31
+ require 'support/models/team'
32
+
33
+ let(:team) { Team.new }
34
+ let(:ichiro) { Player.new(:name => 'Ichiro', :position => 'Right Field') }
35
+ let(:satchel_paige) { Player.new(:position => 'Pitcher') }
36
+ let(:unknown) { Player.new }
37
+
38
+ before(:each) do
39
+ team.players << ichiro
40
+ team.players << satchel_paige
41
+ team.players << unknown
42
+ end
43
+
44
+ it 'is invalid when the associated records are invalid' do
45
+ satchel_paige.should_not be_valid
46
+ unknown.should_not be_valid
47
+ team.should_not be_valid
48
+ end
49
+
50
+ it "includes the associated records' validation error messages in the error message" do
51
+ team.valid?
52
+ team.errors[:players].size.should == 1
53
+
54
+ expected_errors = [
55
+ "for player 2: Name can't be blank",
56
+ "for player 3: Name can't be blank and Position can't be blank"
57
+ ]
58
+
59
+ expected_errors = expected_errors.join('; ')
60
+ team.errors[:players].first.should == "are invalid (#{expected_errors})"
61
+ end
62
+
63
+ it 'is valid when the associated records are all valid' do
64
+ ichiro.position = 'Right Field'
65
+ ichiro.should be_valid
66
+
67
+ satchel_paige.name = 'Satchel Paige'
68
+ satchel_paige.should be_valid
69
+
70
+ unknown.name = 'John Doe'
71
+ unknown.position = 'First Base'
72
+ unknown.should be_valid
73
+
74
+ team.should be_valid
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,104 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ripple::Validations do
4
+ require 'support/models/box'
5
+
6
+ before :each do
7
+ @box = Box.new
8
+ @client = Ripple.client
9
+ @client.stub!(:backend).and_return(mock("Backend", :store_object => true))
10
+ end
11
+
12
+ it "should add validation declarations to the class" do
13
+ [:validates, :validate, :validates_with, :validates_each,
14
+ :validates_acceptance_of, :validates_confirmation_of, :validates_exclusion_of,
15
+ :validates_format_of, :validates_inclusion_of, :validates_length_of,
16
+ :validates_numericality_of, :validates_presence_of].each do |meth|
17
+ Box.should respond_to(meth)
18
+ end
19
+ end
20
+
21
+ it "should add validation methods to the instance" do
22
+ %w{errors valid? invalid?}.each do |meth|
23
+ @box.should respond_to(meth)
24
+ end
25
+ end
26
+
27
+ it "should override save to run validations" do
28
+ @box.should_receive(:valid?).and_return(false)
29
+ @box.save.should be_false
30
+ end
31
+
32
+ it "should allow skipping validations by passing save :validate => false" do
33
+ Ripple.client.http.stub!(:perform).and_return(mock_response)
34
+ @box.should_not_receive(:valid?)
35
+ @box.save(:validate => false).should be_true
36
+ end
37
+
38
+ describe "when using save! on an invalid record" do
39
+ before(:each) { @box.stub!(:valid?).and_return(false) }
40
+
41
+ it "should raise DocumentInvalid" do
42
+ lambda { @box.save! }.should raise_error(Ripple::DocumentInvalid)
43
+ end
44
+
45
+ it "should raise an exception that has the invalid document" do
46
+ begin
47
+ @box.save!
48
+ rescue Ripple::DocumentInvalid => invalid
49
+ invalid.document.should == @box
50
+ end
51
+ end
52
+ end
53
+
54
+ it "should not raise an error when save! is called and the document is valid" do
55
+ @box.stub!(:save).and_return(true)
56
+ @box.stub!(:valid?).and_return(true)
57
+ lambda { @box.save! }.should_not raise_error(Ripple::DocumentInvalid)
58
+ end
59
+
60
+ it "should return true from save! when no exception is raised" do
61
+ @box.stub!(:save).and_return(true)
62
+ @box.stub!(:valid?).and_return(true)
63
+ @box.save!.should be_true
64
+ end
65
+
66
+ it "should allow unexpected exceptions to be raised" do
67
+ robject = mock("robject", :key => @box.key, "data=" => true, "content_type=" => true)
68
+ robject.should_receive(:store).and_raise(Riak::HTTPFailedRequest.new(:post, 200, 404, {}, "404 not found"))
69
+ @box.stub!(:robject).and_return(robject)
70
+ @box.stub!(:valid?).and_return(true)
71
+ lambda { @box.save! }.should raise_error(Riak::FailedRequest)
72
+ end
73
+
74
+ it "should not raise an error when creating a box with create! succeeds" do
75
+ @box.stub!(:new?).and_return(false)
76
+ Box.stub(:create).and_return(@box)
77
+ lambda { @new_box = Box.create! }.should_not raise_error(Ripple::DocumentInvalid)
78
+ @new_box.should == @box
79
+ end
80
+
81
+ it "should raise an error when creating a box with create! fails" do
82
+ @box.stub!(:new?).and_return(true)
83
+ Box.stub(:create).and_return(@box)
84
+ lambda { Box.create! }.should raise_error(Ripple::DocumentInvalid)
85
+ end
86
+
87
+ it "should automatically add validations from property options" do
88
+ Box.property :size, Integer, :inclusion => {:in => 1..30 }
89
+ @box.size = 0
90
+ @box.should_not be_valid
91
+ Box.properties.delete :size
92
+ end
93
+
94
+ it "should run validations at the correct lifecycle state" do
95
+ Box.property :size, Integer, :inclusion => {:in => 1..30, :on => :update }
96
+ @box.size = 0
97
+ @box.should be_valid
98
+ Box.properties.delete :size
99
+ end
100
+
101
+ after :each do
102
+ Box.reset_callbacks(:validate)
103
+ end
104
+ end
@@ -0,0 +1,26 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', '..', 'riak-client','lib'))
4
+
5
+ require 'rubygems' # Use the gems path only for the spec suite
6
+ require 'ripple'
7
+ require 'ripple/conflict/test_helper'
8
+ require 'rspec'
9
+
10
+ # Only the tests should really get away with this.
11
+ Riak.disable_list_keys_warnings = true
12
+
13
+ Dir[File.join(File.dirname(__FILE__), "support", "*.rb")].sort.each {|f| require f }
14
+
15
+ RSpec.configure do |config|
16
+ config.mock_with :rspec
17
+ config.after(:each) do
18
+ $test_server.recycle if $test_server
19
+ end
20
+
21
+ config.treat_symbols_as_metadata_keys_with_true_values = true
22
+ config.filter_run :focus
23
+ config.run_all_when_everything_filtered = true
24
+ config.debug = true
25
+ config.include Ripple::Conflict::TestHelper
26
+ end
@@ -0,0 +1 @@
1
+ require File.expand_path("../associations/proxies", __FILE__)
@@ -0,0 +1,17 @@
1
+
2
+
3
+ class FakeNilProxy < Ripple::Associations::Proxy
4
+ def find_target; nil end
5
+ end
6
+
7
+ class FakeBlankProxy < Ripple::Associations::Proxy
8
+ def find_target; '' end
9
+ end
10
+
11
+ class FakeNumberProxy < Ripple::Associations::Proxy
12
+ def find_target; 17 end
13
+ end
14
+
15
+ class FakeProxy < Ripple::Associations::Proxy
16
+ def find_target; [1, 2] end
17
+ end
@@ -0,0 +1,11 @@
1
+ # auto-tag all integration specs with :integration => true
2
+ module IntegrationSpecs
3
+ def self.included(klass)
4
+ klass.metadata[:integration] = true
5
+ end
6
+ end
7
+
8
+ RSpec.configure do |config|
9
+ config.include IntegrationSpecs, :example_group => { :file_path => %r{spec/integration} }
10
+ end
11
+
@@ -0,0 +1,4 @@
1
+
2
+ def mock_response(overrides={})
3
+ {:headers => {"content-type" => ["application/json"]}, :body => '{}'}.merge(overrides)
4
+ end
@@ -0,0 +1,4 @@
1
+
2
+ Dir[File.expand_path("../models/*.rb", __FILE__)].sort.each do |file|
3
+ require "support/models/#{File.basename(file)}"
4
+ end
@@ -0,0 +1,12 @@
1
+
2
+ require 'support/models/note'
3
+
4
+ class Address
5
+ include Ripple::EmbeddedDocument
6
+ property :street, String, :presence => true
7
+ many :notes
8
+ embedded_in :user
9
+ end
10
+
11
+ class SpecialAddress < Address
12
+ end
@@ -0,0 +1,13 @@
1
+
2
+ class Box
3
+ include Ripple::Document
4
+ property :shape, String
5
+ many :sides, :class_name => 'BoxSide'
6
+ timestamps!
7
+ end
8
+
9
+ class BoxSide
10
+ include Ripple::EmbeddedDocument
11
+ embedded_in :box
12
+ property :material, String
13
+ end
@@ -0,0 +1,16 @@
1
+
2
+ class Car
3
+ include Ripple::Document
4
+
5
+ property :make, String
6
+ property :model, String
7
+
8
+ one :driver # linked, key_on :name
9
+ many :passengers # linked, standard :key
10
+ one :engine # embedded
11
+ many :seats # embedded
12
+ many :wheels
13
+
14
+ accepts_nested_attributes_for :driver, :passengers, :engine, :seats
15
+ accepts_nested_attributes_for :wheels, :reject_if => proc{|attrs| attrs['diameter'] < 12 }, :allow_destroy => true
16
+ end
@@ -0,0 +1,3 @@
1
+
2
+ class CardboardBox < Box
3
+ end
@@ -0,0 +1,12 @@
1
+
2
+
3
+ class Clock
4
+ include Ripple::Document
5
+ timestamps!
6
+ many :modes
7
+ end
8
+
9
+ class Mode
10
+ include Ripple::EmbeddedDocument
11
+ timestamps!
12
+ end
@@ -0,0 +1,3 @@
1
+
2
+ class ClockObserver < ActiveModel::Observer
3
+ end
@@ -0,0 +1,23 @@
1
+ class Company
2
+ include Ripple::Document
3
+ property :name, String
4
+ many :departments
5
+ many :invoices # linked
6
+ one :ceo, :class_name => 'CEO'
7
+ end
8
+
9
+ class Department
10
+ include Ripple::EmbeddedDocument
11
+ property :name, String
12
+ many :managers
13
+ end
14
+
15
+ class Manager
16
+ include Ripple::EmbeddedDocument
17
+ property :name, String
18
+ end
19
+
20
+ class CEO
21
+ include Ripple::EmbeddedDocument
22
+ property :name, String
23
+ end
@@ -0,0 +1,4 @@
1
+
2
+ class Customer
3
+ include Ripple::Document
4
+ end
@@ -0,0 +1,6 @@
1
+
2
+ class Driver
3
+ include Ripple::Document
4
+ property :name, String
5
+ key_on :name
6
+ end
@@ -0,0 +1,4 @@
1
+
2
+ class Email
3
+ include Ripple::Document
4
+ end
@@ -0,0 +1,5 @@
1
+
2
+ class Engine
3
+ include Ripple::EmbeddedDocument
4
+ property :displacement, String
5
+ end
@@ -0,0 +1,16 @@
1
+ class Parent
2
+ include Ripple::Document
3
+ one :child
4
+ validates_associated :child
5
+ end
6
+
7
+ class Child
8
+ include Ripple::EmbeddedDocument
9
+ property :name, String, :presence => true
10
+ property :age, Integer, :presence => true
11
+ one :gchild, :class_name => 'Grandchild'
12
+ end
13
+
14
+ class Grandchild
15
+ include Ripple::EmbeddedDocument
16
+ end
@@ -0,0 +1,4 @@
1
+
2
+ class Favorite
3
+ include Ripple::EmbeddedDocument
4
+ end
@@ -0,0 +1,7 @@
1
+
2
+ class Invoice
3
+ include Ripple::Document
4
+ one :customer
5
+ one :note
6
+ timestamps!
7
+ end
@@ -0,0 +1,3 @@
1
+
2
+ class LateInvoice < Invoice
3
+ end
@@ -0,0 +1,9 @@
1
+ require 'support/models/user'
2
+
3
+ class Ninja < User
4
+ property :name, String
5
+
6
+ def key
7
+ "ninja-#{name.downcase}"
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+
2
+ class Note
3
+ include Ripple::EmbeddedDocument
4
+ property :text, String
5
+ end
@@ -0,0 +1,4 @@
1
+
2
+ class Page
3
+ include Ripple::Document
4
+ end
@@ -0,0 +1,4 @@
1
+
2
+ class PaidInvoice < Invoice
3
+ self.bucket_name = "paid"
4
+ end
@@ -0,0 +1,6 @@
1
+
2
+ class Passenger
3
+ include Ripple::Document
4
+ property :name, String
5
+
6
+ end