Create a trigger that validates the data for the Product object - triggers

I have an apex trigger (before insert/update) and a helper class to that trigger. The problem is: When creating an object record, the trigger should check if the AddedDate field is filled and if it's not - then assign it today's date and current time.
And when I create and update a Product object record, the trigger must check the length of the Description field, if the field is longer than 200 characters, I must trim it to 197 characters and add a triple to the end of the line.
What am I doing wrong and how should I proceed?
My trigger:
trigger ProductTrigger on Product__c (before insert, before update) {
if(Trigger.isUpdate && Trigger.isAfter){
ProductTriggerHelper.producthandler(Trigger.new);
}
}
Trigger helper class:
public class ProductTriggerHelper {
public static void producthandler(List<Product__c> products) {
Schema.DescribeFieldResult F = Product__c.Description__c.getDescribe();
Integer lengthOfField = F.getLength();
//List<Product__c> prList = new list<Product__c>();
for(Product__c pr: products){
pr.AddedDate__c=system.today();
if (String.isNotEmpty(pr.Description__c)) {
pr.Description__c = pr.Description__c.abbreviate(lengthOfField);
}
}
}
}

According to your requirements
When creating an object record, the trigger should check if the AddedDate field is filled and if it's not - then assign it today's date and current time.
You aren't doing that.
Change pr.AddedDate__c=system.today(); to
if (pr.AddedDate__c == null) { pr.AddedDate__c=system.today(); }
Also according to the abbreviate function documentation the parameter it takes is the max length including the 3 elipses.
So change pr.Description__c = pr.Description__c.abbreviate(lengthOfField); to
pr.Description__c = pr.Description__c.abbreviate(200);

