ServiceStack route definition for a parameter that is an array - servicestack

I have a service that will return top N items in sales given a bunch of different criteria. So if I have a GET route, how do I set the route to handle an array of a certain parameter?
Top 100 items for group A,C,D,E,F for the current week.
Top 100 items for store 1,10,11,18,40 for the current month.
How could the route be structured to handle this?

Its already wired up for you. For the groups example the route declaration will look like this:
Items/{Groups}
The Items DTO will need this property
public string[] Groups { get; set; }
Then you can just call it like so /Items/A,C,D,E,F
The array will get populated correctly.

Related

In Core Data, how sort an NSFetchRequest depending on the sum of an attribute of a child entity? (SwiftUI)

I am building an iOS app in SwiftUI for which I have a Core Data model with two entities:
CategoryEntity with attribute: name (String)
ExpenseEntity with attributes: name (String) and amount (Double)
There is a To-many relationship between CategoryEntity and ExpenseEntity (A category can have many expenses).
I’m fetching the categories and showing them in a list together with the sum of the expenses for each category as follows: Link to app screenshot
I would like to add a sort to the fetch request so the categories appear in order depending on the total amount of their expenses. In the example of the previous picture, the order of appearance that I would like to get would be: Tech, Clothes, Food and Transport. I don’t know how to approach this problem. Any suggestions?
In my current implementation of the request, the sorted is done alphabetically:
// My current implementation for fetching the categories
func fetchCategories() {
let request = NSFetchRequest<CategoryEntity>(entityName: "CategoryEntity")
let sort = NSSortDescriptor(keyPath: \CategoryEntity.name, ascending: true)
request.sortDescriptors = [sort]
do {
fetchedCategories = try manager.context.fetch(request)
} catch let error {
print("Error fetching. \(error.localizedDescription)")
}
}
You don't have to make another FetchRequest, you can just sort in a computed property like this:
(I assume your fetched results come into a var called fetchedCategories.)
var sortedCategories: [CategoryEntity] {
return fetchedCategories.sorted(by: { cat1, cat2 in
cat1.expensesArray.reduce(0, { $0 + $1.amount }) >
cat2.expensesArray.reduce(0, { $0 + $1.amount })
})
}
So this sorts the fetchedCategories array by a comparing rule, that looks at the sum of all cat1.expenses and compares it with the sum of cat2.expenses. The >says we want the large sums first.
You put the computed var directly in the View where you use it!
And where you used fetchedCategories before in your view (e.g. a ForEach), you now use sortedCategories.
This will update in the same way as the fetched results do.
One approach would be to include a derived attribute in your CategoryEntity model description which keeps the totals for you. For example, to sum the relevant values from the amount column within an expenses relation:
That attribute should be updated whenever you save your managed object context. You'll then be able to sort it just as you would any other attribute, without the performance cost of calculating the expense sum for each category whenever you sort.
Note that this option only really works if you don't have to do any filtering on expenses; for example, if you're looking at sorting based on expenses just in 2022, but your core data store also has seconds in 2021, the derived attribute might not give you the sort order you want.

Obtaining FluentValidation max string length rules and their max values

We want to implement a character counter in our Javascript data entry form, so the user gets immediate keystroke feedback as to how many characters he has typed and how many he has left (something like "25/100", indicating current string length is 25 and 100 is the max allowed).
To do this, I would like to write a service that returns a list of dto property names and their max allowed lengths.
{Name='SmallComment', MaxLength=128}
{Name='BigComment', MaxLength=512}
The best way I can think of to do this would be to create an instance of the validator for that dto and iterate through it to pull out the .Length(min,max) rules. I had other ideas as well, like storing the max lengths in an attribute, but this would require rewriting all the validators to set up the rules based on the attributes.
Whatever solution is best, the goal is to store the max length for each property in a single place, so that changing that length affects the validation rule and the service data passed down to the javascript client.
If you want to maintain a single source of reference for both client/server I would take a metadata approach and provide a Service that returns the max lengths to the client for all types, something like:
public class ValidationMetadataServices : Service
{
public object Any(GetFieldMaxLengths request)
{
return new GetFieldMaxLengthsResponse {
Type1 = GetFieldMaxLengths<Type1>(),
Type2 = GetFieldMaxLengths<Type2>(),
Type3 = GetFieldMaxLengths<Type3>(),
};
}
static Dictionary<string,int> GetFieldMaxLengths<T>()
{
var to = new Dictionary<string,int>();
typeof(T).GetPublicProperties()
.Where(p => p.FirstAttribute<StringLengthAttribute>() != null)
.Each(p => to[p.PropertyName] =
p.FirstAttribute<StringLengthAttribute>().MaximumLength);
return to;
}
}
But FluentValidation uses Static properties so that would require manually specifying a rule for each property that validates against the length from the property metadata attribute.

