# Routes # This file defines all application routes (Higher priority routes first) # ~~~~ # Get news GET / news controllers.NewsController.news (tag: String? = "", PubDate: Int? = (System.currentTimeMillis () / 1000) .toInt) # Parse news GET / parse controllers.NewsController.parseRSS # Get tags GET / tags controllers.TagsController.tags # Map static resources from the URL path GET / assets / * file controllers.Assets.at (path = "/ public", file) # Home page GET / controllers.Application.index
Marking in the fields:
Separately, I want to highlight the fact that in addition to the very possibility of setting default values for the arguments passed to the specified method, you can specify expressions. For example - getting the current timestamp.
By the way, the routing in Play is quite functional, up to regexp when processing a request.
controllers
, use the Treyt Controller
and are objects whose methods accept and respond to user requests in accordance with the routing. package controllers import play.api.mvc._ /** * playRSS entry point */ object Application extends Controller { /** * Main page. So it begins... * @return */ def index = Action { Ok(views.html.index()) } }
Ok
returns the play.api.mvc.SimpleResult
instance, which contains the headers and body of the page. The response from the server will be equal, as they could have guessed especially attentive, 200 OK
.but
If a full-fledged controller for the entire application fits in 20 lines, then it is very likely that you write in Ruby.
NewsController
deals with NewsController
package controllers import play.api.mvc._ import scala.concurrent._ import models.News import play.api.libs.concurrent.Execution.Implicits.defaultContext import models.parsers.Parser import com.mongodb.casbah.Imports._ object NewsController extends Controller { /** * Get news JSON * @param tag optional tag filter * @param pubDate optional pubDate filter for loading news before this UNIX timestamp * @return */ def news(tag: String, pubDate: Int) = Action.async { val futureNews = Future { try { News asJson News.allNews(tag, pubDate) } catch { case e: MongoException => throw e } } futureNews.map { news => Ok(news).as("application/json") }.recover { case e: MongoException => InternalServerError("{error: 'DB Error: " + e.getMessage + "'}").as("application/json") } } /** * Start new RSS parsing and return first N news * @return */ def parseRSS = Action.async { val futureParse = scala.concurrent.Future { try { Parser.downloadItems(News.addNews(_)) News asJson News.allNews() } catch { case e: Exception => throw e } } futureParse.map(newsJson => Ok(newsJson).as("application/json")).recover { case e: MongoException => InternalServerError("{error: 'DB Error: " + e.getMessage + "'}").as("application/json") case e: Exception => InternalServerError("{error: 'Parse Error: " + e.getMessage + "'}").as("application/json") } } }
Future
. Async
. Here it becomes interesting for the first time.Future
comes to the rescue, which allows you to perform an operation asynchronously without blocking the main thread. To perform Future
uses a separate context, so you should not worry about threads.Future[SimpleResult]
, but the Future[SimpleResult]
, the async
method of the ActionBuilder
trait (which uses the Action
object) is used <!DOCTYPE html> <html> <head> <title> playRSS </title> <link rel="shortcut icon" href='@routes.Assets.at("images/favicon.png")' type="image/png"> <link rel="stylesheet" href="http://netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css"/> <link rel="stylesheet" href='@routes.Assets.at("stylesheets/main.css")'> @helper.requireJs(core = routes.Assets.at("javascripts/require.js").url, module = routes.Assets.at("javascripts/main").url) </head> <body> <div class="container" id="container" ng-controller="MainCtrl"> <a href="/"><h1>playRSS</h1></a> @control() <div class="row"> <div class="col-lg-12"> @news() </div> </div> </div> </body> </html>
@helper
connects the requireJS supplied by the framework itself and indicates the path to main.js, where the frontend is initialized. @news()
and @control()
are the news.scala.html and control.scala.html templates, respectively. Perform the function and display the result inside the current template. Nicely.And also
you can work with loops, if / else, etc. There is detailed documentation
libraryDependencies
inside the build.sbt:"org.mongodb" %% "casbah" % "2.6.3"
import com.mongodb.casbah.Imports._
package models import com.mongodb.casbah.Imports._ import play.api.Play /** * Simple object for DB connection */ object Database { private val db = MongoClient( Play.current.configuration.getString("mongo.host").get, Play.current.configuration.getInt("mongo.port").get). getDB(Play.current.configuration.getString("mongo.db").get) /** * Get collection by its name * @param collectionName * @return */ def collection(collectionName:String) = db(collectionName) /** * Clear collection by its name * @param collectionName * @return */ def clearCollection(collectionName:String) = db(collectionName).remove(MongoDBObject()) }
Marking in the fields:
In Scala, objects are in fact singletons. If you turn on the bore mode, an anonymous class is created and instantiated with static methods (in the Java / JVM view). So our connection will rise during the creation of the object and will be available throughout the application's working cycle.
/** * Default news container * @param id MongoID * @param title * @param link * @param content * @param tags Sequence of tags. Since categories could be joined into one * @param pubDate */ case class News(val id: String = "0", val title: String, val link: String, val content: String, val tags: Seq[String], val pubDate: Long) /** * News object allows to operate with news in database. Companion object for News class */ object News { .... /** * Method to add news to database * @param news filled News object * @return */ def addNews(news: News) = { val toInsert = MongoDBObject("title" -> news.title, "content" -> news.content, "link" -> news.link, "tags" -> news.tags, "pubDate" -> news.pubDate) try { col.insert(toInsert) } catch { case e: Exception => } } .... /** * Get news from DB * @param filter filter for find() method * @param sort object for sorting. by default sorts by pubDate * @param limit limit for news select. by default equals to newsLimit * @return */ def getNews(filter: MongoDBObject, sort: MongoDBObject = MongoDBObject("pubDate" -> -1), limit: Int = newsLimit): Array[News] = { try { col.find(filter). sort(sort). limit(limit). map((o: DBObject) => { new News( id = o.as[ObjectId]("_id").toString, title = o.as[String]("title"), link = o.as[String]("link"), content = o.as[String]("content"), tags = o.as[MongoDBList]("tags").map(_.toString), pubDate = o.as[Long]("pubDate")) }).toArray } catch { case e: MongoException => throw e } } }
/** * News tag container * @param name * @param total */ case class Tags(name: String, total: Int) /** * Tags object allows to operate with tags in DB */ object Tags { /** * News collection contains all tag info */ private val col: MongoCollection = Database.collection("news") /** * Get all tags as [{name: "", total: 0}] array of objects * @return */ def allTags: Array[Tags] = { val group = MongoDBObject("$group" -> MongoDBObject( "_id" -> "$tags", "total" -> MongoDBObject("$sum" -> 1) )) val sort = MongoDBObject("$sort" -> MongoDBObject("total"-> -1)) try { col.aggregate(group,sort).results.map((o: DBObject) => { val name = o.as[MongoDBList]("_id").toSeq.mkString(", ") val total = o.as[Int]("total") Tags(name, total) }).toArray } catch { case e: MongoException => throw e } } }
.aggregate
allows .aggregate
to work wonders without mapReduce. And the principle of work in Scala is the same as from the console. A kind of pipeline-way, only separated by commas. Grouped by tags, summed up the same in total and sorted the whole thing. Fine.Never gonna give you up
Never gonna let you down
package models.parsers import scala.xml._ import models.News import java.util.Locale import java.text.{SimpleDateFormat, ParseException} import java.text._ import play.api.Play import collection.JavaConversions._ /** * Simple XML parser */ object Parser { /** * RSS urls from application.conf */ val urls = try { Play.current.configuration.getStringList("rss.urls").map(_.toList).getOrElse(List()) } catch { case e: Throwable => List() } /** * Download and parse XML, fill News object and pass it to callback * @param cb */ def downloadItems(cb: (News) => Unit) = { urls.foreach { (url: String) => try { parseItem(XML.load(url)).foreach(cb(_)) } catch { case e: Exception => throw e } } } /** * Parse standart RSS time * @param s * @return */ def parseDateTime(s: String): Long = { try { new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH).parse(s).getTime / 1000 } catch { case e: ParseException => 0 } } /** * For all items in RSS parse its content and return list of News objects * @param xml * @return */ def parseItem(xml: Elem): List[News] = (xml \\ "item").map(buildNews(_)).toList /** * Fill and return News object * @param node * @return */ def buildNews(node: Node) = new News( title = (node \\ "title").text, link = (node \\ "link").text, content = (node \\ "description").text, pubDate = parseDateTime((node \\ "pubDate").text), tags = Seq((node \\ "category").text)) }
I agree
At first, methods with the name of the form \ or \\ are driven into a stupor. However, this makes some sense when you remember BigInteger from Java.
play.api.libs.json
package play.api.libs.json
. Does anyone know the JsonSerializable
interface from PHP 5.4? So in Play is still easier! case class News(val id: String = "0", val title: String, val link: String, val content: String, val tags: Seq[String], val pubDate: Long) /** * News object allows to operate with news in database. Companion object for News class */ object News { /** * Play Magic * @return */ implicit def newsWrites = Json.writes[News] /** * Converts array of news to json * @param src Array of News instances * @return JSON string */ def asJson(src: Array[News]) = { Json.stringify(Json.toJson(src)) } }
someObjectWrites
-line method someObjectWrites
in simple cases of serialization removes all the questions. Implicit conversions in Scala are powerful and convenient tools used in practice. define ["angular","ngInfinite"],(angular,infiniteScroll) -> newsModule = angular.module("News", ['infinite-scroll']) newsModule.factory 'broadcastService', ["$rootScope", ($rootScope) -> broadcastService = message: {}, broadcast: (sub, msg)-> if typeof msg == "number" then msg = {} this.message[sub] = angular.copy msg $rootScope.$broadcast(sub) ] newsModule
define ["app/NewsModule"], (newsModule)-> newsModule.controller "PanelCtrl", ["$scope", "$http", "broadcastService", ($scope, $http, broadcastService)-> $scope.loadByTag = (tag) -> if tag.active tag.active = false broadcastService.broadcast("loadAll",0) else broadcastService.broadcast("loadByTag",tag.name) ]
define ["app/NewsModule","url"], (newsModule,urlParser)-> newsModule.controller "NewsCtrl", ["$scope", "$http", "broadcastService", ($scope, $http, broadcastService)-> #recieving message $scope.$on "loadAll", ()-> $scope.after = 0 $scope.tag = false $scope.busy = false $scope.loadByTag() ]
In angular
Services are singletons. Therefore, we can drive messages back and forth, without fruiting instances.
Source: https://habr.com/ru/post/197930/
All Articles