ripple 0.7.1 → 0.8.0.beta

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 (45) hide show
  1. data/Rakefile +5 -4
  2. data/lib/rails/generators/ripple/model/model_generator.rb +20 -0
  3. data/lib/rails/generators/ripple/model/templates/model.rb +10 -0
  4. data/lib/rails/generators/ripple_generator.rb +60 -0
  5. data/lib/ripple.rb +24 -6
  6. data/lib/ripple/associations.rb +70 -3
  7. data/lib/ripple/associations/linked.rb +17 -5
  8. data/lib/ripple/associations/many.rb +5 -4
  9. data/lib/ripple/associations/many_embedded_proxy.rb +2 -0
  10. data/lib/ripple/associations/many_linked_proxy.rb +35 -0
  11. data/lib/ripple/associations/one.rb +4 -0
  12. data/lib/ripple/associations/one_embedded_proxy.rb +1 -1
  13. data/lib/ripple/associations/one_linked_proxy.rb +29 -0
  14. data/lib/ripple/attribute_methods/dirty.rb +2 -2
  15. data/lib/ripple/core_ext.rb +1 -0
  16. data/lib/ripple/core_ext/casting.rb +6 -8
  17. data/lib/ripple/document.rb +1 -0
  18. data/lib/ripple/document/bucket_access.rb +1 -1
  19. data/lib/ripple/document/finders.rb +1 -1
  20. data/lib/ripple/document/persistence.rb +28 -8
  21. data/lib/ripple/embedded_document.rb +1 -0
  22. data/lib/ripple/embedded_document/persistence.rb +11 -1
  23. data/lib/ripple/inspection.rb +26 -0
  24. data/lib/ripple/locale/en.yml +8 -5
  25. data/lib/ripple/railtie.rb +2 -12
  26. data/lib/ripple/validations.rb +5 -0
  27. data/lib/ripple/validations/associated_validator.rb +1 -2
  28. data/spec/fixtures/config.yml +6 -1
  29. data/spec/integration/ripple/associations_spec.rb +21 -0
  30. data/spec/ripple/associations/many_embedded_proxy_spec.rb +6 -0
  31. data/spec/ripple/associations/many_linked_proxy_spec.rb +103 -0
  32. data/spec/ripple/associations/one_embedded_proxy_spec.rb +5 -0
  33. data/spec/ripple/associations/one_linked_proxy_spec.rb +76 -0
  34. data/spec/ripple/associations/proxy_spec.rb +1 -1
  35. data/spec/ripple/bucket_access_spec.rb +3 -4
  36. data/spec/ripple/core_ext_spec.rb +11 -0
  37. data/spec/ripple/embedded_document/persistence_spec.rb +12 -0
  38. data/spec/ripple/finders_spec.rb +1 -1
  39. data/spec/ripple/inspection_spec.rb +48 -0
  40. data/spec/ripple/persistence_spec.rb +52 -6
  41. data/spec/ripple/properties_spec.rb +6 -0
  42. data/spec/ripple/ripple_spec.rb +12 -1
  43. data/spec/support/models/tasks.rb +13 -0
  44. data/spec/support/models/widget.rb +5 -1
  45. metadata +43 -18
@@ -0,0 +1,29 @@
1
+ # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require 'ripple'
15
+
16
+ module Ripple
17
+ module Associations
18
+ class OneLinkedProxy < Proxy
19
+ include One
20
+ include Linked
21
+
22
+ protected
23
+ def find_target
24
+ return nil if links.blank?
25
+ klass.send(:instantiate,robjects.first)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -20,7 +20,7 @@ module Ripple
20
20
  include ActiveModel::Dirty
21
21
 
22
22
  # @private
23
- def save
23
+ def save(*args)
24
24
  if result = super
25
25
  changed_attributes.clear
26
26
  end
@@ -29,7 +29,7 @@ module Ripple
29
29
 
30
30
  # @private
31
31
  def reload