loopback relational database hasManyThrough pivot table

I seem to be stuck on a classic ORM issue and don't know really how to handle it, so at this point any help is welcome.
Is there a way to get the pivot table on a hasManyThrough query? Better yet, apply some filter or sort to it. A typical example
Table products
id,title
Table categories
id,title
table products_categories
productsId, categoriesId, orderBy, main
So, in the above scenario, say you want to get all categories of product X that are (main = true) or you want to sort the the product categories by orderBy.
What happens now is a first SELECT on products to get the product data, a second SELECT on products_categories to get the categoriesId and a final SELECT on categories to get the actual categories. Ideally, filters and sort should be applied to the 2nd SELECT like
SELECT `id`,`productsId`,`categoriesId`,`orderBy`,`main` FROM `products_categories` WHERE `productsId` IN (180) WHERE main = 1 ORDER BY `orderBy` DESC
Another typical example would be wanting to order the product images based on the order the user wants them to
so you would have a products_images table
id,image,productsID,orderBy
and you would want to
SELECT from products_images WHERE productsId In (180) ORDER BY orderBy ASC
Is that even possible?
EDIT : Here is the relationship needed for an intermediate table to get what I need based on my schema.
Products.hasMany(Images,
{
as: "Images",
"foreignKey": "productsId",
"through": ProductsImagesItems,
scope: function (inst, filter) {
return {active: 1};
}
});
Thing is the scope function is giving me access to the final result and not to the intermediate table.
I am not sure to fully understand your problem(s), but for sure you need to move away from the table concept and express your problem in terms of Models and Relations.
The way I see it, you have two models Product(properties: title) and Category (properties: main).
Then, you can have relations between the two, potentially
Product belongsTo Category
Category hasMany Product
This means a product will belong to a single category, while a category may contain many products. There are other relations available
Then, using the generated REST API, you can filter GET requests to get items in function of their properties (like main in your case), or use custom GET requests (automatically generated when you add relations) to get for instance all products belonging to a specific category.
Does this helps ?
Based on what you have here I'd probably recommend using the scope option when defining the relationship. The LoopBack docs show a very similar example of the "product - category" scenario:
Product.hasMany(Category, {
as: 'categories',
scope: function(instance, filter) {
return { type: instance.type };
}
});
In the example above, instance is a category that is being matched, and each product would have a new categories property that would contain the matching Category entities for that Product. Note that this does not follow your exact data scheme, so you may need to play around with it. Also, I think your API query would have to specify that you want the categories related data loaded (those are not included by default):
/api/Products/13?filter{"include":["categories"]}
I suggest you define a custom / remote method in Product.js that does the work for you.
Product.getCategories(_productId){
// if you are taking product title as param instead of _productId,
// you will first need to find product ID
// then execute a find query on products_categories with
// 1. where filter to get only main categoris and productId = _productId
// 2. include filter to include product and category objects
// 3. orderBy filter to sort items based on orderBy column
// now you will get an array of products_categories.
// Each item / object in the array will have nested objects of Product and Category.
}

Magento observer (sales_order_grid_collection_load_before), filter collection by a product attribute

