Skip to content
This repository has been archived by the owner on Dec 12, 2018. It is now read-only.

Commit

Permalink
some progress on has_one association testing. ran into wall trying to…
Browse files Browse the repository at this point in the history
… replace an existing singular association.
  • Loading branch information
cainlevy committed Dec 12, 2008
1 parent d06a4b0 commit 69908c4
Show file tree
Hide file tree
Showing 4 changed files with 98 additions and 14 deletions.
34 changes: 25 additions & 9 deletions lib/nested_assignment.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
# NestedAssignment
module NestedAssignment
def self.included(base)
base.class_eval { extend ClassMethods }
base.class_eval do
extend ClassMethods

alias_method_chain :save, :associated
alias_method_chain :valid?, :associated
alias_method_chain :changed?, :associated
end
end

module ClassMethods
Expand All @@ -12,7 +18,13 @@ def accessible_associations(*associations)
define_method("#{name}_params=") do |hash|
assoc = self.send(name)
hash.values.each do |row|
record = row[:id].blank? ? assoc.build : assoc.select{|r| r.id == row[:id].to_i}
# TODO: need to bypass the replace() call inside singular associations (has_one and belongs_to). but they
# do serve a purpose: disassociating or destroying an existing record. if that is not to happen during
# assignment, then those records need to be collected for later disassociation (or removal, if :dependent
# => :destroy). that would need to be part of the saving process. ALSO, this makes sense to handle while
# deleting from plural associations. so perhaps instead of setting #_delete, i should add to a
# disassociation hash for later.
record = row[:id].blank? ? assoc.build : [assoc].flatten.detect{|r| r.id == row[:id].to_i}
if row[:_delete]
record._delete = true
else
Expand All @@ -29,26 +41,30 @@ def association_names
end
end

# marks the record to be deleted in the next save
# marks the (associated) record to be deleted in the next deep save
attr_accessor :_delete

# deep validation of any changed (or new) records.
# makes sure that any single invalid record will not halt the
# validation process, so that all errors will be available
# afterwards.
def valid?
[changed_associated.all?(&:valid?), super].all?
def valid_with_associated?
[changed_associated.all?(&:valid?), valid_without_associated?].all?
end

# deep saving of any new, changed, or deleted records.
def save
def save_with_associated
self.class.transaction do
super
changed_associated.each(&:save)
deletable_associated.each(&:destroy)
changed_associated.all?(&:save) &&
deletable_associated.all?(&:destroy) &&
save_without_associated
end
end

def changed_with_associated?
changed_without_associated? or instantiated_associated.any?(&:changed?)
end

protected

def deletable_associated
Expand Down
14 changes: 13 additions & 1 deletion test/db/models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,37 @@ def self.included(base)

class Address < ActiveRecord::Base
belongs_to :addressable, :polymorphic => true

validates_presence_of :name
end

class User < ActiveRecord::Base
has_and_belongs_to_many :roles
has_one :subscription
has_one :address, :as => :addressable

accessible_associations :subscription

validates_presence_of :name
end

class Service < ActiveRecord::Base
has_many :subscriptions
has_many :users, :through => :subscriptions

validates_presence_of :name
end

class Subscription < ActiveRecord::Base
belongs_to :service
belongs_to :user

validates_presence_of :name
end

class Role < ActiveRecord::Base
has_and_belongs_to_many :users

validates_presence_of :name
end
end
end
7 changes: 4 additions & 3 deletions test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
require 'active_record'
require 'active_record/fixtures'

# load the code-to-be-tested
ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__) + '/../lib/'
require File.dirname(__FILE__) + '/../init'

# establish the database connection
ActiveRecord::Base.configurations = YAML::load(IO.read(File.dirname(__FILE__) + '/db/database.yml'))
ActiveRecord::Base.establish_connection('nested_assignment_test')
Expand All @@ -31,6 +35,3 @@ class ActiveSupport::TestCase
fixtures :all
end

# load the code-to-be-tested
ActiveSupport::Dependencies.load_paths << File.dirname(__FILE__) + '/../lib/'
require File.dirname(__FILE__) + '/../init'
57 changes: 56 additions & 1 deletion test/unit/nested_assignment_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,63 @@

require 'ruby-debug'

class NestedAssignmentTest < ActiveSupport::TestCase
class NestedAssignmentHasOneTest < ActiveSupport::TestCase
def setup
@user = users(:bob)
@subscription = subscriptions(:bob_is_free)
end

def test_updating_a_subscription
@user.subscription_params = {
"1" => {
:id => @subscription.id,
:name => "Bobtastic"
}
}
assert !@user.subscription.new_record?, "the association was not rebuilt"
assert_equal "Bobtastic", @user.subscription.name, "the existing subscription's name has changed"
assert_equal "Bob/Free", @subscription.reload.name, "the name change has not been saved"
end

def test_assigning_a_replacement_subscription
@user.subscription_params = {
"1" => {
:name => "Bobtastic"
}
}
assert @user.subscription.new_record?, "the association is a new object"
assert_equal "Bobtastic", @user.subscription.name, "the new record has the specified name"
assert !@subscription.reload.user_id.nil?, "the previously associated object has not been disassociated yet"
end

def test_assigning_a_removed_subscription
@user.subscription_params = {
"1" => {
:id => @subscription.id,
:name => "Bobtastic",
:_delete => "1"
}
}
assert @user.subscription._delete, "the association is marked for deletion"
assert_nothing_raised("the associated object has not been deleted yet") do @subscription.reload end
assert_equal "Bob/Free", @user.subscription.name, "the association attribute did not update"
end

end

class NestedAssignmentBelongsToTest < ActiveSupport::TestCase
end

class NestedAssignmentHasManyTest < ActiveSupport::TestCase
end

class NestedAssignmentHasAndBelongsToManyTest < ActiveSupport::TestCase
end

class NestedAssignmentHasManyThroughTest < ActiveSupport::TestCase
end

class NestedAssignmentHelperTest < ActiveSupport::TestCase
def test_association_names
assert_equal [:address, :roles, :subscription], User.association_names.sort_by(&:to_s)
end
Expand Down

0 comments on commit 69908c4

Please sign in to comment.