I am using userpoints_basic to award points for usual things - blogs, forums, comments, etc. Previous versions of userpoints_basic used to enter things like "post comment", "blog", "poll", etc as the operation in the userpoints_txn table. However, version 3.5 simply enters "insert".
It's an easy enough patch - any reason why this is different in the later versions?
Comments
Comment #1
kbahey commentedI can't think of a valid reason why all would have insert, other than it being a bug that crept in.
Go ahead and roll a patch.
Comment #2
jredding commentedI can think of a valid reason ;)
This is what is actually happening. The Insert is the operation occurring (i.e. hook_nodeapi $op == insert). There is a hook called just before these "operation" are sent to the screen so its completely themeable to change this wording around.
The change from 2 to 3 introduced a few new fields. One of them was "entity type" which is "node, comment, etc." thus
"post blog" was redundant data stored in the DB.
moreover this was incredibly bad to store in the DB. For example what if you originally named your node "blog" but then changed it to "Thoughts". All of your points are now lost in relation to points received for the blog node type (as its now called "Thoughts"). In 3.x this is not lost because we simply track that it is a point for an entity type and we can join on the proper table to get the correct name.
Additionally what if you wanted to know how many points a user received for posting nodes or comments. In 2.x this was fairly difficult because you would first need to know the name of every node type in the system and do a query like
Select count(points) from userpoints_txn where operation = 'post blog' OR operation = 'post comment' OR operation = 'post node-type'
or you do the vulgar
SELECT count(points) from userpoints_txn where operation LIKE 'post %' AND operation != 'post comment'
etc. etc.
in short this is an intentional and good change (imho).
If there is any change it would to simply change the default display of this field on the report screen by using the available hook (which already provides a default) and providing a better default that translates "insert" into "New " or even the legacy "post " but I would highly, highly advise against reverting to the previous method.
Comment #3
jredding commentedlet me be clear...
the operation in the userpoints_txn table is abundantly clear and extremely descriptive.... to a developer and this is exactly how it should stay.
the operation displayed to the user is obtuse and should be modified but you should NOT do this by modifying the DB. Simply utilize the hook (already provided) and modify the default wording.
Comment #4
ebeyrent commentedHere's my problem.
I am integrating my site with a points vendor. When a user creates a blog, he gets n points. I can either send this event to the vendor at this time, or I can process transactions via hook_cron, which kbahey and I agree is the best way to go.
In order to do either way, I have to call a method called CreateEventInstance, which simply consists of me referencing something like 'insert blog', 'post comment', etc. The old way of storing the transactions allowed me to do this easily. However, the new way prevents me from doing this because all I get in the userpoints_txn table is 'insert' and 'node'.
I could simply set up another table with the txn_id and my own action, but that's an ugly approach, IMHO. Without the information about what actually happened, I can't reference an event.
What about storing what I need in the description field? That doesn't look like it's being used...
Comment #5
jredding commentedhhmm.. ok. so why isn't this good enough.
Select n.type, up.operation, up.points FROM userpoints_txn up INNER JOIN node on userpoints_txn.entity_id = node.nid WHERE up.operation = 'insert' AND up.entity_type = 'node';
no need for a secondary table you have everything you need with a simple join.
i tried to find you on IRC but you weren't logged in, if you log in I can help you right now ;)
Comment #6
jredding commentedby the way with the above query I'd get something like
"blog" "insert" "5"
Which could be quickly interpreted as "insert blog" for 5 points.
Seems like enough information to me and keeps you future proofed if node types change names as well as providing an exact link to the item that produced the points (whereas in version 2.x this information was lost)
Comment #7
ebeyrent commentedThat'll do just fine - thanks!
Comment #8
jredding commentedI'm changing this to a support request as I don't believe this is a bug. The wording has changed but I'm fairly steadfast that we have changed this for the better and for future proofing this module.
I agree that the wording is a little odd to the end users and it should be changed, but to a developer "insert" should be extremely intuitive as it is inline with the rest of Drupal core (i.e. nodeapi, hook_comment, hook_user, etc.)
So.. maybe we should roll a feature request in order to change the default wording of this. I think that this goes back to my original argument for wanting to separate the administrative interface from the API so that this distinction is made a bit more clear and allows for more flexibility like this. Feel free to provide feedback on my grandiose plan ;)
http://groups.drupal.org/node/10539
Comment #9
ebeyrent commentedI agree - now that I know what's going on, it's not a bug. I had upgraded from userpoints 2 to userpoints 3, and saw a difference in the way the transactions were being recorded.
Comment #10
ebeyrent commented... with the exception that now I have to do separate queries for different entity types, like comment and user. Or, I just grab everything I need from the userpoints_txn table and retrieve the node type if the entity is a node. Either way, it'll have to be done in multiple steps, unless you can think of an uber-query that would encompass everything. Of course, some of that isn't your problem - userpoints votingapi for example doesn't pass much information over to userpointsapi.
Comment #11
jredding commentedtheoretically there is an uber-query that will pull all the information you need but it'd be really messy as well as hard to read. Unfortunately yes you'll have to do multiple queries based upon entity-type and that's exactly how I'd suggest doing it. Don't pull all from userpoints_txn and then do separate queries to the node table to get your information (that's a lot of selects).
Just pull by entity-type and JOIN on the correct table (i.e. entity-type == node JOIN on node, entity-type == comment JOIN on comment, etc.).
I know its a bit more of a pain but I promise up/down/left/right that this is a better model for data storage especially as it maintains somewhat more consist with Drupal core.
If I ever find the time I think the outcome of this would be
(1) Modify the report screen such that it doesn't display "insert" as I agree that it is confusing.
(2) Document the DB changes, in regards to userpoints_basic in the documentation.
If you have time for either feel free to tackle 'em. ;)
Comment #12
kmillecam commentedI've sounded off before about the use of "insert" as a descriptive term but I understand the reasoning behind it.
I just wanted to say that I /really/ like having the ability to click the "insert" hyperlink and do a quick check of the submitted content from the moderation table. That's a super-useful feature ... don't want to lose that one just because "insert" doesn't fit my fancy.
Thanks,
Kevin
Comment #13
jredding commentedI hear ya about the non-descriptive-ness about the "operation" and I agree that it should be changed. Heck I'd even support change it back to its former "post blog, post page", etc. which should be an easy frontpage switch without changing around the backend.
Comment #14
ebeyrent commentedUnfortunately, I don't think that's going to work for me. The integration I am doing is via SOAP - for every transaction, I have to make a SOAP call to send the event. For scaling and performance reasons, this has to happen via hook_cron instead of in real time. Because it's over SOAP, I have to keep track of the last txn_id that was processed, and I'm only processing n rows per cron run. So, for each successful SOAP call, I update a table and set the last txn_id that was processed. If the SOAP call fails for whatever reason, that "queue" never gets updated, so I know where to pick up on the next cron run - I just grab all the rows from the userpoints_txn table with a txn_id greater than the last processed id.
This implementation means I have to handle each row at a time, which means I really only have a few options:
1. Create a new table and every time points are awarded, insert a new row into the table with the txn_id and descriptive action. The problem with this is that I have duplicate data now, and hook_userpoints still doesn't have access to any of the information I need. I'd need to implement what we discussed at http://drupal.org/node/247470.
2. Process each row in the userpoints_txn table and for each row, do another query to get the relevant information I need. The problem with this approach is as you noted, there's going to a lot of selects.
3. Modify userpoints to enter the data I need into the description field in the userpoints_txn table. It doesn't look like this field is being used. I hate making changes to modules unless the patch will become official - any chance you'd agree to this approach? For me and what I am doing, this is the best option.
Comment #15
jredding commentedor you could just simply change the way you did it because you were doing it wrong in the first place.
(1) you can add a single field to the userpoints_txn table as a flag to track the status
(2) if you don't want to add a single field to the userpoints_txn table create a separate table with two field
userpoints_txn_id and status.
Join the two tables and find all the rows that have no status. Work on those (a LEFT join would work well here, depending on the order of the tables).
If you have these fields/table you can simply add a WHERE clause to pull items that haven't been processed.
Running through a table sequential is a really bad way of doing it because it assumes that everything works perfectly everytime.
I still stand by the decision to make this change to the table as it is a much, much cleaner interface and I can point to nearly the entire Drupal database as an example (specific example, term_data, term_node) or CCK or... We shouldn't store duplicate data that may get completely out of sync.
Comment #16
ebeyrent commentedI 100% agree with your decision regarding the table design. That's why I am suggesting that I use the description field instead to enter notes regarding the transaction. It seems logical to me to use the description field for useful notes.
I'm not sure however, why processing rows in order is bad. In my integration, it's equally logical to process transaction rows in sequence, because if the SOAP call fails on one transaction, there's no point in jumping around the rest of the table. If there's a networking issue or the service is unavailable, the other SOAP calls will fail as well, which means exiting the process is the most logical thing to do.
Again, why can't we use the description field in the userpoints_txn table? It's not currently being used.
Comment #17
jredding commentedYou can use the description field if you want I would add one myself simply because the core module itself defines the description field and its meant to be used by contrib modules (not sure which ones off the top of my head though).
Its your DB though so provided you know what's going in/out you can use it no problem.
I'd almost suggest using the reference field as I have no idea what that is still doing in the DB. A while back there was a discussion with a separate, to remain unnamed, developer that begged and pleaded to get that field into the "core" of userpoints so that his module would work properly. We urged him to use the description field but his argument won out and the reference field went in (it was actually a better decision) but that module has since been deprecated and abandoned so... this field is pretty much wasted (not that I hold a grudge or anything).
Why is processing rows in order bad?
because you're making assumptions, broad based assumptions.
What if you get a false-positive response from the client?
What if you want to recheck all the rows to ensure they actually did go correctly?
What if you accidentally made a mistake in your code and you started processing things in evens or odds or??
What if your keys aren't sequential? (i.e. deleted rows)
The list could go on but in short the answer is yes in a perfect world your code and processing things sequentially should never have any problems but the world and code isn't perfect. There is an argument that processing rows sequentially and tracking the last row number processed is faster due to the dramatically less number of update statements you'd have to do (i.e. 1 vs. 1 x # of rows) but that the amount of data your losing (i.e. which rows have been fully processed) is important. Especially if your on a system that reuses numbers (userpoints' table use autoincrement so it won't do that).
Anyhow if this were a programming class it'd be a negative point ;) but eh.. if it works whatever.. life isn't perfect ;)
Comment #18
ebeyrent commentedJust to be clear - I'm not necessarily processing rows in order. I am processing rows like:
Perhaps my description of what I am doing was bad.
Any chance you'll add support for the description field in version 3.x if I give you a patch?
Comment #19
jredding commentedI don't want to go too far down the code critique path on this b/c if its working for you then its working for you.
userpoints already has a description field/column as well as a reference field/column so I'm not sure what you'd be submitting a patch for.
Comment #20
ebeyrent commentedThe patch is for userpoints_basic to actually stick something into the description field. Nothing gets stored there, and because of the fact that hook_userpoints doesn't know about the parameters or the node, I can't simply use that hook to modify the parameters going into the transaction table.
Comment #21
jredding commentedWhat would you store in there?
I hate duplicating data and I think its a massive, massive mistake to essentially take steps backwards and store information such as "post blog" "post comment" etc in the userpoints table (for the reasons I already outlined). Especially since it simply encourages bad programming practices.
Comment #22
ebeyrent commentedI understand what you are saying. I don't mean to be argumentative and I mean no disrespect, but honestly, if you were worried about proper database normalization and not duplicating data, then why is the operation field in the userpoints_txn table filled with 'insert', instead of a foreign key to a table containing userpoints operations? How is 'post blog' redundant when 'insert' isn't?
Again, I don't mean to be argumentative. It just seems that what you are proposing - performing multiple queries with joins just to figure out what really happened as part of the transaction - is bad for performance. Shouldn't you have indexes on the entity_id or entity_type fields?
You have a description field that isn't being used. You have a reference field that isn't being used. As you and I both pointed out (#16, #17), either of these fields would be the ideal place to store notes about the transaction - and IMHO, notes such as 'post blog' and 'forum ownership change' are completely appropriate for these fields. I agree 100% that this kind of data has no business being in the operation field, as you use that field for reports and sorting, as you pointed out in #2.
I hope some of my logic makes sense...
Comment #23
jredding commentedThe description field is being used by some module, I don't know what it is off hand but I really don't care.
The reference field should be taken out; I'm fairly positive there is consensus on this.
Its not the duplication of data I don't like otherwise I'd yell at Drupal for storing the word "page" a bazillion times in the node table ;) its that....
1) It means nothing. There is no such thing as "post" in Drupal. no where, it doesn't exist. The operation is an insert.
2) Its not scalable to OTHER modules. userpoints_basic is the basic module that utilizes this field. I'm more concerned about other applications and keeping everything inline and consistent throughout.
3) It is not simply 'post blog' its also 'post page' 'post comment' 'modify blog' 'modify comment', etc. It is really, really messy extremely fast.
4) Performance.. put an index on it. sheesh, that's not hard to do. Most of Drupal isn't optimized out of the box. Currently we have an index on reference. Reference should be removed and the index placed on entity_type (most likely). I 100% agree with you.
I'm still steadfast in that I think its cleaner to keep this in line with the whole of Drupal. insert, update, etc. are in line with what hook_nodeapi, hook_comment, hook_user, etc. use and a programmer should recognize those immediately and not have to decipher what the module is doing.
I understand that this change broke your existing code but to fix your code its as simple as a quick JOIN statement. I really don't understand why this is such a big deal
Comment #24
jredding commentedok I sounded off before really reading the full extent of the post; I suck.
leaving the operation field alone. OK agreed. Thanks ;)
Putting this data into the description field.. I'm still not terribly fond of the idea to be honest because its duplicating the node type names but I could get let that slide.
I'd get even more behind it IF
(1) the reports were modified to change the operation field from being shown to the description field. So we stop showing that crazy "insert" word to the users (but still make it a clickable link via the hook).
(2) possibly the description field was tweakable by the administrator, just like the point expiration description message is.
That I could see working and, you're right, we'd utilize that under-utilized description field.
So.. maybe being argumentative works ;) that is.. if I read ;)
Comment #25
kbahey commented@Erich
There are several approaches you can take that would preserve the data model, and achieve your design goals with the SOAP application.
Option 1: You keep last txn, but by type
Basically, you do keep the id of the last transaction processed for each type. So you can have a table with 'type' and 'last_txn_id', and as many rows in it as there are types.
The code would look like this:
This way, you process all the "node" types first, and have one variable for each type. You update the variable for each transaction that you successfully process.
Option 2: Maintain processing in sequence, and lookup the data for each row:
If you want it guaranteed that you are processing in transaction order, you lookup each up txn, fetch the operation, ...etc and then process it.
Like so:
If you don't like switch/case, like some people do, then you can do this:
How about those?
Comment #26
ebeyrent commented@Khalid - I looked at doing the switch code when jredding posted #5. While it's definitely an option, I didn't want to do it mostly because it required doing three different queries when one would do just fine, as I pointed out in #22. I just happen to think that using the description field is the easiest and most appropriate way to get information regarding what happened during a transaction.
I think that jredding and I are on the same page (see #24).
Comment #27
jredding commentedmaybe I'm an antagonist ;) (kidding) but I just want to be clear.
I really don't like putting the "post comment, post blog, etc." in the description field but I would tolerate it with a clinched face smile but i would tolerate it. I just don't like the can-get-out-of-sync data model.
eh.. there are a lot of things in life that I don't like though ;)
Comment #28
kbahey commentedI am not in favor of overloading the description field. Or rather, making its content site specific, module specific and structured.
You are now dependent on a text field storing discrete fields with special meaning, concatenated. You cannot query those fields independently (like the data field in the users table, you have to sift thru all the rows to find what you need, you cannot filter).
It also makes coexistence with other modules that do not use it, or use it in their own way difficult. You have to have your own fork of those modules.
It may well work for you, but in the long run these things tend to be a maintenance burden/hackish in nature.
Here is Option 3: Your own store and forward table:
Create your own "store and forward" table with exactly the fields you need, and a status field (sent/not sent). In points after, you create a row in that table, and when cron runs, you send and flag as you go (or even delete the row you sent). This way, you are not dependent on any overloading.
Comment #29
ebeyrent commentedThis would work, except for the issue that I have no access to the transaction details in the 'points after' operation, as discussed on another ticket number. In comment #4, I suggested exactly what you just proposed, but without any knowledge of the params or the node in hook_userpoints, I can't go down that path.
I guess my question is what do you intend the description field to be used for?
1. It's not currently used by any of the 150 modules I have running on my site.
2. It's a text field for storing arbitrary text.
3. Are there currently mechanisms for querying against this field? Are there mechanisms for filtering?
4. Since it's a text field, should there be mechanisms for filtering on this?
I was operating under the belief that the description field was kind of intended to store notes regarding the transaction. Clearly, my understanding is incorrect, if you are not in favor of storing arbitrary text into this field.
Is anyone using this field, and if so, how is it being used?
Comment #30
jredding commentedOk so this is starting to break down and lets get back on track here.
-I searched through contrib and the following modules utilize the field as well as the string they store.
referral_points "Shared points"
userpoints_votingapi "Vote cast on node $id";
so the field is being used even if heavily underutilized.
-Description is used for arbitrary text but the argument is whether or not to store the node type in this field. Lets be 100% clear about this.
The question/decision to be made is
in userpoints_basic what is stored in the description field?
Should it include the node type as event did historically?
ex. "post blog", "update blog", "post story", "update story", etc.
That is the question at hand.
I do not support this because of the reasons I've mentioned before (out-of-sync) module and a simple JOIN grabs this data.
I DO support utilizing the description field and putting something more descriptive in there something like
"Content created"
"Content updated"
or something like that.
I support this because (a) the node type can be pulled easily (b) the reports already support hooks to modify what is displayed (ex. a link to the direct node being created).
in short you're correct we have a heavily underutilized field and we should use it. So now, what do we put in there?
Comment #31
jredding commentedin regards to the 'points after' hook and not having access in your other ticket I stated that I support sending the entire params over and I'd love to roll a version 4 in which we did this. This would make everyone's coding easier and make the module more extensible.
Comment #32
ebeyrent commentedI agree with you on this issue - it just doesn't help me now...
Comment #33
berdirSorry for spamming the participants in this issue. Due to the release of Drupal 7 and the lack of time from the maintainers, I'm closing all remaining 5.x issues for Userpoints.
Note that in 7.x, the operation column has been changed to become basically a machine-readable key for which modules can provide a translatable and user-facing description.