When you compare the tests for your back-end code to the tests for your front-end code, is it like comparing Beauty to The Beast? It’s not entirely your fault; Javascript is notoriously hard to test.

There are a lot of tactics you can use to help mitigate this issue. One of the most effective that I’ve seen is to simply render the Javascript component to the DOM. That’s right, render the heck out of that component ;) . We use jstestdriver to run our Javascript tests; the tests execute in the browser, so why not make use of the awesomeness of the browser in your tests?

Let’s take the following example:

'test should show tooltip immediately if foo is true': function() {
        var cmp = Ext.create('MyComponent', {
            foo: true
        });

        cmp.render(Ext.getBody());

        Assert.isTrue(cmp.tooltip.isVisible());
}

This simple example renders cmp (an Ext Component) to the body element and asserts that it’s tooltip is visible. The reason that this test works is because the logic determining whether or not to show the tooltip immediately is in cmp’s onRender method, so we have to actually render cmp to exercise that logic.

That’s seems pretty simple, right? Well, it kinda is because of we rendered the heck out of it! :)

If we hadn’t rendered it, we probably would’ve had to write the test like this (we use sinon for stubbing):

'test should show tooltip immediately if foo is true': function() {
        var cmp = Ext.create('MyComponent', {
            foo: true,
        });
        sinon.stub(cmp, 'callParent');  //Make callParent do nothing
        sinon.stub(cmp, '_someMethodCalledByOnRenderThatDoesSomethingWithTheDom');  //Make someMethod... do nothing
        cmp.toolTip = {
            on: sinon.stub(),
            showNow: sinon.stub()
        };

        cmp.onRender();  //Directly call the onRender method

        Assert.isTrue(cmp.toolTip.showNow.calledOnce);
}

Notice how there’s a lot of stubbing out of methods (both public and private) so that methods that manipulate the DOM (onRender in the parent class and _someMethodCalledByOnRenderThatDoesSomethingWithTheDom) don’t execute. Still works but not as robust or easy to read. Rendering the component made the test a lot easier to write! :)

In the third post I showed you how you can create an App from scratch using the App Starter Kit.

In this post I will show you how to take our existing Kanban Board App and enhance it to show colors based upon certain Tags. Many of our customers are using Tags to show Kanban Cards that need special attention or should be expedited through the development process.

First download the Kanban App here. You should place the unzipped contents of the folder into a sub-folder beneath your App Starter Kit.

So the first thing we want to do is add a CSS class to the card for each Tag. Since Tag names can have spaces in them it is probably best to do a little work to sanitize them. Inside the KanbanCardRender class you will want to add a function like this:

    this.applyTagStyles = function () {
        rally.forEach(item.Tags, function (tag) {
            dojo.addClass(card, tag.Name.replace(" ", "-"));
        });
    };

Then at the bottom of the _populateCard function you can add a call to this function:

     that.applyTagStyles();

This code will add a CSS class to each card for every tag in the artifact’s collection. The next step is to add some CSS classes to change the colors of the headers.

At the bottom of the Kanban.css you can add the following CSS for each Tag that you want to color code a card.

.cardboard .your-tag-name-here .cardHeader {
    background-color: #FF1111;
    !important
}

Here is a link to my completed version. It will change items that have Tags named ‘expedited’ to show a horrid red color.

Duplication in a codebase is problematic for several reasons. First, if the code really should be the same, it makes it easy to introduce bugs if you need to change it. The more places the same snippet of code exists, the more chances you have to forget to implement changes. Perhaps more importantly, if some parts of the code are intentionally different, it can become hard to tell whether the differences are intentional.

Today, I ran across a code snippet that looked something like this. The comment was there already – as much as I wish otherwise, I didn’t add it.

