Permalink
Browse files

added one associations using key correspondence

  • Loading branch information...
1 parent c7e0b9e commit c8c7b93e8d5d08924206a342e34a51c0180b41a4 @adamhunter adamhunter committed May 27, 2011
@@ -12,6 +12,7 @@
require 'ripple/associations/many_embedded_proxy'
require 'ripple/associations/one_linked_proxy'
require 'ripple/associations/many_linked_proxy'
+require 'ripple/associations/one_key_proxy'
module Ripple
# Adds associations via links and embedding to {Ripple::Document}
@@ -50,6 +51,7 @@ module Associations
extend ActiveSupport::Concern
module ClassMethods
+ include Translation
# @private
def inherited(subclass)
super
@@ -68,14 +70,20 @@ def embedded_associations
# Creates a singular association
def one(name, options={})
+ configure_for_key_correspondence if options[:using] === :key
create_association(:one, name, options)
end
# Creates a plural association
def many(name, options={})
+ raise ArgumentError, t('many_key_association') if options[:using] === :key
create_association(:many, name, options)
end
+ def configure_for_key_correspondence
+ include Ripple::Associations::KeyDelegator
+ end
+
private
def create_association(type, name, options={})
association = associations[name] = Association.new(type, name, options)
@@ -0,0 +1,58 @@
+require 'ripple/associations/proxy'
+require 'ripple/associations/one'
+
+module Ripple
+ module Associations
+ class OneKeyProxy < Proxy
+ include One
+
+ def replace(doc)
+ @reflection.verify_type!(doc, owner)
+
+ reset_previous_target_key_delegate
+ assign_new_target_key_delegate(doc)
+
+ loaded
+ @target = doc
+ end
+
+ def find_target
+ klass.find(owner.key)
+ end
+
+ protected
+ def instantiate_target(instantiator, attrs={})
+ @target = super
+ @target.key = owner.key
+ @target
+ end
+
+ private
+ def reset_previous_target_key_delegate
+ @target.key_delegate = @target if @target
+ end
+
+ def assign_new_target_key_delegate(doc)
+ doc.class.send(:include, Ripple::Associations::KeyDelegator) unless doc.class.include?(Ripple::Associations::KeyDelegator)
+ owner.key_delegate = doc.key_delegate = owner
+ end
+
+ end
+
+ module KeyDelegator
+ attr_accessor :key_delegate
+
+ def key_delegate
+ @key_delegate || self
+ end
+
+ def key
+ self === key_delegate ? super : key_delegate.key
+ end
+
+ def key=(value)
+ self === key_delegate ? super(value) : key_delegate.key = value
+ end
+ end
+ end
+end
@@ -14,4 +14,5 @@ en:
many_association_validation_error: "are invalid (%{association_errors})"
associated_document_error_summary: "for %{doc_type} %{doc_id}: %{errors}"
undefined_property: "Undefined property :%{prop} for class '%{class}'"
+ many_key_association: "You cannot use a many association using :key"
@@ -12,6 +12,7 @@ class User
property :email, String, :presence => true
many :friends, :class_name => "User"
one :emergency_contact, :class_name => "User"
+ one :credit_card, :using => :key
end
class Profile
include Ripple::EmbeddedDocument
@@ -24,6 +25,11 @@ class Address
property :kind, String, :presence => true
embedded_in :user
end
+ class CreditCard
+ include Ripple::Document
+ one :user, :using => :key
+ property :number, Integer
+ end
end
end
@@ -32,8 +38,9 @@ class Address
@profile = Profile.new(:name => 'Ripple')
@billing = Address.new(:street => '123 Somewhere Dr', :kind => 'billing')
@shipping = Address.new(:street => '321 Anywhere Pl', :kind => 'shipping')
- @friend1 = User.create(:email => "friend@ripple.com")
- @friend2 = User.create(:email => "friend2@ripple.com")
+ @friend1 = User.create(:email => "friend@ripple.com")
+ @friend2 = User.create(:email => "friend2@ripple.com")
+ @cc = CreditCard.new(:number => '12345')
end
it "should save one embedded associations" do
@@ -135,6 +142,23 @@ class Address
found_friend.friends.should == [found_user]
end
+ it "should find the object associated by key after saving" do
+ @user.key = 'paying-user'
+ @user.credit_card = @cc
+ @user.save && @cc.save
+ @found = User.find(@user.key)
+ @found.reload
+ @found.credit_card.should eq(@cc)
+ end
+
+ it "should assign the generated riak key to the associated object using key" do
+ @user.key.should be_nil
+ @user.credit_card = @cc
+ @user.save
+ @cc.key.should_not be_blank
+ @cc.key.should eq(@user.key)
+ end
+
after :each do
User.destroy_all
end
@@ -143,6 +167,7 @@ class Address
Object.send(:remove_const, :User)
Object.send(:remove_const, :Profile)
Object.send(:remove_const, :Address)
+ Object.send(:remove_const, :CreditCard)
end
end
@@ -0,0 +1,82 @@
+require File.expand_path("../../../spec_helper", __FILE__)
+
+describe Ripple::Associations::OneKeyProxy do
+
+ before :each do
+ @user = User.new
+ @profile = Profile.new(:color => 'blue')
+ end
+
+ describe "User with a key corresponded profile" do
+ it "should not have a profile before it is set" do
+ @user.profile.should be_nil
+ end
+
+ it "should be able to get and set its profile" do
+ @user.profile = @profile
+ @user.profile.should eq(@profile)
+ end
+
+ it "should return the assignment when assigning" do
+ rtn = @user.profile = @profile
+ rtn.should eq(@profile)
+ end
+
+ it "should be able to replace its profile with a new one" do
+ @user.profile = @profile
+ @user.profile = Profile.new(:color => 'red')
+ @user.profile.color.should eq('red')
+ end
+
+ it "should be able to build a new profile" do
+ Profile.stub(:new).and_return(@profile)
+ @user.profile.build.should eq(@profile)
+ end
+
+ it "should assign its key to the associated profile when assigning" do
+ @user.key = "foo"
+ @user.profile = @profile
+ @profile.key.should eq("foo")
+ end
+
+ it "should assign its key to the built profile" do
+ @user.key = "foo"
+ @user.profile.build.key.should eq("foo")
+ end
+
+ it "should update the key on the profile when updating it on the user" do
+ @user.profile = @profile
+ @user.key = "foo"
+ @profile.key.should eq("foo")
+ end
+
+ it "should not update the key of a previous profile" do
+ @profile2 = Profile.new
+ @user.profile = @profile
+ @user.profile = @profile2
+ @user.key = "foo"
+ @profile.key.should_not eq("foo")
+ end
+
+ it "should not infinitely loop when assigning a user to the profile" do
+ @user2 = User.new
+ @user.profile = @profile
+ @profile.user = @user2
+ @profile.key = "foo"
+ @user2.key.should eq("foo")
+ end
+
+ it "should update the user's key when being updated on the profile" do
+ @user.profile = @profile
+ @profile.key = "foo"
+ @user.key.should eq("foo")
+ end
+
+ it "should work properly with custom key methods" do
+ @user = Ninja.new(:name => 'Naruto')
+ @user.profile = @profile
+ @profile.key.should eq("ninja-naruto")
+ end
+ end
+
+end
@@ -102,4 +102,19 @@ class Invoice < @orig_invoice; end
it "should determine an instance variable based on the name" do
Ripple::Association.new(:many, :pages).ivar.should == "@_pages"
end
+
+ describe "key correspondence" do
+ require 'support/models/profile'
+ require 'support/models/user'
+
+ it "should raise an exception if trying to use a many association when :using => :key" do
+ expect { Profile.many :user, :using => :key }.to raise_error(ArgumentError, "You cannot use a many association using :key")
+ end
+
+ it "should add the key_delegate attr_accessor to the model declaring the association" do
+ Profile.one :user, :using => :key
+ Profile.new.should respond_to(:key_delegate)
+ Profile.new.should respond_to(:key_delegate=)
+ end
+ end
end
@@ -0,0 +1,9 @@
+require 'support/models/user'
+
+class Ninja < User
+ property :name, String
+
+ def key
+ "ninja-#{name.downcase}"
+ end
+end
@@ -0,0 +1,10 @@
+
+require 'support/models/user'
+
+class Profile
+ include Ripple::Document
+
+ one :user, :using => :key
+
+ property :color, String
+end
@@ -1,7 +1,10 @@
require 'support/models/address'
+require 'support/models/profile'
class User
include Ripple::Document
many :addresses
+
+ one :profile, :using => :key
end

0 comments on commit c8c7b93

Please sign in to comment.