Dealing with complex query results in IHP without having to manually specify columns? - ihp

The "Dealing With Complex Query Results" section of https://ihp.digitallyinduced.com/Guide/database.html shows how you can "include extra data" when querying for a database type, by creating a type like this:
data PostWithCommentsCount = PostWithCommentsCount
{ id :: Id Post
, title :: Text
, commentsCount :: Int
}
deriving (Eq, Show)
This works well, but it has the disadvantage that you need to manually specify all the columns of posts to include in the new PostWithCommentsCount type:
instance FromRow PostWithCommentsCount where
fromRow =
PostWithCommentsCount
<$> field
<*> field
<*> field
fetchPostsWithCommentsCount :: (?modelContext :: ModelContext) => IO [PostWithCommentsCount]
fetchPostsWithCommentsCount = do
trackTableRead "posts" -- This is needed when using auto refresh, so auto refresh knows that your action is accessing the posts table
sqlQuery "SELECT posts.id, posts.title, (SELECT COUNT(*) FROM comments WHERE comments.post_id = posts.id) AS comments_count FROM posts" ()
This is tedious to maintain over time, if changes in the posts table means you also have to change this manual query. I think it could be better if the type looked like this:
data PostWithCommentsCount = PostWithCommentsCount
{ post :: Post
, commentsCount :: Int
}
deriving (Eq, Show)
So that I wouldn't have to manually specify all the columns of posts that I'm interested in - I would just get all the whole Post. Is there a way to accomplish this currently?

Yes this is possible if you use the following FromRow instance:
instance FromRow PostWithCommentsCount where
fromRow = do
post <- fromRow
commentsCount <- field
pure PostWithCommentsCount { post, commentsCount }
You can also write this with the operators above like this:
instance FromRow PostWithCommentsCount where
fromRow = PostWithCommentsCount
<$> fromRow
<*> field

Related

The Elm way of transforming flags to model

I have the following types in my app:
type Page
= Welcome
| Cards
type alias Flags =
{ recipientName : String
, products : List Product
}
type alias Product =
{ id : Int
, name : String
, price : Float
, liked : Maybe Bool
}
type alias Model =
{ recipientName : String
, currentPage : Page
, products : List Product
}
I am passing an array of products as flags. Here's what my init looks like:
init : Flags -> ( Model, Cmd Msg )
init flags =
let
{ recipientName, products } =
flags
in
Model recipientName Welcome products
|> withNoCmd
The challenge I'm facing is that the products in this array only have id, name, and price attributes. So, given the Flags definition, every time I extend Product with a new attribute (such as liked), the array of products passed as flags will need to have that attribute as well. For now, I just render them as empty, but this doesn't feel right, so I was wondering what is the Elm way™ of receiving flags and transforming them into the model? Thank you!
It sounds like your Product is already defined as an input (or the environment) of your app:
type alias Product =
{ id : Int
, name : String
, price : Float
}
and you are augmenting this with info that relates the Recipient to the Products. I'd suggest splitting this out into its own type that can grow as your app grows, eg:
type alias Opinion =
{ liked : Maybe Bool
, review : String
, preferredColor : Color
}
then you can tie these together in your Model:
type alias Model =
{ recipientName : String
, currentPage : Page
, products : List (Product, Opinion)
}
or, depending on how the application works, you might end up wanting to look up the recipient's opinion by product.id:
...
, products : List Product
, opinions : Dict Int Opinion
The point is that if you keep the original Product unchanged, you can build a small library of functions that work on Product, both for inventory (where no recipient is involved) and for the customer. Maybe you can re-use the Opinion type for customer metrics.
If these two types are likely to evolve, keeping them separate can help ensure you don't end up with messy and bug-attracting interdependencies.

Slick query for one to optional one (zero or one) relationship

Given tables of:
case class Person(id: Int, name: String)
case class Dead(personId: Int)
and populated with:
Person(1, "George")
Person(2, "Barack")
Dead(1)
is it possible to have a single query that would produce a list of (Person, Option[Dead]) like so?
(Person(1, "George"), Some(Dead(1)))
(Person(2, "Barack"), None)
For slick 3.0 it should be something like this:
val query = for {
(p, d) <- persons joinLeft deads on (_.id === _.personId)
} yield (p, d)
val results: Future[Seq[(Person, Option[Dead])]] = db.run(query.result)
In slick, outer joins are automatically wrapped in an Option type. You can read more about joining here: http://slick.typesafe.com/doc/3.0.0/queries.html#joining-and-zipping