final Class queryClass = queryType.getTypeClass();
// TODO Yuck, this could use some refactoring
if (queryClass.equals(Project.class)) {
    Workspace workspace = RallyRequestContext.getCurrent().getWorkspace();
    Collection<Project> accessibleProjs = filter(workspace.getProjects(), accessibleTo(user));
    Collection<Long> oids = transform(accessibleProjs, toOids());
    criteria.add(new InstancesForOidsCriterion(oids));
} else if (queryClass.equals(Scope.class)) {
    List<Long> oids = newArrayList();
    Workspace workspace = RallyRequestContext.getCurrent().getWorkspace();
    oids.add(workspace.getOID());
    Collection<Project> accessibleProjs = filter(workspace.getProjects(), accessibleTo(user));
    for (Project project : accessibleProjs) {
       oids.add(project.getOID());
    }
    criteria.add(new InstancesForOidsCriterion(oids));
} else if(queryClass.equals(AttributeConfiguration.class)) {
    List<Long> oids = newArrayList();
    Workspace workspace = RallyRequestContext.getCurrent().getWorkspace();
    oids.add(workspace.getOID());
    Collection<Project> accessibleProjs = filter(workspace.getProjects(), accessibleTo(user));
    for (Project project : accessibleProjs) {
       oids.add(project.getOID());
    }
    criteria.add(new AttributeConfigurationsInScopesCriterion(oids));
}

Clearly, there’s duplication here, both subtle and blatant. There are subtle differences in the three blocks above. Can you tell which are intentional and which are just noise?

First, there’s the alternating usage of Google Guava’s Collections2.transform and for each loops. Let’s at least make those consistent. That gives us this:

final Class queryClass = queryType.getTypeClass();
// TODO Yuck, this could use some refactoring
if (queryClass.equals(Project.class)) {
    Workspace workspace = RallyRequestContext.getCurrent().getWorkspace();
    Collection<Project> accessibleProjs = filter(workspace.getProjects(), accessibleTo(user));
    Collection<Long> oids = transform(accessibleProjs, toOids());
    criteria.add(new InstancesForOidsCriterion(oids));
} else if (queryClass.equals(Scope.class)) {
    List<Long> oids = newArrayList();
    Workspace workspace = RallyRequestContext.getCurrent().getWorkspace();
    oids.add(workspace.getOID());
    Collection<Project> accessibleProjs = filter(workspace.getProjects(), accessibleTo(user));
    oids.addAll(transform(accessibleProjs, toOids()));
    criteria.add(new InstancesForOidsCriterion(oids));
} else if(queryClass.equals(AttributeConfiguration.class)) {
    List<Long> oids = newArrayList();
    Workspace workspace = RallyRequestContext.getCurrent().getWorkspace();
    oids.add(workspace.getOID());
    Collection<Project> accessibleProjs = filter(workspace.getProjects(), accessibleTo(user));
    oids.addAll(transform(accessibleProjs, toOids()));
    criteria.add(new AttributeConfigurationsInScopesCriterion(oids));
}

Now that we’ve eliminated the subtle duplication, we’re left with blatant duplication. Let’s fix that up some by moving all of the repeated code out of the individual if-blocks:

final Class queryClass = queryType.getTypeClass();

Workspace workspace = RallyRequestContext.getCurrent().getWorkspace();
List<Long> oids = newArrayList();

Collection<Project> accessibleProjs = filter(workspace.getProjects(), accessibleTo(user));
oids.addAll(transform(accessibleProjs, toOids()));

// TODO Yuck, this could use some refactoring
if (queryClass.equals(Project.class)) {
    criteria.add(new InstancesForOidsCriterion(oids));
} else if (queryClass.equals(Scope.class)) {
    oids.add(workspace.getOID());
    criteria.add(new InstancesForOidsCriterion(oids));
} else if (queryClass.equals(AttributeConfiguration.class)) {
    oids.add(workspace.getOID());
    criteria.add(new AttributeConfigurationsInScopesCriterion(oids));
}

This is getting better. We can now see what’s different about each case. Project and Scope queries both use the same type of criterion. Only Scope includes the workspace. Like Scope, AttributeConfiguration includes the workspace, but it uses a different criterion.

Could you quickly see any of that in the original code sample?

One problem is that we might actually be doing some extra work now, because code that was previously hidden behind conditionals is now executed every time. We can fix that by extracting this code into a method and adding a guard clause. This code could use some method extraction anyway.

One of the things I really enjoy about being in Operations is the variety of work that comes your way. Some of this variety comes from projects in different areas of the system, but some of this variety comes from being exposed to interrupts that come along each day. While Agile methods are designed to allow for pivots, handling daily interrupts on the scale that you see in Operations can be very intrusive to the process.

Most organizations have an on-call schedule to handle after-hours issues on a rotating basis, but we have taken that one step further. What we wanted to do was try to offload much of that daily interrupt driven work away from those folks working through their normal project oriented stories. This has the effect of allowing the majority of the team to focus for 3 weeks at a time (we have a 4 person rotation) while one person handles the majority of these interrupt type issues. This lucky person is the “Op of the Week” or OW for short.

