Welcome to the new site! Some areas are "Coming Soon!" as the content is being migrated.
Hello! My name is Colin Loretz, I'm a web developer and designer based in Reno, Nevada, focusing on projects using the Salesforce.com, WordPress and iPhone/Mac platforms.

latest blog posts -

Salesforce Rollup Summary Fields using Apex Code

October 13th, 2008

Rollup Summary Fields on any field/object using APEX Code
Level: Intermediate

Rollup Summary Fields
The Rollup Summary fields provided by Salesforce offer a great way to summarize relationship data. If you have two objects, “Timesheet” and “Time Entry”, a rollup summary field can total the number of hours assigned to any given timesheet.

What problem does this address?
Standard rollup summary fields require a master-detail relationship. Currently, you can not setup a master-detail-detail+ relationship so what happens when you have more than 2 objects that you want to summarize? You won’t be able to.

The following Apex code helps solve this situation by allowing you to summarize information on objects that are related by either a Master-Detail relationship or a Lookup relationship field. You can even summarize information in a cascading fashion, by linking more than 2 objects together. I’ve been working on a project management application on the Force.com platform and we have Projects->Tasks->Time Entries. The time entries are summarized by hours and dollars at both the Task and Project level.

In tackling this problem, there are five use cases we must consider:
1. Creating a new record that rolls up to the parent record
2. Deleting an existing record that currently rolls up to the parent record
3. Updating a record with a new value and remains under the same parent record
4. Updating a record with a new value and changing the parent record that it rolls up to
5. The child value remains the same and we change the parent record that is being rolled up to

Disclaimer & Warning:
There are other ways to accomplish this type of functionality so please feel free to critique the code in the comments. The code has been written for processing bulk records but has not been extensively tested for excessively large record sets. IE: A parent object that summarizes 100+ child records that summarizes another 100+ child records below that. The more objects you have cascading under one another, the greater the chances of running into the Salesforce governor limits. Please feel free to provide feedback and recommendations on this as you encounter them.

GETTING STARTED

Step 1: Create the Parent and Child objects
1. Create a parent object:

  • Object Label: Weekly Timesheet
  • Object Name: Weekly_Timesheet
  • Save

2. In the Custom Fields & Relationships area of the new object, click New.

3. Create a new field to store the summarized data:

  • Field Type: Number
  • Field Label: Total Hours
  • Field Name: Total_Hours
  • Length: 16
  • Decimals: 2
  • Save

4. Create a child object:

  • Object Label: Time Entry
  • Object Name: Time_Entry
  • Save

5. In the Custom Fields & Relationships area of the new object, click New.

6. Create two fields on the child object:

  • Field Type: Number
  • Field Label: Hours
  • Field Name: Hours
  • Length: 16
  • Decimals: 2
  • Follow prompts and Save
  • Field Type: Lookup Relationship
  • Field Label: Timesheet
  • Field Name: Timesheet
  • Follow prompts and Save

7. Create a trigger (Download formatted code here)

trigger rollupHoursToTimesheet on Time_Entry__c (after insert, after update, after delete) {
double sumTotalHours = 0.0;
Weekly_Timesheet__c [] sheetsToUpdate = new Weekly_Timesheet__c[]{};

//***********************************************
//Code for updating existing records and new records
//***********************************************

if(Trigger.isInsert)
{
Time_Entry__c [] teNew = trigger.new;

for(Time_Entry__c te : teNew)
{
for (Weekly_Timesheet__c timesheet : [select Id, Name, Total_Hours__c from Weekly_Timesheet__c where Id = :te.Timesheet__c])
{

//Sum all the timesheet entries
for (Time_Entry__c timeEntries: [select Id, Hours__c from Time_Entry__c where Timesheet__c = :timesheet.id])
{
sumTotalHours += timeEntries.Hours__c;
}

timesheet.Total_Hours__c = sumTotalHours;

//add timesheet to list to be updated outside of the loop
sheetsToUpdate.add(timesheet);
}
}

//commit the changes to Salesforce
update sheetsToUpdate;
}

//***********************************************
//Code for updating when a record is updated
//***********************************************

else if(Trigger.isUpdate)
{
//sum total both old and new
Time_Entry__c [] oldTime = Trigger.old;
Time_Entry__c [] newTime = Trigger.new;
Double newSum = 0.0;
Double oldSum = 0.0;

for(Time_Entry__c newTe: newTime)
{
for(Time_Entry__c oldTe : oldTime)
{

Weekly_Timesheet__c oldTimesheet = [Select Id, Name, Total_Hours__c from Weekly_Timesheet__c where Id = :oldTe.Timesheet__c];
Weekly_Timesheet__c newTimesheet = [Select Id, Name, Total_Hours__c from Weekly_Timesheet__c where Id = :newTe.Timesheet__c];

Time_Entry__c [] newSumHours = [Select Id, Name, Hours__c from Time_Entry__c where Timesheet__c = :newTimesheet.Id];
Time_Entry__c [] oldSumHours = [Select Id, Name, Hours__c from Time_Entry__c where Timesheet__c = :oldTimesheet.Id];

//sum premiums from child objects
for(Time_Entry__c oldSumHour : oldSumHours)
{
oldSum += oldSumHour.Hours__c;
}

for(Time_Entry__c newSumHour : newSumHours)
{
newSum += newSumHour.Hours__c;
}

newTimesheet.Total_Hours__c = newSum;
oldTimesheet.Total_Hours__c = oldSum;

sheetsToUpdate.add(newTimesheet);
sheetsToUpdate.add(oldTimesheet);
}
}

update sheetsToUpdate;
}

//***********************************************
//Code for updating when a record is deleted
//***********************************************

else if(Trigger.isDelete)
{

Time_Entry__c [] teOld = trigger.old;

for(Time_Entry__c te: teOld)
{

for (Weekly_Timesheet__c timesheet: [select Id, Name, Total_Hours__c from Weekly_Timesheet__c where Id = :te.Timesheet__c])
{
for (Time_Entry__c timeEntries: [select Id, Hours__c from Time_Entry__c where Timesheet__c = :timesheet.id])
{
sumTotalHours += timeEntries.Hours__c;
}

timesheet.Total_Hours__c = sumTotalHours;

sheetsToUpdate.add(timesheet);
}
}

update sheetsToUpdate;
}

}

