Have a favorite custom Grails tag? - grails

I've been using tags in my projects. I was browsing the custom tags on grails.org to find some new tags for my library.
http://www.grails.org/Contribute+a+Tag
I was wondering if people in the StackOverflow community have a favorite custom tag that they would like to share.

I find the DecimalFormat class (and Grails's formatNumber tag by extension) a bit opaque for certain use cases, and I still haven't found a reasonable way to do some pretty basic formatting with it without some ugly pre-processing to generate an appropriate format string. I threw together a simple number formatting tag several months ago which essentially constructs a format string and does some minimal processing to the number itself.
It's not as generic or elegant as I'd like (it's all we needed at the time - it's super basic, but it still keeps some ugly processing out of GSPs), but it should be easy to read, and it's obvious where it could be trivially improved (i.e. making the scaling iterative instead of naive if-elseif slop, allowing the user to pass in custom scaling markers, allowing for a custom number validator as a parameter, etc.).
// Formats a number to 3 significant digits, appending appropriate scale marker
// (k, m, b, t, etc.). Defining var allows you to use a string representation
// of the formatted number anywhere you need it within the tag body, and
// provides the scale as well (in case highlighting or other special formatting
// based upon scale is desired).
def formatNumberScaled = {attrs, body -> // number, prefix, suffix, invalid, var
Double number
String numberString
String scale
try {
number = attrs.'number'.toDouble()
} catch (Exception e) {
number = Double.NaN
}
if (number.isNaN() || number.isInfinite()) {
numberString = scale = attrs.'invalid' ?: "N/A"
} else {
Boolean negative = number < 0d
number = negative ? -number : number
if (number < 1000d) {
scale = ''
} else if (number < 1000000d) {
scale = 'k'
number /= 1000d
} else if (number < 1000000000d) {
scale = 'm'
number /= 1000000d
} else if (number < 1000000000000d) {
scale = 'b'
number /= 1000000000d
} else if (number < 1000000000000000d) {
scale = 't'
number /= 1000000000000d
}
String format
if (number < 10d) {
format = '#.00'
} else if (number < 100d) {
format = '##.0'
} else {
format = '###'
}
format = "'${attrs.'prefix' ?: ''}'${format}'${scale} ${attrs.'suffix' ?: ''}'"
numberString = g.formatNumber('number': negative ? -number : number, 'format': format)
}
// Now, either print the number or output the tag body with
// the appropriate variables set
if (attrs.'var') {
out << body((attrs.'var'): numberString, 'scale': scale)
} else {
out << numberString
}
}

I have a "fmt:relDate" tag that gives you Twitter-like relative dates "3 days ago", "less than 30 seconds ago", etc., with the real time as a tooltip.
The current implementation is basically a gigantic chain of if/then statements with the boundaries that I like. A binary-search based algorithm would be better (in the sense of "more efficient"), and the current implementation has my personal preferences encoded into it, so I'm reluctant to share the tag.

I have a remote paginate tab, that helps me paginate results via ajax. Its an improvement over the default tab and takes in custom arguments.
Here's the code:
class CustomRemotePaginateTagLib {
static namespace = 'myTagLib'
/** * Creates next/previous links to support pagination for the current controller * * <g:paginate total="$ { Account.count() } " /> */
def remotePaginate = {attrs ->
def writer = out
if (attrs.total == null) throwTagError("Tag [remotePaginate] is missing required attribute [total]")
if (attrs.update == null) throwTagError("Tag [remotePaginate] is missing required attribute [update]")
def locale = RequestContextUtils.getLocale(request)
def total = attrs.total.toInteger()
def update = attrs.update
def action = (attrs.action ? attrs.action : (params.action ? params.action : "list"))
def controller = (attrs.controller ? attrs.controller : params.controller)
def offset = params.offset?.toInteger()
def max = params.max?.toInteger()
def maxsteps = (attrs.maxsteps ? attrs.maxsteps.toInteger() : 10)
if (!offset) offset = (attrs.offset ? attrs.offset.toInteger() : 0)
if (!max) max = (attrs.max ? attrs.max.toInteger() : 10)
def linkParams = [offset: offset - max, max: max]
if (params.sort) linkParams.sort = params.sort
if (params.order) linkParams.order = params.order
if (attrs.params) linkParams.putAll(attrs.params)
linkParams['action'] = action
linkParams['controller'] = controller
def linkTagAttrs = [url: "#"]
if (attrs.controller) { linkTagAttrs.controller = attrs.controller }
if (attrs.id != null) { linkTagAttrs.id = attrs.id }
// determine paging variables
def steps = maxsteps > 0
int currentstep = (offset / max) + 1
int firststep = 1
int laststep = Math.round(Math.ceil(total / max))
// display previous link when not on firststep
if (currentstep > firststep) {
linkTagAttrs.class = 'prevLink'
def prevOffset = linkParams.offset
def params = attrs.params ?: []
params.'offset' = prevOffset
linkTagAttrs.onclick = g.remoteFunction(update: update, action: linkParams.action, controller: linkParams.controller, params: params)
writer << link(linkTagAttrs.clone()) {
(attrs.prev ? attrs.prev : g.message(code: 'default.paginate.prev', default: 'Previous'))
}
}
// display steps when steps are enabled and laststep is not firststep
if (steps && laststep > firststep) {
linkTagAttrs.class = 'step'
// determine begin and endstep paging variables
int beginstep = currentstep - Math.round(maxsteps / 2) + (maxsteps % 2)
int endstep = currentstep + Math.round(maxsteps / 2) - 1
if (beginstep < firststep) {
beginstep = firststep
endstep = maxsteps
}
if (endstep > laststep) {
beginstep = laststep - maxsteps + 1
if (beginstep < firststep) {
beginstep = firststep
}
endstep = laststep
}
// display firststep link when beginstep is not firststep
if (beginstep > firststep) {
linkParams.offset = 0
def params = attrs.params ?: []
params['offset'] = linkParams.offset
linkTagAttrs.onclick = g.remoteFunction(update: update, action: linkParams.action, controller: linkParams.controller, params: params)
writer << link(linkTagAttrs.clone()) { firststep.toString() }
writer << '<span class="step">..</span>'
}
// display paginate steps
(beginstep..endstep).each {i ->
if (currentstep == i) {
writer << "<span class=\"currentStep\">${i}</span>"
} else {
linkParams.offset = (i - 1) * max
def params = attrs.params ?: []
params['offset'] = linkParams.offset
linkTagAttrs.onclick = g.remoteFunction(update: update, action: linkParams.action, controller: linkParams.controller, params: params)
writer << link(linkTagAttrs.clone()) { i.toString() }
}
}
// display laststep link when endstep is not laststep
if (endstep < laststep) {
writer << '<span class="step">..</span>'
linkParams.offset = (laststep - 1) * max
def params = attrs.params ?: []
params['offset'] = linkParams.offset
linkTagAttrs.onclick = g.remoteFunction(update: update, action: linkParams.action, controller: linkParams.controller, params: params)
writer << link(linkTagAttrs.clone()) { laststep.toString() }
}
}
// display next link when not on laststep
if (currentstep < laststep) {
linkTagAttrs.class = 'nextLink'
linkParams.offset = offset + max
def params = attrs.params ?: []
params['offset'] = linkParams.offset
linkTagAttrs.onclick = g.remoteFunction(update: update, action: linkParams.action, controller: linkParams.controller, params: params)
writer << link(linkTagAttrs.clone()) {
(attrs.next ? attrs.next : g.message(code: 'default.paginate.next', default: 'Next'))
}
}
}

Related

add contents of 1 hash into another

I have a parent hash which changes and I want to ensure that the child hashes take these changes but also retain keys that they had before and those should not be lost
These are the sample hashes that I have
one = {"endpoints"=>["get_route"], "features"=>["channel_selection"], "get_route"=>{"output"=>["total_length", "seca_length"], "options"=>["panama", "abcd"]}}
other = {"endpoints"=>["get_route"], "features"=>["channel_selection"], "get_route"=>{"output"=>["total_length", "seca_length"], "options"=>["panama", "suez", "kiel"]}}
I want the other hash to now look like
other = {"endpoints"=>["get_route"], "features"=>["channel_selection"], "get_route"=>{"output"=>["total_length", "seca_length"], "options"=>["panama", "abcd", suez", "kiel"]}}
I have tried the following code but it is not working
result = propogate_changes(one, other)
def propogate_changes(one, other)
one_keys = one.keys
other_keys = other.keys
combined = Hash.new
unique_keys = one_keys.concat(other_keys).uniq
unique_keys.each do |key|
if(one[key].is_a?(Array)) then
# if(other[key] == nil) then
# combined[key] = one[key]
# else
combined[key] = one[key].concat(other[key]).uniq
# end
else
combined[key] = add_allowance(one[key], other[key])
end
end
return combined
end
The above code fails when a key is present in one but missing in another
I also tried merge, deep_merge, reverse_merge but they all overwrite my other hash with one hash but none of them retain the original data.
Any advise on this will be appreciated
Try this custom merge logic.
def find_missing_items_in_arr(arr1, arr2)
arr1_size = arr1.size
arr2_size = arr2.size
if (arr1_size == arr2_size) && (arr1 & arr2).size == arr1_size
return [] # Same array
end
arr2 - arr1
end
def custom_merge(target_hash, source_hash)
# If you want to preserve frozen state of entries, please use `clone`
duped_target_hash = target_hash.dup
source_hash.each do |k, v|
unless duped_target_hash.key?(k)
duped_target_hash[k] = v
next
end
case v
when Array
missing_items_in_arr = find_missing_items_in_arr(duped_target_hash[k], v)
if missing_items_in_arr.size > 0
duped_target_hash[k] += missing_items_in_arr
end
when Hash
duped_target_hash[k] = custom_merge(duped_target_hash[k], v)
else
# Nothing to do here
end
end
duped_target_hash
end
Usage
one = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "abcd"]
}
}
other = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "suez", "kiel"]
}
}
rs_hash = custom_merge(other, one)
puts rs_hash
Note: Rails provides a deep_merge but this can be used outside Rails. I have tested and it returns your desired output. Also it handles more nested entries like
one = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "abcd"],
"custom_options" => {
"custom_output" => ["abc"],
"custom_input" => ["xyz" ]
}
}
}
other = {
"endpoints"=>["get_route"],
"features"=>["channel_selection"],
"get_route"=> {
"output"=> ["total_length", "seca_length"],
"options"=> ["panama", "suez", "kiel"],
"custom_options" => {
"custom_output" => ["abc", "def"]
}
}
}
Hope this helps.