32
- returning super do
32
+ super.tap do
33
33
  changed_attributes.clear
34
34
  end
35
35
  end
@@ -0,0 +1 @@
1
+ require 'ripple/core_ext/casting'
@@ -57,13 +57,15 @@ end
57
57
  # @private
58
58
  class String
59
59
  def self.ripple_cast(value)
60
+ return nil if value.nil?
60
61
  value.respond_to?(:to_s) && value.to_s or raise Ripple::PropertyTypeMismatch.new(self, value)
61
62
  end
62
63
  end
63
64
 
64
65
  # Stand-in for true/false property types.
65
- module Boolean
66
- def self.ripple_cast(value)
66
+ module ::Boolean
67
+ extend self
68
+ def ripple_cast(value)
67
69
  case value
68
70
  when NilClass
69
71
  nil
@@ -83,16 +85,12 @@ end
83
85
 
84
86
  # @private
85
87
  class TrueClass
86
- def self.ripple_cast(value)
87
- Boolean.ripple_cast(value)
88
- end
88
+ extend Boolean
89
89
  end
90
90
 
91
91
  # @private
92
92
  class FalseClass
93
- def self.ripple_cast(value)
94
- Boolean.ripple_cast(value)
95
- end
93
+ extend Boolean
96
94
  end
97
95
 
98
96
  # @private
@@ -58,6 +58,7 @@ module Ripple
58
58
  include Ripple::Callbacks
59
59
  include Ripple::Conversion
60
60
  include Ripple::Document::Finders
61
+ include Ripple::Inspection
61
62
  end
62
63
 
63
64
  module ClassMethods
@@ -25,7 +25,7 @@ module Ripple
25
25
 
26
26
  # @return [Riak::Bucket] The bucket assigned to this class.
27
27
  def bucket
28
- Ripple.client[bucket_name, {:keys => false}]
28
+ Riak::Bucket.new(Ripple.client, bucket_name)
29
29
  end
30
30
 
31
31
  # Set the bucket name for this class and its subclasses.
@@ -110,7 +110,7 @@ module Ripple
110
110
 
111
111
  private
112
112
  def find_one(key)
113
- instantiate(bucket.get(key))
113
+ instantiate(bucket.get(key, quorums.slice(:r)))
114
114
  rescue Riak::FailedRequest => fr
115
115
  return nil if fr.code.to_i == 404
116
116
  raise fr
@@ -18,9 +18,9 @@ module Ripple
18
18
  module Persistence
19
19
  extend ActiveSupport::Concern
20
20
  extend ActiveSupport::Autoload
21
-
21
+
22
22
  module ClassMethods
23
-
23
+
24
24
  # Instantiates a new record, applies attributes from a block, and saves it
25
25
  def create(attrs={}, &block)
26
26
  new(attrs, &block).tap {|s| s.save}
@@ -31,7 +31,13 @@ module Ripple
31
31
  def destroy_all
32
32
  all(&:destroy)
33
33
  end
34
-
34
+
35
+ attr_writer :quorums
36
+ alias_method "set_quorums", "quorums="
37
+
38
+ def quorums
39
+ @quorums ||= {}
40
+ end
35
41
  end
36
42
 
37
43
  module InstanceMethods
@@ -46,12 +52,27 @@ module Ripple
46
52
  @new || false
47
53
  end
48
54
 
55
+ # Updates a single attribute and then saves the document
56
+ # NOTE: THIS SKIPS VALIDATIONS! Use with caution.
57
+ # @return [true,false] whether the document succeeded in saving
58
+ def update_attribute(attribute, value)
59
+ send("#{attribute}=", value)
60
+ save(:validate => false)
61
+ end
62
+
63
+ # Writes new attributes and then saves the document
64
+ # @return [true,false] whether the document succeeded in saving
65
+ def update_attributes(attrs)
66
+ self.attributes = attrs
67
+ save
68
+ end
69
+
49
70
  # Saves the document in Riak.
