Create comma separated array using each loop - ruby-on-rails

How can I create list of IP address with comma separated when using each loop?
ip_list = [];
hostnames.each do |host|
puts host.ip
end
I tried ip_list.join but that isn't working at all. I want all the host IP in a variable with comma seperated.
I want the output to be comma separated string.
Example:
puts ip_list
10.10.10.10, 10.10.10.11

I know you asked to use each but why not use collect?
This gives you an array:
hostnames.collect(&:ip)
If you want a comma-separated list do:
hostnames.collect(&:ip).join(',')
If you need conditions in the iterator, you can use the longer block syntax:
hostnames.collect { |host|
next unless host.name.match(/some_regex/)
host.ip
}.compact # b/c otherwise you'll end up with some nil entries

How about this? You can use Array#push.
ip_list = [];
hostnames.each do |host|
ip_list.push host.ip
end
p ip_list
Or, Array#map would be more useful.
ip_list = hostnames.map { |host| host.ip }
p ip_list

If you want to get new array based on the existing one and filter by condition at the same time, you can use filter_map (was introduced in Ruby 2.7)
If you wan to get string from array just use join
hostnames.filter_map { |hostname| hostname.ip if some.condition }.join(", ")

Related

How can I iterate over part of a hash in Ruby

In a Rails environment i get a params hash from a form.
Besides others, like
params[:vocab][:name]
params[:vocab][:priority]
I have the 3 fields tag_1, tag_2, tag_3 whose values are stored in
params[:vocab][:tag_1]
params[:vocab][:tag_2]
params[:vocab][:tag_3]
My question is:
Can I iterate over these three hash keys?
I want to do sth like
for i in 1..3 do
iterator = ":tag_" + i.to_s
puts params[:vocab][iterator.to_sym]
end
but that doesn't work.
Your approach doesn't work because:
':tag_2'.to_sym
# => :":tag_2"
so it would work if you remove leading colon from ":tag_" + i.to_s
You can also do it this way, iterating over keys:
[:tag_1, :tag_2, :tag_3].each { |key| puts params[:vocab][key] }
You can construct symbols via interpolation; prefixing a string with a colon will cause Ruby to interpret it as a symbol.
(1..3).each do |i|
puts params[:vocab][:"tag_#{i}"]
end

.where.not with empty array

I have a problem trying to work with a NOT IN query (using Rails 4/Postgres, for reference) in an elegant way. I'm trying to get a list of all objects of a certain model that don't show up in a join table for a certain instance. It works , when you try a NOT IN query with an empty array, it throws an error because you can't look for NOT IN NULL.
The below code now works, but is there a better way than to use an unintuitive conditional to make a pseudo-null object?
def characters_selected
self.characters_tagged.pluck(:name)
end
def remaining_characters
characters = self.characters_selected
characters = ["SQL breaks if this is null"] if characters.empty?
# this query breaks on characters_selected == [] without the above line
Character.where("name NOT IN (?)", characters )
end
This is the ActiveRecord way:
def remaining_characters
characters = self.characters_selected
Character.where.not(:name => characters)
end
When characters.empty? the where clause becomes "WHERE (1=1)".

Sending array of values to a sql query in ruby?

I'm struggling on what seems to be a ruby semantics issue. I'm writing a method that takes a variable number of params from a form and creates a Postgresql query.
def self.search(params)
counter = 0
query = ""
params.each do |key,value|
if key =~ /^field[0-9]+$/
query << "name LIKE ? OR "
counter += 1
end
end
query = query[0..-4] #remove extra OR and spacing from last
params_list = []
(1..counter).each do |i|
field = ""
field << '"%#{params[:field'
field << i.to_s
field << ']}%", '
params_list << field
end
last_item = params_list[-1]
last_item = last_item[0..-3] #remove trailing comma and spacing
params_list[-1] = last_item
if params
joins(:ingredients).where(query, params_list)
else
all
end
end
Even though params_list is an array of values that match in number to the "name LIKE ?" parts in query, I'm getting an error: wrong number of bind variables (1 for 2) in: name LIKE ? OR name LIKE ? I tried with params_list as a string and that didn't work any better either.
I'm pretty new to ruby.
I had this working for 2 params with the following code, but want to allow the user to submit up to 5 ( :field1, :field2, :field3 ...)
def self.search(params)
if params
joins(:ingredients).where(['name LIKE ? OR name LIKE ?',
"%#{params[:field1]}%", "%#{params[:field2]}%"]).group(:id)
else
all
end
end
Could someone shed some light on how I should really be programming this?
PostgreSQL supports standard SQL arrays and the standard any op (...) syntax:
9.23.3. ANY/SOME (array)
expression operator ANY (array expression)
expression operator SOME (array expression)
The right-hand side is a parenthesized expression, which must yield an array value. The left-hand expression is evaluated and compared to each element of the array using the given operator, which must yield a Boolean result. The result of ANY is "true" if any true result is obtained. The result is "false" if no true result is found (including the case where the array has zero elements).
That means that you can build SQL like this:
where name ilike any (array['%Richard%', '%Feynman%'])
That's nice and succinct so how do we get Rails to build this? That's actually pretty easy:
Model.where('name ilike any (array[?])', names.map { |s| "%#{s}%" })
No manual quoting needed, ActiveRecord will convert the array to a properly quoted/escaped list when it fills the ? placeholder in.
Now you just have to build the names array. Something simple like this should do:
fields = params.keys.select { |k| k.to_s =~ /\Afield\d+\z/ }
names = params.values_at(*fields).select(&:present)
You could also convert single 'a b' inputs into 'a', 'b' by tossing a split and flatten into the mix:
names = params.values_at(*fields)
.select(&:present)
.map(&:split)
.flatten
You can achieve this easily:
def self.search(string)
terms = string.split(' ') # split the string on each space
conditions = terms.map{ |term| "name ILIKE #{sanitize("'%#{term}%'")}" }.join(' OR ')
return self.where(conditions)
end
This should be flexible: whatever the number of terms in your string, it should returns object matching at least 1 of the terms.
Explanation:
The condition is using "ILIKE", not "LIKE":
"ILIKE" is case-insensitive
"LIKE" is case-sensitive.
The purpose of the sanitize("'%#{term}%'") part is the following:
sanitize() will prevent from SQL injections, such as putting '; DROP TABLE users;' as the input to search.
Usage:
User.search('Michael Mich Mickey')
# can return
<User: Michael>
<User: Juan-Michael>
<User: Jean michel>
<User: MickeyMouse>