Accessing the data in db via Domain class in Controller via grails testing

def index() {
//println params
String params_key =params['key']
def c = get_value(params_key)
def resp = ['key': params_key, 'value':c]
render resp as JSON
}
private static hash_conv(String value)
{
def matches = Eval.me(value.replace(':','').replace('{','[').replace('=>',':').replace('#','//').replace('}',']'))
return matches
}
private get_value(String key, default_value=null){
def app_preferences = get_preferences()
def result = app_preferences[key]
if (result == null) {
result = default_value
}
return result
}
private get_preferences(Boolean mobile_app = false){
def all_app_preference = AppPreferences.all
def mapped_value = [:]
def all_app = all_app_preference.each{obj -> mapped_value << get_preference(obj)}
return mapped_value
}
private static get_preference(AppPreferences preference){
def value_type = preference.value_type.toLowerCase()
def val = value_type == 'integer' ? preference.value.toBigInteger() : (value_type == 'boolean' ? (preference.value == 'true' || preference.value == '1' ? true : false):(value_type == 'array' ? preference.value.split(',') : (value_type == 'hash' ? hash_conv(preference.value) :(value_type == 'json' ? new JsonSlurper().parseText(preference.value) : preference.value.toString()))))
def map_value = [:]
map_value[preference.preference_key] = val
return map_value
}
Here I am using AppPreferences domain . It is returning some value on localhost.But when I test it in grails it is returning Null.
My test code as follows:
#TestFor(AppPreferencesController)
#Mock( [AppPreferences] )
//controller.session.SPRING_SECURITY_CONTEXT = [authentication:[principal:[id: 'blah']]]
class AppPreferencesControllerSpec extends Specification {
def setup() {
}
def cleanup() {
}
/*void "test something"() {
expect:"fix me"
true == false
}*/
void test_for_index()
{
when:
controller.session.SPRING_SECURITY_CONTEXT = [authentication:[principal:[id: 'blah']]]
params.key = 'role_to_feature_map'
controller.index()
then:
1 == 1
2 == 2
println response.text
}
}
the response.text is Returning as null.
In local host it is returning a hash value.
Perhaps:
void test_for_index() {
when:
controller.session.SPRING_SECURITY_CONTEXT = [authentication:[principal:[id: 'blah']]]
controller.params.key = 'role_to_feature_map' <-- the params attached to the controller
controller.index()
then:
1 == 1
2 == 2
println response.text
}
Tests typically run against a different database than development, or production. Your test (I assume it is a unit test) will need to mock your AppPreferences domain class. A unit test is just that, only the unit of code being tested. There isn't a Grails application surrounding the tested code.
I would probably add a given: section to the current test, and initialize the entities in the AppPreferences domain class there.
given:
def appPref1 = new AppPreferences("whatever you must set to pass constraints").save(flush:true)
controller.session.SPRING_SECURITY_CONTEXT = [authentication:[principal:[id: 'blah']]]
controller.params.key = 'role_to_feature_map'
when:
controller.index()
then:
1 == 1
2 == 2
println response.text
Personal opinion: The line of code below is unreadable. It would never pass code review where I work. Try a switch statement.
def val = value_type == 'integer' ? preference.value.toBigInteger() : (value_type == 'boolean' ? (preference.value == 'true' || preference.value == '1' ? true : false):(value_type == 'array' ? preference.value.split(',') : (value_type == 'hash' ? hash_conv(preference.value) :(value_type == 'json' ? new JsonSlurper().parseText(preference.value) : preference.value.toString()))))