50
71
  # @return [true,false] whether the document succeeded in saving
51
- def save
72
+ def save(*args)
52
73
  robject.key = key if robject.key != key
53
74
  robject.data = attributes_for_persistence
54
- robject.store
75
+ robject.store(self.class.quorums.slice(:w,:dw))
55
76
  self.key = robject.key
56
77
  @new = false
57
78
  true
@@ -70,7 +91,7 @@ module Ripple
70
91
 
71
92
  # Deletes the document from Riak and freezes this instance
72
93
  def destroy
73
- robject.delete unless new?
94
+ robject.delete(self.class.quorums.slice(:rw)) unless new?
74
95
  freeze
75
96
  true
76
97
  rescue Riak::FailedRequest
@@ -81,8 +102,7 @@ module Ripple
81
102
  def freeze
82
103
  @attributes.freeze; super
83
104
  end
84
-
85
- protected
105
+
86
106
  attr_writer :robject
87
107
 
88
108
  def robject
@@ -34,6 +34,7 @@ module Ripple
34
34
  include Ripple::Callbacks
35
35
  include Ripple::Conversion
36
36
  include Finders
37
+ include Ripple::Inspection
37
38
  end
38
39
 
39
40
  module ClassMethods
@@ -42,6 +42,16 @@ module Ripple
42
42
  end
43
43
  end
44
44
 
45
+ def update_attributes(attrs)
46
+ self.attributes = attrs
47
+ save
48
+ end
49
+
50
+ def update_attribute(attribute, value)
51
+ send("#{attribute}=", value)
52
+ save(:validate => false)
53
+ end
54
+
45
55
  def save(*args)
46
56
  if _root_document
47
57
  _root_document.save(*args)
@@ -57,7 +67,7 @@ module Ripple
57
67
  def _root_document
58
68
  @_parent_document.try(:_root_document)
59
69
  end
60
-
70
+
61
71
  def _parent_document=(value)
62
72
  @_parent_document = value
63
73
  end
@@ -0,0 +1,26 @@
1
+ # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ require 'ripple'
16
+
17
+ module Ripple
18
+ # Makes IRB and other inspect output a bit friendlier
19
+ module Inspection
20
+ def inspect
21
+ attribute_list = attributes_for_persistence.except("_type").map {|k,v| "#{k}=#{v.inspect}" }.join(' ')
22
+ identifier = self.class.embeddable? ? "" : ":#{key || '[new]'}"
23
+ "<#{self.class.name}#{identifier} #{attribute_list}>"
24
+ end
25
+ end
26
+ end
@@ -13,11 +13,14 @@
13
13
  # limitations under the License.
14
14
  en:
15
15
  ripple:
16
- property_type_mismatch: "Cannot cast {{value}} into a {{class}}"
17
16
  attribute_hash: "value of attributes must be a Hash"
18
- document_invalid: "Validation failed: {{errors}}"
17
+ document_invalid: "Validation failed: %{errors}"
19
18
  document_not_found:
20
19
  no_key: "Couldn't find document without a key"
21
- one_key: "Couldn't find document with key: {{key}}"
22
- many_keys: "Couldn't find documents with keys: {{keys}}"
23
- no_root_document: "You cannot call {{method}} on {{doc}} without a root document"
20
+ one_key: "Couldn't find document with key: %{key}"
21
+ many_keys: "Couldn't find documents with keys: %{keys}"
22
+ invalid_association_value: "Invalid value %{value} for association %{name} of type %{klass} on %{owner}"
23
+ missing_configuration: "You are missing your ripple configuration file that should be at %{file}"
24
+ no_root_document: "You cannot call %{method} on %{doc} without a root document"
25
+ property_type_mismatch: "Cannot cast %{value} into a %{class}"
26
+
@@ -12,21 +12,11 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  require 'ripple'
15
- require 'rails'
16
15
 
