How to pass a hash as a query with "OR-join"? - ruby-on-rails

I have a hash with booleans
({"u25" => true, "f26t49" => true, "o50" => true, ..});
all in all there are 19 booleans and I want to check these with a table in my db and get those data sets, how have at least one match. I had try it with
"Model.all(:conditions => hash)" or "Model.where(hash)"
but there I get the query
"..u25 == true AND f26t49 == true AND o50 == true..."
but I need something like this:
"..u25 == true OR f26t49 == true..."
I hope you could help me!
ADDITION:
I tryed to make a workaround and generate a query string out of the hash. If i write this string directly (m = Model.where("u25 == 't'")) it works but if I pass a varaible
#query = '"u25 == ' + "'t'" + '"'
m = Model.where(#query)
than m is nil!

I think that your workaround put you in the right direction. Here's what I would do:
# model.rb
def self.search_with_conditions(hash)
query = hash.map{|k,v| "#{k} == #{v}"}.join(' OR ')
where(query)
end
And then in your controller, simply call:
m = Model.search_with_conditions(hash)
Sidenote: This might work for some DBs and not for others as some use 1 as the value for true. Also, I didn't have time to try this so I may be missing some quotation marks in that condition. I think it could be '#{v}'.
EDIT: After learning a little bit more about Rails, I must add a disclaimer here:
If the strings k or v are part of some user input, this approach is susceptible to SQL injection. Use this method with care.

Related

Is there a method like "ILIKE" I can use in a Rails conditional statement?

I want to perform an action if a string is contained, non-case-sensitively, in another string.
So my if statement would look something like this:
#a = "search"
if #a ILIKE "fullSearch"
#do stuff
end
You can use the include? method. So in this case:
#var = 'Search'
if var.include? 'ear'
#action
end
Remember include? method is case-sensitive. So if you use something like include? 'sea' it would return false. You may want to do a downcase before calling include?()
#var = 'Search'
if var.downcase.include? 'sea'
#action
end
Hope that helped.
There are many ways to get there. Here are three:
'Foo'.downcase.include?('f') # => true
'Foo'.downcase['f'] # => "f"
Those are documented in the String documentation which you need to become very familiar with if you're going to program in Ruby.
'Foo'[/f/i] # => "F"
This is a mix of String's [] slice shortcut and regular expressions. I'd recommend one of the first two because they're faster, but for thoroughness I added it because people like hitting things with the regex hammer. Regexp contains documentation for /f/i.
You'll notice that they return different things. Ruby considers anything other than false or nil as true, AKA "truthiness", so all three are returning a true value, and, as a result you could use them in conditional tests.
You can use a regexp with i option. i for insensitive I think.
a = "fullSearch"
a =~ /search/i
=> 4
a =~ /search/
=> nil
Or you could downcase your string and check if it's present in the other
a = "fullSearch"
a.downcase.include?('search')
=> true

Query multiple key values with Rails + Postgres hstore

I am trying to make a query to search for values in my hstore column properties. I am filtering issues by user input by attribute. It is possible to search Issues where email is X, or Issues where email is X and the sender is "someone". Soon I need to change to search using LIKE for similar results. So if you know how to do it with LIKE also, show both options please.
If I do this:
Issue.where("properties #> ('email => pugozufil#yahoo.com') AND properties #> ('email => pugozufil#yahoo.com')")
it returns a issue.
If I do this:
Issue.where("properties #> ('email => pugozufil#yahoo.com') AND properties #> ('sender => someone')")
Here I got an error, telling me:
ERROR: Syntax error near 'd' at position 11
I change the "#>" to "->" and now this error is displayed:
PG::DatatypeMismatch: ERROR: argument of AND must be type boolean, not type text
I need to know how to query the properties with more than one key/value pair, with "OR" or "AND", doesn't matter.
I wish to get one or more results that include those values I am looking for.
I end up doing like this. Using the array option of the method where. Also using the suggestion from #anusha in the comments. IDK why the downvote though, I couldn't find anything on how to do something simple like this. I had doubt in formatting my query and mostly with hstore. So I hope it helps someone in the future as sure it did for me now.
if params[:filter].present?
filters = params[:filter]
conditions = ["properties -> "]
query_values = []
filter_query = ""
filters.each do |k, v|
if filters[k].present?
filter_query += "'#{k}' LIKE ?"
filter_query += " OR "
query_values << "%#{v}%"
end
end
filter_query = filter_query[0...-(" OR ".size)] # remove the last ' OR '
conditions[0] += filter_query
conditions = conditions + query_values
#issues = #issues.where(conditions)
end

Construct a dynamic route path in Ruby

I want to construct a dynamic route path in ruby, something like this
route = (a == a) ? "foo" : (b == b) ? "bar" : "default"
link_to(event.try(:name), admin_"#{route}"_path('params goes here')
I very well know what I have tried is wrong. It should be done with dynamic method creations using class_eval or define_method I am not sure about that. Also, I am not familiar with those concepts. I can google but it would take much time to get a solution.
Anyone, please help me solve this quickly. Thanks in advance.
This is pretty straightforward:
send("admin_#{route}_path", params)
You may want to wrap that up in a helper method to clean things up:
def admin_path_for_ab(a, b, params = nil)
route =
if (a == 'a')
"foo"
elsif (b == 'b')
"bar"
else
"default"
end
send("admin_#{route}_path", params)
end
As a note, nesting ternaries (x ? y : z) is usually a bad idea, and a == a is always true.

find_all elements in an array that match a condition?

I've an array of hash entries, and want to filter based on a paramater passed into the function.
If there are three values in the hash, A, B, and C, I want to do something similar to:
data = [{A:'a1', B:'b1', C:'c1'},
{A:'a1', B:'b2', C:'c1'},
{A:'a1', B:'b2', C:'c2'},
{A:'a2', B:'b1', C:'c1'},
{A:'a2', B:'b2', C:'c1'}]
data.find_all{ |d| d[:A].include?params[:A] }
.find_all{ |d| d[:B].include?params[:B] }
.find_all{ |d| d[:C].include?params[:C] }
find all where A == 'a1' AND B='b2'
so for above I get:
{A:'a1', B:'b2', C:'c1'} and {A:'a1', B:'b2', C:'c2'}
* put if / else statements, like params.has_key? 'B' then do something.
* modify my code each time a new type of value is added to the Hash map (say now I have 'D').
Note: The key of the hash is a symbol, but the value is a string and I want to do a "contains" not "equals".
I think of it as SQL Select statement with where zero or more '==' clause
If I understand your question correctly, then I believe that the select method of Array class may be helpful to you.
select takes a block of code which is intended to be a test condition on each element in your array. Any elements within your array which satisfy that test condition will be selected, and the result is an array of those selected elements.
For example:
arr = [ 4, 8, 15, 16, 23, 42 ]
result = arr.select { |element| element > 20 }
puts result # prints out [23, 42]
In your example, you have an array of hashes, which makes it only slightly more complicated than my example with a simple array of numbers. In your example, we have:
data = [{A:'a1', B:'b1', C:'c1'},
{A:'a1', B:'b2', C:'c1'},
{A:'a1', B:'b2', C:'c2'},
{A:'a2', B:'b1', C:'c1'},
{A:'a2', B:'b2', C:'c1'}]
I believe what you want your code to do is something like: Select from my data array all of the hashes where, within the hash, :A equals some value AND :B equals some other value.
Let's say you want to find all of the hashes where :A == 'a1' and :B == 'b2'. You would do that like this:
data.select { |hash_element| hash_element[:A] == 'a1' && hash_element[:B] == 'b2' }
This line returns to you an array with those hashes from your original data array which satisfy the condition we provided in the block - that is, those hashes where :A == 'a1' and :B == 'b2'. Hope that that helps shed some light on your problem!
More information about the select method here:
http://www.ruby-doc.org/core-2.1.0/Array.html#method-i-select
edited - below is an addition to original answer
To follow up on your later question about if/else clauses and the addition of new parameters... the block of code that you pass to select can, of course, be much more complicated than what I've written in the example. You just need to keep this in mind: If the last line of the block of code evaluates to true, then the element will be selected into the result array. Otherwise, it won't be selected.
So, that means you could define a function of your own, and call that function within the condition block passed to select. For example, something like this:
def condition_test(hash_element, key_values)
result = true
key_values.each do |pair|
if hash_element[pair[:key]] != pair[:value]
result = false
end
end
return result
end
# An example of the key-value pairs you might require to satisfy your select condition.
requirements = [ {:key => :A, :value => 'a1'},
{:key => :B, :value => 'b2'} ]
data.select { |hash_element| condition_test(hash_element, requirements) }
This makes your condition block a bit more dynamic, rather than hard-wired for :A == 'a1' and :B == 'b2' like we did in the earlier example. You can tweak requirements on the fly based on the keys and values that you need to look for. You could also modify condition_test to do more than just check to see if the hash value at some key equals some value. You can add in your if/else clauses in condition_test to test for things like the presence of some key, etc.
I think you want to use .values_at(key)
http://www.ruby-doc.org/core-2.1.0/Hash.html#method-i-values_atvalues_at method

Not assigning nil values to a hash

Is there a short hand or best practice for assigning things to a hash when they are nil in ruby? For example, my problem is that I am using another hash to build this and if something in it is nil, it assigns nil to that key, rather than just leaving it alone. I understand why this happens so my solution was:
hash1[:key] = hash2[:key] unless hash2[:key].nil?
Because I cannot have a value in the has where the key actually points to nil. (I would rather have an empty hash than one that has {:key => nil}, that can't happen)
My question would be is there a better way to do this? I don't want to do a delete_if at the end of the assignments.
a little bit shorter if you negate the "unless" statement
hash1[:key] = hash2[:key] if hash2[:key] # same as if ! hash2[:key].nil?
you could also do the comparison in a && statement as suggested in other answers by Michael or Marc-Andre
It's really up to you, what you feel is most readable for you. By design, there are always multiple ways in Ruby to solve a problem.
You could also modify the hash2 :
hash1 = hash2.reject{|k,v| v.nil?}
hash2.reject!{|k,v| v.nil?} # even shorter, if in-place editing of hash2
this would remove key/value pairs :key => nil from hash2 (in place, if you use reject! )
I like this the best, loop and conditional overriding all in one line!
h1 = {:foo => 'foo', :bar => 'bar'}
h2 = {:foo => 'oof', :bar => nil}
h1.merge!(h2) { |key, old_val, new_val| new_val.nil? ? old_val : new_val }
#=> {:foo => 'oof', :bar => 'bar'}
This will replace every value in h1 with the value of h2 where the keys are the same and the h2 value is not nil.
I'm not sure if that's really any better, but
hash2[:key] && hash[:key] = hash2[:key]
could work. Note that this would behave the same way for false and nil, if that's not what you want
!hash2[:key].nil? && hash[:key] = hash2[:key]
would be better. All of this assuming that :key would be an arbitrary value that you may not have control over.
How about something like this?
hash2.each_pair do |key, value|
next if value.nil?
hash1[key] = value
end
If you are doing just a single assignment, this could shave a few characters:
hash2[:key] && hash1[:key] = hash2[:key]
My first example could also be shaved a bit further:
hash2.each_pair{ |k,v| v && hash1[k] = v }
I think the first is the easiest to read/understand. Also, examples 2 and 3 will skip anything that evaluates false (nil or false). This final example is one line and won't skip false values:
hash2.each_pair{ |k,v| v.nil? || hash1[k] = v }
I believe the best practice is to copy the nil value over to the hash. If one passes an option :foo => nil, it can mean something and should override a default :foo of 42, for example. This also makes it easier to have options which should default to true, although one should use fetch in those cases:
opt = hash.fetch(:do_cool_treatment, true) # => will be true if key is not present
There are many ways to copy over values, including nil or false.
For a single key, you can use has_key? instead of the lookup:
hash1[:key] = hash2[:key] if hash2.has_key? :key
For all (or many) keys, use merge!:
hash1.merge!(hash2)
If you only want to do this for a couple of keys of hash2, you can slice it:
hash1.merge!(hash2.slice(:key, ...))
OK, so if the merge doesn't work because you want more control:
hash1[:key] = hash2.fetch(:key, hash1[:key])
This will set hash1's :key to be hash2, unless it doesn't exist. In that case, it will use the default value (2nd argument to fetch), which is hash1's key
Add this to your initializers hash.rb
class Hash
def set_safe(key,val)
if val && key
self[key] = val
end
end
end
use
hash = {}
hash.set_safe 'key', value_or_nil

Resources