From a7a6b2f0ac45f913d34657735b8f0d8d5bc1021f Mon Sep 17 00:00:00 2001 From: Sam Lown Date: Thu, 17 Jun 2010 02:39:09 +0200 Subject: [PATCH] adding initial support for belongs_to associations --- history.txt | 2 + lib/couchrest/extended_document.rb | 1 + lib/couchrest/mixins.rb | 1 + lib/couchrest/mixins/associations.rb | 58 ++++++++++++++++++++ spec/couchrest/assocations_spec.rb | 79 ++++++++++++++++++++++++++++ spec/fixtures/more/invoice.rb | 4 +- 6 files changed, 143 insertions(+), 2 deletions(-) create mode 100644 lib/couchrest/mixins/associations.rb create mode 100644 spec/couchrest/assocations_spec.rb diff --git a/history.txt b/history.txt index 4214618..4c41ef6 100644 --- a/history.txt +++ b/history.txt @@ -11,9 +11,11 @@ * Fixed issue with active_support in Rails3 and text in README for JSON. * Refactoring of properties, added read_attribute and write_attribute methods. * Now possible to send anything to update_attribtues method. Invalid or readonly attributes will be ignored. + * Attributes with arrays are *always* instantiated as a CastedArray. * Major enhancements * Added support for anonymous CastedModels defined in Documents + * Added initial support for simple belongs_to associations == 1.0.0.beta5 diff --git a/lib/couchrest/extended_document.rb b/lib/couchrest/extended_document.rb index 2aaf63d..e037e85 100644 --- a/lib/couchrest/extended_document.rb +++ b/lib/couchrest/extended_document.rb @@ -19,6 +19,7 @@ module CouchRest include CouchRest::Mixins::Collection include CouchRest::Mixins::AttributeProtection include CouchRest::Mixins::Attributes + include CouchRest::Mixins::Associations # Including validation here does not work due to the way inheritance is handled. #include CouchRest::Validation diff --git a/lib/couchrest/mixins.rb b/lib/couchrest/mixins.rb index 3681b1c..509e1a6 100644 --- a/lib/couchrest/mixins.rb +++ b/lib/couchrest/mixins.rb @@ -10,3 +10,4 @@ require File.join(mixins_dir, 'class_proxy') require File.join(mixins_dir, 'collection') require File.join(mixins_dir, 'attribute_protection') require File.join(mixins_dir, 'attributes') +require File.join(mixins_dir, 'associations') diff --git a/lib/couchrest/mixins/associations.rb b/lib/couchrest/mixins/associations.rb new file mode 100644 index 0000000..68b2e84 --- /dev/null +++ b/lib/couchrest/mixins/associations.rb @@ -0,0 +1,58 @@ +module CouchRest + module Mixins + module Associations + + # Basic support for relationships between ExtendedDocuments + + def self.included(base) + base.extend(ClassMethods) + end + + module ClassMethods + + def belongs_to(attrib, *options) + opts = { + :foreign_key => attrib.to_s + '_id', + :class_name => attrib.to_s.camelcase + } + case options.first + when Hash + opts.merge!(options.first) + end + + begin + opts[:class] = opts[:class_name].constantize + rescue + raise "Unable to convert belongs_to class name into Constant for #{self.name}##{attrib}" + end + + prop = property(opts[:foreign_key]) + + create_belongs_to_getter(attrib, prop, opts) + create_belongs_to_setter(attrib, prop, opts) + + prop + end + + def create_belongs_to_getter(attrib, property, options) + class_eval <<-EOS, __FILE__, __LINE__ + 1 + def #{attrib} + @#{attrib} ||= #{options[:class_name]}.get(self.#{options[:foreign_key]}) + end + EOS + end + + def create_belongs_to_setter(attrib, property, options) + class_eval <<-EOS, __FILE__, __LINE__ + 1 + def #{attrib}=(value) + @#{attrib} = value + self.#{options[:foreign_key]} = value.nil? ? nil : value.id + end + EOS + end + + end + + end + end +end diff --git a/spec/couchrest/assocations_spec.rb b/spec/couchrest/assocations_spec.rb new file mode 100644 index 0000000..7382ae4 --- /dev/null +++ b/spec/couchrest/assocations_spec.rb @@ -0,0 +1,79 @@ +# encoding: utf-8 +require File.expand_path('../../spec_helper', __FILE__) + +class Client < CouchRest::ExtendedDocument + use_database DB + + property :name + property :tax_code +end + +class SaleInvoice < CouchRest::ExtendedDocument + use_database DB + + belongs_to :client + belongs_to :alternate_client, :class_name => 'Client', :foreign_key => 'alt_client_id' + + property :date, Date + property :price, Integer +end + + +describe "Assocations" do + + describe "of type belongs to" do + + before :each do + @invoice = SaleInvoice.create(:price => "sam", :price => 2000) + @client = Client.create(:name => "Sam Lown") + end + + it "should create a foreign key property with setter and getter" do + @invoice.properties.find{|p| p.name == 'client_id'}.should_not be_nil + @invoice.respond_to?(:client_id).should be_true + @invoice.respond_to?("client_id=").should be_true + end + + it "should set the property and provide object when set" do + @invoice.client = @client + @invoice.client_id.should eql(@client.id) + @invoice.client.should eql(@client) + end + + it "should set the attribute, save and return" do + @invoice.client = @client + @invoice.save + @invoice = SaleInvoice.get(@invoice.id) + @invoice.client.id.should eql(@client.id) + end + + it "should remove the association if nil is provided" do + @invoice.client = @client + @invoice.client = nil + @invoice.client_id.should be_nil + end + + it "should raise error if class name does not exist" do + lambda { + class TestBadAssoc < CouchRest::ExtendedDocument + belongs_to :test_bad_item + end + }.should raise_error + end + + it "should allow override of foreign key" do + @invoice.respond_to?(:alternate_client).should be_true + @invoice.respond_to?("alternate_client=").should be_true + @invoice.properties.find{|p| p.name == 'alt_client_id'}.should_not be_nil + end + + it "should allow override of foreign key and save" do + @invoice.alternate_client = @client + @invoice.save + @invoice = SaleInvoice.get(@invoice.id) + @invoice.alternate_client.id.should eql(@client.id) + end + + end + +end diff --git a/spec/fixtures/more/invoice.rb b/spec/fixtures/more/invoice.rb index 0666e5c..2f2e185 100644 --- a/spec/fixtures/more/invoice.rb +++ b/spec/fixtures/more/invoice.rb @@ -4,7 +4,7 @@ class Invoice < CouchRest::ExtendedDocument # Set the default database to use use_database DB - + # Official Schema property :client_name property :employee_name @@ -14,4 +14,4 @@ class Invoice < CouchRest::ExtendedDocument validates_presence_of :client_name, :employee_name validates_presence_of :location, :message => "Hey stupid!, you forgot the location" -end \ No newline at end of file +end