trigger rollupHoursToTimesheet on Time_Entry__c (after insert, after update, after delete) {

double sumTotalHours = 0.0;
Weekly_Timesheet__c [] sheetsToUpdate = new Weekly_Timesheet__c[]{};

//***********************************************
//Code for updating existing records and new records
//***********************************************

if(Trigger.isInsert)
{
Time_Entry__c [] teNew = trigger.new;

for(Time_Entry__c te : teNew)
{
for (Weekly_Timesheet__c timesheet : [select Id, Name, Total_Hours__c from Weekly_Timesheet__c where Id = :te.Timesheet__c])
{

//Sum all the timesheet entries
for (Time_Entry__c timeEntries: [select Id, Hours__c from Time_Entry__c where Timesheet__c = :timesheet.id])
{
sumTotalHours += timeEntries.Hours__c;
}

timesheet.Total_Hours__c = sumTotalHours;

//add timesheet to list to be updated outside of the loop
sheetsToUpdate.add(timesheet);
}
}

//commit the changes to Salesforce
update sheetsToUpdate;
}

//***********************************************
//Code for updating when a record is updated
//***********************************************

else if(Trigger.isUpdate)
{
//sum total both old and new
Time_Entry__c [] oldTime = Trigger.old;
Time_Entry__c [] newTime = Trigger.new;
Double newSum = 0.0;
Double oldSum = 0.0;

for(Time_Entry__c newTe: newTime)
{
for(Time_Entry__c oldTe : oldTime)
{

Weekly_Timesheet__c oldTimesheet = [Select Id, Name, Total_Hours__c from Weekly_Timesheet__c where Id = :oldTe.Timesheet__c];
Weekly_Timesheet__c newTimesheet = [Select Id, Name, Total_Hours__c from Weekly_Timesheet__c where Id = :newTe.Timesheet__c];

Time_Entry__c [] newSumHours = [Select Id, Name, Hours__c  from Time_Entry__c where Timesheet__c = :newTimesheet.Id];
Time_Entry__c [] oldSumHours = [Select Id, Name, Hours__c from Time_Entry__c where Timesheet__c = :oldTimesheet.Id];

//sum premiums from child objects
for(Time_Entry__c oldSumHour : oldSumHours)
{
oldSum += oldSumHour.Hours__c;
}

for(Time_Entry__c newSumHour : newSumHours)
{
newSum += newSumHour.Hours__c;
}

newTimesheet.Total_Hours__c = newSum;
oldTimesheet.Total_Hours__c = oldSum;

sheetsToUpdate.add(newTimesheet);
sheetsToUpdate.add(oldTimesheet);
}
}

update sheetsToUpdate;
}

//***********************************************
//Code for updating when a record is deleted
//***********************************************

else if(Trigger.isDelete)
{

Time_Entry__c [] teOld = trigger.old;

for(Time_Entry__c te: teOld)
{

for (Weekly_Timesheet__c timesheet: [select Id, Name, Total_Hours__c from Weekly_Timesheet__c where Id = :te.Timesheet__c])
{
for (Time_Entry__c timeEntries: [select Id, Hours__c from Time_Entry__c where Timesheet__c = :timesheet.id])
{
sumTotalHours += timeEntries.Hours__c;
}

timesheet.Total_Hours__c = sumTotalHours;

sheetsToUpdate.add(timesheet);
}
}

update sheetsToUpdate;
}

}