A similar idea exists on the Development side, we call them the “Dev of the Week” or DW. They are the first point of escalation for the OW on application related issues where we need Dev team assistance.

One of the areas we have struggled a little bit with our OW rotation is making sure everyone agrees about what falls under the responsibility of the OW. As a result we’ve created a document which defines our working agreements around the OW role. We review this as a team and make adjustments where necessary.

Here’s an overview of the types of stuff we focus the OW on for their given week & the role we expect them to hold:

Rotation: OW duties rotate every Monday at 12pm. This provides time for the outgoing OW to provide an update at SoS (Scrum of Scrums) and to provide any turnover to the new OW.

General Duties:

  • Respond to all critical pages (Failed hardware, software, high load events, etc.).
  • Communicate status to team when issues are being worked. Include DW & general Dev team as appropriate.
  • File defects when needed for application events or problems that require assistance from DW or Dev team.
  • Issues raised during OW are owned to completion, coordinating additional resources as necessary.
  • Attend Scrum of Scrums & provide status on any new Ops issues (customer problems, production outages, etc)
  • Respond to all incoming operations interrupt type requests (includes all Ops responsibilities, not just prod)
  • Remediation of critical security issues. Drive required security patches / workarounds to completion.
  • Review nightly jobs including Backups for errors / problems.

We also maintain a calendar of events in this same document which describes the routine daily activities which are part of the OW duties. For example, because we perform weekly deploys to production we have a pre-production push on Thursday evening of the Release Candidate that the OW performs. Saturday mornings when our production release rolls out, the OW is expected to be on high alert for problems.

This process has worked well with some adjustment over time. It is also not intended to be completely rigid. For example, we certainly have cases where something will come up around a certain part of our infrastructure where the OW is not the expert. When appropriate, and where both parties agree, other team members are welcome to take ownership of issues that come up if they are passionate about getting those issues resolved and/or have expertise in that area. We don’t expect team members to do this all the time though, as being OW also forces some cross-training, something we think is really important.

If you’ve found a good way to offload interrupts or have feedback on what we’ve done, let us know in the comments.

Today we want to share a nice integration between JsTestDriver and SinonJS that allows for great stubbing and mocking facilities in your JavaScript tests.

A common thing needed when writing JavaScript unit tests is to mock out an object or the return value of a function. In addition to this, it’s important to restore the original object or function at the end of the test. Let’s check out a pretty trivial example:

Rally.getVersion = function() {
  return 2;
};

Rally.isSupported = function(operation) {
  return Rally.getVersion() > 1;
};

TestCase('IsSupportedTest', {
  'test should support version greater than one': function() {
    assertTrue(Rally.isSupported('read'));
  },
  'test should not support version less than two': function() {
    var orig = Rally.getVersion = function() { return 1; };
    try {
      assertFalse(Rally.isSupported('read'));
    } finally {
      Rally.getVersion = orig;
    }
  }
});

Now let’s say you have 20 tests around the Rally.isSupported method. Each of which you might need to clean up any mocked out methods. This would get tedious, so you might leverage the setUp and tearDown methods provided by JsTestDriver’s TestCase:

TestCase('IsSupportedTest', {
  setUp: function() {
    this.origGetVersion = Rally.getVersion;
  },
  tearDown: function() {
    Rally.getVersion = this.origGetVersion;
  },

  'test should support version greater than one': function() {
    assertTrue(Rally.isSupported('read'));
  },
  'test should not support version less than two': function() {
    Rally.getVersion = function() { return 1; };
    assertFalse(Rally.isSupported('read'));
  }
});

Another way to do this would be to use the SinonJS framework, specifically the stub() method. This allows you to stub out an existing method on an object, and provide expected return values as well as a variety of methods to assert the method was called:

TestCase('IsSupportedTest', {
  setUp: function() {
    this.getVersion = sinon.stub(Rally, 'getVersion');
  },
  tearDown: function() {
    this.getVersion.restore();
  },

  'test should support version greater than one': function() {
    this.getVersion.returns(2);
    assertTrue(Rally.isSupported('read'));
  },
  'test should not support version less than two': function() {
    this.getVersion.returns(1);
    assertFalse(Rally.isSupported('read'));
  }
});