How to push key and value to an empty hash with a loop in Ruby on rails

I want to use Bing search results for my webpage. To use their json data I found this solution:
new_bing_results = bing_results[0][:Web]
result = { }
result[:title] = new_bing_results[0][:Title]
result[:description] = new_bing_results[0][:Description]
result[:url] = new_bing_results[0][:Url]
result[:display_url] = new_bing_results[0][:DisplayUrl]
result[:title1] = new_bing_results [1][:Title]
result[:description1] = new_bing_results [1][:Description]
result[:url1] = new_bing_results [1][:Url]
result[:display_url1] = new_bing_results [1][:DisplayUrl]
result[:title2] = new_bing_results [2][:Title]
result[:description2] = new_bing_results [2][:Description]
result[:url2] = new_bing_results [2][:Url]
result[:display_url2] = new_bing_results [2][:DisplayUrl]
....
result
How can I create a loop that is doing the same thing 50 times without having to repeat the same code.
I tried this but only get errors:
new_bing_results = bing_results[0][:Web]
$i = 0
$num = 50
result2 = {}
while $i < $num do
result[:title$i] = new_bing_results[$i][:Title]
......
end
result
The problem is that I do not find a solution for adding my $i number to the key result[:title] as in the value new_bing_results[$i][:Title]
This should do the trick
result = {}
50.times do |i|
result["title#{i}".to_sym] = new_bing_results[i][:Title]
result["description#{i}".to_sym] = new_bing_results[i][:Description]
result["url#{i}".to_sym] = new_bing_results[i][:Url]
result["display_url#{i}".to_sym] = new_bing_results[i][:DisplayUrl]
end
50.times will run from 0 to 49 and you can use interpolation to avoid the repetition.
You can use .to_sym method. For example:
new_bing_results = [{Title: "Title"}]
result = {}
result["title#{i}".to_sym] = new_bing_results[i][:Title]
result
=> {:title0=>"Title"}
You can use string interpolation and then the to_sym method.
result = {}
50.times do |n|
result["title#{n}".to_sym] = new_bing_results[n][:Title]
end

