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.
- data/Rakefile +5 -4
- data/lib/rails/generators/ripple/model/model_generator.rb +20 -0
- data/lib/rails/generators/ripple/model/templates/model.rb +10 -0
- data/lib/rails/generators/ripple_generator.rb +60 -0
- data/lib/ripple.rb +24 -6
- data/lib/ripple/associations.rb +70 -3
- data/lib/ripple/associations/linked.rb +17 -5
- data/lib/ripple/associations/many.rb +5 -4
- data/lib/ripple/associations/many_embedded_proxy.rb +2 -0
- data/lib/ripple/associations/many_linked_proxy.rb +35 -0
- data/lib/ripple/associations/one.rb +4 -0
- data/lib/ripple/associations/one_embedded_proxy.rb +1 -1
- data/lib/ripple/associations/one_linked_proxy.rb +29 -0
- data/lib/ripple/attribute_methods/dirty.rb +2 -2
- data/lib/ripple/core_ext.rb +1 -0
- data/lib/ripple/core_ext/casting.rb +6 -8
- data/lib/ripple/document.rb +1 -0
- data/lib/ripple/document/bucket_access.rb +1 -1
- data/lib/ripple/document/finders.rb +1 -1
- data/lib/ripple/document/persistence.rb +28 -8
- data/lib/ripple/embedded_document.rb +1 -0
- data/lib/ripple/embedded_document/persistence.rb +11 -1
- data/lib/ripple/inspection.rb +26 -0
- data/lib/ripple/locale/en.yml +8 -5
- data/lib/ripple/railtie.rb +2 -12
- data/lib/ripple/validations.rb +5 -0
- data/lib/ripple/validations/associated_validator.rb +1 -2
- data/spec/fixtures/config.yml +6 -1
- data/spec/integration/ripple/associations_spec.rb +21 -0
- data/spec/ripple/associations/many_embedded_proxy_spec.rb +6 -0
- data/spec/ripple/associations/many_linked_proxy_spec.rb +103 -0
- data/spec/ripple/associations/one_embedded_proxy_spec.rb +5 -0
- data/spec/ripple/associations/one_linked_proxy_spec.rb +76 -0
- data/spec/ripple/associations/proxy_spec.rb +1 -1
- data/spec/ripple/bucket_access_spec.rb +3 -4
- data/spec/ripple/core_ext_spec.rb +11 -0
- data/spec/ripple/embedded_document/persistence_spec.rb +12 -0
- data/spec/ripple/finders_spec.rb +1 -1
- data/spec/ripple/inspection_spec.rb +48 -0
- data/spec/ripple/persistence_spec.rb +52 -6
- data/spec/ripple/properties_spec.rb +6 -0
- data/spec/ripple/ripple_spec.rb +12 -1
- data/spec/support/models/tasks.rb +13 -0
- data/spec/support/models/widget.rb +5 -1
- 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
|
-
|
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
|
-
|
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
|
-
|
87
|
-
Boolean.ripple_cast(value)
|
88
|
-
end
|
88
|
+
extend Boolean
|
89
89
|
end
|
90
90
|
|
91
91
|
# @private
|
92
92
|
class FalseClass
|
93
|
-
|
94
|
-
Boolean.ripple_cast(value)
|
95
|
-
end
|
93
|
+
extend Boolean
|
96
94
|
end
|
97
95
|
|
98
96
|
# @private
|
data/lib/ripple/document.rb
CHANGED
@@ -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
|
@@ -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
|
data/lib/ripple/locale/en.yml
CHANGED
@@ -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: {
|
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: {
|
22
|
-
many_keys: "Couldn't find documents with keys: {
|
23
|
-
|
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
|
+
|
data/lib/ripple/railtie.rb
CHANGED
@@ -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
|
data/lib/ripple/validations.rb
CHANGED
@@ -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).
|
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
|
data/spec/fixtures/config.yml
CHANGED
@@ -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
|