8. Create the unit test for the trigger in an Apex class

[COMING SOON]

Helvetica-themed version of Monopoly

October 8th, 2008

A Helvetica-themed version of Monopoly. (via Daring Fireball and Kottke)

Created by Floretz Guerlain.

I’m now an Adobe Student Rep for Rich Internet Applications

October 7th, 2008

I’m now a member of the Adobe Student Rep program and will be promoting Adobe Flex and AIR to the students and colleges for building Rich Internet Applications. 

I see a lot of opportunity for using Flex/AIR in both the Computer Science and Journalism departments and will be raising awareness across the campus through demonstration events and workshops. I will also be exploring the possibility of starting a campus club that focuses on learning and promoting these types of skills including Flex/AIR, Flash, graphic design, and web development.

I’ll be posting code samples, tutorials, and more here in the future so keep checking in!

Commercial space for Reno Collective

October 4th, 2008

I’m working to find an available building and secure funding for Reno Collective. The plan is to find approximately 2,400 to 3,000 sqft to provide Reno with a workspace that inspires and encourages collaboration.

The following pictures are of a building on 1st street, right on the river. The building has already been renovated downstairs to accommodate a Port of Subs and the top floor is perfect for what we want to accomplish.

After speaking with the owner, I was slightly discouraged because of the amount of tenant improvements that he claimed would be necessary to get the building in shape. However, I think a minimalist esthetic will work very well.  Below is a picture of Souk Space in Portland, OR. Accomplishing what they have done in their space would be affordable and provide some charm to the place.


[Image credit: Souk Space]

Alternatively, The World Boardshop, also on 1st street and Arlington has recently vacated their building. Depending on the lease terms, the building is already in perfect shape to start using right away.

Toolkit for House Hunting on the Web

September 16th, 2008

I’m about to move out of my apartment and I’ve been on the hunt for where I will end up next. With work and school, I haven’t had a lot of time to drive and look around for a new place but I have been able to check out a lot of places via the web.

Here are the tools I’ve been using:

Craigslist
If you haven’t heard of Craigslist by now, you’ve probably been living under a rock. Craigslist is an online provider of classified ads. The website lists items for sale and purchase from cities all over the world. I’m looking for another apartment so I’ve been searching http://reno.craigslist.org/apa/ for something within my price range.

Google Street View
When I find a place I like on Craigslist, I pop the address into Google Maps and pull up the street view option to get another perspective on the property and its surroundings.

Twitter
You never know what opportunities your friends may know about. I have been reminding my followers on Twitter that I’m looking for a new place to live and have had a few people respond with available options in their neighborhood.

Zillow
If you’re looking to actually buy or sell a house or condo, Zillow provides you with a few handy tools. First off, they provide an interactive map via Microsoft Virtual Earth that allows you to interact with properties that are currently for sale or have sold recently. The map shows estimates for each of these properties and can help in valuing your property. Second, Zillow can provide you with a Zestimate, which is a valuation of your property based on the data they have on similar properties nearby.

Keep in mind, while there are a lot of cool resources online, you can find some pretty cool deals out there the old fashioned way by hopping in your car and driving around.

Secret to Making Money from Startup School 08

September 14th, 2008

David Heinemeier Hansson at Startup School 08 on The A Secret to Making Money.

His presentation is to the point and reinforces the direction I’m heading while working on my own web app in the little “spare/free” time I find. Quite inspiring.

<div><a href='http://www.omnisio.com' onclick="pageTracker._trackPageview('/outgoing/www.omnisio.com?referer=');">Share and annotate your videos</a> with Omnisio!</div> <p>

Installing MySQL from source on Mac OS X Leopard

September 14th, 2008

I spent a few hours going through these steps with a friend of mine, Glenn, who is starting to learn Ruby on Rails. He wanted to install MySQL so we decided to do it from source. Glenn was trying to install MySQL using Dan Benjamin’s tutorial at Hivelogic. I found the same steps that Dan posted on a few other websites but the remote source code his example tries to download no longer exists. This process does not take a few hours but we ran into a few issues trying to find a working copy of the version we used: mysql-5.0.45.tar.gz.

This is a quick post, which I hope may help anyone else trying to install MySQL, but I have not gone in depth on any of the steps. If you have any issues or questions, please feel free to comment below.

Disable any existing installs of MySQL:
sudo rm /usr/local/mysql

Remove the startup item:
sudo rm -rf /Library/StartupItems/MySQLCOM/

Setup your path by editing your .bash_login file, I’ll be using Textmate:
mate ~/.bash_login

Add to the end of the file:
export PATH="/usr/local/bin:/usr/local/sbin:/usr/local/mysql/bin:$PATH"