17
- # require in gemfile using
18
- # <tt>gem "ripple", :require_as => ["ripple", "ripple/railtie"]</tt>
19
16
  module Ripple
20
17
  class Railtie < Rails::Railtie
21
- railtie_name :ripple
22
-
23
18
  initializer "ripple.configure_rails_initialization" do
24
- Ripple.load_configuration
19
+ Ripple.load_configuration Rails.root.join('config', 'ripple.yml'), [Rails.env]
25
20
  end
26
21
  end
27
-
28
- def self.load_configuration
29
- config_file = Rails.root.join('config', 'database.yml')
30
- self.config = YAML.load_file(File.expand_path config_file).with_indifferent_access[:ripple][Rails.env]
31
- end
32
- end
22
+ end
@@ -64,6 +64,11 @@ module Ripple
64
64
  def save!
65
65
  (raise Ripple::DocumentInvalid.new(self) unless save) || true
66
66
  end
67
+
68
+ def update_attributes!(attrs)
69
+ self.attributes = attrs
70
+ save!
71
+ end
67
72
  end
68
73
  end
69
74
  end
@@ -14,14 +14,13 @@
14
14
  #
15
15
  # Taken from ActiveRecord::Validations::AssociatedValidators
16
16
  #
17
-
18
17
  require 'ripple'
19
18
 
20
19
  module Ripple
21
20
  module Validations
22
21
  class AssociatedValidator < ActiveModel::EachValidator
23
22
  def validate_each(record, attribute, value)
24
- unless Array(value).all? {|r| r.nil? || r.valid? }
23
+ unless Array(value).map {|r| r.nil? || r.valid? }.all?
25
24
  record.errors.add(attribute, :invalid, :default => options[:message], :value => value)
26
25
  end
27
26
  end
@@ -1,3 +1,8 @@
1
1
  ripple:
2
2
  port: 9000
3
- host: localhost
3
+ host: localhost
4
+
5
+ ripple_rails:
6
+ development:
7
+ port: 9001
8
+ host: 127.0.0.1
@@ -21,6 +21,8 @@ describe "Ripple Associations" do
21
21
  one :profile
22
22
  many :addresses
23
23
  property :email, String, :presence => true
24
+ many :friends, :class_name => "User"
25
+ one :emergency_contact, :class_name => "User"
24
26
  end
25
27
  class Profile
26
28
  include Ripple::EmbeddedDocument
@@ -41,6 +43,8 @@ describe "Ripple Associations" do
41
43
  @profile = Profile.new(:name => 'Ripple')
42
44
  @billing = Address.new(:street => '123 Somewhere Dr', :kind => 'billing')
43
45
  @shipping = Address.new(:street => '321 Anywhere Pl', :kind => 'shipping')
46
+ @friend1 = User.create(:email => "friend@ripple.com")
47
+ @friend2 = User.create(:email => "friend2@ripple.com")
44
48
  end
45
49
 
46
50
  it "should save one embedded associations" do
@@ -66,6 +70,23 @@ describe "Ripple Associations" do
66
70
  @bill.should be_a(Address)
67
71
  @ship.should be_a(Address)
68
72
  end
73
+
74
+ it "should save a many linked association" do
75
+ @user.friends << @friend1 << @friend2
76
+ @user.save
77
+ @user.should_not be_new_record
78
+ @found = User.find(@user.key)
79
+ @found.friends.map(&:key).should include(@friend1.key)
80
+ @found.friends.map(&:key).should include(@friend2.key)
81
+ end
82
+
83
+ it "should save a one linked association" do
84
+ @user.emergency_contact = @friend1
85
+ @user.save
86
+ @user.should_not be_new_record
87
+ @found = User.find(@user.key)
88
+ @found.emergency_contact.key.should == @friend1.key
89
+ end
69
90
 
70
91
  after :each do
71
92
  User.destroy_all
@@ -121,4 +121,10 @@ describe Ripple::Associations::ManyEmbeddedProxy do
121
121
  @user.addresses << @address
