Grails Search Map - grails

I am a Java programmer trying to write some Groovy code.
I want to iterate through the map called loginAttributevalues, and for each key (e.g. username) find the cross-reference attribute value and print this:
john : display_name
However, I am having trouble getting my Java brain around the Groovy syntax. Can someone point me in the right direction? Thanks.
loginAttributeValues = [username: 'john', email: 'john#smith.com']
def mapXref = [username: 'display_name',
firstname: 'first_name',
lastname: 'last_name',
email: 'email'}
for (String x : loginAttributeValues) {
if (mapXref[x])
println(mapXref.get(x))
}

This should work for you:
loginAttributeValues.each {
if (mapXref[it])
println(mapXref.get(it))
}
The Java for : each syntax is not valid in groovy. You are best of using an each closure.
Or, if you really want to keep the for loop syntax, then substitute in for :.

loginAttributeValues = [username: 'john', email: 'john#smith.com']
def mapXref = [username: 'display_name',
firstname: 'first_name',
lastname: 'last_name',
email: 'email']
def newMap = [:]
loginAttributeValues.each {k, v ->
newMap << (k in mapXref.keySet() ? [(v): mapXref[k]] : [:])
}
assert newMap.'john' == 'display_name'
assert newMap.'john#smith.com' == 'email'
I hope you need a map like
[john:display_name, john#smith.com:email]
but why do you need a value john as a key and display_name as value. should not it be otherwise. In that case, you would need
newMap << (k in mapXref.keySet() ? [mapXref[k] : v] : [:])
which would assert as
assert newMap.display_name == 'john'
assert newMap.email == 'john#smith.com'
Note:-
If it is guaranteed that loginAttributeValues will have keys subset of the keys in mapXref, then the logic can be optimized as below
def newMap = loginAttributeValues.collectEntries{k, v ->
[v, mapXref[k]]
}

Related

Convert keys in inner hash to keys of outer/parent hash

I have a hash, say,
account = {
name: "XXX",
email: "xxx#yyy.com",
details: {
phone: "9999999999",
dob: "00-00-00",
address: "zzz"
}
}
Now I want to convert account to a hash like this:
account = {
name: "XXX",
email: "xxx#yyy.com",
phone: "9999999999",
dob: "00-00-00",
address: "zzz"
}
I'm a beginner and would like to know if there is any function to do it? (Other than merging the nested hash and then deleting it)
You could implement a generic flatten_hash method which works roughly similar to Array#flatten in that it allows to flatten Hashes of arbitrary depth.
def flatten_hash(hash, &block)
hash.dup.tap do |result|
hash.each_pair do |key, value|
next unless value.is_a?(Hash)
flattened = flatten_hash(result.delete(key), &block)
result.merge!(flattened, &block)
end
end
end
Here, we are still performing the delete / merge sequence, but it would be required in any such implementation anyway, even if hidden below further abstractions.
You can use this method as follows:
account = {
name: "XXX",
email: "xxx#yyy.com",
details: {
phone: "9999999999",
dob: "00-00-00",
address: "zzz"
}
}
flatten(account)
# => {:name=>"XXX", :email=>"xxx#yyy.com", :phone=>"9999999999", :dob=>"00-00-00", :address=>"zzz"}
Note that with this method, any keys in lower-level hashes overwrite existing keys in upper-level hashes by default. You can however provide a block to resolve any merge conflicts. Please refer to the documentation of Hash#merge! to learn how to use this.
This will do the trick:
account.map{|k,v| k==:details ? v : {k => v}}.reduce({}, :merge)
Case 1: Each value of account may be a hash whose values are not hashes
account.flat_map { |k,v| v.is_a?(Hash) ? v.to_a : [[k,v]] }.to_h
#=> {:name=>"XXX", :email=>"xxx#yyy.com", :phone=>"9999999999",
# :dob=>"00-00-00", :address=>"zzz"}
Case 2: account may have nested hashes
def doit(account)
recurse(account.to_a).to_h
end
def recurse(arr)
arr.each_with_object([]) { |(k,v),a|
a.concat(v.is_a?(Hash) ? recurse(v.to_a) : [[k,v]]) }
end
account = {
name: "XXX",
email: "xxx#yyy.com",
details: {
phone: "9999999999",
dob: { a: 1, b: { c: 2, e: { f: 3 } } },
address: "zzz"
}
}
doit account
#=> {:name=>"XXX", :email=>"xxx#yyy.com", :phone=>"9999999999", :a=>1,
# :c=>2, :f=>3, :address=>"zzz"}
Explanation for Case 1
The calculations progress as follows.
One way to think of Enumerable#flat_map, as it is used here, is that if, for some method g,
[a, b, c].map { |e| g(e) } #=> [f, g, h]
where a, b, c, f, g and h are all arrays, then
[a, b, c].flat_map { |e| g(e) } #=> [*f, *g, *h]
Let's start by creating an enumerator to pass elements to the block.
enum = account.to_enum
#=> #<Enumerator: {:name=>"XXX", :email=>"xxx#yyy.com",
# :details=>{:phone=>"9999999999", :dob=>"00-00-00",
# :address=>"zzz"}}:each>
enum generates an element which is passed to the block and the block variables are set equal to those values.
k, v = enum.next
#=> [:name, "XXX"]
k #=> :name
v #=> "XXX"
v.is_a?(Hash)
#=> false
a = [[k,v]]
#=> [[:name, "XXX"]]
k, v = enum.next
#=> [:email, "xxx#yyy.com"]
v.is_a?(Hash)
#=> false
b = [[k,v]]
#=> [[:email, "xxx#yyy.com"]]
k,v = enum.next
#=> [:details, {:phone=>"9999999999", :dob=>"00-00-00", :address=>"zzz"}]
v.is_a?(Hash)
#=> true
c = v.to_a
#=> [[:phone, "9999999999"], [:dob, "00-00-00"], [:address, "zzz"]]
d = account.flat_map { |k,v| v.is_a?(Hash) ? v.to_a : [[k,v]] }
#=> [*a, *b, *c]
#=> [[:name, "XXX"], [:email, "xxx#yyy.com"], [:phone, "9999999999"],
# [:dob, "00-00-00"], [:address, "zzz"]]
d.to_h
#=> <the return value shown above>

storing a lambda in the value portion of a hash

Can anyone tell me what's going on here:
value: -> f { view_context.fr_user_column(f) }
This is from a larger hash:
def self.columns
{
user: {
title: "Applicant",
value: -> f { view_context.fr_user_column(f) }
},
ch_rep: {
title: "CH Rep",
value: -> f { view_context.fr_ch_rep_column(f) }
}
}
That gets used in a method to create a table:
def self.render_zable_columns(context, options = {})
self.columns.each do |key, col|
next if options[:except] && options[:except].include?(key)
col_options = {title: col[:title]}
col_options[:class] = "franchise_#{key}"
col_options[:sort] = col.key?(:sort) ? col[:sort] : true
if col[:value].present?
context.send(:column, key, col_options, &col[:value])
else
context.send(:column, key, col_options)
end
end
end
The reason I ask is because I also have the CH rep which is just a different name for the User model, and I'm trying to get the table to display the CH rep (User) name but this isn't working:
value: -> f { view_context.fr_ch_rep_column(f) }
This is storing a lambda in the value portion of a hash. f corresponds to the variable that will be available inside of the lambda when the lambda is called.
if col[:value].present? checks for the lambda
context.send(:column, key, col_options, &col[:value]) sends (in order)
column as a symbol
the key (i.e. ch_rep or `user)
column_options which includes the title and other non lambda keys/values
the lambda specified as the value key, to context

Rails4: Is it possible to do an optional ActiveRecord search?

Say i had a record in my database like
+----+-----------+----------+
| id | firstname | lastname |
+----+-----------+----------+
| 1 | 'Bill' | nil |
+----+-----------+----------+
(note last name is nil)
Is there any where I can retrieve the above record using the following hash structure as search parameters:
vals = {firstname: "Bill", lastname: "test"}
Table.where(vals)
(ie: find the closest match, ignoring the nil column value in the table)
(I'm thinking of checking each key in the hash individually and stopping when a match is found, but just wondering if there is a more efficient way, specially for larger tables)
You could make custom search.
def self.optional_where params
query_params = params.keys.map do |k|
"(#{k} = ? OR #{k} IS NULL)"
end.join(" AND ")
where(query_params, *params.values)
end
Then you would use it like
Table.optional_where(vals)
This will produce next query
SELECT "tables".* FROM "tables" WHERE ((firstname = 'Bill' OR first_name IS NULL) AND (lastname = 'test' OR last_name IS NULL))
Let make a custom search like this:
scope :custom_search, -> (params) {
params.each do |k, v|
params[k] = if
if v.is_a? Array
(v << nil).uniq
else
[v, nil]
end
where(params)
end
}
Then we use it like:
search_params = {firstname: "Bill", lastname: "test"}
Table.custom_search(search_params)
The generated sql will be:
SELECT * FROM tables where firstname IN ['Bill', null] AND lastname IN ['test', null]
This means you don't care if one or more fields are nil

Checking if a collection is null or empty in Groovy

I need to perform a null or empty check on a collection; I think that !members?.empty is incorrect. Is there a groovier way to write the following?
if (members && !members.empty) {
// Some Work
}
There is indeed a Groovier Way.
if (members) {
//Some work
}
does everything if members is a collection. Null check as well as empty check (Empty collections are coerced to false). Hail Groovy Truth. :)
FYI this kind of code works (you can find it ugly, it is your right :) ) :
def list = null
list.each { println it }
soSomething()
In other words, this code has null/empty checks both useless:
if (members && !members.empty) {
members.each { doAnotherThing it }
}
def doAnotherThing(def member) {
// Some work
}
!members.find()
I think now the best way to solve this issue is code above. It works since Groovy 1.8.1 http://docs.groovy-lang.org/docs/next/html/groovy-jdk/java/util/Collection.html#find(). Examples:
def lst1 = []
assert !lst1.find()
def lst2 = [null]
assert !lst2.find()
def lst3 = [null,2,null]
assert lst3.find()
def lst4 = [null,null,null]
assert !lst4.find()
def lst5 = [null, 0, 0.0, false, '', [], 42, 43]
assert lst5.find() == 42
def lst6 = null;
assert !lst6.find()

Create dynamically closures in Groovy from a String object

i would like to create a query with the Criteria API in Grails (GORM).
The query will have to be something like this:
MyEntity.createCriteria().list{
assoc{
parent{
eq("code", val)
}
}
}
What i need is to build the nested closure dynamically from a String object. The String for the example above will be "assoc.parent.code" . I splitted the String by dot (by doing String.split("\\.") ) but i don't know how to construct the nested closures:
assoc{
parent{
eq("code", val)
}
}
dynamically based on the array of the splitted Strings above.
What about createAlias?. You could try something like this:
def path = "assoc.parent.code"
def split = path.split(/\./)
MyEntity.createCriteria().list {
// this will get you 'createAlias( assoc.parent, alias1 )'
createAlias split.take( split.size() - 1 ), "alias1"
// this will get you 'eq(alias1.code, userInput)'
eq "alias1.${split[-1]}", userInput
}
This snippet is not generic, but you get the idea.
Update
Not conventional, but you can build a string containing the code with the closures and evaluate it using GroovyShell:
assoc = 'assoc.parent.child.name'
split = assoc.split( /\./ )
path = split[-2..0] // will get us 'child.parent.assoc';
// we will build it from inside-out
def firstClosure = "{ eq '${split[-1]}', 'john doe' }"
def lastClosure = firstClosure
for (entity in path) {
def criteriaClosure = "{ ${entity} ${lastClosure} }"
lastClosure = criteriaClosure
}
assert lastClosure == "{ assoc { parent { child { eq 'name', 'john doe' } } } }"
def builtClosure = new GroovyShell().evaluate("return " + lastClosure)
assert builtClosure instanceof Closure
A more generic approach would be to metaClass String as below, and use it for any kind of separator
. | , - ~ and more.
String.metaClass.convertToClosureWithValue = {op, val ->
split = delegate.split(op) as List
if(split.size() == 1) {return "Cannot split string '$delegate' on '$op'"}
items = []
split.each{
if(it == split.last()){
items << "{ eq '$it', $val }"
split.indexOf(it).times{items.push("}")}
} else {
items << "{$it"
}
}
println items.join()
new GroovyShell().evaluate("return " + items.join())
}
assert "assoc.parent.child.name".convertToClosureWithValue(/\./, "John Doe") instanceof Closure
assert "assoc-parent-child-name".convertToClosureWithValue(/\-/, "Billy Bob") instanceof Closure
assert "assoc|parent|child|grandChild|name".convertToClosureWithValue(/\|/, "Max Payne") instanceof Closure
assert "assoc~parent~child~grandChild~name".convertToClosureWithValue('\\~', "Private Ryan") instanceof Closure
assert "assocparentchildname".convertToClosureWithValue(/\|/, "Captain Miller") == "Cannot split string 'assocparentchildname' on '\\|'"
//Print lines from items.join()
{assoc{parent{child{ eq 'name', John Doe }}}}
{assoc{parent{child{ eq 'name', Billy Bob }}}}
{assoc{parent{child{grandChild{ eq 'name', Max Payne }}}}}
{assoc{parent{child{grandChild{ eq 'name', Private Ryan }}}}}

Resources