This is nice and clean. The restore() method will automatically unwrap the stubbed method, and we can easily control the return values of the stubbed method. But we can make this even cleaner. We can have sinon automatically restore all stubbed and mocked methods upon destruction of the TestCase object. This can be done by leveraging Sinon’s sinon.testCase, which is an integration wrapper between SinonJS and JsTestDriver.

(function() {
    var origJstestDriverTestCase = window.TestCase;
    var origSinonTestCase = window.sinon.testCase;

    window.sinon.testCase = function() {
        throw Error('** Do not call sinon.testCase() directly, TestCase() will do this for you! **');
    };

    var globalSetUp = function() {
        // ... global setup stuff goes here
        if(this.originalSetUp) {
            this.originalSetUp();
        }
    };

    var globalTearDown = function() {
        // ... global tear down stuff goes here
        if(this.originalTearDown) {
            this.originalTearDown();
        }
    };

    window.TestCase = function(name, prototype, optType) {
        prototype.originalSetUp = prototype.setUp;
        prototype.setUp = globalSetUp;

        prototype.originalTearDown = prototype.tearDown;
        prototype.tearDown = globalTearDown;

        prototype = origSinonTestCase(prototype);
        return origJstestDriverTestCase(name, prototype, optType);
    };
})();

With this in place, we can now write the following test:

TestCase('IsSupportedTest', {
  setUp: function() {
    this.getVersion = this.stub(Rally, 'getVersion');
  },

  'test should support version greater than one': function() {
    this.getVersion.returns(2);
    assertTrue(Rally.isSupported('read'));
  },
  'test should not support version less than two': function() {
    this.getVersion.returns(1);
    assertFalse(Rally.isSupported('read'));
  }
});

Wrapping the TestCase’s prototype with sinon.testCase exposes all of the SinonJS methods through the TestCase object – so this.stub(), this.mock(), etc will all work. Additionally, SinonJS will automatically unwrap the stubbed/mocked objects at the end of the test. We’ve also put some logic in to ensure the following will throw an error:

TestCase('IsSupportedTest', sinon.testCase({  // <--- don't need to do this, it's done automatically
  setUp: function() {
    this.getVersion = this.stub(Rally, 'getVersion');
  },

  'test should support version greater than one': function() {
    this.getVersion.returns(2);
    assertTrue(Rally.isSupported('read'));
  },
  'test should not support version less than two': function() {
    this.getVersion.returns(1);
    assertFalse(Rally.isSupported('read'));
  }
}));

In my second post I explained how to get started making your own Apps using Rally’s APP SDK. In this post I will show you how to use the starter kit to create an App that shows the stories that have changed in the past day.

First we will use the starter kit to create up a new App.

Rake Create

Note: In the example image you will see that I had to escape the brackets in order to get the rake task to run in oh-my-zsh.

Open the LastDay.js file that is contained in the folder that the starter kit created for you. For editing JavaScript I like the JetBrains IDEs, for this tutorial I will be using RubyMine to help create the App.

IDE example

One of the important things to remember when using the SDK is to make sure that all of your JavaScript code is called after the addOnLoad callback has been completed. The addOnLoad callback is called by the underlying SDK code after all the resources that are needed are loaded and the environment for your App has been set up.

For this App we will use the RallyDataSource to query Rally for work items that have been changed in the last day and put those items into a table.

First we need to configure the RallyDataSource. By default a RallyDataSource will follow the current projects scoping. The server replaces the special variables like __PROJECT_OID__ with the current scoping information from your Rally session. When you change scope in Rally your App will be reloaded and those scoping variables will be replaced with the updated project information.

var rallyDataSource = new rally.sdk.data.RallyDataSource(&amp;quot;__WORKSPACE_OID__&amp;quot;,
&amp;quot;__PROJECT_OID__&amp;quot;,
&amp;quot;__PROJECT_SCOPING_UP__&amp;quot;,
&amp;quot;__PROJECT_SCOPING_DOWN__&amp;quot;);

Since we are going to only show stories that have changed in the last day we will need to calculate yesterday’s date.

var yesterday = rally.sdk.util.DateTime.add(new Date(), &amp;quot;day&amp;quot;, -1);
var startOfYesterday = new Date(yesterday.getFullYear(), yesterday.getMonth(), yesterday.getDate());
var stringDate = rally.sdk.util.DateTime.toIsoString(startOfYesterday);

