According to this entry from the JOOQ blog, the section titled "SQL DEFAULT expressions and POJOs" indicates that any null fields from a POJO will set the column to null, since every Record.changed() flag will be set to true.
In practice, I have not found this to be the case-- if a POJO field is null and I follow the example in the blog, existing values are retained. I want to set these fields to null, but how can I do it using POJOs?
// Load the customer record from a POJO
CustomerRecord customer = ctx.newRecord(CUSTOMER, customerPojo);
customer.update();
// Expected behavior: null fields in customerPojo will set db values to null
// Actual behavior: null fields in customerPojo do not change values in db
Edit: This is using Jooq 3.11.10, Postgres 10.6.
Create customer script:
drop table if exists customers;
create table customers
(
id serial not null primary key,
first_name text,
last_name text
);
Customer Class:
#Data
#Builder(toBuilder = true)
#NoArgsConstructor
#AllArgsConstructor
public class Customer {
private Integer id;
private String firstName;
private String lastName;
public static Customers TAB = Customers.CUSTOMERS;
static DSLContext ctx = PostgresSingleton.getMainCtx();
public Customer store(){
CustomersRecord rec = ctx.newRecord(TAB, this);
if(getId() == null) {
rec.store();
}
else {
rec.update();
}
rec.refresh();
return rec.into(Customer.class);
}
}
Test (Spock/Groovy):
def "Customer test"() {
when: "save a new customer"
Customer cust = Customer.builder().firstName("Phil").lastName("H").build().store()
then: "ID populated"
def custId = cust.getId()
custId != null
when: "null out last name"
cust.toBuilder().lastName(null).build().store()
then: "last name actually set to null"
cust.getId() == custId
cust.getFirstName() == "Phil"
cust.getLastName() == null //fails, still is "H"
}
Edit 2:
It appears the .update() call is in fact nulling the field in the DB! However, the .into() call afterwards does not refresh the POJO with the null field as expected. I verified the value for lastName is null after .refresh(), but the into() call doesn't null the value.
Well, it was a PEBKAC error-- I wasn't storing the result of my update back into the same object in my test:
when: "null out last name"
cust.toBuilder().lastName(null).build().store() //not storing the result!
should be:
when: "null out last name"
cust = cust.toBuilder().lastName(null).build().store()
Easy to miss.
Related
My test class:
public class TestA
{
public IEnumerable<string> Collection { get; set; }
}
When I call extension method .ToJsv() from ServiceStack.Text on TestA object with property Collection which is an array with some null values, the result does not contain null values. Even setting JsConfig.IncludeNullValues = true; does not bring the solution.
Test code:
var obj = new TestA { Collection = new[] { "T", null } };
var result = obj.ToJsv(); //here I get {Collection:[T,]} instead of {Collection:[T,null]}
Thanks for any suggestions.
Given its format JSV can’t serialize null values as they’d be indistinguishable from the ‘null’ literal string so it’s lack of a value is what indicates a property is null, but that’s no something that’s you’ll be able to rely on if you want to send null items in a collection in which case you’d need to special casing like maintaining which items are null in a different property or you’d need to use a different serializer.
I am trying to lookup all records in a table where an id equals a variable and the order number equals another variable. When the order number variable is blank or null, I don't get any records even though there are matches in the database.
var chkExcepts = new PXSelect<EDImportExcept,
Where<EDImportExcept.eDIRefNbr, Equal<Required<EDImportExcept.eDIRefNbr>>,
And<Where<EDImportExcept.orderNbr, Equal<Required<EDImportExcept.orderNbr>>,
And<Where<EDImportExcept.active, Equal<Required<EDImportExcept.active>>>>>>>>(this);
PXResultset<EDImportExcept> excepts =
chkExcepts.Select(strDocumentId, ediOrder.OrderNbr ?? "", true);
The database column is set as nullable.
The DAC entry is defined as string:
#region OrderNbr
public abstract class orderNbr : PX.Data.IBqlField
{
}
protected string _OrderNbr;
[PXDBString(15, IsUnicode = true)]
[PXUIField(DisplayName = "Order Nbr")]
public virtual string OrderNbr
{
get
{
return this._OrderNbr;
}
set
{
this._OrderNbr = value;
}
}
#endregion
Why aren't the rows with the matching DocumentId and blank OrderNbr returned?
I moved the coalesce out of the select and set it to a variable and that seems to work. I was just trying to simplify the code.
In acumatica requisition screen, we saw that even if the sub-account doesnt exists, it creates it when the user enters a new value. How can we set new values to the dimension selector using code?
The subid in the backend is an integer, so we are not sure how to handle.
Please assist.
The Account and the Sub-Account fields work in a way similar to the PXSelectorAttribute with the SubstituteKey property set. The Account and the Sub-Account fields are usually decorated with the AccountAttribute and the SubAccountAttribute. In their constructors the AccountAttribute and the SubAccountAttribute always initialize an instance of the PXDimensionSelectorAttribute, which subscribes to FieldUpdating and FieldSelecting handlers to transform the field value between the internal (integer) and external (string) presentation.
To set value for Account or Sub-Account field, you should raise FieldUpdating handler with external (string) value passed as second ref parameter:
public class RQRequisitionEntryExt : PXGraphExtension<RQRequisitionEntry>
{
public PXAction<RQRequisition> AddLine;
[PXButton]
[PXUIField(DisplayName = "Add Line")]
protected void addLine()
{
PXCache cache = Base.Lines.Cache;
RQRequisitionLine line = Base.Lines.Insert();
line.InventoryID = 691;
line = Base.Lines.Update(line);
object acctID = "10100";
cache.RaiseFieldUpdating<RQRequisitionLine.expenseAcctID>(line, ref acctID);
line.ExpenseAcctID = (int?)acctID;
object subID = "CONFIN";
cache.RaiseFieldUpdating<RQRequisitionLine.expenseSubID>(line, ref subID);
line.ExpenseSubID = (int?)subID;
Base.Lines.Update(line);
}
}
If there is no sub-account value in database, the SubAccountAttribute generates a temporary negative integer value. New sub-account value will be saved in database by the SubAccountAttribute during execution of the Persist() method.
I have an Azure Table Storage with the following Entity:
SampleEntity : TableEntity
{
public int EmployeeId{get; set;}
}
And I have inserted 100 records to the table.
Now I have a change in requirement that the EmployeeID should be string. Also I should not delete the existing 100 records that were inserted.
Hence I changed the existing SampleEntity as follows:
SampleEntity : TableEntity
{
public string EmployeeId{get; set;}
}
And I have inserted 50 Rows into the table with EmployeeId as string.
Now when I do a GetOperation on the table with new SampleEntity(with string EmployeeID), I am getting 150 rows, but the values of EmployeeID for the first 100 rows inserted using the Old SampleEntity was
0.
On the other hand if I switch to old SampleEntity and do a GetOperaiton, I get null values for EmployeeID for the 50 rows inserted using new SampleEntity.
How can I use the new Sample Entity toget all 150 rows with values for EmployeeId in Strings?
What you could possibly do is change the data type of Integer type EmployeeId to String. For this what you would need to do is fetch all entities as DynamicTableEntity and check the property type of EmployeeId property. If the type is Int32, you would create a new entity with a String type EmployeeId property and set its value to old entity's EmployeeId value and then update the existing entity (you will keep the same PartitionKey and RowKey).
See the sample code below for example:
//Get all entities and make sure we get them as dynamic table entity.
var query = new TableQuery();
var allEntities = table.ExecuteQuery(query);
foreach (var entity in allEntities)
{
var propertyType = entity.Properties["EmployeeId"].PropertyType;
if (propertyType == EdmType.Int32 && entity.Properties["EmployeeId"].Int32Value.HasValue)
{
//This is an entity with Integer type EmployeeId. What we need to do is update this entity with a new entity where data type of EmployeeId is String.
var employeeId = entity.Properties["EmployeeId"].Int32Value.Value;
var newEntityWithStringType = new DynamicTableEntity()
{
PartitionKey = entity.PartitionKey,
RowKey = entity.RowKey,
ETag = "*"
};
newEntityWithStringType.Properties.Add("EmployeeId", new EntityProperty(employeeId.ToString()));
TableOperation updateOperation = TableOperation.Replace(newEntityWithStringType);
table.Execute(updateOperation);
}
}
The code above assumes that you only have one property EmployeeId. If there are more properties, please make sure to include them in newEntityWithStringType Properties.
I have a domain object called User:
class User{
String username;
String firstName;
String lastName;
Zipcode zip;
}
I also have a Zip Code object:
class Zipcode {
String zip;
String city;
String state;
Float lat;
Float long;
}
The zipcode table should never be modified as it contains static reference data prepopulated
A user belongs to one zipcode. The user enters the zipcode as part of the User creation.
How should I model the domain objects relationship? I would like like to make sure that GORM does not attempt to update zipcodes. I would like to make sure that the user only enters valid zipcode numbers. (Which are found in the zipcode table) How do I configure the constraints on the User object? In the controller, I do the following:
def userInstance = new User(params) // where params are form values
How do I set the proper zipcode on the object?
You would not let GORM manage the zip property (and restrict GORM from doing so at a second stage), at all.
That's what mfloryan's approach tells, too; however, his approach doesn't separate concerns, properly (separation of concerns paradigm): In the MVC (Model-View-Controller) pattern, it's not the controllers' task to "model" the data model, but it's the task of the data access layer (which is - in case of GORM - the domain classes theirselves).
Thus, the User class would be implemented like that:
class User {
String userName
String firstName
String lastName
String zip
ZipCode retrieveZipCode() {
ZipCode.findByZip(zip)
}
static constraints = {
zip nullable: false, blank: false, matches: /^\d{5}/,
/* not tested at my machine: */
validator: {
if(!retrieveZipCode(it)) {
return false
}
}
}
}
Note the retrieveZipCode() method. It's not called getZipCode() as, otherwise, Hibernate would throw an exception about a "missing setter method". You can also experiment with adding a zipCode property, a getZipCode() method (that does nothing or, alternatively, throws an exception), and adding the zipCode property to the transinients definition. - Everything of this (in any combination) will not work.
Also note the constraints definition: It matches when the zip consists of exactly five digits. (I believe that's the format of ZIP codes there in the USA.)
It should also make sure that the database contains an entry for the user's ZIP code (syntax not tested).
I've changed the ZipCode class slightly (partly, to avoid a compilation error):
class ZipCode {
String zip;
String city;
String state;
Float latitude;
Float longitude;
}
And finally, there's an integration test:
class UserTests extends GroovyTestCase {
def testUserCreation() {
User user = new User(
userName: "foo", firstName: "bar",
lastName: "baz", zip: "12345")
assert user.validate()
assert user.retrieveZipCode()
user.save()
}
}
Thanks
This sounds like more of an UI issue. Do a Zipcode object lookup in the controller and set the the object located on the user. Otherwise, I can't see how a Zipcode could have been altered upon creation of a user.
save = {
params.zip.id = Zipcode.findByZip(params.zip)
def userInstance = new User(params)
}
or
save = {
def userInstance = new User(params)
userInstance.zip = Zipcode.findByZip(params.zip)
}
You should include some validation logic (if the zip is incorrect) and also consider renaming params.zip to params.userProvidedZip or something like that.
use Domain event callback
transient beforeUpdate = {
// check to make sure that the zip code value remains the same
// and is never changed...
}