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