The SDK ships with a some utilities to make it easier to work with dates. In the above code I find the DateTime for today and subtract one day from it. I then create a new date with the year, month, and day to get a DateTime that is at the beginning of the day. Lastly that DateTime is translated into an ISO string (the format that Rally expects dates to be when they are sent.)

Next we need to create the configuration object for our table.

var config = {
//this is the type for User Story
type: &amp;quot;hierarchicalrequirement&amp;quot;,
query: &amp;quot;( LastUpdateDate &amp;amp;gt; &amp;quot; + stringDate + &amp;quot; )&amp;quot;,
columns:
[
{key: 'FormattedID', header: 'Formatted ID', width: 100},
{key: 'Name'},
{key: 'ScheduleState', header: 'Schedule State'}
]
};
  • type: Type is the Rally item type that you want the table to query for. In this case we want to display User Stories so we use the name WSAPI type name for them which is “hierarchicalrequirement”. You will notice that the type name has all spaces removed from it so if you wanted to see last changed defect suites the type would be “defectsuite”
  • query: Query is a filter you want to put on the data that will shown in your table. LastUpdateDate is changed every time that an artifact is changed in Rally. In the case of our query we want things that have a LastUpdateDate greater than yesterday.
  • columns: Columns is an array that contains what fields you want displayed in your table.

Now we need a place to display the table. First we need to create a div to show it in. That can be done by opening up the LastDay.template.html file and putting a div tag into the body.

&amp;lt;body class=&amp;quot;lastDay&amp;quot;&amp;gt;
&amp;lt;div id=&amp;quot;myDiv&amp;quot;&amp;gt;

&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;

While we are in here we can change the name of the App to something more appropriate. In my case I will change the name of the App to “App: Example Day 3″.

&amp;lt;meta name=&amp;quot;Name&amp;quot; content=&amp;quot;App: Example Day 3&amp;quot;/&amp;gt;

The final code step is to create and show the Table in the browser.

var table = new rally.sdk.ui.Table(config, rallyDataSource);
table.display(&amp;quot;myDiv&amp;quot;);

After you add that last piece of code you should be able to open LastDay.template.html in your browser. If you are currently logged into Rally you should see a list of stories that have been updated in a table.

To combine these files into one file so that it can be deployed into a Rally App tab all you need to do is use the starter kit’s combine task.

rake combine

That will create a file named LastDayApp.html, the contents of this file can be copied into a Rally App tab or Custom Tab.

LastDay zip archive is attached to help illustrate the things I covered in the post.

In my next App Tutorial I will show you how to add the ability to show Kanban cards that are colored by the tags they contain.

We use JSDuck to document our JavaScript.  This is the same tool that Ext uses for its documentation.

A few days ago, I was looking through some of our JavaScript where we’d put comments that would be picked up by JSDuck when it creates the documentation that we use.

We had some config options that were incorrectly documented like this:

/**
 * @cfg {String} some string config with a default value
 */
defaultName: 'fooBar'

JSDuck would see ’some’ as the name of the config since it was the first word following the type (String) and ’some’ would show up in the documentation as a config option for the class that was being documented

I first fixed by putting the appropriate name of the config as the first word following the type:

/**
 * @cfg {String} defaultName Some string config with a default value
 */
defaultName: 'fooBar'

Then I noticed that some of the other configs in the same file were defined slightly differently; they didn’t have the name of the config in the comment, but the documentation was still generated correctly:

/**
 * @cfg {String}                                  <-----There's nothing else on this line!
 * Some string config with a default value
 */
defaultName: 'fooBar'

This works great because then you don’t have to copy/paste the name of the config option, JSDuck figures it out for you. Makes it a lot easier when/if the name of the config option gets changed because it won’t be hidden in a comment right above the declaration. Duplication is bad!!!!

At this point, I thought that I’d learned everything I’d ever need to know about JSDuck ;) so I sent out an email to the other developers sharing my new-found knowledge (read: awesomeness ;) )

That’s when the great Mr. Burke Webster added some more great points:

“If your config option has a default value and it’s a basic type, JSDuck will also auto-detect that. So you can do things like this:

/**
 * @cfg
 * Should do something cool
 */
doSomethingCool: true

It will auto-detect that doSomethingCool is a boolean, with a default value of true.

/**
 * @cfg
 * Some string config with a default value
 */
defaultName: 'fooBar'

It will auto-detect that defaultName is a String, with a default value of ‘fooBar’. And if you don’t want to provide a default value, make sure you use ‘undefined’:

/**
 * @cfg
 * A name config for some component
 */
name: undefined

If you try other empty (or falsy) values, like empty string or null, the docs will report the config option has a default value of null.”

Engineering blog: How long have you been with Rally and what were you doing before?
Matt: I’ve been with Rally for about a year and a half now. Previously, I was working as a software developer/architect for the University of Phoenix.

EB: What made you choose Rally?
Matt: I chose Rally because of its: Passion for quality in all levels of the organization. Commitment to community and environment. Focus on employees maintaining a healthy work-life balance. Fun! – This company seriously knows how to have fun.

EB: What sorts of things have you worked on at Rally?
Matt: Converting from Eclipselink to JPA API (btw, thanks for that week 1 hazing!). Migrating Rally search to use the Solr search engine. Foundation of our new User Interface – ExtJS4-based JavaScript toolkit, Multi-Tab browsing. Upgrading our Ruby Selenium browser tests to use the version 2.0 WebDriver API.

EB: What are your favorite things to work on?
Matt: I prefer working on the front-end, enhancing our user experience and developing the building blocks to enable fast development for both our internal teams and external customers building new UI’s with our Web Services API and JS toolkit.

EB: Explain your nickname.
Matt: P-bomb. Originally coined by Russ while playing foosball against me. It typically refers to me scoring on a thundering shot from the defensive position or a slow change-up shot that drives the goalie crazy when they can’t stop it. At that time, p-bombs were seldom seen, but I’ve improved enough that I think others secretly gather to share how terrified they are of them now.

EB: Anything else you’d like to share about life at Rally?
Matt: Can’t really say good enough good things. It’s a great group of passionate people who challenge me and bring out the best in each other.

In my first post I explained the advantages of creating your own App in Rally. An App is a customized view of Rally data that is served in an IFrame. Apps can create interesting ways to interact with Rally data and internally we use them to prototype new features and ideas that are not quite ready to be a portion of the full product. In this post I will explain to you some of the tools that can be used to help you get started on your first App.

One of the challenges facing new App developers is how to organize their files. It is usually easier to develop an App by breaking out the different language sections into their own files. This is because Editors and IDEs tend to be more efficient when working on a file that contains purely HTML, CSS or JavaScript rather than a mixture. Unfortunately Rally expects Apps to be in one contiguous blob of text when it is pasted into the custom App tab. In my experience it can be error prone trying to copy the contents of each style and script tag from an outside file into a single page HTML file.

One of the ways that we circumvent this problem while we develop Apps internally is through the App Development Starter Kit. The starter kit can be found alongside our other tools in our developer portal. It combines each of the separate CSS and JavaScript files together into the one centralized HTML file. This file can be easily copied into Rally to create a new custom App. Once you have the starter kit, making a new App is a breeze. All you need to do is run rake new[APPNAME] and all the files that you need to start your new App will be combined into that contiguous blob the Rally tool knows and loves.

In part two I will show you how to create an App that shows you all of the work items that have been changed in the past day.

I have a love/hate relationship with ORM. I love how it can be a massive productivity boost for some projects. I hate how it’s so easy to misuse. I still firmly believe that if ORM is used appropriately, it has a place in many applications.

Using a mapped collection on an ORM-managed entity is almost never “appropriate use”.

Consider this code. Assume it’s on an entity named Category. You have another entity type named Document. You can add Documents to Categories.

public void addDocument(Document document) {
  getDocuments().add(document);
}

DON’T DO THIS! Ever. Period. End of story. It will PWN your server one day. Okay, really, don’t do it. When the category accumulates a large number of documents, you’ll spend more time allocating memory and querying than is really appropriate. If you’re not sure what I’m talking about, take a look at how mapped collections work in your ORM of choice. They are mostly the same across most ORMs.

If you need to manage a collection-like relationship when using an ORM, always have the child record manage it. If you absolutely need the addDocument method on category, implement it like this:

public void addDocument(Document document) {
  document.addCategory(this);
}

This saves you from having the ORM hydrate the collection when loading the documents collection on category.

There’s one problem with both code samples. If you’re acting on the document or the category in memory, and you don’t manage the collection from both directions, you could end up with inconsistent results. Even worse, if you’re using a second level cache, you could end up with semi-persistent inconsistent results.

Pay attention to how you use collections with an ORM. If you use them, you’re probably wrong.

« Previous PageNext Page »