We launched new forums in March 2019—join us there. In a hurry for help with your website? Get Help Now!
  • I’m working on a site that’s based very heavily on user-entered data, and as I’m fairly new to Revo, I’m not sure of the best strategy for saving the user data.

    Basically, users will sign up to become members of the site. Once they’re members, they’ll be able to create fairly extensive profiles with lots of statistical data about themselves. This info will be displayed on the user’s publicly accessible profile page. However, these stats will also need to be displayed on several "Top 500" pages. In other words, I need to be able to aggregate and rank data from all users.

    My question is, with the Revo user system in mind, what’s the best way to structure this? Should I save all of this information to each user object? Or would it be better to save the user’s basic info, and then log the statistical info to separate tables (probably via formit2db) using the user ID as the foreign key? The latter seems easier for aggregation, but I want to make sure there’s not an easier way built into Revo.

    Any suggestions would be appreciated!
    • I should also mention that I’m going to be running multiple top-level domains off one install, so contexts may play a big role here. For now, I don’t want to aggregate stats from other contexts, although I should plan for this possibility in the future. Again, separate tables seems like it would be the easiest way to achieve this (just create a column with the context name in the DB), but I’d like to hear what others think. Thanks!
      • Yeah, I’d dive into xPDO and start working with custom tables. Like you said, that’s easier to aggregate and collect.

        This is probably a good start:
        http://rtfm.modx.com/display/revolution20/Using+Custom+Database+Tables+in+your+3rd+Party+Components

        More info on the schemas & creating the models/classes:
        http://rtfm.modx.com/display/xPDO20/Defining+a+Schema
        http://rtfm.modx.com/display/xPDO20/Generating+the+Model+Code
          Mark Hamstra • Developer spending his days working on Premium Extras and a MODX Site Dashboard with the ability to remotely upgrade MODX and extras to make the MODX world a little better.

          Tweet me @mark_hamstra, check my infrequent blog at markhamstra.com, my slightly more frequent ramblings at MODX.today or see code at Github.
        • Thanks, Mark! Those tutorials are going to be invaluable. This might be breaking some database schema rules, but what do you think about also saving any user information that will appear in the aggregated results (such as first/last name) inside of the third-party tables? If I don’t do this, a list of 500 records would require 500 database calls to grab a couple of fields for each user. If I do it, I’m not following best practices, but I’m saving a TON of server load.
          • That’s what database relationships and table joins are for ;-)

            There’s more docs on how to apply them with xpdo on the xpdoquery page but perhaps it’s better to look kt up out of the xpdo paradigns to figureout what they are ;-)
              Mark Hamstra • Developer spending his days working on Premium Extras and a MODX Site Dashboard with the ability to remotely upgrade MODX and extras to make the MODX world a little better.

              Tweet me @mark_hamstra, check my infrequent blog at markhamstra.com, my slightly more frequent ramblings at MODX.today or see code at Github.
            • Oh, duh. Brain lapse. Surprisingly, I do understand joins wink I guess I wasn’t thinking about the user info being in a single table, so it’s really just one additional call if I do it as a join. Derp.

              I will check out the xPDO documentation, though, as that may give me some additional insight on structuring this. Thanks!
              • Mark (or anyone who’s feeling incredibly generous): can I ask just a little more advice on schema planning, and what the code looks like to pull this off?

                Here’s my issue: I have what’s basically a table of stats that each user enters, like this:

                http://dl.dropbox.com/u/2180905/createstats.jpg

                As you can see, each stat is tied to a "school year" tab, and also to a week. I also need to tie all of these stats to a single user. I later need to run a query on this table (I’m pretty sure this should be a single table, anyway) to compare data across users.

                My question, considering our earlier discussion about joining to the user table, is what’s the best way to store this? Should a.) each user just have a single row in the DB with lots and lots of fields? Or b.) should I loop through and create a new row for each field, with a schema that looks like this:

                1. id
                2. userId
                3. year
                4. position
                5. week
                6. statName
                7. statValue

                I can’t think of any reason not to do Option B (I think Option A is going to make it very hard to compare the same stat that falls in different weeks), but I’m not sure of the best way to write the script (I’ve modified formit2DB for this). To split the field, I’m putting the position, week and statName in the field name and exploding it. Here’s what I have so far, which kind of works, but if I have more than one field, it only adds the last field in the form:

                <?php
                $prefix=$scriptProperties['prefix'];
                $packageName = $scriptProperties['packageName'];
                $tablename = $scriptProperties['tablename'];
                
                $packagepath = $modx->getOption('core_path') . 'components/'.$packageName.'/';
                $modelpath = $packagepath.'model/';
                
                $modx->addPackage($packageName,$modelpath,$prefix);
                $manager= $modx->getManager();
                $generator= $manager->getGenerator();
                $classname = $generator->getClassName($tablename);
                
                //$errorMsg = '<pre>'.print_r($scriptProperties['fields'],true).'</pre>';  
                //$hook->addError('error_message',$errorMsg);  
                
                //$scriptProperties['hook']->formit->config['emailTo']='[email protected]';
                $dataobject=$modx->getObject($classname,array('id'=>$hook->getValue('resource_id')));
                
                if (empty($dataobject)){
                    $dataobject = $modx->newObject($classname);
                    //$dataobject->set('createdby', $modx->user->get('id'));
                }
                else{
                    //$dataobject->set('editedby', $modx->user->get('id'));
                }
                if (!is_object($dataobject) || !($dataobject instanceof xPDOObject)) {
                    $errorMsg='Failed to create object of type: ' . $classname;
                    $hook->addError('error_message',$errorMsg); 
                    return false;
                }       	
                $allFormFields = $hook->getValues();    
                foreach ($allFormFields as $field=>$value){	       
                    if ($field !== 'spam' && $field !== 'resource_id'){
                        $user = $modx->getUser();
                        $userid = $user->get('id');
                        $dataobject->set('userId',$userid);  
                	$fieldcheck = substr($field, 0, 9);	
                	   if ($fieldcheck == 'statField') {
                		   $fieldArray = explode('_', $field);
                			  $dataobject->set('position',$fieldArray[1]);
                			  $dataobject->set('week',$fieldArray[2]);
                			  $dataobject->set('statName',$fieldArray[3]);
                			  $dataobject->set('statValue',$value);
                	   }
                		   else {
                		        $dataobject->set($field,$value);
                		   }
                	}   
                	 $dataobject->save();   
                }
                return true;


                Is this the best approach? And if so, can you spot what I’m doing wrong with the looping?
                • You’ll probably need the $dataobject = $modx->newObject() within the loop as well - it’s outside of it now and it will get overwritten all the time.

                  That’s a tough situation imo on choosing the best approach. If they forever stay the same one table row could work. Could also split it up in blocks/tackles/sacks in one table, with each of the weeks being a different row.

                  I personally wouldn’t go for the option b you are sketching there unless all stats are really unrelated and need to be able to be compared to eachother. You’ll get huge result sets for one user.
                    Mark Hamstra • Developer spending his days working on Premium Extras and a MODX Site Dashboard with the ability to remotely upgrade MODX and extras to make the MODX world a little better.

                    Tweet me @mark_hamstra, check my infrequent blog at markhamstra.com, my slightly more frequent ramblings at MODX.today or see code at Github.
                  • That’s pretty much my fear with Option B: the number of rows. I am creating separate tables for each state, which will help (this is a multi-context site for all 50 states), and we’re looking at ideally a few thousand users per state. What you can’t see in the mockup is that changing your position changes the stat fields, and some of them have a lot more fields than others. So, say we get 10,000 users per state x 200 maximum stat fields...200,000 rows? Is that too big?

                    On the other hand, is 10,000 rows with 200 columns each actually any better? Especially if it makes it harder to run a query on any given column? With Option B, I can just ignore the weeks column, but Option A makes that very difficult.

                    Gah! So confused.
                    • MySQL can handle millions of rows (depending on server config, obviously) but I think giving 200 rows per user just isn’t really usable to aggregate data...

                      Something’s flawed about this to begin with but I’m having trouble thinking of a better structure right now...
                        Mark Hamstra • Developer spending his days working on Premium Extras and a MODX Site Dashboard with the ability to remotely upgrade MODX and extras to make the MODX world a little better.

                        Tweet me @mark_hamstra, check my infrequent blog at markhamstra.com, my slightly more frequent ramblings at MODX.today or see code at Github.