@JsonIgnoreProperties(Array("_isPersisted")) case class Customer(id: String, firstName: String, lastName: String, email: Option[String], birthday: Option[Date]) extends KeyedEntity[String] Option[_] correspond to nullable columns of the database. Such fields can take two kinds of values: Some(value) , if the value is, and None , if it is not. Using Option allows you to minimize the chances of a NullPointerException and is common practice in functional programming languages (especially in those in which there is no concept of null at all).@JsonIgnoreProperties excludes certain fields from JSON serialization. In this case, the _isPersisted field, which Squeryl added, had to be excluded. Class.forName("org.h2.Driver") SessionFactory.concreteFactory = Some(() => Session.create(DriverManager.getConnection("jdbc:h2:test", "sa", ""), new H2Adapter)) object DB extends Schema { val customer = table[Customer] } transaction { allCatch opt DB.create } Customer class, and then execute the DDL commands to create this table. In real life, using automatic table creation is usually problematic, but for quick demonstration it is very convenient. If tables in the database already exist, DB.create throw an exception, which we, thanks to allCatch opt , successfully ignore. val mapper = new ObjectMapper().withModule(DefaultScalaModule) def parseCustomerJson(json: String): Option[Customer] = allCatch opt mapper.readValue(json, classOf[Customer]) def readCustomer(req: HttpRequest[_], id: => String): Option[Customer] = parseCustomerJson(Body.string(req)) map (_.copy(id = id)) parseCustomerJson function parseCustomerJson JSON. Thanks to the use of allCatch opt exceptions that occurred during the parsing process will be intercepted and as a result we will get None . The second function, readCustomer , is directly related to the processing of an HTTP request — it reads the request body, turns it into an object of type Customer and sets the id field to the specified value.Customer object (or List[Customer] ) into an HTTP response body — is also not difficult: case class ResponseJson(o: Any) extends ComposeResponse( ContentType("application/json") ~> ResponseString(mapper.writeValueAsString(o))) ResponseJson , and the Unfiltered framework will take care of turning it into the correct HTTP response. def nextId = UUID.randomUUID().toString val service = cycle.Planify { case /* */ => /* , */ } /customer and /customer/[id] . Let's start with the second: case req@Path(Seg("customer" :: id :: Nil)) => req match { case GET(_) => transaction { DB.customer.lookup(id) cata(ResponseJson, NotFound) } case PUT(_) => transaction { readCustomer(req, id) ∘ DB.customer.update cata(_ => Ok, BadRequest) } case DELETE(_) => transaction { DB.customer.delete(id); NoContent } case _ => Pass } /customer/[id] and binds the passed identifier to the id variable (if the immutable variable can be called that at all). In the following lines, we specify the behavior depending on the type of request. Let us analyze, for example, the processing of the PUT method in steps:transaction { ... } : we indicate that while the handler's body is running, a transaction should be opened,readCustomer(req, id) : use a previously written method that reads the request body and returns Option[Customer]∘ : this symbol deserves special attention, in fact, it is synonymous with the map operation and allows you to apply any function to the contents of the Option, if the content isDB.customer.update : the same function that we want to use is an update of the entity in the database,cata(_ => Ok, BadRequest) : returns Ok if Option has a value or BadRequest if the request could not be parsed and we have None instead of the client./customer , we will need two auxiliary functions: val field: PartialFunction[String, Customer => TypedExpressionNode[_]] = { case "id" => _.id case "firstName" => _.firstName case "lastName" => _.lastName case "email" => _.email case "birthday" => _.birthday } val ordering: PartialFunction[String, TypedExpressionNode[_] => OrderByExpression] = { case "asc" => _.asc case "desc" => _.desc } order by part of the request and, most likely, having rummaged in the depths of Squeryl, they could have been written more simply, but this also suited me. Handler code itself: case req@Path(Seg("customer" :: Nil)) => req match { case POST(_) => transaction { readCustomer(req, nextId) ∘ DB.customer.insert ∘ ResponseJson cata(_ ~> Created, BadRequest) } case GET(_) & Params(params) => transaction { import Params._ val orderBy = (params.get("orderby") ∗ first orElse Some("id")) ∗ field.lift val order = (params.get("order") ∗ first orElse Some("asc")) ∗ ordering.lift val pageNum = params.get("pagenum") ∗ (first ~> int) val pageSize = params.get("pagesize") ∗ (first ~> int) val offset = ^(pageNum, pageSize)(_ * _) val query = from(DB.customer) { q => select(q) orderBy ^(orderBy, order)(_ andThen _ apply q).toList } val pagedQuery = ^(offset, pageSize)(query.page) getOrElse query ResponseJson(pagedQuery.toList) } case _ => Pass } ∗ and ^ . The first (neatly, do not confuse it with the usual asterisk * ) is a synonym for flatMap and differs from map in that the function used must also return Option . Thus, we can consistently perform several operations, each of which either returns a value successfully or returns None in case of an error. The second operator is a bit more complicated and allows you to perform some operation only if all the variables used are not equal to None . This allows us to perform sorting only if both the column and the direction are indicated, and split the result into pages only if both the page number and its size are specified. Http(8080).plan(service).run() scala.Either or scalaz.Validation for error scalaz.Validation , and someone might not like the use of Unicode operators. In addition, quite complicated operations can sometimes be hidden behind external simplicity, and in order to understand how everything is arranged “under the hood”, it is necessary to tighten the convolutions. Nevertheless, I hope that this article will encourage someone to take a closer look at Scala: even if you fail to apply this language in your work, you will surely learn something new.flatMap (aka ∗ ) is a monadic bind, and the operator ^ is directly related to applicative functors.Source: https://habr.com/ru/post/150199/
All Articles