Save the file.

Create a directory called src in your home folder.
mkdir src
cd src

To download the mysql source:
curl -O http://mirror.provenscaling.com/mysql/community/source/5.0/mysql-5.0.45.tar.gz

Extrace the code from the gzip and enter the directory:
tar xzvf mysql-5.0.45.tar.gz
cd mysql-5.0.45

You now need to configure MySQL, paste the following into Terminal and press enter:
CC=gcc CFLAGS="-O3 -fno-omit-frame-pointer" CXX=gcc \
CXXFLAGS="-O3 -fno-omit-frame-pointer -felide-constructors \
-fno-exceptions -fno-rtti" \
./configure --prefix=/usr/local/mysql \
--with-extra-charsets=complex --enable-thread-safe-client \
--enable-local-infile --enable-shared

Now we will compile the code:
make

And install the code:
sudo make install

The last two steps may take a while.

Once all of this is complete, it’s time to setup the initial database user and privileges.
cd /usr/local/mysql
sudo ./bin/mysql_install_db --user=mysql
sudo chown -R mysql ./var

Now MySQL should be installed. In order to start the initial MySQL and have it start every time you restart your machine, do the following:

Create a file in /Library/LaunchDaemons named com.mysql.mysqld.plist
Paste the following XML into the new file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>KeepAlive</key>
    <true/>
    <key>Label</key>
    <string>com.mysql.mysqld</string>
    <key>Program</key>
    <string>/usr/local/mysql/bin/mysqld_safe</string>
    <key>RunAtLoad</key>
    <true/>
    <key>UserName</key>
    <string>mysql</string>
    <key>WorkingDirectory</key>
    <string>/usr/local/mysql</string>
</dict>
</plist>

While still in the LaunchDaemons folder, enter:
sudo chown root /com.mysql.mysqld.plist
sudo chown root com.mysql.mysqld.plist 

Return to the top directory and enter:
sudo launchctl load -w /Library/LaunchDaemons/com.mysql.mysqld.plist

MySQL should now be running. To enter MySQL, enter in Terminal:
mysql -u root

You should see the following:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 1
Server version: 5.0.45 Source distribution
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
mysql>

Congratulations, you’ve installed MySQL from binary source!

If you do not see the MySQL monitor, make sure you followed all the steps above. When I did this for the first time, it didn’t work so I disabled the install and started over from the top and it worked fine the second time around.

[Week 3 & 4 Update] 30 day slow-carb experiment

August 12th, 2008

It’s been just over 4 weeks for my 30 day slow-carb experiment, with one more day to go. Overall, I have not been able to stick to the diet religiously but have used its basic principles (such as not drinking my calories and avoiding simple carbs) for nearly every single meal.

The original claim was “lose 20 pounds in 30 days”. I know this has not happened, but again, I did not follow it to a tee. However, I know that I ate healthier in the last month that I ever have in my life and have been cycling for at least 5 hours a week. I took pictures of myself before I started this 30 day experiment and I’m curious to see if there is a difference. Whether or not I share those photos is something I’ll have to think about.

I also got to try Wii Fit yesterday. It was awesome. Nintendo is brilliant. I think it has some real potential, if not for losing weight, then for increasing your balance, helping posture, and having a good time while you’re at it. Apparently the Wii balance board measured my BMI to be 23%, which is normal for my height/age, however based on my balance, it determined my Wii Fit age to be 36!! I was still getting used to how the board detects shifting weight and balance so I think I’ll have to redo that part.

I will be doing a full experiment recap after tomorrow but until then here are shots of some things I have been eating.

Read the rest of this entry »

Reno Collective website now online

August 12th, 2008


Reno Collective is now live at www.renocollective.com. Ed Adkins and I will be adding much more content over the next couple days with our press releases. It will be the home to any of our announcements as well as general coworking and microbusiness news.

It is a great feeling to see how far we have come, especially considering it all started with Ed and I sitting in a coffee shop working away on projects in between our cups of coffee, tea, and beer.

Our companion site is also up. Please feel free to join the Reno Collective Ning if you are interested in using us as your work space this Fall or if you’d just like to show support for what we are trying to accomplish.

The survey for people who make websites

July 29th, 2008

A List Apart has opened their “Survey for people who design websites” survey for 2008.

This is the second annual survey distributed by A List Apart and the results are pretty useful for determining where graphic design, web design, and development fields are going and how they are performing. The survey results are always published to their site some time after the survey closes.

what i do -

Lively Labs

Web App Shop

visit

Reno Collective

Coworking Space

visit

things you should go to -

SEP 18-19

WordCamp PDX

in Portland

more

OCT 23

WordCamp Las Vegas

in Las Vegas

more

NOV 15-17

FOWD

in New York City

more

DEC 6-9

Dreamforce

in San Fran

more