I am using linkedin's version of dustjs. I want to create a custom helper that updates the context to be more specific for rendering the body of the context.
How can I do that?
Say the context is currently:
{
title: "News",
items: [],
nav: {
logo: 'logo.png',
items: []
},
body: {
items: []
}
}
If I just use chunk.render(bodies.block,context) in my helper, it would be at context level. But I'd like it to render with a scope at the lower 'nav' level:
{
logo: 'logo.png',
items: []
}
Somewhat related, is it possible to set the scope/context of the chunk.render call to an object that is higher up?
Say I had been using the array looping helper {#items}{/items} and context inside the loop body is set to each array element, could I write a helper that sets the context for its own body to be higher up and in completely different branch of the object? Like 'body' one?
I know about using {#items:body}, but I am interested in changing scope to different parts of the object based on a key stored in each array element.
To render using a selected subset of the context, you can use context.rebase to generate a new context.
This example uses a context helper for succinctness, but you could use a regular Dust helper as well.
Template
{#subcontext key="nav"}
{#items}{.}{/items}
{/subcontext}
Context
{
"subcontext": function(chunk, context, bodies, params) {
var subcontext = context.get(params.key);
return chunk.render(bodies.block, context.rebase(subcontext));
},
"items": ["Oh", "No"],
"nav": {
"items": ["Yay", "Yay"]
}
}
In response to your second question:
Yes, you can traverse backwards up the context stack. I'll leave this part as an exercise for you, but here's the guidance that you should need:
Your context object in your helper has a property called stack. stack has a property called tail, which is itself an instance of Stack. There's one nested tail for each level of your context, so depending on how you want to do the backwards lookup, you could move anywhere on the stack and then call context.rebase with tail's head to set a different context.
Related
Say I have the following struct:
struct Event: Codable {
var id: Int
.... // many non nested fields
}
In my application a user is allowed to create a list of events. Once the user is finished I'd like to pass that list to my server via a POST request.
In order to do so I need to create a valid JSON object that looks like this.
This is a list of Event with a leading key of "events".
{ "events": [{"id": 1, ... more of the non nested fields ... },{ ... }]}
How can I setup my Event object in such a way that JSONEncoder.encode(events) will return the expected JSON above? I would really like to avoid having a CodingKey for each field because they encode,decode just fine expect in this circumstance. I'd also like to avoid nesting this Event object inside another struct called Events to get the desired result.
You can just encode a dictionary which associates your events array to the key "events"
JSONEncoder.encode(["events": events])
I am using Grails 3.3.9 and json views 1.2.10.
In another test I have a url mapping like this
"/api/test"(resources:'test')
And I have an empty views/test folder, and I don't have a Test domain object.
But I do have a Device domain entity, and have a views/device/_device.gson template in that directory to help render a device object.
The TestController's show action looks like this
def show (Device device) {
if(device == null) {
render status:404
} else {
respond device}
}
This looks for a Device in domain model and populates into Shows parameter list, and I respond with the device.
There is however no show.gson in the views/test directory (it's empty).
However when you hit the url in the browser "http://localhost:8080/api/test/1", grails renders a response using the "views/device/_device.gson" template.
This is very confusing as it's very hard to figure exactly view view respond is actually using (unless you force it using a view:'xxx' map variable.
How/why is the TestController/show action response return, picking up "views/device/_device.gson" template ?
(Posted answer on behalf of the question author).
Step 1) if you create a new controller (I used grails create-controller <controller>, and then edit the generated controller to extend from RestfulController<domain type> (but don't override any methods). Then Setup the URL mapping for your controller, in my example this was
"/api/org"(resources:'OrgRoleInstance')
for which I have the corresponding domain class 'OrgRoleInstance' in my model.
The create-controller action also generates an empty view package 'views/orgRoleInstance' (no gson files are generated)
a) if you do the run app now and open the url
http://localhost:8080/api/org
then your still get a rendered response (like this) !
[{"id":1,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Service_Provider"},"name":"Vodafone","mags":[{"id":2}]},{"id":2,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Maintainer"},"name":"Cisco","mags":[{"id":1}]},{"id":3,"sites":[],"domains":[],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Supplier"},"name":"Cisco","mags":[]},{"id":4,"sites":[{"id":1},{"id":2}],"domains":[{"id":1}],"role":{"enumType":"com.softwood.domain.OrgRoleInstance$OrgRoleType","name":"Customer"},"name":"Acme","mags":[]}]
Ipso facto there is some 'default framework logic' that tries to render the default implementation of the 'index' (and 'show' etc) action inherited from RestfulController'. This is done even with with no pre-existing gson - no warnings are provided.
Step 2: next stop the app (it's still caching the previous 'default' behaviour).
Now go to the empty view directory and create an 'index.gson' like this with no model
json ([1,2,3])
Then restart the app in your IDE. Now when it starts, it detects that an index.gson exists, and when you call the url 'http://localhost:8080/api/org' what you get is the rendered json for the static list [1,2,3]
step 3: gson views are using static compilation under the covers and very fiddly to get exactly right. However in the case of default RestfulController index action , a list of OrgRoleInstances is selected from the domain model and passed as a lit to the view. if you want the databinding to the view to work, then if your domain object is type T, the you get a List<T> returned.
The internal default in the code base, is if you get List<T> returned to respond then, the data model in the view is assumed to be List<T> <T>List i.e. in my example this would be
List<OrgRoleInstance> orgRoleInstanceList in the gson view model. Now with a revised index.gson (you can edit this without stop/start of server in dev mode)
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
}
json {
recordCount orgRoleInstanceList.size()
}
Now when you Get the URL you get a json response with the size of the list:
{"recordCount":4}
Note that if you add an extra variable to your model like this:
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
json {
recordCount orgRoleInstanceList.size()
size orgRoleInstanceCount
}
and modify the index.gson to print the new Integer variable - then it is not data bound from the default respond action.
Browser response looks like this:
{"recordCount":4,"size":null}
Option 4: - look at invoking a 'template' class. Having set up a model that's expecting a List<T>, the runTime type built via data binding is an instance of "grails.orm.PagedResultList". This is an Iterable type.
This is very confusing to read in the documentation - it's really not clear however if you create a file called _<file>.gson. Then this is treated is a template gson file.
This can have its own model/json, but because all this is statically compiled you have to get the types exactly matching and where necessary declare the 'name' of the model variable when calling the template (either via g.render or implicit tmpl.<file>.
When you call the template from the parent view you can pass either an the List<T> iterable model type or iterate in the parent view and pass each <T> to the template. You have to ensure that the model type you pass to the tmpl is declared as the same type in the tmpl model.
e.g. assuming a List<T> in the parent view but a <T> in the template, you need to invoke the tmpl for each item in the list. e.g.
if you have a tmpl like this "_orgRoleInstance.gson".
If you have the parent view like this (note single <T> declared and model variable named 'org'
import com.softwood.domain.OrgRoleInstance
model {
OrgRoleInstance org
}
json {
id org.id
name org.name
}
Then the parent view "index.gson" you need something like this that invokes the tmpl as many times as you have entries in the list, but I've had to tell the framework that the tmpl model variable name is 'org' by passing a map. This will render as you expect.
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
orgRoleInstanceList.each { OrgRoleInstance org ->
json tmpl.orgRoleInstance(org:org)
}
If you declare the tmpl variable to be "def org", it still works but as this is statically typed, the variable is passed as static instance of Object (run time type is correct, but you cant just access the properties as the static type is Object), and it gets hard sorting out the casts required to access the properties.
If you want the parent view to pass the model List<T> variable to the tmpl, you can = but you have to ensure that the model variable in the tmpl is List<T> else the data binding doesn't work.
Now in the template you can invoke the json and iterate over the List
e.g. with a modified tmpl like this
import com.softwood.domain.OrgRoleInstance
model {
//OrgRoleInstance org
List<OrgRoleInstance> orgs
}
json {
id orgs.id
name orgs.name
}
and a revised parent view like this to invoke the tmpl:
import com.softwood.domain.OrgRoleInstance
model {
List<OrgRoleInstance> orgRoleInstanceList
Integer orgRoleInstanceCount
}
/*
orgRoleInstanceList.each { OrgRoleInstance org ->
json tmpl.orgRoleInstance(orgs:org)
}*/
//alternate approach
json tmpl.orgRoleInstance (orgs:orgRoleInstanceList)
What get rendered is this:
{"id":[1,2,3,4],"name":["Vodafone","Cisco","Cisco","Acme"]}
You will note that there is a single json clause so it iterates all the ids first and the all the names second.
If this is not what you want you have to iterate over the list to do each in turn
i.e. a modified tmpl like this will iterate over each entry in turn
import com.softwood.domain.OrgRoleInstance
model {
//OrgRoleInstance org
List<OrgRoleInstance> orgs
}
json (orgs) {OrgRoleInstance org ->
id org.id
name org.name
}
and produce this in the browser
[{"id":1,"name":"Vodafone"},{"id":2,"name":"Cisco"},{"id":3,"name":"Cisco"},{"id":4,"name":"Acme"}]
For anyone else who is confused by Grails Views, I hope this shows how this works between url mappings (you can use the Gradle "urlMappingsReport" to see these) and shows what urls are mapped to what actions. You then need gson views created with the same name as these actions, and configure your gson views and any tmpl you create to be aware of the implicit behaviour that the JsonViews applies when processing your code.
When you generate grails views, grails looks at your relationships and generates the right html for your form data to be automatically binded to the back end domain. For one to one associations grails creates a drop down list.
However, you might not want to present that property as a drop down list but something more custom (for example a text field with autocomplete). As soon as you do that the value that comes to the controller from that field, comes in as a String and you have to first:
Clear errors
Perform a findBy based on a given param and assign it to the property of the domain
I really want to avoid doing findBys in the controller as much as possible because it seems like I am doing logic/things that should not go there. The controller should delegate to the Service layer. It is not clear to me from the grails documentation how would I do that by using bindData which seems to work really well with String, date, Integer properties etc.. but I do not see how bindData is used for properties that are other domains.
I also really want to avoid passing the params object to the Service layer as it seems less reusable (or maybe not, correct me if I am wrong). I guess that I do not like how it looks semantically. I would prefer the first over the second:
#Transactional
class WithdrawService {
def addWithdraw(Withdraw withdraw) {
//perform business logic here
}
def createWithdraw(Map params){
//perform business logic here
}
}
Let's take the following example:
class Withdraw {
Person person
Date withdrawDate
}
and the parent lookup table
class Person {
String name
String lastName
static constraints = {
}
#Override
public String toString() {
return "$name $lastName"
}
}
In order for the bind to happen automatically without any extra work grails passes in the following request params to automatically bind the one to one:
person.id
a person map with the id.
[person.id:2, person:[id:2], withdrawDate:date.struct, withdrawDate_month:11, create:Create, withdrawDate_year:2015, withdrawDate_day:10, action:save, format:null, controller:withdraw]
What is the best way to go about this?
Pass two hidden fields that look exactly like this: person.id:2, person:[id:2] that get populated as a result of the Ajax call that populates the autocomplete?
In the controller do a Person.findBySomeKnownProperty(params.someKnownValue)
Or any other approach?
I have an application in production use, when a user goes to a proposal index page it takes a very long time and sometimes times out. I've narrowed down the issue to be a SQL statement that is selecting all the Proposal objects. The problem is the Proposal object has many images (byte[]) stored in memory that aren't being used in the index page. These images are huge thus causing the problem.
What are the different ways I can optimize this query in Grails to remove the attributes I don't need on that page or only add the attributes I have in my GSP?
Here is the controller code (scaffolded):
def index(Integer max) {
params.max = Math.min(max ?: 10, 100)
respond Proposal.list(params), model:[proposalInstanceCount: Proposal.count()]
}
Thanks!
I wrote a plugin for this scenario, see http://grails.org/plugin/lazylob
Another option is to refactor the domain class into two. Put the image data in the new domain class:
class ProposalImage {
byte[] image
}
and reference it from the Proposal class:
class Proposal {
ProposalImage proposalImage
// other properties
}
Since references are lazy by default, GORM will only load the image data from the new domain class if you specifically refer to it.
EDIT (updated with subselect approaches):
You can also use custom queries to select a subset of the properties. Probably the most convenient would be using "select new map" in an HQL query:
def results = Proposal.executeQuery(
'select new map(prop1 as prop1, prop2 as prop2) from Proposal',
[max:params.max as int, params.offset as int])
This is convenient because each element in the results list is a map keyed with the property names, so it will look the same as a real Proposal instance in the GSP.
Another option if you prefer criteria queries is to use projections to limit which properties are returned:
def results = Proposal.withCriteria {
projections {
property 'prop1'
property 'prop2'
}
maxResults(params.max as int)
firstResult(params.offset as int)
}
Each item in the results is an Object[] array and each element in the array is the actual type of the property. You would need to manually build a list of maps, e.g.
results = results.collect { result -> [prop1: result[0], prop2: result[1]] }
Additionally, you can automate this by finding all of the names of the persistent properties and excluding the one (or ones) you want to avoid: def propNames = grailsApplication.getDomainClass(Proposal.name).persistentProperties*.name
Here is the scenario, I need to load up a view model object from a several domain objects that are being returned from multiple web service service calls. The code that transforms the domain model objects into the digestible view model object is a bit of fairly complex code. The three places that I have thought about putting it are:
Internal methods inside of the controller to load up an instance view model.
Static get method or property on the view model class itself that returns a loaded instance of view model.
An altogether separate builder or helper class that has a Static get method, property, or overloaded constructor that returns the loaded instance of view model.
To be clear, I don't want to use AutoMapper, or tools like it. From a best practices standpoint I'd like to know where this logic should go, and why.
EDIT
So here is what I have so far, this does give me "skinny" controller logic and separation of concerns. How can I make it better though?
// ** Controller **
public ActionResult Default()
{
var viewModel = MyViewModelBuilder.BuildViewModel(MarketType.Spot);
return View(SpotViewUrl, viewModel);
}
// ** Builder **
// Lives in MVC project under ViewModelBuilders folder
public class MyViewModelBuilder
{
public static ChartsModel BuildViewModel(MarketType rateMarket)
{
var result = new ChartsModel
{
RateMarket = rateMarket,
DateRange = new DateRange()
};
LoadGroupedRateLists(result, rateMarket);
LoadCoorespondingRates(result);
return result;
}
private static void LoadGroupedRateLists(ChartsModel model, RateMarket rateMarket)
{
var rateHistSvc = new RateHistoryService(RatesPrincipal.Current.Session.Token);
var serviceResult = (rateMarket == RateMarket.Spot)
? rateHistSvc.GetSpotNationalRateHistory()
: rateHistSvc.GetContractNationalRateHistory();
// Break lists apart by category, and re-sort and trim.
model.Cat1Rates = CategorizeTrimAndSort("cat1", false, serviceResult);
model.Cat2Rates = CategorizeTrimAndSort("cat2", true, serviceResult);
model.Cat3Rates = CategorizeTrimAndSort("cat3", false, serviceResult);
model.Cat4Rates = CategorizeTrimAndSort("cat4", true, serviceResult);
model.Cat5Rates = CategorizeTrimAndSort("cat5", false, serviceResult);
model.Cat6Rates = CategorizeTrimAndSort("cat6", true, serviceResult);
// Get Date range from results.
var sortedRateMonths = serviceResultNational
.Select(rate => rate.YearMonth)
.OrderBy(ym => ym.Year)
.ThenBy(ym => ym.Month);
model.DateRange.Start = sortedRateMonths.First();
model.DateRange.End = sortedRateMonths.Last();
}
...
}
1 or 3, not 2. Provided that if you do #3, you do not actually let the static method do the web service calls, just let it do the mapping. Domain object(s) in, viewmodel(s) out. Prefer extension method to overloaded constructor, if the object doesn't need to track state, there's no benefit to making it non-static.
Why?
As soon as you put a logic method on the model, it ceases to be a POCO. Best practice is to treat viewmodels like boring data buckets as much as possible. Some people also try to do the mapping in a viewmodel constructor which is not a good idea once you get into any kind of complexity in the mapping.
If you only need to do the mapping in one controller, you can put it in a sub-routine. Keep in mind if you want to test the sub in isolation and keep it internal, your project will have to have InternalsVisibleTo your test project.
Update
After looking at your code, I am kind of inclined to agree with #C Sharper that this belongs neither in the controller, viewmodel, nor helper class / method. Composing this ChartsModel is very interesting code, and contains a lot of business logic. It really should be in a separate layer. Your controller should call down into that layer and delegate all of this interesting and important code to another abstraction. That abstraction should then return a domain object, as #C Sharper said. Whether you use that domain object as your viewmodel, or DTO it into a different viewmodel, is up to you. Here's how something like that might look like:
public class MyController : Controller
{
private readonly IComposeChartData _chartDataComposer;
public MyController(IComposeChartData chartDataComposer)
{
_chartDataComposer = chartDataComposer;
}
public ActionResult Default()
{
var chartComposition = new ChartCompositionSettings
{
MarketType = MarketType.Spot,
Token = RatesPrincipal.Current.Session.Token,
};
var chartData = _chartDataComposer.ComposeChartData(chartComposition);
var chartModel = Mapper.Map<ChartsModel>(chartData);
return View(SpotViewUrl, chartModel);
}
}
That is a nice lean controller body. The abstraction might then look something like this:
public class ChartDataComposer : IComposeChartData
{
public ChartData ComposeChartData(ChartCompositionSettings settings)
{
// all of the interesting code goes here
}
}
This way, your viewmodel does not need to move out into a separate layer, but you do need to create a similar object (ChartData) in that layer. The interface decouples your controller from the data it needs, and the object it returns is cohesive with your presentation data (viewmodel).
I guess I don't really see this code as business logic, but more as
presentation logic. Why do you see it as business logic?
Think of your RateHistoryService class as a supplier. You receive raw materials from it, and transform those raw materials into something different, creating value in the process. This is what businesses do.
In this case, the chart visualizations are the value you are providing. Otherwise, your customers would have to sift through the raw data, trim it, categorize it, sort it, group it, etc., themselves before they could create similar charts.
I probably should have explained this earlier, but the service calls
are to our own business layer already, and return domain layer
business objects. It seems weird to me to have more than one business
layer.
Your business layer can have its own internal layering. In this case, you can create a RateChartingService that uses the RateHistoryService to return a RateChartingResult busines object. Then map that to a ChartsModel (or like I said before, use it directly as your viewmodel).
I would say don't do it in your Controller. Controllers should be as "skinny" as possible. I would do it like this.
Your "Data Layer" would assign the Domain objects its properties and values.
Then your subsequent Layer call it "Business Layer" will transfer your Domain Object into your ViewModel. And you will simply pass the view model to your controller without having the Controller handle any of that logic.
Separation is very important. Domain Objects should stay out of the controller, and the controllers should only care about View Models.