String in alphabetical order Ruby

I am new to Ruby. I have written a solution in Java
public boolean checkOrder(String input) {
boolean x = false;
for (int i = 0; i < input.length() - 1; i++) {
if (input.charAt(i) < input.charAt(i + 1) || input.charAt(i) == input.charAt(i + 1)) {
x = true;
} else {
return false;
}
}
return x;
}
I want to do the same in ruby how can I convert the same into ruby. Thanks.
def checkOrder(input)
input.chars.sort == input.chars.to_a
end
Variation of Sam's answer, in case you ever want this as a String method:
class String
def sorted?
self.chars.sort == self.chars.to_a
end
end
'abc'.sorted?
'acb'.sorted?
As requested:
def checkOrder(input)
x = false
(input.length-1).times do |i|
if input[i] < input[i+1] or input[i] == input[i+1]
x = true
else
return false
end
end
x
end

Grails Pagination: how to use pagination to restrict the number of rows

I have a query which gives a result of about 100 rows. Here is the code for the controllers
def bandsOneTrack = {
def bands = Band.executeQuery("Select b from Band as b where size(b.tracks) > (select count(t.id) from Band as ba join ba.tracks as t where ba.id = b.id and t.ourContract is not null) and (select count(t.id) from Band as ba join ba.tracks as t where ba.id = b.id and t.ourContract is not null) >= 1" )
render(view: 'bands_list' , model: [ bands : bands ])
}
It gives me the result set of about 100 rows but they are appearing inside a same page.
Now I want to use pagination so that I can restrict it to only 20 rows per page.
What should I do, and also how to use pagination for this.
On your pagination tag check the total parameter. That should be the total number of records. In your case 100 so that the pagination tag can calculate the total number of pages.
Something like this here:
<g:paginate controller="Book" action="list" total="${bookInstanceTotal}" />
You might need to execute your query once to find the total number of records.
def list() {
params.max = Math.min(params.max ? params.int('max') : 10, 100)
def ls = Book.executeQuery("from Book a",[max: params.max, offset: params.offset])
def totalCount = Book.executeQuery("from Book a").size()
[bookInstanceList: ls, bookInstanceTotal: totalCount]
}
If I remember correctly, you add max and offset properties to the model you're passing to your view, it should automatically wire in pagination. At that point, you should be able to use the paginate tag to iterate through your result sets. See the docs here:
http://grails.org/doc/latest/ref/Tags/paginate.html
Grails Criteria Query and pagination params
params.max = params?.max as Integer ?: 10
params.offset = params?.offset as Integer ?: 0
params.sort = params?.sort ?: "email"
params.order = params?.order ?: "asc"
params.filter = params?.filter ?: ""
params.packet = params?.packet ?: ""
def members = Member.createCriteria().list(params)
{
or
{
if(params.filter != ""){
ilike("firstName", "%" + params.filter + "%")
ilike("lastName", "%" + params.filter + "%")
ilike("email", "%" + params.filter + "%")
try {
params.filter as Long
eq("citizenId" , params.filter.toLong())
}catch (e) {
}
ilike("mobile", "%" + params.filter + "%")
}
}
}
def dataMembers = [:]
dataMembers.data = members
dataMembers.totalRecord = members.totalCount
render dataMembers as JSON
Output
{
"data": [
{
"id":1,
"firstName":name
},
{
"id":2,
"firstName":name
}
],
"totalRecord":5
}

Resources