Coming from play anorm, create a model from json without passing anorm PK value in the json I'm trying to add Seq[Address] to case class like
case class User(
id: Pk[Long] = NotAssigned,
name: String = "",
email: String = "",
addresses: Seq[Address])
Address is a simple object/class with three Strings. An user can have more than 1 address, how do I get all the addresses along with the User in findAll.
def findAll(): Seq[User] = {
Logger.info("select * from pt_users")
DB.withConnection { implicit connection =>
SQL(
"""
select * from pt_users where name like {query} limit {size} offset {offset}
""").as(User.list *)
}
}
A side note about something I have found useful: if you're not sure you will always want to fetch the addresses for a user, you can avoid adding that relation as a field and instead use tuples or other data structures for representing them. This would allow you to have a method like this:
def allUsersWithAddresses(): Map[User, Seq[Address])] = ...
But still have methods that return only users without having the joined data.
To read a join (or subselect) you will have to parse the combined output with a parser, something like this:
.as(User ~ Address *).groupBy(_._1)
If you really want to put the addresses inside of user, you'd have to make the address list empty from the user parser and then map each distinct user into one with the addresses:
.as(User ~ Address *).groupBy(_._1).map {
case (user, addresses) => user.copy(addresses = addresses)
}
Note, the examples are just pointers to an approximate solution, not copy-paste-and-compile ready code.
Hope it helped!
This will work
/** Parses a `Blog` paired with an optional `Post` that can be later be collapsed into one object. */
val parser: RowParser[(Blog, Option[Post])] = {
simple ~ Post.parser.? map {
case blog~post => (blog, post)
}
}
def list: List[Blog] = {
DB.withConnection { implicit c =>
SQL("""
SELECT * FROM blogs b
LEFT OUTER JOIN posts p ON(p.blog_id = b.id)
""").as(parser *)
.groupBy(_._1)
.mapValues(_.map(_._2).flatten)
.toList
.map{ case (blog, posts) => blog.copy(posts = posts) }
}
}
Copied from https://index.scala-lang.org/mhzajac/anorm-relational/anorm-relational/0.3.0?target=_2.12
Related
I have users and groups in my system. Users belong to groups using a sibling relationship. I need to transform the Users/Groups into an Array of users/groups to return to the client. When I lookup a user I get a Future, if I then map the use and attempt to access its groups I get a Future.
What is the best way of handling nested futures so I can get to the actual data and munge it?
struct ReturnUserStruct {
var userId: Int
var primaryGroupName: String
}
let futureUsers = Users.query(on: request).all()
futureUsers.map { users in
var returnUsersArray<returnUserStruct> = []
users.ForEach { user in
var returnUserStruct: ReturnUserStruct
returnUserStruct.userId = user.id
// This a sibling relationship that returns a Future
primaryGroupFuture = user.primaryGroup
primaryGroupFuture.map { group in
returnUserStruct.primaryGroupName = group.name
}
returnUsersArray.append(returnUserStruct)
}
return returnUsersArray
This code doesn't work. The map call are obviously scheduled asynchronously and returnUsersArray ends up empty. I can rearrange this and get the user ids into the return array but haven't figured out a good way to get the groupName.
Good to hear about Fluent4 and Vapor4. Here is the solution I've settled on.
return Users.query(on: request)
.all()
.and(Group.find(user.primaryGroup).first())
.map { (users, groupOptional) -> [ReturnUserStruct]
guard let group = groupOptional else {
Abort(.notFound)
}
var returnArray: Array<ReturnUserStruct>
users.forEach { user in
var returnUserStruct: ReturnUserStruct
returnUserStruct.userId = user.id
returnUserStruct.primaryGroupName = group.name
returnArray.append(returnUserStruct)
}
}
It seems to work, here's hope .and sticks around.
Thanks
I'm trying to perform a GraphQL query using Django and Graphene. To query one single object using the id I did the following:
{
samples(id:"U2FtcGxlU2V0VHlwZToxMjYw") {
edges {
nodes {
name
}
}
}
}
And it just works fine. Problem arise when I try to query with more than one id, like the following:
{
samples(id_In:"U2FtcGxlU2V0VHlwZToxMjYw, U2FtcGxlU2V0VHlwZToxMjYx") {
edges {
nodes {
name
}
}
}
}
In the latter case I got the following error:
argument should be a bytes-like object or ASCII string, not 'list'
And this is a sketch of how defined the Type and Query in django-graphene
class SampleType(DjangoObjectType):
class Meta:
model = Sample
filter_fields = {
'id': ['exact', 'in'],
}
interfaces = (graphene.relay.Node,)
class Query(object):
samples = DjangoFilterConnectionField(SampleType)
def resolve_sample_sets(self, info, **kwargs):
return Sample.objects.all()
GlobalIDMultipleChoiceFilter from django-graphene kinda solves this issue, if you put "in" in the field name. You can create filters like
from django_filters import FilterSet
from graphene_django.filter import GlobalIDMultipleChoiceFilter
class BookFilter(FilterSet):
author = GlobalIDMultipleChoiceFilter()
and use it by
{
books(author: ["<GlobalID1>", "<GlobalID2>"]) {
edges {
nodes {
name
}
}
}
}
Still not perfect, but the need for custom code is minimized.
You can easily use a Filter just put this with your nodes.
class ReportFileFilter(FilterSet):
id = GlobalIDMultipleChoiceFilter()
Then in your query just use -
class Query(graphene.ObjectType):
all_report_files = DjangoFilterConnectionField(ReportFileNode, filterset_class=ReportFileFilter)
This is for relay implementation of graphql django.
None of the existing answers seemed to work for me as they were presented, however with some slight changes I managed to resolve my problem as follows:
You can create a custom FilterSet class for your object type, and filter the field by using the GlobalIDMultipleChoiceFilter. for example:
from django_filters import FilterSet
from graphene_django.filter import GlobalIDFilter, GlobalIDMultipleChoiceFilter
class SampleFilter(FilterSet):
id = GlobalIDFilter()
id__in = GlobalIDMultipleChoiceFilter(field_name="id")
class Meta:
model = Sample
fields = (
"id_in",
"id",
)
Something I came cross is that you can not have filter_fields defined with this approach. Instead, you have to only rely on the custom FilterSet class exclusively, making your object type effectively look like this:
from graphene import relay
from graphene_django import DjangoObjectType
class SampleType(DjangoObjectType):
class Meta:
model = Sample
filterset_class = SampleFilter
interfaces = (relay.Node,)
I had trouble implementing the 'in' filter as well--it appears to be misimplemented in graphene-django right now and does not work as expected. Here are the steps to make it work:
Remove the 'in' filter from your filter_fields
Add an input value to your DjangoFilterConnectionField entitled 'id__in' and make it a list of IDs
Rename your resolver to match the 'samples' field.
Handle filtering by 'id__in' in your resolver for the field. For you this will look as follows:
from base64 import b64decode
def get_pk_from_node_id(node_id: str):
"""Gets pk from node_id"""
model_with_pk = b64decode(node_id).decode('utf-8')
model_name, pk = model_with_pk.split(":")
return pk
class SampleType(DjangoObjectType):
class Meta:
model = Sample
filter_fields = {
'id': ['exact'],
}
interfaces = (graphene.relay.Node,)
class Query(object):
samples = DjangoFilterConnectionField(SampleType, id__in=graphene.List(graphene.ID))
def resolve_samples(self, info, **kwargs):
# filter_field for 'in' seems to not work, this hack works
id__in = kwargs.get('id__in')
if id__in:
node_ids = kwargs.pop('id__in')
pk_list = [get_pk_from_node_id(node_id) for node_id in node_ids]
return Sample._default_manager.filter(id__in=pk_list)
return Sample._default_manager.all()
This will allow you to call the filter with the following api. Note the use of an actual array in the signature (I think this is a better API than sending a comma separated string of values). This solution still allows you to add other filters to the request and they will chain together correctly.
{
samples(id_In: ["U2FtcGxlU2V0VHlwZToxMjYw", "U2FtcGxlU2V0VHlwZToxMjYx"]) {
edges {
nodes {
name
}
}
}
}
Another way is to tell the Relay filter of graphene_django to also deals with a list. This filter is register in a mixin in graphene_django and applied to any filter you define.
So here my solution:
from graphene_django.filter.filterset import (
GlobalIDFilter,
GrapheneFilterSetMixin,
)
from graphql_relay import from_global_id
class CustomGlobalIDFilter(GlobalIDFilter):
"""Allow __in lookup for IDs"""
def filter(self, qs, value):
if isinstance(value, list):
value_lst = [from_global_id(v)[1] for v in value]
return super(GlobalIDFilter, self).filter(qs, value_lst)
else:
return super().filter(qs, value)
# Fix the mixin defaults
GrapheneFilterSetMixin.FILTER_DEFAULTS.update({
AutoField: {"filter_class": CustomGlobalIDFilter},
OneToOneField: {"filter_class": CustomGlobalIDFilter},
ForeignKey: {"filter_class": CustomGlobalIDFilter},
})
Sometimes we use MySql Views to organize related tables to make it easier to search and sort. For example if you have Posts with a Status, and a Source.
Post
subject
body
source_id
status_id
Status
id
label
other_field
Source
id
label
other_field
View
create view read_only_posts as
SELECT statuses.label as status, sources.label as source, posts.*
from posts
left join statuses on statuses.id = posts.status_id
left join sources on sources.id = posts.source_id
Then we have the Post model and an extra model:
// Post.php
class Post extends Model
{
//
}
// ReadOnlyPost.php
class ReadOnlyPost extends Post
{
protected $table = 'read_only_posts';
}
This is nice because now you can directly sort or filter on Status or Source as a string not the id's. You can also include the 'other_field'.
But we have a problem that I need help with. If you have a polymorphic many-to-many relationship on Posts, I can't get it to work on the read only version. For example if you have polymorphic tags:
// Post.php Model
public function tags()
{
return $this->morphToMany(Tag::class, 'taggable');
}
The problem is when you filter a post (using the read only model) with a specific tag you get sql like this:
select count(*) as aggregate from read_only_posts where exists (select * from tags inner join taggables on tags.id = taggables.taggable_id where read_only_posts.id = taggables.taggable_type and taggables.taggable_type = 'read_only_posts' and label = 'test')
As you can see the problem is the taggables.taggable_type = 'read_only_posts'.
I can't find a way to override the morph type for a model. (I am on laravel 5.4 and the MorphClass isn't there anymore). The morph map is an associative array so you can't do this:
// AppServiceProvider
public function boot()
{
Relation::morphMap([
'posts' => Post::class,
'posts' => ReadOnlyPost::class, <--- Can't do this
My stupid fix is when I attach a tag to a post I also attach it to ready_only_posts, which is kind of a mess.
Anyone else uses Views for read only models? Anyone have a better way to overriding the many to many polymorphic type for a specific model?
Looking at the code, I believe this might work.
class ReadOnlyPost extends Posts
{
public function getMorphClass() {
return 'posts';
}
}
In theory you should need to have the Posts model/table listed in the morph map, since the system will auto generate the type of "posts" for it based on naming.
I'm figuring out my way into Scala 2.11 with Slick 2.1.0. I've got two entities persons and users with users extending person. How can i have a projection in users that would allow fetching person as part of users everytime i fetch a user entity.
Here are the entity classes
import scala.slick.driver.MySQLDriver.simple._
case class Person(
id: Option[Long],
name: String,
createdAt: Option[java.sql.Timestamp],
deletedAt: Option[java.sql.Timestamp]
);
class Persons(tag: Tag) extends Table[Person](tag, "persons") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def name = column[String]("name", O.NotNull)
def createdAt = column[java.sql.Timestamp]("created_at")
def deletedAt = column[java.sql.Timestamp]("deleted_at")
def * = (
id.?,
name,
createdAt.?,
deletedAt.?) <> (Person.tupled, Person.unapply)
}
and
import scala.slick.driver.MySQLDriver.simple._
case class User(
id: Option[Long],
personId: Long,
active: Boolean,
createdAt: Option[java.sql.Timestamp],
modifiedAt: Option[java.sql.Timestamp],
deletedAt: Option[java.sql.Timestamp]
);
class Users(tag: Tag) extends Table[User](tag, "users") {
def id = column[Long]("id", O.PrimaryKey, O.AutoInc)
def personId = column[Long]("person_id", O.NotNull)
def active = column[Boolean]("active")
def createdAt = column[java.sql.Timestamp]("created_at")
def modifiedAt = column[java.sql.Timestamp]("modified_at")
def deletedAt = column[java.sql.Timestamp]("deleted_at")
def * = (
id.?,
personId,
active,
createdAt.?,
modifiedAt.?,
deletedAt.?) <> (User.tupled, User.unapply)
def person = foreignKey(
"user_person_fk",
personId,
TableQuery[Users])(_.id)
}
Now when i query using
val users = TableQuery[Users]
def loadAll: Option[List[Any]] = {
db withDynSession {
val query = for {
u <- users
p <- u.person
} yield (u,p)
var result = query.list map {
case (u, p) => Map("user" -> u, "person" -> p)
}
return Option(result)
}
}
I get,
List[Map:("user" -> User, "person" -> Person)]
Is there a way i can use a projection or map to get person as part of User.
Instead of mapping your tables using <>, you can transform them after executing the query into whatever shape you like and then map them to case classes using .map.
HOWEVER: This is not recommended practice. Best practice with Slick is to keep foreign keys in your case classes and associate case classes only using tuples (or equivalent named association classes as EndeNeu suggests). Having user embed Person directly takes away some flexibility from you. To quote the Slick manual:
From: http://slick.typesafe.com/doc/2.1.0/orm-to-slick.html#relationships
case class Address( … )
case class Person( …, address: Address )
The problem is that this hard-codes that to exist, a Person requires an Address. It can not be loaded without it. This does’t fit to Slick’s philosophy of giving you fine-grained control over what you load exactly. With Slick it is advised to map one table to a tuple or case class without them having object references to related objects. Instead you can write a function that joins two tables and returns them as a tuple or association case class instance, providing an association externally, not strongly tied one of the classes.
val tupledJoin: Query[(People,Addresses),(Person,Address), Seq]
= people join addresses on (_.addressId === _.id)
case class PersonWithAddress(person: Person, address: Address)
val caseClassJoinResults = tupledJoin.run map PersonWithAddress.tupled
Also be aware that unlike with some ORMs, Slick makes it very easy to write queries with finer granularity than whole rows for better performance. See
http://slick.typesafe.com/doc/2.1.0/orm-to-slick.html#query-granularity
How to get it so i return all of the projections from the below
def c = Company.createCriteria()
def a = c.list(params){
projections{
property 'id', property 'name'
}
}
if(a.size() == 0)
render "404"
else {
render (contentType: 'text/json'){
totalCount = a.totalCount
data = a
}
}
The result comes out like this:
{"totalCount":2,"data":["company1","company2"]}
Where i need it to be:
{"totalCount":2,"data":[{"class":"org.example.Company","id":1,"name":"company1"},{"class":"org.example.Company","id":2,"name":"company2"}]}
In the company domain i have lots of relationships (some one to one, one to many etc...)
my domain looks like the following:
package org.example
import java.sql.Timestamp
class Company {
String name
String abn
String cname
String email
String phone
String position
String address
String city
String postcode
int style
int openbookings;
Date date;
int tokenTotal = 0
int totaltokens
int totalboosts
int totalposts
Timestamp tokenstamp
static hasMany = [users: User, broadcast: Broadcast, bookings: Booking, locations: Location,vimsurvey:VimSurvey,rewards: Reward, tokens: CompanyToken]
static constraints = {
abn nullable: true
date nullable: true
style nullable: true
}
}
Any help would be great:)
????
http://grails.org/doc/1.1/ref/Domain%20Classes/createCriteria.html
See the property section under projections: 'property Returns the given property in the returned results'. I don't really get what you are asking for by 'all the projections'.
Are you simply looking to Find all for your domain? Why are you using a projection?
def a = c.list(params){
projections{
property 'id', property 'name'
}
}
should be
def a = c.list(params){
projections{
property 'id'
property 'name'
}
}
In fact, I get a compilation error when I attempt to do it your way. I still feel like it makes more sense to simply get the entire domain itself unless there is a very specific reason not to.