To add to Programmatic's answer...
You defined the trigger as before insert, before update. Cool, that's perfect place for doing data validations, field prepopulation... And you'll get save to database for free!
But then this clashes with next line if(Trigger.isUpdate && Trigger.isAfter){. With this setup it'll never fire. Either remove the if completely or (if you think trigger can get more events in future) go with trigger.isBefore && (trigger.isInsert || trigger.isUpdate).
P.S. It's datetime field? So pr.AddedDate__c=system.now(); is better

Related

Comparing document timestamps in Firestore rules

I'm running into a weird problem while writing and testing my Firestore rules. Here's what I want to achieve:
When the application starts, the user gets logged in anonymously. The
user starts a new game.
I create a 'Session' that basically consists of just a timestamp.
The player plays the game, gets a certain highscore and goes to a screen where the score can be sent to the global highscore list. When the highscore is submitted, I check if there's an existing session for this player and if the time that has passed is long enough for the highscore to be considered valid.
On the client (javascript) I use the following line to send the timestamp in my documents:
firebase.firestore.FieldValue.serverTimestamp()
This is the current ruleset. You can see that a score can only be created when the createdAt of the new higscore is later than the createdAt of the session.
service cloud.firestore {
match /databases/{database}/documents {
function isValidNewScoreEntry() {
return request.resource.data.keys().hasOnly(['createdAt', 'name', 'score']) &&
request.resource.data.createdAt is timestamp &&
request.resource.data.name is string &&
request.resource.data.score is int &&
request.resource.data.name.size() <= 20
}
match /highscores/{entry} {
allow list: if request.query.limit <= 10;
allow get: if true;
allow create: if isValidNewScoreEntry() &&
request.resource.data.createdAt > get(/databases/$(database)/documents/sessions/$(request.auth.uid)).data.createdAt;
}
function isValidNewSession() {
return request.resource.data.keys().hasOnly(['createdAt']) &&
request.resource.data.createdAt is timestamp
}
match /sessions/{entry} {
allow list: if false;
allow get: if false;
allow create: if isValidNewSession();
allow update: if isValidNewSession();
}
}
}
When I simulate/test these rules, I get an error that says that I cannot compare a 'timestamp' to a 'map'. I don't know why the 'createdAt' value is a map, but it seems like the get() method returns something different than expected.
My question is: What would be the correct way to compare the property createdAt from the newly submitted entry to the property createdAt of the existing session document, like I'm trying to do in the rules described above.
This is what a'Score' entry look like
This is what a 'Session' entry looks like
EDIT:
I've done some more digging, and found that this line works:
if request.resource.data.createdAt.toMillis() > get(/databases/$(database)/documents/sessions/$(request.auth.uid)).data.createdAt.seconds * 1000;
This makes it pretty clear that not both createdAt are the same format. The last one seems to be a basic object with the properties 'seconds' and 'nanoseconds'. I'm sure it stems from the Timestamp interface, but it gets returned as a flat object since none of the methods found here exist and give an error when trying to call them. The property 'seconds' however does exists on the second timestamp, but is not accessible on the first one.
I've found out why the timestamp is not what I expected and got cast to a 'map'.
After digging through the documentation I found that the get() method returns a resource. resource has a property data: a map. So the get() method does not return a document as I expected but a flat JSON object that gives me all properties found in de database.
https://firebase.google.com/docs/reference/rules/rules.firestore
https://firebase.google.com/docs/reference/rules/rules.firestore.Resource

Which is better for database seeding: Add or AddOrUpdate?

I don't understand why it is recommended everywhere to use AddOrUpdate in the Seed method?
We develop application for half a year already and the AddOrUpdates overwrites user changes every time we update the server. E.g. if we call in the Seed:
context.Styles.AddOrUpdate(new Style { Id = 1, Color = "red" });
And user changes the Style to "green" then on next server update we overwrite it to "red" again and we get very annoyed user.
It looks that if we change AddOrUpdate to Add we will be guaranteed from overwriting user data. If we still need some special case we can put it to separate migration. Unlike the general Configuration.Seed method particular migrations don't run twice over the same database version.
I assume that Style's primary key is Id. The overload of AddOrUpdate that you use only checks if there is a record having Id == 1. If so, it updates it. That's all.
What's going wrong here is that the primary key is a surrogate key, i.e. it's there for querying convenience, but it's got no business meaning. Usually, with migrations you want to look for the natural keys of entities though. That's how the user identifies data. S/he wants a green style, not a style identified by 1.
So I think you should use this overload of AddOrUpdate:
context.Styles.AddOrUpdate( s => s.Color,
new Style { Id = 1, Color = "red" });
Now when there is no red style anymore, a new one is inserted, overriding the Id value (assuming that it's generated by the database).
From your later comments I understand that you want to Add data when they're new, but not update them when they exist (compared by primary key). For this you could use a slightly adapted version of an AddWhenNew method I described here. For your case I would do it like so:
public T void MarkAsAddedWhenNew<T>(this DbContext context,
Expression<Func<T, object>> identifierExpression, T item)
where T : class
{
context.Set<T>().AddOrUpdate(identifierExpression, item);
if (context.Entry(item).State != System.Data.Entity.EntityState.Added)
{
var identifierFunction = identifierExpression.Compile();
item = context.Set<T>()
.Local
.Single(x => identifierFunction(item)
.Equals(identifierFunction(x)));
context.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
}
return item;
}
Re-fetching the item from the local collection is a nuisance, but necessary because of a bug in AddOrUpdate(). This bug also caused the error you got when setting the state of the original entry to Unchanged: it was a different instance than the attached one.
The way Add method acts is misleading. It Inserts data into database even if there is already a row with the same PrimaryKey as we do Add. It just creates new PrimaryKey ignoring our value silently. I should have tried it before asking the question, but anyway, I think I'm not the only one who confused by this. So, in my situation Add is even worse than AddOrUpdate.
The only solution I've come to is following:
public static void AddWhenNew<T>(this DbContext ctx, T item) where T : Entity
{
var old = ctx.Set<T>().Find(item.Id);
if (old == null)
ctx.Set<T>().AddOrUpdate(item);
/* Unfortunately this approach throws exception when I try to set state to Unchanged.
Something like:"The entity already exists in the context"
ctx.Set<T>().AddOrUpdate(item);
if (ctx.Entry(item).State != System.Data.Entity.EntityState.Added)
ctx.Entry(item).State = System.Data.Entity.EntityState.Unchanged;
*/
}

How to check null date condition in drools?

I have to check a condition on date whether a date field of a entity is in range of another two sets of date from other entity
First Entity :
1. id
2. name
3. date
Second Entity ;
1. id
.
.
.
.
17 : Start Date
18 : End Date
I have to check whether the date field of first entity is in range of Start Date and End Date of second entity.
e.g.
(t1.date>= t2.Start Date and t1.date <= t2.End Date)
Problem is that, there are some row where t2 is null.. if it is null, then second condition return true.
My Attempt
PriorAuthorizationLogVO( cardid == $paidClaimVO.cardholderId, ruleType == 'INCL' , $paidClaimVO.time>=etime , (ttime==null || (ttime!=null && $paidClaimVO.time<=ttime))
But, i am not able to confirm whether it is working....
Please help.
You could add that check for date between the range of dates in either a static helper method or within one of your entities. In my opinion this will make your rules more readable and you can easily write unit tests for the date check method.
Option 1 - Static helper method
Create a class for having static helper methods, something like this
public class DateUtils {
private DateUtils() {} // Cannot be initialized
public static boolean dateInRange( Date toCheck, Date min, Date max ) {
// Add null checks here
// Use Date#before(Date) and Date#after(Date) for checking
return true|false
}
}
Your rule would then be like
import static DateUtils.*
rule "date in range"
when:
dateInRange( e1.date, e2.start, e2.end )
then:
// logic
end
Option 2 - Method within fact/entity
Create the check method inside one of your facts. In which entity to put this depends on your use case, the information you've given does not specify this yet. I think you can figure out the best place by yourself. Anyway, the code would be something like this
public class Entity1 {
Date date
}
public class Entity2 {
Date start
Date end
public boolean entity1InRange( Entity1 e ) {
// null checks
// Use the Date#before() and Date#after() as above
}
}
And the rule
rule "date in range"
when:
e2.entity1InRange( e1 )
then:
// Logic
end

Entity Framework - Linq to Entities - strange issue with Anonymous function

Following is the code, I am trying:
public List<Movie> GetMovies()
{
Func<Movie, Movie> prepareMovieOutput =
(input) =>
{
input.DisplayHtmlContent = String.Empty;
return input;
};
var moviesOutput = from m in db.Movies.ToList()
select prepareMovieOutput(m);
return moviesOutput.ToList();
}
public List<Movie> SearchMovies(string searchTerm)
{
var moviesOutput = db.Movies.Where(m => m.Name.Contains(searchTerm)).ToList();
return moviesOutput.ToList();
}
The GetMovies function is working properly, as it returns List collection after clearing DisplayHtmlContent field, whereas, SearchMovies function is supposed to return Movie collection with DisplayHtmlContent field, but inspite of that it returns that field empty.
If I set DisplayHtmlContent to some fixed value (like, "ABC"),both GetMovies and SearchMovies return the list with all Movie having DisplayHtmlContent field as "ABC" value. I don't understand why the function defined in one method should affect the other one. and also how to fix this issue?
Ideally, I want GetMovies to hold all Movie with that particular field as empty string, and SearchMovies to hold all Movie with that field containing value.
Any help on this much appreciated.
this was due to the use of repository. I have removed it and it started working fine. with having EF 5, I didn't need to use repository

Grails Domain class property Set failed Validation

I have problem with a setter in grails. I have two properties beforeTax and afterTax. I only want to store one property in the db beforeTax. In the ui I want the user to enter either before or after tax. So I made the afterTax a transient property like this:
double getAfterTax(){
return beforeTax * tax
}
void setAfterTax(double value){
beforeTax = value / tax
}
When I now enter the after tax value and want to save the object the validation fails (before tax can not be an empty value)
What am I doing wrong?
If I understand your question correctly, you want to compute beforeTax based on the value of afterTax?
You could use the event handler methods beforeXXX() where XXX is Validate, Insert, and Update to compute beforeTax. Then beforeTax's constraint can be nullable:false.
def beforeValidate() {
computeBeforeTax()
}
You have to flag one property as transient, in order to prevent GORM from trying to save this variable into DB. Try to add this line into your domain class, which contains afterTax.
static transients = ['afterTax']