122
122
  @user.addresses.to_ary.should == [@address]
123
123
  end
124
+
125
+ it "should refuse assigning documents of the wrong type" do
126
+ lambda { @user.addresses = nil }.should raise_error
127
+ lambda { @user.addresses = @address }.should raise_error
128
+ lambda { @user.addresses = [@note] }.should raise_error
129
+ end
124
130
  end
@@ -0,0 +1,103 @@
1
+ # Copyright 2010 Sean Cribbs, Sonian Inc., and Basho Technologies, Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ require File.expand_path("../../../spec_helper", __FILE__)
15
+
16
+ describe Ripple::Associations::ManyLinkedProxy do
17
+ require 'support/models/tasks'
18
+
19
+ before :each do
20
+ @person = Person.new {|p| p.key = "riak-user" }
21
+ @task = Task.new {|t| t.key = "one" }
22
+ @other_task = Task.new {|t| t.key = "two" }
23
+ [@person, @task, @other_task].each do |doc|
24
+ doc.stub!(:new?).and_return(false)
25
+ end
26
+ end
27
+
28
+ it "should be empty before any associated documents are set" do
29
+ @person.tasks.should be_empty
30
+ end
31
+
32
+ it "should accept an array of documents" do
33
+ @person.tasks = [@task]
34
+ end
35
+
36
+ it "should set the links on the RObject when assigning" do
37
+ @person.tasks = [@task]
38
+ @person.robject.links.should include(@task.robject.to_link("tasks"))
39
+ end
40
+
41
+ it "should return the assigned documents when assigning" do
42
+ t = (@person.tasks = [@task])
43
+ t.should == [@task]
44
+ end
45
+
46
+ it "should save unsaved documents when assigning" do
47
+ @task.should_receive(:new?).and_return(true)
48
+ @task.should_receive(:save).and_return(true)
49
+ @person.tasks = [@task]
50
+ end
51
+
52
+ it "should link-walk to the associated documents when accessing" do
53
+ @person.robject.links << @task.robject.to_link("tasks")
54
+ @person.robject.should_receive(:walk).with(Riak::WalkSpec.new(:bucket => "tasks", :tag => "tasks")).and_return([])
55
+ @person.tasks.should == []
56
+ end
57
+
58
+ it "should replace associated documents with a new set" do
59
+ @person.tasks = [@task]
60
+ @person.tasks = [@other_task]
61
+ @person.tasks.should == [@other_task]
62
+ end
63
+
64
+ it "should be able to append documents to the associated set" do
65
+ @person.tasks << @task
66
+ @person.tasks << @other_task
67
+ @person.should have(2).tasks
68
+ end
69
+
70
+ it "should be able to chain calls to adding documents" do
71
+ @person.tasks << @task << @other_task
72
+ @person.should have(2).tasks
73
+ end
74
+
75
+ it "should set the links on the RObject when appending" do
76
+ @person.tasks << @task << @other_task
77
+ [@task, @other_task].each do |t|
78
+ @person.robject.links.should include(t.robject.to_link("tasks"))
79
+ end
80
+ end
81
+
82
+ it "should be able to count the associated documents" do
83
+ @person.tasks << @task
84
+ @person.tasks.count.should == 1
85
+ @person.tasks << @other_task
86
+ @person.tasks.count.should == 2
87
+ end
88
+
89
+ # it "should be able to build a new associated document" do
90
+ # pending "Need unsaved document support"
91
+ # end
92
+
93
+ it "should return an array from to_ary" do
94
+ @person.tasks << @task
95
+ @person.tasks.to_ary.should == [@task]
96
+ end
97
+
98
+ it "should refuse assigning a collection of the wrong type" do
99
+ lambda { @person.tasks = nil }.should raise_error
100
+ lambda { @person.tasks = @task }.should raise_error
101
+ lambda { @person.tasks = [@person] }.should raise_error
102
+ end
103
+ end