Dynamic fields for my index not coming up in the list of fields

I am trying to get the fields of my index using the below code snippet.
var fieldsList= DocumentStore.DatabaseCommands.GetIndex("IndexName").Fields.ToList();
This is returning a string list with all fields defined in the index except the dynamic fields ( fields returned from _ ).
Here is the Map command for my index.
Map = products =>
from product in product s
select new
{
product.Title,
product.Subject,
product.From,
_ = product.
Attributes.Select(attribute =>
CreateField(attribute.Name, attribute.Value, false, true))
};
That is by design. The list of fields is the static fields that are in your index.
We don't try to find the dynamic ones.

Preserve Order of IN in ORM Order

I'm trying to do a query where I preserve the order of the ids in a IN statement. I can't seem to do it with either the Model Manage Query Builder or the standard ORM 'order' array parameter. Am I missing something? I keep getting:
UNEXPECTED TOKEN IDENTIFIER(, NEAR TO 'id`enter code here`,17743,16688,16650
Here's my model manager:
$query = $this->modelsManager->createQuery('SELECT * FROM Projects WHERE id IN ('.implode(',', array_keys($finalIterations)).')
ORDER BY FIELD(id,'.implode(',', array_keys($finalIterations)).'');
It's pretty obvious PhQL doesn't like the FIELD key word. Is there a way for me to do what I'm trying to do with PhQL? It seems I will not be able to do what I need to.
Unfortunately as previously said, this is missing a feature in Phalcon.
Have a look at this function, I've put it into my ModelBase abstract class which is parent class of all my models. It uses PhQL variable binding, so it's safe for handling direct user input.
You could have reimplemented custom \Phalcon\Mvc\Model\Criteria but this solution seems to be easier to work with, at least for me.
ModelBase abstract
public function appendCustomOrder( \Phalcon\Mvc\Model\CriteriaInterface &$criteria, $orderField, array &$orderValues = [] ) {
if(!empty($orderValues)) {
$queryKeys = $bindParams = [];
foreach($orderValues as $key => $id) {
$queryKey = 'pho'.$key;
$queryKeys[] = ':'.$queryKey.':';
$bindParams[$queryKey] = $id;
}
// TODO: add support for multiple orderBy fields
$criteria->orderBy('FIELD('.$orderField.','.implode(',',$queryKeys).')');
// there's no 'addBind' function, need to merge old parameters with new ones
$criteria->bind( array_merge( (array) #$criteria->getParams()['bind'], $bindParams ) );
}
}
Controller usage
$projectIDs = [17743, 16688, 16650];
$projectsModel = new Projects();
$criteria = $projectsModel->query->inWhere( 'id', $projectIDs );
$projectsModel->appendCustomOrder( $criteria, 'id', $projectIDs );
$projectsData = $criteria->execute();
This will generate valid PhQL syntax similar to this one:
SELECT `projects`.`id` AS `id`, `projects`.`title` AS `title`
FROM `projects`
WHERE `projects`.`id` IN (:phi0, :phi1, :phi2)
ORDER BY FIELD(`projects`.`id`, :pho0, :pho1, :pho2)

Get data from another column where first column id is equals other and other column equals X

My problem is:
I would like to get all advertistments where id of Description column is equals with Advertistment's column.
Let's say that Advertistment column is connected with Description column.
I would like to gain all description's id where one of its column called type_of_house is equals m.
Then show all advertistment where advertistment's id is equals with description's id.
In short way: advertistment shows info about houses, descriptions store houses type D and M and I want show all advertistments with houses type of M.
This is correct sql:
SELECT * FROM advertistment, description WHERE advertistment.id_advertistment = description.id_description AND description.type_of_house = "m"
I have no idea how write it into zend. I tried something like that. This function I wrote in model folder.
public function takeAll() {
$select = $this->_db->select();
$select->from(array('a' => 'advertistment', 'd' => 'description'));
$select->where('a.id_advertistment = d.id_description AND d.type_of_house = m');
return $select->query()->fetchAll();
}
You're actually quite close. This should work:
public function takeAll() {
$select = $this->_db->select();
$select->from(array('a' => 'advertistment'));
$select->join(array('d' => 'description'), 'a.id_advertistment = d.id_description');
$select->where('d.type_of_house = ?', 'm');
return $this->_db->fetchAll($select);
}