Find your content:

Search form

You are here

Why do subsequent calls to StandardController.save() insert records instead of updating current record

 
Share

I encountered some unexpected behavior with the save() method on the standard controller and just curious as to the reasoning behind it.

I have an instance of a StandardController for a new custom object. Calling the save() method successfully inserts a new record for the custom object. If you call save() again, it inserts another new record instead of updating the existing one in the controller's context.

Here's the basic code with a unit test the demonstrates the problem: (thanks @eyescream for the suggestion)

public with sharing class ContactControllerExtension{
    ApexPages.StandardController sc;

public ContactControllerExtension(ApexPages.StandardController sc){
    this.sc = sc;
}

public PageReference click(){
    Contact c = (Contact)sc.getRecord();
    c.LastName = 'Test';

    sc.save();
    System.debug(sc.getId());        
    c.FirstName = sc.getId() + ' ';

    sc.save();
    System.debug(sc.getId());
    c.FirstName += sc.getId();

    return sc.view();
}

static testMethod void testClick(){
    List<Contact> contacts = [select Id, Name from Contact];
    System.assertEquals(0, contacts.size()); // passes

    Contact c = new Contact();
    ApexPages.StandardController std = new ApexPages.StandardController(c);
    ContactControllerExtension ext = new ContactControllerExtension(std);
    ext.click();

    // should have created only 1 Contact but actually creates 2
    contacts = [select Id, Name from Contact];
    system.assertEquals(1, contacts.size()); // fails   
}

}

Why doesn't the second call to save() update the record that was inserted in the first call to save()? It seems to be holding on to the reference of the object that was passed into the constructor. This would kind of make sense to me if the call to getId() returned null. At least it would appear that the context of the controller is consistent.


Attribution to: rycornell

Possible Suggestion/Solution #1

EDIT

I've experimented a bit more and kind of managed to get it to work.

public with sharing class stack{
    ApexPages.StandardController sc;
    Contact c;

    public stack(ApexPages.StandardController sc){
        this.sc = sc;
        Contact c = (Contact)sc.getRecord();
        c.LastName = 'Test';
    }

    public PageReference click(){
        sc.save();
        System.debug(sc.getId());    // 003...
        System.debug(sc.getRecord());// Contact:{LastName=Test}
        System.debug(c);             // null (no Id, no LastName... just null)

        // getId() works but getRecord() seems to be unreliable after save()'ing new record, let's fetch it ourselves
        c = [SELECT Id, FirstName, LastName FROM Contact WHERE Id = :sc.getId()];
        sc = new ApexPages.StandardController(c);
        c = (Contact)sc.getRecord();

        c.FirstName = sc.getId();
        sc.save();
        System.debug(sc.getId());    // 003... (same value)
        System.debug(c);             // Contact:{FirstName=003..., Id=003..., LastName=Test}

        // And now getRecord() seems to behave as expected.
        c = (Contact)sc.getRecord();
        c.LastName +=' 2';
        sc.save();

        return sc.view();
    }

    static testMethod void testClick(){
        List<Contact> contacts = [select Id, Name from Contact];
        System.assertEquals(0, contacts.size()); // passes

        Contact c = new Contact();
        ApexPages.StandardController std = new ApexPages.StandardController(c);
        Stack ext = new Stack(std);
        System.assert(ext.click().getUrl().contains(std.getId()));

        contacts = [select Id, FirstName, LastName from Contact];
        system.assertEquals(1, contacts.size());
        system.assert(String.valueOf(contacts[0].Id).startsWith(contacts[0].FirstName)); // I know, looks hilarious
        system.assertEquals('Test 2', contacts[0].LastName);
    }
}

And the page:

<apex:page standardController="Contact" extensions="stack" action="{!click}">
    {!Contact.FirstName} {!Contact.LastName} {!contact.Id}
</apex:page>

I'll have to think what actually happens here, it's too early in the morning. What puzzles me is that first System.debug(c); gives null!

I'm suspecting some magic is happening to give us "consistent behavior" compared to inserts (meaning that insert returns back only the Id of the object, you want the fields that were set to defaults on DB level or in triggers - you have to query for them).

Your best option might be calling your own upsert myObject; instead of save() :/


Attribution to: eyescream

Possible Suggestion/Solution #2

Here's a snippet of the solution I've implemented. After the save(), it fetches the object explicitly and does an upsert. Thanks @eyescream and @PeterKnolle for your suggestions.

// controller extension for Contact object
public with sharing class ContactControllerExtension{
    ApexPages.StandardController sc;

    public ContactControllerExtension(ApexPages.StandardController sc){
        this.sc = sc;
    }

    public PageReference click(){
        // object values have been set on the vf page
        // just need to save the object
        sc.save();

        // do a bunch of other stuff here...
        // if certain conditions are met, make some additional updates to the object...
        // getId() works but getRecord() seems to be unreliable after save()'ing new record, let's fetch it ourselves
        Contact c = [SELECT Id, FirstName, LastName FROM Contact WHERE Id = :sc.getId()];
        c.FirstName = 'John';

        upsert c;

        return sc.view();
    }
}

Attribution to: rycornell

Possible Suggestion/Solution #3

I wouldn't recommend using save() from a controller level. Use DML exclusively so that you have more specific control. You can get the current record off the standard controller when you construct the extension:

public myControllerExtension(ApexPages.StandardController stdController) {
    this.acct = (Account)stdController.getRecord();
    acct.Name = 'New Name';
    update acct; }

Attribution to: joshbirk
This content is remixed from stackoverflow or stackexchange. Please visit https://salesforce.stackexchange.com/questions/4076

My Block Status

My Block Content