Sanitizing User Regexp

I want to write a function that allows users to match data based on a regexp, but I am concerned about sanitation of the user strings. I know with SQL queries you can use bind variables to avoid SQL injection attacks, but I am not sure if there's such a mechanism for regexps. I see that there's Regexp.escape, but I want to allow valid regexps.
Here is is the sample function:
def tagged?(text)
tags.each do |tag|
return true if text =~ /#{tag.name}/i
end
return false
end
Since I am just matching directly on tag.name is there a chance that someone could insert a Proc call or something to break out of the regexp and cause havoc?
Any advice on best practice would be appreciated.
Interpolated strings in a Regexp are not executed, but do generate annoying warnings:
/#{exit -3}/.match('test')
# => exits
foo = '#{exit -3}'
/#{foo}/.match('test')
# => warning: regexp has invalid interval
# => warning: regexp has `}' without escape
The two warnings seem to pertain to the opening #{ and the closing } respectively, and are independent.
As a strategy that's more efficient, you might want to sanitize the list of tags into a combined regexp you can run once. It is generally far less efficient to construct and test against N regular expressions than 1 with N parts.
Perhaps something along the lines of this:
class Taggable
def tags
#tags
end
def tags=(value)
#tags = value
#tag_regexp = Regexp.new(
[
'^(?:',
#tags.collect do |tag|
'(?:' + tag.sub(/\#\{/, '\\#\\{').sub(/([^\\])\}/, '\1\\}') + ')'
end.join('|'),
')$'
].to_s,
Regexp::IGNORECASE
)
end
def tagged?(text)
!!text.match(#tag_regexp)
end
end
This can be used like this:
e = Taggable.new
e.tags = %w[ #{exit-3} .*\.gif .*\.png .*\.jpe?g ]
puts e.tagged?('foo.gif').inspect
If the exit call was executed, the program would halt there, but it just interprets that as a literal string. To avoid warnings it is escaped with backslashes.
You should probably create an instance of the Regexp class instead.
def tagged?(text)
return tags.any? { |tag| text =~ Regexp.new(tag.name, Regexp::IGNORECASE) }
end

Verifying if an object is in an array of objects in Rails

I'm doing this:
#snippets = Snippet.find :all, :conditions => { :user_id => session[:user_id] }
#snippets.each do |snippet|
snippet.tags.each do |tag|
#tags.push tag
end
end
But if a snippets has the same tag two time, it'll push the object twice.
I want to do something like if #tags.in_object(tag)[...]
Would it be possible? Thanks!
I think there are 2 ways to go about it to get a faster result.
1) Add a condition to your find statement ( in MySQL DISTINCT ). This will return only unique result. DBs in general do much better jobs than regular code at getting results.
2) Instead if testing each time with include, why don't you do uniq after you populate your array.
here is example code
ar = []
data = []
#get some radom sample data
100.times do
data << ((rand*10).to_i)
end
# populate your result array
# 3 ways to do it.
# 1) you can modify your original array with
data.uniq!
# 2) you can populate another array with your unique data
# this doesn't modify your original array
ar.flatten << data.uniq
# 3) you can run a loop if you want to do some sort of additional processing
data.each do |i|
i = i.to_s + "some text" # do whatever you need here
ar << i
end
Depending on the situation you may use either.
But running include on each item in the loop is not the fastest thing IMHO
Good luck
Another way would be to simply concat the #tags and snippet.tags arrays and then strip it of duplicates.
#snippets.each do |snippet|
#tags.concat(snippet.tags)
end
#tags.uniq!
I'm assuming #tags is an Array instance.
Array#include? tests if an object is already included in an array. This uses the == operator, which in ActiveRecord tests for the same instance or another instance of the same type having the same id.
Alternatively, you may be able to use a Set instead of an Array. This will guarantee that no duplicates get added, but is unordered.
You can probably add a group to the query:
Snippet.find :all, :conditions => { :user_id => session[:user_id] }, :group => "tag.name"
Group will depend on how your tag data works, of course.
Or use uniq:
#tags << snippet.tags.uniq

Resources