Skip to content

Commit

Permalink
Integrated ActiveRecord::Dirty check support for delegated attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
pahanix committed Jan 8, 2010
1 parent f3c13ca commit 431e8d6
Show file tree
Hide file tree
Showing 3 changed files with 224 additions and 15 deletions.
48 changes: 33 additions & 15 deletions lib/delegate_belongs_to.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@
# Creates methods on object which delegate to an association proxy.
# see delegate_belongs_to for two uses
#
# Todo - integrate with ActiveRecord::Dirty to make sure changes to delegate object are noticed
# Should do
# class User < ActiveRecord::Base; delegate_belongs_to :contact, :firstname; end
# class Contact < ActiveRecord::Base; end
# u = User.first
# u.changed? # => false
# u.firstname = 'Bobby'
# u.changed? # => true
#
# Right now the second call to changed? would return false
#
# Todo - add has_one support. fairly straightforward addition
##
module DelegateBelongsTo
Expand Down Expand Up @@ -41,39 +37,61 @@ def delegate_belongs_to(association, *attributes)

def delegates_attributes_to(association, *attributes)
raise ArgumentError, "Unknown association #{association}" unless reflection = reflect_on_association(association)

if attributes.empty? || attributes.delete(:defaults)
column_names = reflection.klass.column_names
default_rejected_delegate_columns.each {|column| column_names.delete(column) }
attributes += column_names
end

attributes.map!(&:to_s)

dirty_associations.merge!(association => attributes)

attributes.each do |attribute|
delegate attribute, :to => association, :allow_nil => true
define_method("#{attribute}=") do |value|
send("build_#{association}") unless send(association)
send(association).send("#{attribute}=", value)
end

# if dirty = true
# ActiveRecord::Dirty::DIRTY_SUFFIXES.each do |suffix|
# define_method("#{attribute}#{suffix}") do
# send("build_#{association}") unless send(association)
# send(association).send("#{attribute}#{suffix}")
# end
# end
# end
#

ActiveRecord::Dirty::DIRTY_SUFFIXES.each do |suffix|
define_method("#{attribute}#{suffix}") do
send("build_#{association}") unless send(association)
send(association).send("#{attribute}#{suffix}")
end
end
end
end

end

module InstanceMethods

private

def changed_attributes_with_associations
result = {}
self.class.dirty_associations.each do |association, attributes|
association_changed_attributes = send(association).try(:changed_attributes) || {}
result.merge! association_changed_attributes.slice(*attributes)
end
changed_attributes_without_associations.merge!(result)
changed_attributes_without_associations
end
end

def self.included(base)
base.extend ClassMethods
base.send :include, InstanceMethods

base.class_inheritable_accessor :default_rejected_delegate_columns
base.default_rejected_delegate_columns = ['created_at','created_on','updated_at','updated_on','lock_version','type','id','position','parent_id','lft','rgt']

base.class_inheritable_accessor :dirty_associations
base.dirty_associations = {}

base.alias_method_chain :changed_attributes, :associations
end
end

Expand Down
35 changes: 35 additions & 0 deletions spec/model/dirty_partial_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper')

describe DelegateBelongsTo, 'with partial dirty delegations' do

before :all do
@fields = [:firstname]
UserNoDefault.belongs_to :contact
UserNoDefault.delegates_attributes_to :contact, :firstname
end

before :each do
@user = UserNoDefault.new
end

[:lastname, :lastname_change, :lastname_changed?, :lastname_was, :lastname_will_change!].each do |method|
it "should not respond_to #{method}" do
@user.should_not respond_to(method)
end
end

describe "changing not tracked attribute" do
before :each do
@user.build_contact
@user.contact.lastname = "Smith"
end

it "should NOT be changed as user" do
@user.should_not be_changed
end

it "should be changed as user.contact" do
@user.contact.should be_changed
end
end
end
156 changes: 156 additions & 0 deletions spec/model/dirty_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
require File.join(File.dirname(__FILE__), '..', 'spec_helper')

describe DelegateBelongsTo, 'with dirty delegations' do

before :all do
@fields = Contact.column_names - ActiveRecord::Base.default_rejected_delegate_columns
UserDefault.belongs_to :contact
UserDefault.delegates_attributes_to :contact
end

before :each do
@user = UserDefault.new
end

describe "reading from no contact" do
it "should return nil as firstname" do
@user.firstname.should be_nil
end

it "should return nil as change" do
@user.firstname_change.should be_nil
end

it "should not be changed" do
@user.firstname_changed?.should be_false
end

it "should return nil as firstname_was" do
@user.firstname_was.should be_nil
end

it "should return nil as lastname" do
@user.firstname_will_change!
@user.firstname_change.should == [nil, nil]
end
end


describe "reading from existing contact" do
before :each do
@user.build_contact
@user.contact.firstname = "John"
@user.contact.lastname = "Smith"
end

it "should be changed as user" do
@user.should be_changed
end

it "should be changed as contact" do
@user.contact.should be_changed
end

it "should read firstname" do
@user.firstname.should == "John"
end

it "should read lastname" do
@user.lastname.should == "Smith"
end

it "should return [nil, 'John'] as change" do
@user.firstname_change.should == [nil, "John"]
end

it "should not be changed" do
@user.firstname_changed?.should be_true
end

it "should return nil as firstname_was" do
@user.firstname_was.should be_nil
end

it "should return nil as lastname" do
@user.firstname_will_change!
@user.firstname_change.should == ['John', 'John']
end
end

describe "assigning value to delegators" do
it "should initialize association" do
@user.contact.should be_nil
@user.firstname = "John"
@user.contact.should_not be_nil
@user.firstname.should == "John"
end

it "should NOT initialize association second time" do
@user.firstname = "John"
contact_object_id = @user.contact.object_id
@user.lastname = "Smith"
@user.contact.object_id.should == contact_object_id
@user.firstname.should == "John"
end

end

[false, true].each do |bool|
describe "partial update #{bool}" do
before :all do
ActiveRecord::Base.partial_updates = bool
end

describe "#save" do
it "should clear changed_attribute in dirty assosiations" do
@user.firstname = "John"
@user.send(:changed_attributes).size.should == 1
@user.contact.send(:changed_attributes).size.should == 1
@user.save
@user.send(:changed_attributes).size.should == 0
@user.contact.send(:changed_attributes).size.should == 0
end
end

describe "#save(false)" do
it "should clear changed_attribute in dirty assosiations" do
@user.firstname = "John"
@user.send(:changed_attributes).size.should == 1
@user.contact.send(:changed_attributes).size.should == 1
@user.save(false)
@user.send(:changed_attributes).size.should == 0
@user.contact.send(:changed_attributes).size.should == 0
end
end

describe "#save!" do
it "should clear changed_attribute in dirty assosiations" do
@user.firstname = "John"
@user.send(:changed_attributes).size.should == 1
@user.contact.send(:changed_attributes).size.should == 1
@user.save!
@user.send(:changed_attributes).size.should == 0
@user.contact.send(:changed_attributes).size.should == 0
end
end

describe "#reload" do
before :each do
@user.firstname = "John"
@user.save!
end

it "should clear changed_attribute in dirty assosiations" do
@user.firstname = "Bob"
@user.send(:changed_attributes).size.should == 1
@user.contact.send(:changed_attributes).size.should == 1
@user.reload

@user.firstname.should == "John"
@user.send(:changed_attributes).size.should == 0
@user.contact.send(:changed_attributes).size.should == 0
end
end
end
end
end

0 comments on commit 431e8d6

Please sign in to comment.