So I'm working with sales_order_grid_collection_load_before observer event at the moment, where I can get the collection being used through $collection = $observer->getEvent()->getOrderGridCollection();, I'm just wondering, if it is possible to filter this collection by a product from the order attribute.
What I mean with that is the order grid collection has sub products related to that order, I need to only show orders if at least one of the products match a specific criteria (in my case, I've given the products an admin_id attribute, which is set to the administrator who added the product).
Thanks!
I've done a very similar thing by doing the following:
Override the sales order grid block. To do this you will need to set up your own extension (it looks like you might already be doing this, but just in case, there is some handy doco in the Magento wiki)
<config>
<modules>
<Company_Module>
<version>0.1.0</version>
</Company_Module>
</modules>
<global>
<blocks>
<company_module>
<class>Company_Module_Block</class>
</company_module>
<adminhtml>
<rewrite>
<sales_order_grid>Company_Module_Block_Sales_Order_Grid</sales_order_grid>
</rewrite>
</adminhtml>
</blocks>
</global>
</config>
I then copied the /app/code/core/Mage/Adminhtml/Block/Sales/Order/Grid.php into my extensions folder at /app/code/local/Company/Module/Block/Sales/Order
In the copied file I changed the class name to class Company_Module_Block_Sales_Order_Grid extends Mage_Adminhtml_Block_Widget_Grid
I then changed the _prepareCollection function. In this case I was interested in grabbing the customer_group_id and customer_email from the sales_flat_order table
protected function _prepareCollection() {
$collection = Mage::getResourceModel($this->_getCollectionClass());
// left join onto the sales_flat_order table, retrieving the customer_group_id and customer_email columns -< this can be expanded
$collection->getSelect()->join('sales_flat_order', 'main_table.entity_id=sales_flat_order.entity_id', array('customer_group_id'=>'customer_group_id', 'customer_email'=>'customer_email'), null, 'left');
// grab the current user and get their associated customer groups (additional coding required to associate the customer groups to an admin user
$user = Mage::getSingleton('admin/session')->getUser();
$roleId = implode('', $user->getRoles());
$customerGroupIds = Mage::getModel('admin/roles')->load($roleId)->getCustomerGroupIds();
$orders = Mage::getResourceModel('sales/order_collection');
// if the admin user has associated customer group ids then find the orders associated with those
// this would be where you would do your filtering of the products
if (count($customerGroupIds)) {
$orders->addAttributeToFilter('customer_group_id', array('in' => $customerGroupIds));
}
$orderIds = $orders->getAllIds();
$collection->addAttributeToFilter('entity_id', array('in' => $orderIds));
$this->setCollection($collection);
return parent::_prepareCollection();
}
You may not need the join to the sales_flat_order table...you might be able to do it just by doing the filtering in the second part of the _prepareCollection function shown above. In my case, I was displaying the customer_group_id and the customer_email in the grid so that the user could manually filter, if required.
I am not sure if you can access directly the product from the order_grid_collection (I don't think so) but you can join this collection with sales_flat_order_item and then filter as you wish.
HTH

Couchdb: filter and group in a single view

I have a Couchdb database with documents of the form: { Name, Timestamp, Value }
I have a view that shows a summary grouped by name with the sum of the values. This is straight forward reduce function.
Now I want to filter the view to only take into account documents where the timestamp occured in a given range.
AFAIK this means I have to include the timestamp in the emitted key of the map function, eg. emit([doc.Timestamp, doc.Name], doc)
But as soon as I do that the reduce function no longer sees the rows grouped together to calculate the sum. If I put the name first I can group at level 1 only, but how to I filter at level 2?
Is there a way to do this?
I don't think this is possible with only one HTTP fetch and/or without additional logic in your own code.
If you emit([time, name]) you would be able to query startkey=[timeA]&endkey=[timeB]&group_level=2 to get items between timeA and timeB grouped where their timestamp and name were identical. You could then post-process this to add up whenever the names matched, but the initial result set might be larger than you want to handle.
An alternative would be to emit([name,time]). Then you could first query with group_level=1 to get a list of names [if your application doesn't already know what they'll be]. Then for each one of those you would query startkey=[nameN]&endkey=[nameN,{}]&group_level=2 to get the summary for each name.
(Note that in my query examples I've left the JSON start/end keys unencoded, so as to make them more human readable, but you'll need to apply your language's equivalent of JavaScript's encodeURIComponent on them in actual use.)
You can not make a view onto a view. You need to write another map-reduce view that has the filtering and makes the grouping in the end. Something like:
map:
function(doc) {
if (doc.timestamp > start and doc.timestamp < end ) {
emit(doc.name, doc.value);
}
}
reduce:
function(key, values, rereduce) {
return sum(values);
}
I suppose you can not store this view, and have to put it as an ad-hoc query in your application.

Resources