How to replace relationship field type object IDs with names / titles in KeystoneJS list CSV download / export? - keystonejs

In Keystone admin list view the handy download link exports all list items in a CSV file, however, if some of the fields are of Relationship type, the exported CSV contains Mongo ObjectIDs instead of nmeaningful strings (name, title, etc) which would be useful.
How can one force the ObjectIDs to be mapped / replaced by another field?

Keystone has an undocumented feature that allows you to create your own custom CSV export function. This feature was added back in April (see KeystoneJS Issue #278).
All you need to do is add a method to the schema called toCSV. Keystone will inject any of the following dependencies when specified as arguments to this method.
- req (current express request object)
- user (currently authenticated user)
- row (default row data, as generated without custom toCSV())
- callback (invokes async mode, must be provided last)
You could, for example, use the Mongoose Model.Populate method to replace the Object Ids of any relationship field with whatever data you want.
Assume you have a Post list with an author field of Types.Relationship to another list (let's say User) which has a name field. You could replace the author Object Id with the author's name (from the User list) by doing the following.
Post.schema.methods.toCSV = function(callback) {
var post = this,
rtn = this.toJSON();
this.populate('author', function() {
rtn.author = post.author.name; // <-- author now has data from User list
callback(null, rtn);
});
};
.toCSV() will be called for every document returned with the Model as the context. When used asynchronously (as above) you should return a JSON representation of the new CSV data by passing it as the second argument of the callback. When using it synchronously simply return the updated JSON object.

Related

Phalcon working with MySQL routines

I have a MySQL database which has GUID's stored as binary(16) for the primary keys. I'm using a MySQL user defined routine when inserting and selecting to convert the id's to and from GUID's (GUIDToBinary() and BinaryToGUID()).
In order to use my routines in Phalcon, I am setting the 'columns' parameter for the find() and findFirst() model functions which now means i'm working with incomplete objects as the functions return an instance of Phalcon\Mvc\Model\Row.
The docs state when using the columns parameter the following occurs;
Return specific columns instead of the full columns in the model. When
using this option an incomplete object is returned
UserController.php
// Returns Phalcon\Mvc\Model\Row
$incompleteUser = User::find(['columns' => 'BinaryToGUID(id) as id, status, username, password, .....']);
// Create a new user object to update
$user = new User();
// Populate with existing data
$user->assign($incompleteUser->toArray());
// Assign new changes requested by the user
$user->assign($this->request->getPost());
// Update
$user->updateUser();
User.php
public function updateUser()
{
$manager = $this->getModelsManager();
return $manager->executeQuery("UPDATE User SET ..... WHERE id = GUIDToBinary(".$this->getDI()->get('db')->escapeString($this->id).")");
}
Irrespective of the fact that I've explicitly defined an update, an insert is performed due to the Model being in a transient state.
One solution I thought of was to move the binary to GUID conversion into Phalcon by using Model events however I can't find a suitable method for performing the conversion when selecting. Updating/Inserting is possible by using the beforeSave() and beforeUpdate() events. Perhaps I could just have different properties getId() and getIdGuid() within the model but I would prefer to avoid this if possible.
Is there a way to use MySQL user defined routines in Phalcon and hydrate my model so that it remains in a persistent state? Or do i need to go down the raw SQL route for my updates and avoid PHQL?
Thanks in advance for your time.

Transaction BODY Field from COLUMN Field (Netsuite)

I have an issue where some of our orders are being imported directly into Netsuite, and there is information from the first line item, that I need to copy into the transaction record (i.e. custom field on sales order)
I want to set this up so that it is automatic, I don't have access to the system that is used to bring the orders into Netsuite, and I only JUST got suitescript access and everything I read about that is way above my head..
I know basic HTML and some of the scripting formulas from Netsuite and that's all.
I was hoping there would be a CUSTOM FIELD FORMULA or some other similar way that I can just easily source the information directly from the first item in the item sublist?
This would be quite trivial to implement using SuiteScript. The example below assumes you want to copy the Memo field (description) from the first line item to the body Memo field. The basic idea would be something like the below (untested code):
function userEventBeforeSubmit(type){
if (type === 'create') {
var record = nlapiGetNewRecord();
var memo = record.getLineItemValue('item', 'memo', 1);
record.setFieldValue('memo', memo);
}
}
If want to accomplish this via custom fields etc. it is possible using "Custom Fields with Values Derived from Summary Search Results".
To do this create a Saved Search as follows:
Type: Transaction
Criteria: [none]
Results: Formula (Text), Summary
Type = Maximum, Formula: DECODE({line}, 1, {memo}, NULL)
Available Filters: Internal ID
Then create a Custom Transaction Body Field as follows:
Type: Free Form Text
Store Value: F
Validation & Filtering > Search: [Saved Search from previous step]
As this is a dynamically calculated field (Store Value = F), it would be available when viewing the record, but not in Saved Searches and lists. To remove this limitation, you can create a Workflow that would copy this field to another one that is stored.

RavenDB Index created incorrectly

I have a document in RavenDB that looks looks like:
{
"ItemId": 1,
"Title": "Villa
}
With the following metadata:
Raven-Clr-Type: MyNamespace.Item, MyNamespace
Raven-Entity-Name: Doelkaarten
So I serialized with a type MyNamespace.Item, but gave it my own Raven-Entity-Name, so it get its own collection.
In my code I define an index:
public class DoelkaartenIndex : AbstractIndexCreationTask<Item>
{
public DoelkaartenIndex()
{
// MetadataFor(doc)["Raven-Entity-Name"].ToString() == "Doelkaarten"
Map = items => from item in items
where MetadataFor(item)["Raven-Entity-Name"].ToString() == "Doelkaarten"
select new {Id = item.ItemId, Name = item.Title};
}
}
In the Index it is translated in the "Maps" field to:
docs.Items
.Where(item => item["#metadata"]["Raven-Entity-Name"].ToString() == "Doelkaarten")
.Select(item => new {Id = item.ItemId, Name = item.Title})
A query on the index never gives results.
If the Maps field is manually changed to the code below it works...
from doc in docs
where doc["#metadata"]["Raven-Entity-Name"] == "Doelkaarten"
select new { Id = doc.ItemId, Name=doc.Title };
How is it possible to define in code the index that gives the required result?
RavenDB used: RavenHQ, Build #961
UPDATE:
What I'm doing is the following: I want to use SharePoint as a CMS, and use RavenDB as a ready-only replication of the SharePoint list data. I created a tool to sync from SharePoint lists to RavenDB. I have a generic type Item that I create from a SharePoint list item and that I serialize into RavenDB. So all my docs are of type Item. But they come from different lists with different properties, so I want to be able to differentiate. You propose to differentiate on an additional property, this would perfectly work. But then I will see all list items from all lists in one big Items collection... What would you think to be the best approach to this problem? Or just live with it? I want to use the indexes to create projections from all data in an Item to the actual data that I need.
You can't easily change the name of a collection this way. The server-side will use the Raven-Entity-Name metadata, but the client side will determine the collection name via the conventions registered with the document store. The default convention being to use the type name of the entity.
You can provide your own custom convention by assigning a new function to DocumentStore.Conventions.FindTypeTagName - but it would probably be cumbersome to do that for every entity. You could create a custom attribute to apply to your entities and then write the function to look for and understand that attribute.
Really the simplest way is just to call your entity Doelkaarten instead of Item.
Regarding why the change in indexing works - it's not because of the switch in linq syntax. It's because you said from doc in docs instead of from doc in docs.Items. You probably could have done from doc in docs.Doelkaartens instead of using the where clause. They are equivalent. See this page in the docs for further examples.

Overload of actions in the controller

Is it possible to do an overload of the actions in the controller? I haven't found any info about it and when I tried, I got this error:
The current request for action 'Create' on controller type 'InterviewController' is >ambiguous between the following action methods:
System.Web.Mvc.ViewResult Create() on type >MvcApplication4.MvcApplication4.InterviewController
System.Web.Mvc.ViewResult Create(Int32) on type >MvcApplication4.MvcApplication4.InterviewController
I've tried to do this on another way and I also get a new error that I can't fix. In fact, I created a new action (called create_client instead of create)
I need 2 ways of creating an "opportunite".
I just call the action, and I receive an empty formular in which I just have to insert data.
From a client's page, I must create an "opportunite" with the client that's already completed when the form is displayed to the user. (there is a need of productivity, the user must perform actions as fast as possible).
In the table "opportunite", I've got a column called "FK_opp_client", which is equal to the column "idClient" from the client's table.
I don't get how I can do the second way.
I've created a new action in the controller.
'
' GET: /Opportunite/Create_client
Function Create_client(idclient) As ViewResult
'Dim FK_Client = (From e In db.client
'Where(e.idClient = idclient)
' Select e.nomCompteClient).ToString()
'ViewBag.FK_client = New SelectList(db.client, "idClient", "nomCompteClient", idclient)
Dim opportunite As opportunite = db.opportunite.Single(Function(o) o.idOpportunite = 5)
opportunite.FK_Client = idclient
ViewBag.FK_Client = New SelectList(db.client, "idClient", "nomCompteClient", opportunite.FK_Client)
Return View(opportunite)
End Function
I've tried a few things to get what I wanted, the last one was to copy what was done in the "Edit" action, but for an empty rank. (so I created an empty rank in my DB). I don't think it was a good idea (imagine someone wants to update the DB where idOpportunite = 5...)
Any better ideas?
If you want to keep those two methods under the same name, you will have to implement an ActionSelectionAttribute to decorate them, or use them with different verbs (for example POST and PUT). Please read more details on action method selection process here (old but still true).
Different approach might be making your parameter optional and make action to check if it has been passed or not (through nullable type).

Rails: Difference between create and new methods in ActiveRecord?

I'm following a Rails 3.0 tutorial by lynda.com.
What's the difference between these two lines?
first_page = Page.new(:name => "First page")
first_page = Page.create(:name => "First page")
By the way, this is great tutorial; I recommend it for any other newbies like me.
Basically the new method creates an object instance and the create method additionally tries to save it to the database if it is possible.
Check the ActiveRecord::Base documentation:
create method
Creates an object (or multiple objects) and saves it to the database, if validations pass. The resulting object is returned whether the object was saved successfully to the database or not.
new method
New objects can be instantiated as either empty (pass no construction parameter) or pre-set with attributes but not yet saved (pass a hash with key names matching the associated table column names).